@angular/animations#query TypeScript Examples

The following examples show how to use @angular/animations#query. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: animations.ts    From Angular-Cookbook with MIT License 6 votes vote down vote up
ANIMATIONS = {
  LIST_ANIMATION: trigger('listAnimation', [
    transition('* <=> *', [
      query(
        ':enter',
        [
          style({
            opacity: 0,
          }),
          stagger(100, [
            animate(
              '0.5s ease',
              style({
                opacity: 1,
              })
            ),
          ]),
        ],
        { optional: true }
      ),
      query(
        ':leave',
        [
          stagger(100, [
            animate(
              '0.5s ease',
              style({
                opacity: 0,
              })
            ),
          ]),
        ],
        { optional: true }
      ),
    ]),
  ]),
}
Example #2
Source File: animations.ts    From Angular-Cookbook with MIT License 6 votes vote down vote up
ANIMATIONS = {
  LIST_ANIMATION: trigger('listAnimation', [
    transition('* <=> *', [
      query(':enter', [
        style({
          opacity: 0
        }),
        stagger(100, [
          animate('0.5s ease', style({
            opacity: 1
          }))
        ])
      ], { optional: true }),
      query(':leave', [
        stagger(100, [
          animate('0.5s ease', style({
            opacity: 0
          }))
        ])
      ], {optional: true})
    ]),
  ])
}
Example #3
Source File: helpers.ts    From youpez-admin with MIT License 6 votes vote down vote up
defaultRouterTransition = trigger('defaultRouterAnimation', [
  transition('* => *', [
    query(
      ':enter',
      [style({opacity: 0,})],
      {optional: true}
    ),
    query(
      ':leave',
      [style({opacity: 1,}), animate('0.3s cubic-bezier(.785, .135, .15, .86)', style({opacity: 0}))],
      {optional: true}
    ),
    query(
      ':enter',
      [style({opacity: 0,}), animate('0.3s cubic-bezier(.785, .135, .15, .86)', style({opacity: 1}))],
      {optional: true}
    )
  ])
])
Example #4
Source File: animations.ts    From Angular-Cookbook with MIT License 6 votes vote down vote up
ROUTE_ANIMATION = trigger('routeAnimation', [
  transition('* <=> *', [
    style({
      position: 'relative',
    }),
    query(
      ':enter, :leave',
      [
        style({
          position: 'absolute',
          width: '100%',
        }),
      ],
      { optional: true }
    ),
    query(
      ':enter',
      [
        style({
          opacity: 0,
        }),
      ],
      { optional: true }
    ),
    query(':leave', [animate('300ms ease-out', style({ opacity: 0 }))], {
      optional: true,
    }),
    query(':enter', [animate('300ms ease-in', style({ opacity: 1 }))], {
      optional: true,
    }),
  ]),
])
Example #5
Source File: animations.ts    From Angular-Cookbook with MIT License 6 votes vote down vote up
ROUTE_ANIMATION = trigger('routeAnimation', [
  transition('* <=> *', [
    style({
      position: 'relative'
    }),
    query(':enter, :leave', [
      style({
        position: 'absolute',
        width: '100%',
      })
    ], optional),
    query(':enter', [
      style({
        opacity: 0,
      })
    ], optional)
  ])
])
Example #6
Source File: animations.ts    From Angular-Cookbook with MIT License 6 votes vote down vote up
ANIMATIONS = {
  LIST_ANIMATION: trigger('listAnimation', [
    transition('* <=> *', [
      query(':enter', [
        style({
          opacity: 0
        }),
        stagger(100, [
          animate('0.5s ease', style({
            opacity: 1
          }))
        ])
      ], { optional: true }),
      query(':leave', [
        stagger(100, [
          animate('0.1s ease', style({
            opacity: 0
          }))
        ])
      ], {optional: true})
    ]),
  ])
}
Example #7
Source File: emote-list.component.ts    From App with MIT License 6 votes vote down vote up
ngAfterViewInit(): void {
		// Get persisted page options?
		this.route.queryParamMap.pipe(
			defaultIfEmpty({} as ParamMap),
			map(params => {
				return {
					page: params.has('page') ? Number(params.get('page')) : 0,
					search: {
						sortBy: params.get('sortBy') ?? 'popularity',
						sortOrder: params.get('sortOrder'),
						globalState: params.get('globalState'),
						query: params.get('query'),
						submitter: params.get('submitter'),
						channel: params.get('channel')
					}
				};
			}),
			tap(() => setTimeout(() => this.skipNextQueryChange = false, 0)),
			filter(() => !this.skipNextQueryChange)
		).subscribe({
			next: opt => {
				const d = {
					pageIndex: !isNaN(opt.page) ? opt.page : 0,
					pageSize: Math.max(EmoteListComponent.MINIMUM_EMOTES, this.calculateSizedRows() ?? 0),
					length: 0,
				};
				this.updateQueryParams(true);
				this.currentSearchOptions = opt.search as any;

				this.paginator?.page.next(d);
				this.pageOptions = d;
			}
		});

		this.pageSize.next(this.calculateSizedRows() ?? 0);
	}
Example #8
Source File: splash.component.ts    From bitcoin-s-ts with MIT License 6 votes vote down vote up
@Component({
  selector: 'splash',
  templateUrl: './splash.component.html',
  animations: [
  // the fade-in/fade-out animation.
  trigger('fadeOut', [
    transition(':leave', [
      query(':leave', animateChild(), {optional: true}),
      animate(300, style({opacity: 0}))
    ]),
  ]),
],
  styleUrls: ['./splash.component.scss']
})
export class SplashComponent implements OnInit {

  showSplash = false

  constructor() { }

