From 71db635ed5b9cf89c5ad1bb31c4777dff82d6995 Mon Sep 17 00:00:00 2001 From: Christoffer Skog Date: Wed, 24 May 2017 16:19:58 +0200 Subject: [PATCH 1/2] Rewrite suggestion --- .DS_Store | Bin 0 -> 10244 bytes src/.DS_Store | Bin 0 -> 8196 bytes src/components/.DS_Store | Bin 0 -> 8196 bytes src/components/ng2-swipe-cards/.DS_Store | Bin 0 -> 6148 bytes src/components/swipe-cards/.DS_Store | Bin 0 -> 6148 bytes src/components/swipe-cards/shared/.DS_Store | Bin 0 -> 6148 bytes .../swipe-cards/shared/components/.DS_Store | Bin 0 -> 6148 bytes .../shared/components/card.component.css | 62 ++++ .../shared/components/card.component.html | 4 + .../shared/components/card.component.ts | 255 ++++++++++++++++ .../shared/components/card.module.ts | 18 ++ .../shared/directives/action.directive.ts | 25 ++ .../directives/tinder-card.directive.ts | 274 ++++++++++++++++++ .../services/swipe-cards-api.service.ts | 18 ++ .../swipe-cards/swipe-cards.component.css | 13 + .../swipe-cards/swipe-cards.component.html | 21 ++ .../swipe-cards/swipe-cards.component.ts | 104 +++++++ .../swipe-cards/swipe-cards.module.ts | 35 +++ 18 files changed, 829 insertions(+) create mode 100644 .DS_Store create mode 100644 src/.DS_Store create mode 100644 src/components/.DS_Store create mode 100644 src/components/ng2-swipe-cards/.DS_Store create mode 100644 src/components/swipe-cards/.DS_Store create mode 100644 src/components/swipe-cards/shared/.DS_Store create mode 100644 src/components/swipe-cards/shared/components/.DS_Store create mode 100644 src/components/swipe-cards/shared/components/card.component.css create mode 100644 src/components/swipe-cards/shared/components/card.component.html create mode 100755 src/components/swipe-cards/shared/components/card.component.ts create mode 100644 src/components/swipe-cards/shared/components/card.module.ts create mode 100644 src/components/swipe-cards/shared/directives/action.directive.ts create mode 100755 src/components/swipe-cards/shared/directives/tinder-card.directive.ts create mode 100644 src/components/swipe-cards/shared/services/swipe-cards-api.service.ts create mode 100644 src/components/swipe-cards/swipe-cards.component.css create mode 100644 src/components/swipe-cards/swipe-cards.component.html create mode 100644 src/components/swipe-cards/swipe-cards.component.ts create mode 100755 src/components/swipe-cards/swipe-cards.module.ts diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..0ee0263e1bb4b892395c88793fb7dab956793731 GIT binary patch literal 10244 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 literal 0 HcmV?d00001 diff --git a/src/components/.DS_Store b/src/components/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..ac290a70bbb92d1f0c159a7aac85a71a94d778a0 GIT binary patch literal 8196 zcmeHLTWl0n82B+sStx}K#s?)bCpqUk zbN>IIGv}Lc&e?wf0K4*f0-zQE6siLGLTYYPgk98IN+cK&Mx=NE7Ybm)I2h1$i+4B> zG7vHlG7vHlG7vIwH!whFwkS%8b6;A+He?`V;GSea><=-j0+RtwOZ2x6YWx&{u#ATN zLVd~u{FqpP$pEJ%`k{1BnLS_-ib0BjGMwx&;ZB$ga9W}a2bAG}!OR$BDCo~laWUT= zFex!?Lk27xHWgsQ2((bnK0ANUf(ALz!R@h}Y{qgjOH!?xL*Kw@teJyjy zGz!DKy4$fm&9sf&Ecc;))7G6rmy@@3*T2sJ%^Rgyf1+VtH#ynTo`}a1tu0gW*kq!m ztvw!VYi^&KQk0s8#Fn08W2a6}K0futS$}1K!Dd4Y-CWMydEFD~oNYPw2ziRTEc{#C zzCoU|xh)ZOH*&f@(Rzc^*7a@wh|?15wcLqrsiDZLMGbv>LqiMIp51%)4s)J&je~}z zdFF(X(mcZ*<`phUt0(K&2Xe*)38z2nSotxVER|W!%I3-0=(Mb~dD?IXJmaJ%dWT&n z=XLvz1180av0+{%;`fhxrenK@jGRlz*u_Olm#B3Rv_}q;sBO!PYU*SmO^fqce1kvF|l zyk1de_>zjnD^{v(NT(;?=jh^YI)6uDzNLPpsvZ%R?wmob9t5muR9UY}tKeGT7;j>% z_c8ifxXWZ{qJ^n_mXejoK}_QiW>7~1kE4SHJcXxm5}(2|_yWF&FX1`7fS2%1d<);j5AZ|$ z2tURz@hkjJK4GM|A?%aa#TKtoue5lB4KQ-_cH)ih*tsk9pNRMBBB9vIs{5*!uBdNp zYT3BC^ZJ{e^DbwL%2$$7GH-8@E&|ZD!c+;!!`H;eu7^RZ~<~(e^FF%Ij+Vv_yDfO zIBvi;QsO4;z!YxBow$qCn8sc_g#9>3YRvj-9K{ouLl5($%4vKOpT=kK*+7*q<16?Y zo}Z(^o}&6B;-%6WoO2ijf%7ERiu`|j>Hq%&?+`);LI(bi3}9I*o$8{M6;HRhixjzb zjOsC}q6oifiGBz*emYL*r{jb{I_~?%$W_E71DuvfBUJwNKLWz@zxd-dJpb?E^ZzgF Ceb0XY literal 0 HcmV?d00001 diff --git a/src/components/ng2-swipe-cards/.DS_Store b/src/components/ng2-swipe-cards/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..3ed109807b81388c78499671fa3924cd86b3bd6d GIT binary patch literal 6148 zcmeHK&2G~`5S~o~b%GEPLTb7Bg2W-MnzV|F5Rw(jp*N%;H~?zx*h($A-Y9lzAO!is zL%D9Omja@qsf)}W3CTSwm5&WFnq>y5o zp!^L{{$jd-l~qXmhPbOFI*3b=7tuS~<)XP5FSaW2_Ypm!F72Sa7RVhVa|iSJm^Vb< zE`CG_Yf1?`t491>8S(C4M_gXj&&n8^SVw|6M`|~fJ=FY4G#;(22Lt9W@7!9C<=9nN z1}p<>!~oX^7s}`vEH$dqfks^cfOT{$fz6)_oZ}kw43-+ziZ+= zgQZ5DPQqM1gqc~G8;Ve~V|-VIlkha!)-qrjSY%*BKeqV%e|G)-e=*5ESq3Zv{}ltG zel$4hV@u|2UD_Os`;oO@D3$)P!{}ww zFHc){pQ@|~ldM0UoBrlilEtd*s!0~-oacF13=qp7I&JMtr~5~KTORnGSzAv1{mxNa z9v*aNGtb+4;6FJz8(v&aKg>RU<~m5=#A3N)@i%;dQLFNOZ=9tndk4Rc*#TQ6B{TwC zkLa9vi{aaat!N0pq6}jh73|9?pF0!5sn|l6c~0+NE8_rphm_WH($|HY+t=3T69vDzOCnb38z5)9ZK;N4n@)wo4C1{?#c#DG2@BvfNyu`lknh)EQVe-Il)ppI!a_uLrrFW56-+UojvW$Gzh&O0swBQgQmOmC!S& p7Co;r$RTL#b* zB2bty6y=n_ODg8Fx#1-$px@mx^+?ef^-<2=-$AB&QKlmF>&3AHU;k?i(IcE_T=X61 zI_hU>QEqJ9h}uGZap{fMXn2jc-ck2djk{%74vS7W_+e^koJ21Es#G?M!{}R@jE|e| zKdZb9(>xj6N? zoqFEtM}PO=`gDqbP)wu&CUlLVX*@_Y07V)b4=s%)$PE5HgofdX#F^H!c60(yUt zaE*b(%A(miP?#$Ku#RS9i1qKH=17Nu!^$FhV8T*?mMYv8Ls&Z6rOOK(Ru(Otgu8qQ zH?wdz6k%q^_|l}42rP2R3a|oq640;0ax-Rt0y+}S$! waQdv3@DsQ;omW|$Q4pA;7`gf=UV|G$yVLh($ literal 0 HcmV?d00001 diff --git a/src/components/swipe-cards/shared/components/.DS_Store b/src/components/swipe-cards/shared/components/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..0c117481670cf2167478f9ffdb930f8f68d1b3fa GIT binary patch literal 6148 zcmeHKu};H441F#gijYu&0md6ysKm??Rb^o22cRtyL((AH?wR=$z7n3#R+XlpZb*PF z+3)Uh_T}D1@fm=O*Yj&&2w*@}>>bj4A^Kc(M|wWeCrXYn!VEPkywHaH1_t!(PI+pN zc*Pv;^IPBv3#{_k63e@?o=wYo%K7@Syi>0}vaki5?TPWsNEOOXl$%bJlRZ&Z-S?35 zPK4uDgo1)7zhUb1q004DuW}#u)#nu5DaV>(EmfC zDi)57Vg7Z{-A85l4If681e~4%#~=a;w6TdJI9mHD;yg`bBB2GA-=MB zg(7)%#!q=Vq%aH{3;W@sQLf)`}%*|DSE*`Fz}}skpAR;GUk>%x9)6Cb8VtN qP*rqZW4Li)Bd21{)l_^z&BF1d4PxQg7;=OX7lBMem|);X8TbU!!A^t# literal 0 HcmV?d00001 diff --git a/src/components/swipe-cards/shared/components/card.component.css b/src/components/swipe-cards/shared/components/card.component.css new file mode 100644 index 0000000..90bfa78 --- /dev/null +++ b/src/components/swipe-cards/shared/components/card.component.css @@ -0,0 +1,62 @@ +.card-wrapper { + width: 100%; + height: 100%; +} + +:host { + transition: transform 1s ease; + position: absolute; + border-radius: 2px; + border: 1px solid white; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3) !important; + transition: transform 0.2s ease; + background-color: white; + touch-action: none !important; +} + +:host(.card-heap) { + transition: transform 1s ease; + display: block; + position: absolute; + background-color: white; + box-shadow: 0 0 0 rgba(0, 0, 0, 0) !important; + transform: perspective(400px) translate3d(0, 30px, -30px); + visibility: hidden; +} + +:host(.card-heap):nth-child(1) { + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3) !important; + z-index:3; + visibility: visible; + transform: perspective(400px) translate3d(0, 0px, 0px); +} + +:host(.card-heap):nth-child(2) { + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3) !important; + z-index:2; + visibility: visible; + transform: perspective(400px) translate3d(0, 30px, -30px); +} + +:host(.card-heap):nth-child(3) { + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3) !important; + z-index:1; + visibility: visible; + transform: perspective(400px) translate3d(0, 60px, -60px); +} + +:host .card-overlay { + transform: translateZ(0); + opacity: 0; + border-radius: 2px; + position: absolute; + width: 100%; + height: 10px; + top: 0; + left: 0; + display: flex; + align-items: center; + justify-content: center; + overflow: hidden; + color: white; +} \ No newline at end of file diff --git a/src/components/swipe-cards/shared/components/card.component.html b/src/components/swipe-cards/shared/components/card.component.html new file mode 100644 index 0000000..cddc10e --- /dev/null +++ b/src/components/swipe-cards/shared/components/card.component.html @@ -0,0 +1,4 @@ +
+ +
+
\ No newline at end of file diff --git a/src/components/swipe-cards/shared/components/card.component.ts b/src/components/swipe-cards/shared/components/card.component.ts new file mode 100755 index 0000000..49f3de0 --- /dev/null +++ b/src/components/swipe-cards/shared/components/card.component.ts @@ -0,0 +1,255 @@ +import { + AfterViewChecked, + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + ElementRef, + EventEmitter, + HostBinding, + HostListener, + Input, + OnDestroy, + OnInit, + Output, + Renderer2, + ViewChild +} from '@angular/core'; + +import {Subscription} from 'rxjs/Subscription'; +import 'rxjs/add/operator/filter'; + +import {SwipeCardsApi} from '@components/swipe-cards/shared/services/swipe-cards-api.service'; + +@Component({ + moduleId: module.id, + selector: 'card', + templateUrl: './card.component.html', + styleUrls: ['./card.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) + +export class CardComponent implements AfterViewChecked, OnInit, OnDestroy { + @Input() card: any; + @Input() cardElement: any; + @Input() position: number; + @Input() beforeSwipeAction: (event: any) => boolean; + + @Input() set config(config) { + + if (config) { + this._config = config; + } + } + + @Input() set orientation(value: string) { + this._orientation = value || 'xy'; + } + + get config() { + return this._config; + } + + get orientation() { + return this._orientation; + } + + @Output() onLike: EventEmitter = new EventEmitter(); + @Output() onDislike: EventEmitter = new EventEmitter(); + @Output() onRelease: EventEmitter = new EventEmitter(); + @Output() onSwipe: EventEmitter = new EventEmitter(); + @Output() onAbort: EventEmitter = new EventEmitter(); + @Output() onDisallowed: EventEmitter = new EventEmitter(); + @Output() onSpecial: EventEmitter = new EventEmitter(); + @Output() removeCard: EventEmitter = new EventEmitter(); + @ViewChild('overlay') overlay: ElementRef; + + public releaseRadius: any; + public released: Boolean = false; + public element: HTMLElement; + + public direction: any = { + x: 0, + y: 0 + }; + + private _config: any; + private _orientation = 'xy'; + + private swipeActionSubscription: Subscription; + + @HostBinding('class.card-heap') isCardHeapClass = true; + + @HostListener('pan', ['$event']) + onPan(event: any) { + + if (!this.config.fixed && !this.released && this.position === 0) { + this.onSwipeCb(event); + } + } + + @HostListener('panend', ['$event']) + onPanEnd(event: any) { + + if (!this.config.fixed && !this.released && this.position === 0) { + + if ((this._orientation === 'x' && (this.releaseRadius.x < event.deltaX || this.releaseRadius.x * -1 > event.deltaX)) || (this._orientation === 'y' && (this.releaseRadius.y < event.deltaY || this.releaseRadius.y * -1 > event.deltaY)) || ((this.releaseRadius.x < event.deltaX || this.releaseRadius.x * -1 > event.deltaX) || (this.releaseRadius.y < event.deltaY || this.releaseRadius.y * -1 > event.deltaY))) { + this.onReleaseCb(event); + } + else { + this.onAbortCb(event); + } + } + } + + constructor( + private cd: ChangeDetectorRef, + protected el: ElementRef, + private renderer: Renderer2, + private swipeCardsApi: SwipeCardsApi + ) {} + + ngOnInit() { + + this.element = this.el.nativeElement; + + this.swipeActionSubscription = this.swipeCardsApi.swipeAction$ + .filter(() => this.position === 0) + .subscribe((options: any) => { + + if (this.beforeSwipeAction) { + + const canProceedWithAction: boolean = this.beforeSwipeAction({action: options.action, card: this.card}); + + if (canProceedWithAction) { + this.onAction(options.action, options.direction); + } + else { + this.onDisallowed.emit({action: options.action, card: this.card}); + } + } + else { + this.onAction(options.action, options.direction); + } + } + ); + } + + ngAfterViewChecked() { + + if (this.element.parentElement) { + + const height = this.element.parentElement.clientHeight; + const width = this.element.parentElement.clientWidth; + + this.renderer.setStyle(this.element, 'height', height + 'px'); + this.renderer.setStyle(this.element, 'width', width + 'px'); + + this.releaseRadius = { + x: width / 4, + y: height / 4 + }; + } + } + + ngOnDestroy() { + this.swipeActionSubscription.unsubscribe(); + } + + translate(params: any) { + + if (!this.config.fixed) { + this.renderer.setStyle(this.element, 'transition', 'transform ' + (params.time || 0) + 's ease'); + this.renderer.setStyle(this.element, 'webkitTransform', `translate3d(${(params.x && (!this._orientation || this._orientation.indexOf('x') !== -1) ? (params.x) : 0)}px, ${(params.y && (!this._orientation || this._orientation.indexOf('y') !== -1) ? (params.y) : 0)}px, 0) rotate(${params.rotate || 0}deg)`); + } + } + + onSwipeCb(event: any) { + + const like = (this.orientation === 'y' && event.deltaY < 0) || (this.orientation !== 'y' && event.deltaX > 0); + const opacity = (event.distance < 0 ? event.distance * -1 : event.distance) * 0.5 / this.element.offsetWidth; + + if (!!this.config.overlay) { + + this.renderer.setStyle(this.overlay.nativeElement, 'transition', 'opacity 0s ease'); + this.renderer.setStyle(this.overlay.nativeElement, 'opacity', opacity.toString()); + this.renderer.setStyle(this.overlay.nativeElement, 'background-color', this.config.overlay[like ? 'like' : 'dislike'].backgroundColor); + } + + this.translate({ + x: event.deltaX, + y: event.deltaY, + rotate: ((event.deltaX * 20) / this.element.clientWidth) + }); + + this.onSwipe.emit(event); + } + + onAbortCb(event: any): void { + + if (!!this.config.overlay) { + this.renderer.setStyle(this.overlay.nativeElement, 'transition', 'opacity 0.2s ease'); + this.renderer.setStyle(this.overlay.nativeElement, 'opacity', '0'); + } + + this.translate({ + x: 0, + y: 0, + rotate: 0, + time: 0.2 + }); + + this.onAbort.emit(event); + } + + onReleaseCb(event: any): void { + + this.released = true; + + const like = (this.orientation === 'y' && event.deltaY < 0) || (this.orientation !== 'y' && event.deltaX > 0); + + if (this.beforeSwipeAction) { + + const canProceedWithAction: boolean = this.beforeSwipeAction({action: like ? 'like' : 'dislike', card: this.card}); + + if (canProceedWithAction) { + this.onAction(like ? 'like' : 'dislike', like ? 'right' : 'left'); + } + else { + this.onDisallowed.emit({action: like ? 'like' : 'dislike', card: this.card}); + } + } + else { + this.onAction(like ? 'like' : 'dislike', like ? 'right' : 'left'); + } + + this.onRelease.emit(event); + } + + onAction(action: string, direction: string): void { + + const el = this.element; + const x = (el.offsetWidth + el.clientWidth) * (direction === 'right' ? 1 : -1); + const y = (el.offsetHeight + el.clientHeight) * (direction === 'right' ? -1 : 1); + + this.translate({ + x: x, + y: y, + rotate: (x * 20) / el.clientWidth, + time: 0.8 + }); + + this.renderer.setStyle(this.overlay.nativeElement, 'transition', 'opacity 0.4s ease'); + this.renderer.setStyle(this.overlay.nativeElement, 'opacity', '0.5'); + this.renderer.setStyle(this.overlay.nativeElement, 'background-color', this.config.overlay[action].backgroundColor); + + switch (action) { + case 'like': this.onLike.emit({like: true, card: this.card}); break; + case 'dislike': this.onDislike.emit({like: false, card: this.card}); break; + case 'special': this.onSpecial.emit({special: true, card: this.card}); break; + } + + setTimeout(() => { + this.removeCard.emit(this.card); + }, 200); + } +} diff --git a/src/components/swipe-cards/shared/components/card.module.ts b/src/components/swipe-cards/shared/components/card.module.ts new file mode 100644 index 0000000..2dbb702 --- /dev/null +++ b/src/components/swipe-cards/shared/components/card.module.ts @@ -0,0 +1,18 @@ +import {NgModule} from '@angular/core'; +import {CommonModule} from '@angular/common'; + +import {CardComponent} from './card.component'; + +@NgModule({ + imports: [ + CommonModule + ], + exports: [ + CardComponent + ], + declarations: [ + CardComponent + ] +}) + +export class CardModule {} diff --git a/src/components/swipe-cards/shared/directives/action.directive.ts b/src/components/swipe-cards/shared/directives/action.directive.ts new file mode 100644 index 0000000..6d0144f --- /dev/null +++ b/src/components/swipe-cards/shared/directives/action.directive.ts @@ -0,0 +1,25 @@ +import {Directive, Input, HostListener} from '@angular/core'; + +import {SwipeCardsApi} from '@components/swipe-cards/shared/services/swipe-cards-api.service'; + +@Directive({ + selector: '[swipeCardsAction]' +}) + +export class ActionDirective { + @Input() action: string; + @Input() direction: string; + @Input() beforeSwipeAction: () => boolean; + + constructor(private swipeCardsApi: SwipeCardsApi) {} + + @HostListener('click') + swipeCard(): void { + + this.swipeCardsApi.swipe({ + action: this.action, + beforeSwipeAction: this.beforeSwipeAction, + direction: this.direction + }); + } +} diff --git a/src/components/swipe-cards/shared/directives/tinder-card.directive.ts b/src/components/swipe-cards/shared/directives/tinder-card.directive.ts new file mode 100755 index 0000000..9db1a30 --- /dev/null +++ b/src/components/swipe-cards/shared/directives/tinder-card.directive.ts @@ -0,0 +1,274 @@ +import { + Component, + Directive, + ElementRef, + EventEmitter, + HostBinding, + HostListener, + Input, + OnChanges, + OnDestroy, + OnInit, + Output, + Renderer +} from '@angular/core'; + + +@Directive({ + selector: '[tinder-card]' +}) + +export class TinderCardDirective implements OnChanges, OnDestroy, OnInit { + @Input() card: any; + + @Input() set overlay(value: any) { + this._overlay = value || {}; + } + + @Input() set callLike(value: EventEmitter) { + this._callLike = value || new EventEmitter(); + this.initCallLike(); + } + + @Input() set callDislike(value: EventEmitter) { + this._callDislike = value || new EventEmitter(); + this.initCallDislike(); + } + + @Input() fixed: boolean; + @Input() orientation = 'xy'; + @Output() onLike: EventEmitter = new EventEmitter(); + @Output() onDislike: EventEmitter = new EventEmitter(); + + get overlay() { + return this._overlay; + } + + get callLike() { + return this._callLike; + } + + public like: boolean; + + public element: HTMLElement; + public renderer: Renderer; + public overlayElement: HTMLElement; + + private _overlay: any; + private _callLike: EventEmitter; + private _callDislike: EventEmitter; + + // @HostBinding('class.card-heap') isCardHeapClass = true; + + // @HostListener('onSwipe', ['$event']) + // onSwipe(event: any) { + + // const like = (this.orientation === 'y' && event.deltaY < 0) || (this.orientation !== 'y' && event.deltaX > 0); + // const opacity = (event.distance < 0 ? event.distance * -1 : event.distance) * 0.5 / this.element.offsetWidth; + + // if (!!this._overlay) { + + // this.renderer.setElementStyle(this.overlayElement, 'transition', 'opacity 0s ease'); + // this.renderer.setElementStyle(this.overlayElement, 'opacity', opacity.toString()); + // this.renderer.setElementStyle(this.overlayElement, 'background-color', this._overlay[like ? 'like' : 'dislike'].backgroundColor); + // } + + // this.translate({ + // x: event.deltaX, + // y: event.deltaY, + // rotate: ((event.deltaX * 20) / this.element.clientWidth) + // }); + // } + + // @HostListener('onAbort', ['$event']) + // onAbort(event: any) { + + // if (!!this._overlay) { + // this.renderer.setElementStyle(this.overlayElement, 'transition', 'opacity 0.2s ease'); + // this.renderer.setElementStyle(this.overlayElement, 'opacity', '0'); + // } + // } + + // @HostListener('onRelease', ['$event']) + // onRelease(event: any) { + + // const like = (this.orientation === 'y' && event.deltaY < 0) || (this.orientation !== 'y' && event.deltaX > 0); + + // if (like) { + + // if (this._callLike) { + // this._callLike.emit({like: like, card: this.card}); + // } + + // if (this.onLike) { + // this.onLike.emit({like: like, card: this.card}); + // } + // } + // else { + + // if (this._callLike) { + // this._callLike.emit({like: like, card: this.card}); + // } + + // if (this.onLike) { + // this.onLike.emit({like: like, card: this.card}); + // } + // this.onDislike.emit({like: false, card: this.card}); + // } + + + // } + + constructor(el: ElementRef, renderer: Renderer) { + this.renderer = renderer; + this.element = el.nativeElement; + } + + ngOnInit() { + this.initOverlay(); + + this._overlay = this._overlay || {}; + this.orientation = this.orientation || 'xy'; + this._callLike = this._callLike || new EventEmitter(); + this.initCallLike(); + } + + ngOnChanges(changes) { + + if (changes.callLike) { + this._callLike = changes.callLike.currentValue || changes.callLike.previousValue || new EventEmitter(); + this.initCallLike(); + } + + if (changes.overlay) { + this._overlay = changes.overlay.currentValue || changes.overlay.previousValue || {}; + } + } + + ngOnDestroy() { + + if (this._callLike) { + this._callLike.unsubscribe(); + } + } + + onReleaseLikeCb(event: any) { + + this.like = event.like; + + const el = this.element; + const x = (el.offsetWidth + el.clientWidth) * ((!!event.like ? 1 : -1) || 0); + const rotate = (x * 20) / el.clientWidth; + + if (this._overlay) { + const overlayElm = this.element.querySelector('.tinder-overlay'); + this.renderer.setElementStyle(overlayElm, 'transition', 'transform 0.6s ease'); + this.renderer.setElementStyle(overlayElm, 'opacity', '0.5'); + } + } + + onSwipeLikeCb(event: any) { + + if (this._overlay) { + + const overlayElm = this.element.querySelector('.tinder-overlay'); + const opacity = (event.distance < 0 ? event.distance * -1 : event.distance) * 0.5 / this.element.offsetWidth; + + this.renderer.setElementStyle(overlayElm, 'transition', 'opacity 0s ease'); + this.renderer.setElementStyle(overlayElm, 'opacity', opacity.toString()); + } + } + + destroy(delay: number = 0) { + + setTimeout(() => { + this.element.remove(); + }, delay); + } + + translate(params: any) { + + if (!this.fixed) { + + this.renderer.setElementStyle(this.element, 'transition', 'transform ' + (params.time || 0) + 's ease'); + this.renderer.setElementStyle(this.element, 'webkitTransform', 'translate3d(' + + (params.x && (!this.orientation || this.orientation.indexOf('x') !== -1) ? (params.x) : 0) + + 'px, ' + + (params.y && (!this.orientation || this.orientation.indexOf('y') !== -1) ? (params.y) : 0) + + 'px, 0) rotate(' + + params.rotate + + 'deg)'); + } + } + + initOverlay() { + + if (!!this._overlay) { + + this.overlayElement = document.createElement('div'); + this.overlayElement.className += ' card-overlay'; + + this.element.appendChild(this.overlayElement); + + this.renderer.setElementStyle(this.overlayElement, 'transform', 'translateZ(0)'); + this.renderer.setElementStyle(this.overlayElement, 'opacity', '0'); + this.renderer.setElementStyle(this.overlayElement, 'border-radius', '2px'); + this.renderer.setElementStyle(this.overlayElement, 'position', 'absolute'); + this.renderer.setElementStyle(this.overlayElement, 'width', '100%'); + this.renderer.setElementStyle(this.overlayElement, 'height', '100%'); + this.renderer.setElementStyle(this.overlayElement, 'top', '0'); + this.renderer.setElementStyle(this.overlayElement, 'left', '0'); + this.renderer.setElementStyle(this.overlayElement, 'display', 'flex'); + this.renderer.setElementStyle(this.overlayElement, 'align-items', 'center'); + this.renderer.setElementStyle(this.overlayElement, 'justify-content', 'center'); + this.renderer.setElementStyle(this.overlayElement, 'overflow', 'hidden'); + this.renderer.setElementStyle(this.overlayElement, 'color', 'white'); + } + } + + initCallLike() { + + this._callLike.subscribe((params: any) => { + + const el = this.element; + const x = (el.offsetWidth + el.clientWidth) * (params.like ? 1 : -1); + const y = (el.offsetHeight + el.clientHeight) * (params.like ? -1 : 1); + + this.translate({ + x: x, + y: y, + rotate: (x * 20) / el.clientWidth, + time: 0.8 + }); + + this.renderer.setElementStyle(this.overlayElement, 'transition', 'opacity 0.4s ease'); + this.renderer.setElementStyle(this.overlayElement, 'opacity', '0.5'); + this.renderer.setElementStyle(this.overlayElement, 'background-color', this._overlay[params.like ? 'like' : 'dislike'].backgroundColor); + + this.destroy(200); + }); + } + + initCallDislike() { + + this._callLike.subscribe((params: any) => { + + const el = this.element; + const x = (el.offsetWidth + el.clientWidth) * (params.like ? 1 : -1); + const y = (el.offsetHeight + el.clientHeight) * (params.like ? -1 : 1); + + this.translate({ + x: x, + y: y, + rotate: (x * 20) / el.clientWidth, + time: 0.8 + }); + + this.renderer.setElementStyle(this.overlayElement, 'transition', 'opacity 0.4s ease'); + this.renderer.setElementStyle(this.overlayElement, 'opacity', '0.5'); + this.renderer.setElementStyle(this.overlayElement, 'background-color', this._overlay[params.like ? 'like' : 'dislike'].backgroundColor); + + this.destroy(200); + }); + } +} diff --git a/src/components/swipe-cards/shared/services/swipe-cards-api.service.ts b/src/components/swipe-cards/shared/services/swipe-cards-api.service.ts new file mode 100644 index 0000000..2f33297 --- /dev/null +++ b/src/components/swipe-cards/shared/services/swipe-cards-api.service.ts @@ -0,0 +1,18 @@ +import {Injectable} from '@angular/core'; +import {Observable} from 'rxjs/Observable'; +import {Subject} from 'rxjs/Subject'; + +@Injectable() + +export class SwipeCardsApi { + + private swipeAction: Subject = new Subject(); + + public swipeAction$: Observable = this.swipeAction.asObservable(); + + constructor() {} + + swipe(options: any): void { + this.swipeAction.next(options); + } +} diff --git a/src/components/swipe-cards/swipe-cards.component.css b/src/components/swipe-cards/swipe-cards.component.css new file mode 100644 index 0000000..8f20aae --- /dev/null +++ b/src/components/swipe-cards/swipe-cards.component.css @@ -0,0 +1,13 @@ +.swipe-cards { + width: 100%; + height: 100%; +} + +.swipe-cards-controls { + position: absolute; + bottom: 0; + left: 0; + width: 100%; + padding: 10px 15px; + text-align: center; +} \ No newline at end of file diff --git a/src/components/swipe-cards/swipe-cards.component.html b/src/components/swipe-cards/swipe-cards.component.html new file mode 100644 index 0000000..e483b12 --- /dev/null +++ b/src/components/swipe-cards/swipe-cards.component.html @@ -0,0 +1,21 @@ +
+ + + +
+ +
+
\ No newline at end of file diff --git a/src/components/swipe-cards/swipe-cards.component.ts b/src/components/swipe-cards/swipe-cards.component.ts new file mode 100644 index 0000000..bfb60b1 --- /dev/null +++ b/src/components/swipe-cards/swipe-cards.component.ts @@ -0,0 +1,104 @@ +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + ContentChild, + EventEmitter, + Input, + OnInit, + Output, + TemplateRef +} from '@angular/core'; + +@Component({ + moduleId: module.id, + selector: 'swipe-cards', + templateUrl: './swipe-cards.component.html', + styleUrls: ['./swipe-cards.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) + +export class SwipeCardsComponent implements OnInit { + @ContentChild('cardRef', {read: TemplateRef}) cardRef: TemplateRef; + @ContentChild('controlsRef', {read: TemplateRef}) controlsRef: TemplateRef; + @Input() beforeSwipeAction: (event: any) => any; + @Input() cards: any[] = []; + + @Input() set config(config) { + + if (config) { + this._config = config; + } + } + + get config() { + return this._config; + } + + @Output() liked: EventEmitter = new EventEmitter(); + @Output() disliked: EventEmitter = new EventEmitter(); + @Output() swiped: EventEmitter = new EventEmitter(); + @Output() aborted: EventEmitter = new EventEmitter(); + @Output() disallowed: EventEmitter = new EventEmitter(); + @Output() special: EventEmitter = new EventEmitter(); + + private _config: any = { + orientation: 'x', + fixed: false, + overlay: { + like: { + backgroundColor: '#28e93b' + }, + dislike: { + backgroundColor: '#e92828' + }, + special: { + backgroundColor: '#ffde39' + } + } + }; + + constructor(private cd: ChangeDetectorRef) {} + + ngOnInit() {} + + onLike(event: any): void { + console.log('liked', event) + this.liked.emit(event); + } + + onDislike(event: any): void { + console.log('disliked', event) + this.disliked.emit(event); + } + + onSwipe(event: any): void { + // console.log('swiped', event) + this.swiped.emit(event); + } + + onAbort(event: any): void { + console.log('aborted', event) + this.aborted.emit(event); + } + + onDisallowed(event: any): void { + this.disallowed.emit(event); + } + + onSpecial(event: any): void { + this.special.emit(event); + } + + removeCard(event: any, i: number): void { + + // setTimeout(() => { + this.cards.splice(i, 1); + this.cd.markForCheck(); + // }, 800); + } + + trackByFn(i, item): number { + return item._id; + } +} diff --git a/src/components/swipe-cards/swipe-cards.module.ts b/src/components/swipe-cards/swipe-cards.module.ts new file mode 100755 index 0000000..fb33667 --- /dev/null +++ b/src/components/swipe-cards/swipe-cards.module.ts @@ -0,0 +1,35 @@ +import {NgModule} from '@angular/core'; +import {CommonModule} from '@angular/common'; + +import {CardModule} from './shared/components/card.module'; +import {ActionDirective} from './shared/directives/action.directive'; +import {SwipeCardsComponent} from './swipe-cards.component'; + +import {HammerGestureConfig, HAMMER_GESTURE_CONFIG} from '@angular/platform-browser'; + +export class HammerConfig extends HammerGestureConfig { + overrides = { + 'pan': { enable: true } + }; +} + +@NgModule({ + imports: [ + CommonModule, + CardModule + ], + declarations: [ + SwipeCardsComponent, + ActionDirective + ], + exports: [ + SwipeCardsComponent, + ActionDirective + ], + providers: [{ + provide: HAMMER_GESTURE_CONFIG, + useClass: HammerConfig + }] +}) + +export class SwipeCardsModule {} From f28f0cfaecbabe1aac66d32b61beeee238774467 Mon Sep 17 00:00:00 2001 From: Christoffer Skog Date: Wed, 24 May 2017 16:25:25 +0200 Subject: [PATCH 2/2] Fixed css file path --- src/.DS_Store | Bin 8196 -> 8196 bytes src/components/.DS_Store | Bin 8196 -> 10244 bytes src/components/swipe-cards/.DS_Store | Bin 6148 -> 8196 bytes src/components/swipe-cards/shared/.DS_Store | Bin 6148 -> 8196 bytes .../shared/components/card.component.ts | 2 +- .../swipe-cards/swipe-cards.component.ts | 2 +- 6 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/.DS_Store b/src/.DS_Store index 746597cc480f64e260cceaf7e7275b73bfaa0e82..43e5674c730821462044d217955003c1093fbe79 100644 GIT binary patch delta 1193 zcmeH_%TH556vpQ(1?CQ~TPWp%6oo=SC|Zz*JfZ~zO2lBWKnVm=+G}!SU-T9dG^Q#T zHNgkDCayHbjfrStLexYz?sP8;+_)j?!i`4b&ctbNB}k0^1I}U|=bM=`^Zm|}bIJMm zim4-cu!R3y&h&&Qnes@saxf#^ebwaT@Y2leM$+Xj_zj-RP+lY z0q=|);G5!kIZ_jAq2f_tY+jD4N;stFXk#7J1iRqzg@Zvk6jSx^A<0R_*K{h8e9)`L zh9Yu^=g;Ba=q{C{@kG4K@0b0QQk%1eB%5G!I`kQ#q?qg@PU0eyL?#h}MQx$L8s8-E z$p`Y0d?sJWSMm)2Ll&|jp&S*cL=|dr08MB?D>~7OKDaP|A$Ty32~5I|AVQc$6lbu6 zi@1c#xP`lTfK@!iBRs`3{Q(A~9x{*`lY@~Eywu9gwvw?w(Y5xPS$~4=$Uo@5%}y9$ z$}cb%mf5Q79L?>AxBS~-**2`&4|AQ?T-Y5KDy@tY7uI=26xUQ*t)hV!Lt6pmnvQldBn=C|xfuk}Rx?^#~@zs34NRBiWN;vPhQ6HFBNY zBdg>&c}3ok4f2j`l26($i_O=DcJw3>^y4^&;l>C?F%2(#9D~d;L^uW&F`Pvl=Wzjx zSjJV{zzQ_n#BJQ!fx@*N1t)QzlaSV4SO&3j&c2o@eK!^ak|q4BLfs*6Q2y`OmTkW| Gw)ZD`SpSd! delta 1134 zcmeH_+e=hI9LMMDC1*~%I_vK0Zl`U_b;C3-n4xQC>n-mIwdq=Ft?sD~>8^TK%Ltaj zA|sMIK`%w&t3iPT(PKA)Ac!OgEQ08zw|eNM>+Bv0B&dI&d6*x+?~mVaX1<@9_D}oo z%vf+$UvIOe%dKtglJ2wh6b03xXk?FiLOH+7}l6ozbv*NL9kg zitLuXlmRo=Ig~Q=<_>t&wV}1)EuLC^|BtL}HpD*U1f(KcxeS!#hG+tZP(>i1W?jElqA& z?oH@*;jj{>lzRLHB)I~S(1_ykml+u&lsHKV@sk$PM-(zjrik8eT^oN#K9bMmEBQ`- zke{GnSPc`L$b$#@D1#5xsKIvBqa7XCg)Ripj{zJ+2!}C>F=&Y4B+lS0W^f)luHiav z;3jV49vvHiF0h}%-KE|d(m&8*APL@ndN+lOh z7vzlUmI_JYX#%ZLt@4TLD#4I0I&&R4Qmy1?mXvPBNwS%FT@qz~=iyxmF2pkiE*n8Yz0$0?jn o5;=!UxGb1l5lrSVuP<5MxFu|MEX?L~U?IctCGEd6e>l^MUo4LY*8l(j diff --git a/src/components/.DS_Store b/src/components/.DS_Store index ac290a70bbb92d1f0c159a7aac85a71a94d778a0..9ed1d87e2bf702be1defc98acf90618ff89f940e 100644 GIT binary patch literal 10244 zcmeHMYitx%6h5a9Ix}tQ6oGDcS-MaPrL@o%gz|Lz0!l&9mbN_0KE`(9cBky@wm>PV zqDBqAA0*-{#KdSZ{6dX?G`=uue1t~Ps4*dGqJND4j6pp2-r04#Z7G@%49-nv&b{Z{ zGqd;1ckXxZT>!wkSg;Df0{{|TOsY9_8>cWkyT+6>OQVoP@&_=XA9Uyi4JyX;wiR{+ z>dIwAp*qNtVr2H+IKqwb_DDQBqG4(2M=9LdM!C5B%eCy#chl2d=dP<(HV2o{>2vOOj*7+j4cuNmzeyiSU7@b|>O4+o9@IwKYin*2H2e9><5RQFcOlg@byytG!P*b$>`}4f}eu z{IoV%9?6|q;3#yuToZNei23#XF+F-vEtaGab!uL&QskN=t7oL$LHZS2j39eckXu|u zt`bSw#q{+NjqXCNl)B1f)hD%@G!XHK3T26`nsV#y^qGZgwX#f7gYx!}&##4qYK5{= zQk&)WJ`qi!T%)X$^08d*2pBy*G)_~fYhCq{`hm2Uy?e9IjBf4K!t{DJy2c`xYX{TU z27_8qlB8m9sQ>{FL$S9~v}7W#LRd^RwGnpH!B;OFgk$g^JOn4;1vm|7;7vFSAH%2c z8GH_3!}str{04u(Mfejgp@cGKV-6PLTr9?UScxlf6|TXxSclEH8E?j|*ou2_FYZGh zc405>M-!uX81Ke=@IJgBAH%2d8GH_($J6*azK!qTyZ9b{g6HrX@dMN1Al@R{@jHG? zwMkN+Kx`#%+TQU%>~37Y;TnPXs~myc8>Y{gU0hmGy<$ym;^U@#&tzzEGzqwH7{y`2 zKk8^~3WzK2?SwY%z(hz_vly0=2p~nLbS{~FJ7X! z)l34fur6JuC~5|wTUcvWl64BNfsD{wj15FS?(A{%i7HsK~hWh=H}2kyX~ z7(k6M*^N3z2$Ox-j|cD$ypv<|UVH=}C3HSc=sbzTDOhZZqfkgZH8~dhS|Z_)5$>X% z4AI$FL{=|R&t$Ti!0WmtsR_oLL{5?M^4hUJU)gw-$Y~r-O7N8+CufnB(Epa4h|(w> z9ivHzO&6|N4rNr5Q*siSk;OC1;Ygk*C}){^UWr5DQwC34rde2~D17GNsme5z$PO{1 z@MLBxN@hoVX5l%^l$6qr#1tha4W?eZ&f!Rzu=otZY<2Xj#wQ^;K|o{p=jdhm=K}ly zKf+Ix_ZKPeX^Ddh&ZOLT;2fNfrIh()xHKv2n{f+gp$=joKV^IndnnTn;vpQsyC}b( zz$fuIvCqpmjHmDwd=+1#oTHN9ozz@^i;NTFGR`;c6doCC)NQQqy=t zsc_QD$tS7l!e%X-)J$QeB@6gBC!HYzFZN4iVWL z#bfvYK1^gIsODJ)y(I8$6z%vOU!>Xu;7cKxxQi|A2wa~LNEgHMu=D@MiU0q literal 8196 zcmeHLTWl0n82B+sStx}K#s?)bCpqUk zbN>IIGv}Lc&e?wf0K4*f0-zQE6siLGLTYYPgk98IN+cK&Mx=NE7Ybm)I2h1$i+4B> zG7vHlG7vHlG7vIwH!whFwkS%8b6;A+He?`V;GSea><=-j0+RtwOZ2x6YWx&{u#ATN zLVd~u{FqpP$pEJ%`k{1BnLS_-ib0BjGMwx&;ZB$ga9W}a2bAG}!OR$BDCo~laWUT= zFex!?Lk27xHWgsQ2((bnK0ANUf(ALz!R@h}Y{qgjOH!?xL*Kw@teJyjy zGz!DKy4$fm&9sf&Ecc;))7G6rmy@@3*T2sJ%^Rgyf1+VtH#ynTo`}a1tu0gW*kq!m ztvw!VYi^&KQk0s8#Fn08W2a6}K0futS$}1K!Dd4Y-CWMydEFD~oNYPw2ziRTEc{#C zzCoU|xh)ZOH*&f@(Rzc^*7a@wh|?15wcLqrsiDZLMGbv>LqiMIp51%)4s)J&je~}z zdFF(X(mcZ*<`phUt0(K&2Xe*)38z2nSotxVER|W!%I3-0=(Mb~dD?IXJmaJ%dWT&n z=XLvz1180av0+{%;`fhxrenK@jGRlz*u_Olm#B3Rv_}q;sBO!PYU*SmO^fqce1kvF|l zyk1de_>zjnD^{v(NT(;?=jh^YI)6uDzNLPpsvZ%R?wmob9t5muR9UY}tKeGT7;j>% z_c8ifxXWZ{qJ^n_mXejoK}_QiW>7~1kE4SHJcXxm5}(2|_yWF&FX1`7fS2%1d<);j5AZ|$ z2tURz@hkjJK4GM|A?%aa#TKtoue5lB4KQ-_cH)ih*tsk9pNRMBBB9vIs{5*!uBdNp zYT3BC^ZJ{e^DbwL%2$$7GH-8@E&|ZD!c+;!!`H;eu7^RZ~<~(e^FF%Ij+Vv_yDfO zIBvi;QsO4;z!YxBow$qCn8sc_g#9>3YRvj-9K{ouLl5($%4vKOpT=kK*+7*q<16?Y zo}Z(^o}&6B;-%6WoO2ijf%7ERiu`|j>Hq%&?+`);LI(bi3}9I*o$8{M6;HRhixjzb zjOsC}q6oifiGBz*emYL*r{jb{I_~?%$W_E71DuvfBUJwNKLWz@zxd-dJpb?E^ZzgF Ceb0XY diff --git a/src/components/swipe-cards/.DS_Store b/src/components/swipe-cards/.DS_Store index 3dffeea81beb41890a124a6973f624815d197854..d6572cf67c74838af477222f1a6cddefb635ab3d 100644 GIT binary patch literal 8196 zcmeHMPiz!b82`R)DKkT%(^6nsDQsvPi!QZWno@1S?G_Dp0N0`2V1NJnO8>dtJJ zQfe9#jS-?JGy(PCK|(kQdhnppcrhL{(I%o74pLBIFjY}0Pr0|%ApyyU&_ z&G+xU@0;Ju+iw;CFqt#r02%;D)EQMb(Xd8ia@JS1NWhVaBH06&VD#EQ7X;;|6o9Fq#cd% zgT0fP*>iK}=byRE;{Xg^Bv2u)m&PkJTM?!0&Gd*UBiTZfx0a$*HffJmD@R7HwCfC5 z=gbMU!lRIT>9Rj-&QL%jY1htW9B)FcOzU|R z=b2BKq-V{The?}EtC*+0Y2R`kZ`jOwMC%hRV$<^V@@=)d>znqq9_Z>`+`OeiRjQQg zkr~UgQnopiw+w%3O!v~JV_44VGjtDy=>tk!QVn^?)>Eb}SZzwXq&_B(%!u#=^FgIks>GN)nszf8+P5dzhqQ#G zej=S>Yai7;zi--fXn7Vza;K(^F@CpUn1&=t^@*k+PbxcQYG4nvK^OGH7#NU;i*N~E zfj8kg+=TbwWB3d{hcDnuxCP(A5AY-W1V6)V_zixCKTyI-tir9h8=G)1-j6Yi<3req z2k{X0;88q=eb|q~IDwOx!Ze;iA9HvXXYpCQfEV#4d>P-stN1qFz?=9EzKtwjoG z>=oCA5kI6^F&cwsE;6zf8tVv+4gV?{zbvv)7~Aet5X?b#AQQcouVRvTISNd zjUmAnjg>w@kY z+<=b=aNoeU@I8U;7r2dpWmt~eaTi9h9viR`o3RC3aX+>Z%pS&WOme6_&Y?Dj<9HHJ z5#XlqDYP+z4(17Nb9f%-@p*g!U*y2Mgs-d%c*AnE86u>4ZUM+^)%s|$$ zUFS6M8BA;FxX6PA({xwo{hJ2o5cBFsRTfxjgKSbBKiaFSdXeuNaS!fNd(^-1ciF}(#rxd{!t94E@laiU9q q7}7jSs>~)4&;>z>L+u~`5Ws#8gy%oqF_C|O@cduv)c&uZ|9=5&%X6s! delta 129 zcmZp1XfcprU|?W$DortDU=RQ@Ie-{Mvv5r;6q~50$jH7iU^g=(`(z%03j)T5X02smxl7rVi@v4N3sF*^r`ATv-a5D0Js30IIQ8wGXxz^GvK9$6MI@2p0bFpvr0#>a%sW(s2!sfP z2!sfP2!sgy7Z9K`n>Uu{+?UpH3=s$sxGoXk?}s>bE)#*Al~TMqXb>d;$pTV~MsunI zL`*!8i9pUuDMIO*vUNbRFudrdT&<%vm7dFdeLgJjyaJiosF zON^{4C|q5{6on}z>{RcBJJsv!e%5Q(GbhE$GR$N!?{i&yVXi0pEpuv2Ti@Y2zHT{Y zW|8~IfaMtORJ)sX3{PC=xb9C-tk`HwtDK&0Y-y;AH#9ZQ)WxS88k<|{;?4CfGc$~p z*EAgH9-lmSe)_`9eb0$}0D~6`l*;wOcrjnLwwLRW*+!n7&-SZ@Y}GZPIr zlIThMmg{)^X2v5qrj>dGLwCw`j%Q46A4s`&cGB_2w6c_Lr?R&0n{Bq;Yn?Z}LEk*< z^VzWHX8aD}IA~EU*Ntgf)-&%mDZt8^ofPJ2s9Y}I5ZiL|j{4j7-rhM^vbIz!Tc@hS zgtOyY_gH4;#FSARP1t_*Edo0w)OCu}`w+ETSgZDraKWgsWfl*;|;K~{!!%3#W!oTNNG zscwsQFztOd%J)91dwx&abZC2?T~}2!I>hyDhG80vvFb#vyz2sn>O{RZTk+v0>IM&LWOOC!#=7n}^e<8O>te~* z8`f{wTwS}fanJs?gX5dDSw}dm*6tI4)4N;#K14$d-xH4hF^(&g;M^j}Hh&K8#P| zQ^d(od^H~fbG;eIcAe8?=j>I%01GnobfxIyu?0Drc}tFtvq>>I@=q=I$MPlqv<%x~1XjsK1+V|R@_+wd91_k$1VRMtqK3iODJgCj^WQ z&1!WNstt|IEOZo1jLjzh5%AjFEY`xfvB8#kF*^r`ATv-q5D0Js30IIw8w