  ngOnInit(): void {
    // Force reset splash key
    // localStorage.removeItem(SPLASH_KEY)

    const show = localStorage.getItem(SPLASH_KEY) === null
    this.showSplash = show
  }

  dontShowSplashAgainClick() {
    console.debug('dontShowSplashAgainClick()')
    localStorage.setItem(SPLASH_KEY, '1')
  }

  onClick() {
    this.showSplash = !this.showSplash
  }

}
Example #9
Source File: fade.animation.ts    From onchat-web with Apache License 2.0 6 votes vote down vote up
fadeRouteAnimation = trigger('fadeRouteAnimation', [
    transition('* <=> *', [
        style({ position: 'relative' }),
        query(':enter, :leave', [
            style({
                position: 'absolute',
                top: 0,
                left: 0,
                width: '100%',
                height: '100%'
            }),
            animateChild(),
        ], options),
        query(':enter', [
            style({ opacity: 0 })
        ], options),
        group([
            query(':leave', [
                animate('5s ease-in', style({ opacity: 0 }))
            ], options),
            query(':enter', [
                animate('5s ease-in', style({ opacity: 1 }))
            ], options)
        ]),
    ])
])
Example #10
Source File: mat-fab-menu.animations.ts    From fab-menu with MIT License 5 votes vote down vote up
speedDialFabAnimations = [
  trigger('fabToggler', [
    state('false', style({
      transform: 'rotate(0deg)'
    })),
    state('true', style({
      transform: 'rotate(225deg)'
    })),
    transition('* <=> *', animate('200ms cubic-bezier(0.4, 0.0, 0.2, 1)')),
  ]),
  trigger('fabsStagger', [
    transition('* => *', [

      query(':enter', style({opacity: 0}), {optional: true}),

      query(':enter', stagger('40ms',
        [
          animate('200ms cubic-bezier(0.4, 0.0, 0.2, 1)',
            keyframes(
              [
                style({opacity: 0, transform: 'translateY(10px)'}),
                style({opacity: 1, transform: 'translateY(0)'}),
              ]
            )
          )
        ]
      ), {optional: true}),

      query(':leave',
        animate('200ms cubic-bezier(0.4, 0.0, 0.2, 1)',
          keyframes([
            style({opacity: 1}),
            style({opacity: 0}),
          ])
        ), {optional: true}
      )

    ])
  ])
]
Example #11
Source File: route.animations.ts    From enterprise-ng-2020-workshop with MIT License 5 votes vote down vote up
STEPS_ALL: any[] = [
  query(':enter > *', style({ opacity: 0, position: 'fixed' }), {
    optional: true
  }),
  query(':enter .' + ROUTE_ANIMATIONS_ELEMENTS, style({ opacity: 0 }), {
    optional: true
  }),
  sequence([
    query(
      ':leave > *',
      [
        style({ transform: 'translateY(0%)', opacity: 1 }),
        animate(
          '0.2s ease-in-out',
          style({ transform: 'translateY(-3%)', opacity: 0 })
        ),
        style({ position: 'fixed' })
      ],
      { optional: true }
    ),
    query(
      ':enter > *',
      [
        style({
          transform: 'translateY(-3%)',
          opacity: 0,
          position: 'static'
        }),
        animate(
          '0.5s ease-in-out',
          style({ transform: 'translateY(0%)', opacity: 1 })
        )
      ],
      { optional: true }
    )
  ]),
  query(
    ':enter .' + ROUTE_ANIMATIONS_ELEMENTS,
    stagger(75, [
      style({ transform: 'translateY(10%)', opacity: 0 }),
      animate(
        '0.5s ease-in-out',
        style({ transform: 'translateY(0%)', opacity: 1 })
      )
    ]),
    { optional: true }
  )
]
Example #12
Source File: slide.animation.ts    From onchat-web with Apache License 2.0 5 votes vote down vote up
horizontalSlideInRouteAnimation = trigger('horizontalSlideInRouteAnimation', [
    transition(':increment', [
        style({ position: 'relative' }),
        query(':enter, :leave', [
            style({
                position: 'absolute',
                top: 0,
                left: 0,
                width: '100%'
            }),
            animateChild(),
        ], options),

        query(':enter', [
            style({ transform: 'translate3d(100%,0,0)' })
        ], options),

        group([
            query(':leave', [
                animate('.3s ease-out', style({ transform: 'translate3d(-100%,0,0)' }))
            ], options),
            query(':enter', [
                animate('.3s ease-out', style({ transform: 'none' }))
            ], options)
        ]),
    ]),

    transition(':decrement', [
        style({ position: 'relative' }),
        query(':enter, :leave', [
            style({
                position: 'absolute',
                top: 0,
                left: 0,
                width: '100%'
            }),
            animateChild(),
        ], options),

        query(':enter', [
            style({ transform: 'translate3d(-100%,0,0)' })
        ], options),

        group([
            query(':leave', [
                animate('.3s ease-out', style({ transform: 'translate3d(100%,0,0)' }))
            ], options),
            query(':enter', [
                animate('.3s ease-out', style({ transform: 'none' }))
            ], options)
        ])
    ])
])
Example #13
Source File: animation.ts    From mns with MIT License 5 votes vote down vote up
slideInAnimation =
  trigger('routeAnimations', [
    transition('HomePage <=> ProductsPage', [
      style({ position: 'relative' }),
      query(':enter, :leave', [
        style({
          position: 'absolute',
          top: 0,
          left: 0,
          width: '100%'
        })
      ]),
      query(':enter', [
        style({ left: '-100%' })
      ]),
      query(':leave', animateChild()),
      group([
        query(':leave', [
          animate('300ms ease-out', style({ left: '100%' }))
        ]),
        query(':enter', [
          animate('300ms ease-out', style({ left: '0%' }))
        ])
      ]),
      query(':enter', animateChild()),
    ]),
    transition('* <=> BlogPage', [
      style({ position: 'relative' }),
      query(':enter, :leave', [
        style({
          position: 'absolute',
          top: 0,
          left: 0,
          width: '100%'
        })
      ]),
      query(':enter', [
        style({ left: '-100%' })
      ]),
      query(':leave', animateChild()),
      group([
        query(':leave', [
          animate('200ms ease-out', style({ left: '100%' }))
        ]),
        query(':enter', [
          animate('300ms ease-out', style({ left: '0%' }))
        ])
      ]),
      query(':enter', animateChild()),
    ]),
    transition('* <=> ContactPage', [
      style({ position: 'relative' }),
      query(':enter, :leave', [
        style({
          position: 'absolute',
          top: 0,
          left: 0,
          width: '100%'
        })
      ]),
      query(':enter', [
        style({ left: '-100%' })
      ]),
      query(':leave', animateChild()),
      group([
        query(':leave', [
          animate('200ms ease-out', style({ left: '100%' }))
        ]),
        query(':enter', [
          animate('300ms ease-out', style({ left: '0%' }))
        ])
      ]),
      query(':enter', animateChild()),
    ])
  ])
Example #14
Source File: slide.animation.ts    From ng-ant-admin with MIT License 5 votes vote down vote up
horizontalSlideInRouteAnimation = trigger('horizontalSlideInRouteAnimation', [
  transition(':increment', [
    style({position: 'relative'}),
    query(':enter, :leave', [
      style({
        position: 'absolute',
        top: 0,
        left: 0,
        width: '100%'
      }),
      animateChild(),
    ], options),

    query(':enter', [
      style({transform: 'translate3d(100%,0,0)'})
    ], options),

    group([
      query(':leave', [
        animate('.3s ease-out', style({transform: 'translate3d(-100%,0,0)'}))
      ], options),
      query(':enter', [
        animate('.3s ease-out', style({transform: 'none'}))
      ], options)
    ]),
  ]),

  transition(':decrement', [
    style({position: 'relative'}),
    query(':enter, :leave', [
      style({
        position: 'absolute',
        top: 0,
        left: 0,
        width: '100%'
      }),
      animateChild(),
    ], options),

    query(':enter', [
      style({transform: 'translate3d(-100%,0,0)'})
    ], options),

    group([
      query(':leave', [
        animate('.3s ease-out', style({transform: 'translate3d(100%,0,0)'}))
      ], options),
      query(':enter', [
        animate('.3s ease-out', style({transform: 'none'}))
      ], options)
    ])
  ])
])
Example #15
Source File: panel.component.ts    From ngx-colors with MIT License 4 votes vote down vote up
@Component({
  selector: "ngx-colors-panel",
  templateUrl: "./panel.component.html",
  styleUrls: ["./panel.component.scss"],
  animations: [
    trigger("colorsAnimation", [
      transition("void => slide-in", [
        // Initially all colors are hidden
        query(":enter", style({ opacity: 0 }), { optional: true }),
        //slide-in animation
        query(
          ":enter",
          stagger("10ms", [
            animate(
              ".3s ease-in",
              keyframes([
                style({ opacity: 0, transform: "translatex(-50%)", offset: 0 }),
                style({
                  opacity: 0.5,
                  transform: "translatex(-10px) scale(1.1)",
                  offset: 0.3,
                }),
                style({ opacity: 1, transform: "translatex(0)", offset: 1 }),
              ])
            ),
          ]),
          { optional: true }
        ),
      ]),
      //popup animation
      transition("void => popup", [
        query(":enter", style({ opacity: 0, transform: "scale(0)" }), {
          optional: true,
        }),
        query(
          ":enter",
          stagger("10ms", [
            animate(
              "500ms ease-out",
              keyframes([
                style({ opacity: 0.5, transform: "scale(.5)", offset: 0.3 }),
                style({ opacity: 1, transform: "scale(1.1)", offset: 0.8 }),
                style({ opacity: 1, transform: "scale(1)", offset: 1 }),
              ])
            ),
          ]),
          { optional: true }
        ),
      ]),
    ]),
  ],
})
export class PanelComponent implements OnInit {
  @HostListener("document:mousedown", ["$event"])
  click(event) {
    if (this.isOutside(event)) {
      this.emitClose("cancel");
    }
  }

  @HostListener("document:scroll")
  onScroll() {
    this.onScreenMovement();
  }
  @HostListener("window:resize")
  onResize() {
    this.onScreenMovement();
  }

  @HostBinding("style.top.px") public top: number;
  @HostBinding("style.left.px") public left: number;
  @ViewChild("dialog") panelRef: ElementRef;
  constructor(
    public service: ConverterService,
    private cdr: ChangeDetectorRef
  ) {}

  public color = "#000000";
  public previewColor: string = "#000000";
  public hsva = new Hsva(0, 1, 1, 1);

  public colorsAnimationEffect = "slide-in";

  public palette = defaultColors;
  public variants = [];

  public colorFormats = formats;
  public format: ColorFormats = ColorFormats.HEX;

  public canChangeFormat: boolean = true;

  public menu = 1;

  public hideColorPicker: boolean = false;
  public hideTextInput: boolean = false;
  public acceptLabel: string;
  public cancelLabel: string;
  public colorPickerControls: "default" | "only-alpha" | "no-alpha" = "default";
  private triggerInstance: NgxColorsTriggerDirective;
  private TriggerBBox;
  public isSelectedColorInPalette: boolean;
  public indexSeleccionado;
  public positionString;
  public temporalColor;
  public backupColor;

  public ngOnInit() {
    this.setPosition();
    this.hsva = this.service.stringToHsva(this.color);
    this.indexSeleccionado = this.findIndexSelectedColor(this.palette);
  }
  public ngAfterViewInit() {
    this.setPositionY();
  }

  private onScreenMovement() {
    this.setPosition();
    this.setPositionY();
    if (!this.panelRef.nativeElement.style.transition) {
      this.panelRef.nativeElement.style.transition = "transform 0.5s ease-out";
    }
  }

  private findIndexSelectedColor(colors): number {
    let resultIndex = undefined;
    if (this.color) {
      for (let i = 0; i < colors.length; i++) {
        const color = colors[i];
        if (typeof color == "string") {
          if (
            this.service.stringToFormat(this.color, ColorFormats.HEX) ==
            this.service.stringToFormat(color, ColorFormats.HEX)
          ) {
            resultIndex = i;
          }
        } else {
          if (this.findIndexSelectedColor(color.variants) != undefined) {
            resultIndex = i;
          }
        }
      }
    }
    return resultIndex;
  }

  public iniciate(
    triggerInstance: NgxColorsTriggerDirective,
    triggerElementRef,
    color,
    palette,
    animation,
    format: string,
    hideTextInput: boolean,
    hideColorPicker: boolean,
    acceptLabel: string,
    cancelLabel: string,
    colorPickerControls: "default" | "only-alpha" | "no-alpha",
    position: "top" | "bottom"
  ) {
    this.colorPickerControls = colorPickerControls;
    this.triggerInstance = triggerInstance;
    this.TriggerBBox = triggerElementRef;
    this.color = color;
    this.hideColorPicker = hideColorPicker;
    this.hideTextInput = hideTextInput;
    this.acceptLabel = acceptLabel;
    this.cancelLabel = cancelLabel;
    if (format) {
      if (formats.includes(format)) {
        this.format = formats.indexOf(format.toLowerCase());
        this.canChangeFormat = false;
        if (
          this.service.getFormatByString(this.color) != format.toLowerCase()
        ) {
          this.setColor(this.service.stringToHsva(this.color));
        }
      } else {
        console.error("Format provided is invalid, using HEX");
        this.format = ColorFormats.HEX;
      }
    } else {
      this.format = formats.indexOf(this.service.getFormatByString(this.color));
    }

    this.previewColor = this.color;
    this.palette = palette ?? defaultColors;
    this.colorsAnimationEffect = animation;
    if (position == "top") {
      let TriggerBBox = this.TriggerBBox.nativeElement.getBoundingClientRect();
      this.positionString =
        "transform: translateY(calc( -100% - " + TriggerBBox.height + "px ))";
    }
  }

  public setPosition(): void {
    if (this.TriggerBBox) {
      const panelWidth = 250;
      const viewportOffset = this.TriggerBBox.nativeElement.getBoundingClientRect();
      this.top = viewportOffset.top + viewportOffset.height;
      if (viewportOffset.left + panelWidth > window.innerWidth) {
        this.left = viewportOffset.right < panelWidth ? window.innerWidth / 2 - panelWidth / 2 : viewportOffset.right - panelWidth;
      } else {
        this.left = viewportOffset.left;
      }
    }
  }

  private setPositionY(): void {
    const triggerBBox = this.TriggerBBox.nativeElement.getBoundingClientRect();
    const panelBBox = this.panelRef.nativeElement.getBoundingClientRect();
    const panelHeight = panelBBox.height;
    // Check for space below the trigger
    if (triggerBBox.bottom + panelHeight > window.innerHeight) {
      // there is no space, move panel over the trigger
      this.positionString =
        triggerBBox.top < panelBBox.height
          ? 'transform: translateY(-' + triggerBBox.bottom + 'px );'
          : 'transform: translateY(calc( -100% - ' + triggerBBox.height + 'px ));';
    } else {
      this.positionString = '';
    }
    this.cdr.detectChanges();
  }

  public hasVariant(color): boolean {
    if (!this.previewColor) {
      return false;
    }
    return (
      typeof color != "string" &&
      color.variants.some(
        (v) => v.toUpperCase() == this.previewColor.toUpperCase()
      )
    );
  }

  public isSelected(color) {
    if (!this.previewColor) {
      return false;
    }
    return (
      typeof color == "string" &&
      color.toUpperCase() == this.previewColor.toUpperCase()
    );
  }

  public getBackgroundColor(color) {
    if (typeof color == "string") {
      return { background: color };
    } else {
      return { background: color?.preview };
    }
  }

  public onAlphaChange(event) {
    this.palette = this.ChangeAlphaOnPalette(event, this.palette);
  }

  private ChangeAlphaOnPalette(
    alpha,
    colors: Array<string | NgxColor>
  ): Array<any> {
    var result = [];
    for (let i = 0; i < colors.length; i++) {
      const color = colors[i];
      if (typeof color == "string") {
        let newColor = this.service.stringToHsva(color);
        newColor.onAlphaChange(alpha);
        result.push(this.service.toFormat(newColor, this.format));
      } else {
        let newColor = new NgxColor();
        let newColorPreview = this.service.stringToHsva(color.preview);
        newColorPreview.onAlphaChange(alpha);
        newColor.preview = this.service.toFormat(newColorPreview, this.format);
        newColor.variants = this.ChangeAlphaOnPalette(alpha, color.variants);
        result.push(newColor);
      }
    }
    return result;
  }

  /**
   * Change color from default colors
   * @param string color
   */
  public changeColor(color: string): void {
    this.setColor(this.service.stringToHsva(color));
    // this.triggerInstance.onChange();
    this.emitClose("accept");
  }

  public onChangeColorPicker(event: Hsva) {
    this.temporalColor = event;
    this.color = this.service.toFormat(event, this.format);
    // this.setColor(event);
    this.triggerInstance.sliderChange(
      this.service.toFormat(event, this.format)
    );
  }

  public changeColorManual(color: string): void {
    this.previewColor = color;
    this.color = color;
    this.hsva = this.service.stringToHsva(color);
    this.triggerInstance.setColor(this.color);
    // this.triggerInstance.onChange();
  }

  setColor(value: Hsva) {
    this.hsva = value;
    this.color = this.service.toFormat(value, this.format);
    this.setPreviewColor(value);
    this.triggerInstance.setColor(this.color);
  }

  setPreviewColor(value: Hsva) {
    this.previewColor = this.service.hsvaToRgba(value).toString();
  }
  hsvaToRgba;
  onChange() {
    // this.triggerInstance.onChange();
  }

  public onColorClick(color) {
    if (typeof color == "string") {
      this.changeColor(color);
    } else {
      this.variants = color.variants;
      this.menu = 2;
    }
  }

  public addColor() {
    this.menu = 3;
    this.backupColor = this.color;
    this.color = "#FF0000";
    this.temporalColor = this.service.stringToHsva(this.color);
  }

  public nextFormat() {
    if (this.canChangeFormat) {
      this.format = (this.format + 1) % this.colorFormats.length;
      this.setColor(this.hsva);
    }
  }

  public emitClose(status: "cancel" | "accept") {
    if (this.menu == 3) {
      if (status == "cancel") {
      } else if (status == "accept") {
        this.setColor(this.temporalColor);
      }
    }
    this.triggerInstance.close();
  }

  public onClickBack() {
    if (this.menu == 3) {
      this.color = this.backupColor;
      this.hsva = this.service.stringToHsva(this.color);
    }
    this.indexSeleccionado = this.findIndexSelectedColor(this.palette);
    this.menu = 1;
  }

  isOutside(event) {
    return event.target.classList.contains("ngx-colors-overlay");
  }
}
Example #16
Source File: emote-list.component.ts    From App with MIT License 4 votes vote down vote up
@Component({
	selector: 'app-emote-list',
	templateUrl: './emote-list.component.html',
	styleUrls: ['./emote-list.component.scss'],
	styles: ['.selected-emote-card { opacity: 0 }'],
	animations: [
		trigger('fadeout', [
			state('true', style({ transform: 'translateY(200%)', opacity: 0 })),

			transition('* => true', animate('100ms'))
		]),

		trigger('emotes', [
			transition('* => *', [
				query('.is-emote-card:enter', [
					style({ opacity: 0, transform: 'translateX(0.5em) translateY(-0.5em)', position: 'relative' }),
					stagger(-4, [
						animate('275ms ease-in-out', keyframes([
							style({ opacity: 0, offset: 0.475 }),
							style({ opacity: 1, transform: 'none', offset: 1 })
						]))
					])
				], { optional: true }),

				group([
					query('.is-emote-card:not(.selected-emote-card):leave', [
						style({ opacity: 1 }),
						stagger(2, [
							animate('100ms', style({ transform: 'scale(0)' }))
						])
					], { optional: true }),

					query('.selected-emote-card', [
						style({ opacity: 1 }),
						animate('550ms', keyframes([
							style({ offset: 0, opacity: 1, transform: 'scale(1)' }),
							style({ offset: .2, transform: 'scale(.91)' }),
							style({ offset: .38, transform: 'scale(.64)' }),
							style({ offset: .44, transform: 'scale(.82)' }),

							style({ offset: 1, transform: 'scale(12) translateY(25%)', opacity: 0 })
						])),
					], { optional: true })
				])
			])
		])
	],
	changeDetection: ChangeDetectionStrategy.OnPush
})
export class EmoteListComponent implements  AfterViewInit, OnDestroy {
	destroyed = new Subject<any>().pipe(take(1)) as Subject<void>;
	selecting = new BehaviorSubject(false).pipe(takeUntil(this.destroyed)) as BehaviorSubject<boolean>;
	emotes = new BehaviorSubject<any>([]).pipe(takeUntil(this.destroyed)) as BehaviorSubject<EmoteStructure[]>;
	newPage = new Subject();
	loading = new Subject();
	totalEmotes = new BehaviorSubject<number>(0);
	resized = new Subject<[number, number]>();

	@ViewChild('emotesContainer') emotesContainer: ElementRef<HTMLDivElement> | undefined;
	@ViewChild(MatPaginator, { static: true }) paginator: MatPaginator | undefined;
	@ViewChild('contextMenu') contextMenu: ContextMenuComponent | undefined;
	pageOptions: EmoteListComponent.PersistentPageOptions | undefined;
	pageSize = new BehaviorSubject<number>(16);
	currentSearchOptions: RestV2.GetEmotesOptions | undefined;
	currentSearchQuery = '';
	skipNextQueryChange = false;
	firstPageEvent = true;

	constructor(
		private restService: RestService,
		private renderer: Renderer2,
		private router: Router,
		private route: ActivatedRoute,
		private appService: AppService,
		private dataService: DataService,
		public themingService: ThemingService
	) { }

	selectEmote(ev: MouseEvent, el: any, emote: EmoteStructure): void {
		if (!emote.getID()) return undefined;
		ev.preventDefault();

		this.selecting.next(true);
		this.renderer.addClass(el, 'selected-emote-card');
		this.emotes.next([]);

		setTimeout(() => {
			this.router.navigate(['emotes', emote.getID()]);
		}, 775);
	}

	private updateQueryParams(replaceUrl: boolean = false): void {
		const merged = {
			...this.currentSearchOptions,
			...{ page: (this.pageOptions?.pageIndex ?? 0) }
		};

		this.skipNextQueryChange = true;
		this.router.navigate(['.'], {
			relativeTo: this.route,
			queryParams: Object.keys(merged).map(k => ({ [k]: (merged as any)[k as any] })).reduce((a, b) => ({ ...a, ...b })),
			queryParamsHandling: 'merge',
			replaceUrl
		});
	}

	handleSearchChange(change: Partial<RestV2.GetEmotesOptions>): void {
		const queryString = Object.keys(change).map(k => `${k}=${change[k as keyof RestV2.GetEmotesOptions]}`).join('&');

		this.appService.pushTitleAttributes({ name: 'SearchOptions', value: `- ${queryString}` });
		this.currentSearchOptions = { ...this.currentSearchOptions, ...change as RestV2.GetEmotesOptions };
		this.updateQueryParams();

		this.goToFirstPage();
		this.getEmotes(undefined, this.currentSearchOptions).pipe(
			delay(50),
			tap(emotes => this.emotes.next(emotes))
		).subscribe();
	}

	getEmotes(page = 0, options?: Partial<RestV2.GetEmotesOptions>): Observable<EmoteStructure[]> {
		this.emotes.next([]);
		this.newPage.next(page);
		const timeout = setTimeout(() => this.loading.next(true), 1000);
		const cancelSpinner = () => {
			this.loading.next(false);
			clearTimeout(timeout);
		};

		const size = this.calculateSizedRows();
		return this.restService.awaitAuth().pipe(
			switchMap(() => this.restService.v2.SearchEmotes(
				(this.pageOptions?.pageIndex ?? 0) + 1,
				Math.max(EmoteListComponent.MINIMUM_EMOTES, size ?? EmoteListComponent.MINIMUM_EMOTES),
				options ?? this.currentSearchOptions
			)),

			takeUntil(this.newPage.pipe(take(1))),
			tap(res => this.totalEmotes.next(res?.total_estimated_size ?? 0)),
			delay(200),
			map(res => res?.emotes ?? []),
			mergeAll(),
			map(data => this.dataService.add('emote', data)[0]),
			toArray(),
			defaultIfEmpty([] as EmoteStructure[]),

			tap(() => cancelSpinner()),
			catchError(() => defer(() => cancelSpinner()))
		) as Observable<EmoteStructure[]>;
	}

	onOpenCardContextMenu(emote: EmoteStructure): void {
		if (!this.contextMenu) {
			return undefined;
		}

		this.contextMenu.contextEmote = emote;
		emote.getOwner().pipe(
			tap(usr => !!this.contextMenu ? this.contextMenu.contextUser = (usr ?? null) : noop())
		).subscribe();
	}

	goToFirstPage(): void {
		this.paginator?.page.next({
			pageIndex: 0,
			pageSize: this.pageOptions?.pageSize ?? 0,
			length: this.pageOptions?.length ?? 0,
		});
	}

	/**
	 * Handle pagination changes
	 */
	onPageEvent(ev: PageEvent): void {
		this.pageOptions = {
			...ev
		};

		if (this.firstPageEvent) {
			this.updateQueryParams(true);
			this.firstPageEvent = false;
		}
		else {
			this.updateQueryParams();
		}

		// Save PageIndex title attr
		this.appService.pushTitleAttributes({ name: 'PageIndex', value: `- ${ev.pageIndex}/${Number((ev.length / ev.pageSize).toFixed(0))}` });

		// Fetch new set of emotes
		this.getEmotes(ev.pageIndex).pipe(
			tap(emotes => this.emotes.next(emotes))
		).subscribe();
	}

	isEmpty(): Observable<boolean> {
		return this.totalEmotes.pipe(
			take(1),
			map(size => size === 0)
		);
	}

	/**
	 * Calculate how many rows and columns according to the container's size
	 *
	 * @returns the result of rows * columns
	 */
	calculateSizedRows(): number | null {
		if (!this.emotesContainer) {
			return null;
		}

		const marginBuffer = 28; // The margin _in pixels between each card
		const cardSize = 137; // The size of the cards in pixels
		const width = this.emotesContainer.nativeElement.scrollWidth - 32; // The width of emotes container
		const height = this.emotesContainer.nativeElement.clientHeight - 16; // The height of the emotes container

		const rows = Math.floor((width / (cardSize + marginBuffer))); // The calculated amount of rows
		const columns = Math.floor(height / (cardSize + marginBuffer)); // The calculated amount of columns

		// Return the result of rows multiplied by columns
		return rows * columns;
	}

	@HostListener('window:resize', ['$event'])
	onWindowResize(ev: Event): void {
		const size = this.calculateSizedRows();
		this.pageSize.next(size ?? 0);

		this.resized.next([0, 0]);
		timer(1000).pipe(
			takeUntil(this.resized.pipe(take(1))),

			switchMap(() => this.getEmotes(this.pageOptions?.pageIndex, {})),
			tap(emotes => this.emotes.next(emotes))
		).subscribe();
	}

	ngAfterViewInit(): void {
		// Get persisted page options?
		this.route.queryParamMap.pipe(
			defaultIfEmpty({} as ParamMap),
			map(params => {
				return {
					page: params.has('page') ? Number(params.get('page')) : 0,
					search: {
						sortBy: params.get('sortBy') ?? 'popularity',
						sortOrder: params.get('sortOrder'),
						globalState: params.get('globalState'),
						query: params.get('query'),
						submitter: params.get('submitter'),
						channel: params.get('channel')
					}
				};
			}),
			tap(() => setTimeout(() => this.skipNextQueryChange = false, 0)),
			filter(() => !this.skipNextQueryChange)
		).subscribe({
			next: opt => {
				const d = {
					pageIndex: !isNaN(opt.page) ? opt.page : 0,
					pageSize: Math.max(EmoteListComponent.MINIMUM_EMOTES, this.calculateSizedRows() ?? 0),
					length: 0,
				};
				this.updateQueryParams(true);
				this.currentSearchOptions = opt.search as any;

				this.paginator?.page.next(d);
				this.pageOptions = d;
			}
		});

		this.pageSize.next(this.calculateSizedRows() ?? 0);
	}

	ngOnDestroy(): void {
		this.loading.complete();
	}

}
Example #17
Source File: animations.ts    From Angular-Cookbook with MIT License 4 votes vote down vote up
ROUTE_ANIMATION = trigger('routeAnimation', [
  transition('* <=> *', [
    style({
      position: 'relative',
      perspective: '1000px',
    }),
    query(
      ':enter, :leave',
      [
        style({
          position: 'absolute',
          width: '100%',
        }),
      ],
      optional
    ),
    query(
      ':enter',
      [
        style({
          opacity: 0,
        }),
      ],
      optional
    ),
    group([
      query(
        ':leave',
        [
          animate(
            '1s ease-in',
            keyframes([
              style({
                opacity: 1,
                offset: 0,
                transform: 'rotateY(0) translateX(0) translateZ(0)',
              }),
              style({
                offset: 0.25,
                transform:
                  'rotateY(45deg) translateX(25%) translateZ(100px) translateY(5%)',
              }),
              style({
                offset: 0.5,
                transform:
                  'rotateY(90deg) translateX(75%) translateZ(400px) translateY(10%)',
              }),
              style({
                offset: 0.75,
                transform:
                  'rotateY(135deg) translateX(75%) translateZ(800px) translateY(15%)',
              }),
              style({
                opacity: 0,
                offset: 1,
                transform:
                  'rotateY(180deg) translateX(0) translateZ(1200px) translateY(25%)',
              }),
            ])
          ),
        ],
        optional
      ),

      query(
        ':enter',
        [
          animate(
            '1s ease-out',
            keyframes([
              style({
                opacity: 0,
                offset: 0,
                transform: 'rotateY(180deg) translateX(25%) translateZ(1200px)',
              }),
              style({
                offset: 0.25,
                transform:
                  'rotateY(225deg) translateX(-25%) translateZ(1200px)',
              }),
              style({
                offset: 0.5,
                transform: 'rotateY(270deg) translateX(-50%) translateZ(400px)',
              }),
              style({
                offset: 0.75,
                transform: 'rotateY(315deg) translateX(-50%) translateZ(25px)',
              }),
              style({
                opacity: 1,
                offset: 1,
                transform: 'rotateY(360deg) translateX(0) translateZ(0)',
              }),
            ])
          ),
        ],
        optional
      ),
    ]),
  ]),
])
Example #18
Source File: project-navigation.component.ts    From taiga-front-next with GNU Affero General Public License v3.0 4 votes vote down vote up
@Component({
  selector: 'tg-project-navigation',
  templateUrl: './project-navigation.component.html',
  styleUrls: ['./project-navigation.component.css'],
  changeDetection: ChangeDetectionStrategy.OnPush,
  animations: [
    trigger('openCollapse', [
      transition('open => collapsed', [
        query('[data-animation="text"]', style({ opacity: 1 })),
        query(':self', style({ width: '200px' })),

        query('[data-animation="text"]', animate(100, style({ opacity: 0 }))),
        query(':self', animate(300, style({ width: '48px' }))),
      ]),
      transition('collapsed => open', [
        query(':self', style({ width: '48px' })),

        query(':self', animate(300, style({ width: '200px' }))),
      ]),
    ]),
  ],
})
export class ProjectNavigationComponent implements OnChanges, OnInit {
  @Input()
  public project: Project;
  @Output()
  public search = new EventEmitter();

  public videoUrl: string | null;
  public scrumVisible = false;
  public collapseText = true;
  public section!: string;
  public dialog: ProjectMenuDialog = {
    open: false,
    hover: false,
    mainLinkHeight: 0,
    isSearch: false,
    type: '',
    slug: '',
    top: 0,
    left: 0,
    text: '',
    height: 0,
    children: [],
  };
  public milestoneId$: Observable<Milestone['id'] | undefined | null>;

  @HostBinding('class.collapsed')
  public collapsed = false;

  private dialogCloseTimeout?: number;

  @HostBinding('@openCollapse') get openCollapseAnimation() {
    return this.collapsed ? 'collapsed' : 'open';
  }

  @HostListener('@openCollapse.done') animationDone() {
    this.collapseText = this.collapsed ? true : false;
  }

  constructor(
    private readonly translateService: TranslateService,
    private readonly cd: ChangeDetectorRef,
    private readonly legacyService: LegacyService,
    private readonly router: Router) {}

  public ngOnInit() {
    this.collapsed = (localStorage.getItem('projectnav-collapsed') === 'true');
    this.section = this.getActiveSection();

    // LEGACY
    this.milestoneId$ = this.legacyService.legacyState
    .pipe(
      pluck('detailObj'),
      map((obj) => {
        return obj?.milestone;
      })
    );

    if (this.section === 'backlog') {
      this.scrumVisible = (localStorage.getItem('projectnav-scrum') === 'true');
    }
  }

  public getActiveSection() {
    const { breadcrumb, sectionName } = this.getSection();
    const indexBacklog = breadcrumb.lastIndexOf('backlog');
    const indexKanban = breadcrumb.lastIndexOf('kanban');

    let oldSectionName = '';

    if (indexBacklog !== -1 || indexKanban !== -1) {
        if (indexKanban === -1 || indexBacklog > indexKanban) {
            oldSectionName = 'backlog';
        } else {
            oldSectionName = 'kanban';
        }
    }

    // task & us the sectionName is backlog-kanban
    if  (sectionName  === 'backlog-kanban') {
        if (['backlog', 'kanban'].includes(oldSectionName)) {
          return oldSectionName;
        } else if (this.project.isBacklogActivated && !this.project.isKanbanActivated) {
          return 'backlog';
        } else if (!this.project.isBacklogActivated && this.project.isKanbanActivated) {
          return 'kanban';
        }
    }

    return sectionName;
  }

  public popup(event: MouseEvent, type: string) {
    if (!this.collapsed) {
      return;
    }

    this.initDialog(event.target as HTMLElement, type);
    this.dialog.type = type;
  }

  public popupScrum(event: MouseEvent) {
    if (!this.collapsed) {
      return;
    }

    const children: ProjectMenuDialog['children'] = this.milestones.map((milestone) => {
      return {
        text: milestone.name,
        link: ['/project', this.project.slug, 'taskboard', milestone.slug],
      };
    });

    children.unshift({
      text: this.translateService.instant('PROJECT.SECTION.BACKLOG'),
      link: ['/project', this.project.slug, 'backlog'],
    });

    this.initDialog(event.target as HTMLElement, 'scrum', children);
  }

  public get milestones() {
    return this.project.milestones
    .filter((milestone) => !milestone.closed)
    .reverse()
    .slice(0, 7);
  }

  public initDialog(el: HTMLElement, type: string, children: ProjectMenuDialog['children'] = []) {
    if (this.dialogCloseTimeout) {
      clearTimeout(this.dialogCloseTimeout);
    }
    const text = el.querySelector('.menu-option-text')?.innerHTML;

    if (text) {
      const link = el.querySelector('a')?.getAttribute('href');

      if (link) {
        this.dialog.slug = link;
      } else {
        this.dialog.slug = '';
      }

      const navigationBarWidth = 48;

      this.dialog.hover = false;
      this.dialog.mainLinkHeight = el.offsetHeight;
      this.dialog.left = navigationBarWidth;
      this.dialog.top = el.offsetTop;
      this.dialog.open = true;
      this.dialog.text = text;
      this.dialog.children = children;
      this.dialog.type = type;
    }
  }

  public out() {
    this.dialogCloseTimeout = setTimeout(() => {
      if (!this.dialog.hover) {
        this.dialog.open = false;
        this.dialog.type = '';
        this.cd.markForCheck();
      }
    }, 100);
  }

  public enterDialog() {
    this.dialog.open = true;
    this.dialog.hover = true;
  }

  public outDialog() {
    this.dialog.hover = false;
    this.out();
  }

  public ngOnChanges(changes: SimpleChanges) {
    if (changes.project) {
      this.videoUrl = this.videoConferenceUrl();
    }
  }

  get isMenuEpicsEnabled() {
    return this.project.isEpicsActivated && this.project.myPermissions.includes(Permissions.viewEpics);
  }

  get isMenuScrumEnabled() {
    return this.project.isBacklogActivated && this.project.myPermissions.includes(Permissions.viewUserstory);
  }
  get isMenuKanbanEnabled() {
    return this.project.isKanbanActivated && this.project.myPermissions.includes(Permissions.viewUserstory);
  }
  get isMenuIssuesEnabled() {
    return this.project.isIssuesActivated && this.project.myPermissions.includes(Permissions.viewIssues);
  }

  get isMenuWikiEnabled() {
    return this.project.isWikiActivated && this.project.myPermissions.includes(Permissions.viewWikiPages);
  }

  public toggleScrum() {
    if (this.collapsed) {
      this.router.navigate(['/project', this.project.slug, 'backlog']);
    } else {
      this.scrumVisible = !this.scrumVisible;
      localStorage.setItem('projectnav-scrum', String(this.scrumVisible));
    }
  }

  public toggleCollapse() {
    this.collapsed = !this.collapsed;
    localStorage.setItem('projectnav-collapsed', String(this.collapsed));

    if (this.collapsed) {
      this.scrumVisible = false;
    }
  }

  private videoConferenceUrl(): string | null {
    let baseUrl = '';

    if (!this.project.videoconferences) {
      return null;
    }

    if (this.project.videoconferences === 'whereby-com') {
      baseUrl = 'https://whereby.com/';
    } else if (this.project.videoconferences === 'talky') {
      baseUrl = 'https://talky.io/';
    } else if (this.project.videoconferences === 'jitsi') {
      baseUrl = 'https://meet.jit.si/';
    } else if (this.project.videoconferences === 'custom' && this.project.videoconferencesExtraData) {
      return this.project.videoconferencesExtraData;
    }

    let url = '';

    // Add prefix to the chat room name if exist
    if (this.project.videoconferencesExtraData) {
      url = `${this.project.slug}-${UtilsService.slugify(this.project.videoconferencesExtraData)}`;
    } else {
      url = this.project.slug;
    }

    // Some special cases
    if (this.project.videoconferences === 'jitsi') {
      url = url.replace(/-/g, '');
    }

    return baseUrl + url;
  }

  // LEGACY
  private getSection() {
    const injector = this.legacyService.getInjector();
    const projectService = injector.get('tgProjectService');

    return {
      breadcrumb: projectService.sectionsBreadcrumb.toJS(),
      sectionName: projectService.section,
    };
  }
}