Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
NoelDeMartin committed Aug 4, 2024
1 parent 943fd71 commit dfa52f3
Show file tree
Hide file tree
Showing 27 changed files with 399 additions and 931 deletions.
21 changes: 4 additions & 17 deletions src/pages/workspace/WorkspaceContentBody.vue
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
<template>
<AnimatedGroup class="flex flex-col px-4">
<TasksForm v-if="$tasksList.tasks?.length" ref="$tasksForm" @submit="createTask($event)" />
<div class="relative flex flex-grow flex-col">
<TasksList
class="transition-[margin] duration-500"
:tasks="tasks.pending"
:disable-editing="disableEditing"
:class="{ 'mt-4': tasks.pending.length, 'mt-0': !tasks.pending.length }"
:class="tasks.pending.length ? 'mt-4' : 'mt-0'"
/>
<TasksStart v-if="!tasks.pending.length && !tasks.completed.length" @create="createTask($event)" />
<!-- <AnimationElement name="empty-tasks"
:show="!tasks.pending.length && tasks.completed.length && !$focus.showCompleted"> -->
<!-- TODO or configure show in JS... like forms? -->
<!-- TODO v-animate instead? can we re-implement v-if? -->
<AnimatedTransition
enter-active-class="transition-[opacity,transform] delay-200 duration-500"
enter-from-class="max-h-0 !py-0 opacity-0 -translate-y-12"
Expand All @@ -36,7 +34,7 @@
}"
>
<TextButton
v-animate
v-animate-layout
color="clear"
class="ml-1 self-start pl-1 pr-2 font-medium uppercase tracking-wider"
:aria-label="$focus.showCompleted ? $t('tasks.hideCompleted') : $t('tasks.showCompleted')"
Expand All @@ -54,12 +52,10 @@
@enter="allPendingCompleted ? showCompletedTasks($event) : grow($event)"
@leave="allPendingCompleted ? hideCompletedTasks($event) : shrink($event)"
>
<!-- TODO use different transition if we have some pending tasks... -->
<div v-if="$focus.showCompleted" class="list-wrapper overflow-hidden">
<TasksList :tasks="tasks.completed" :disable-editing="disableEditing" class="mt-4" />
</div>
</AnimatedTransition>
<span>&nbsp;</span>
</div>
</div>
</AnimatedGroup>
Expand Down Expand Up @@ -99,15 +95,6 @@ const tasks = computed(() => ({
const showPending = computed(() => !!tasks.value.pending.length);
const allPendingCompleted = computed(() => !tasks.value.pending.length && tasks.value.completed.length);
// useAnimation(
// 'empty-tasks',
// () => !!(!tasks.value.pending.length && tasks.value.completed.length && !Focus.showCompleted),
// {
// show: showEmptyTasks, // TODO extend Animation instead? nah, maybe just if you want to reuse
// hide: hideEmptyTasks,
// },
// );
function compareTasks(a: Task, b: Task): number {
const importantComparison = compare(b.important, a.important);
const dueDateComparison = !a.dueDate || !b.dueDate ? compare(b.dueDate, a.dueDate) : compare(a.dueDate, b.dueDate);
Expand Down
46 changes: 23 additions & 23 deletions src/pages/workspace/animations/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
// - Toggle last completed task (both with expanded & with collapsed list) --> collapse automatically to show animation?
// - Switching workspaces/lists

import type { AnimatedElement } from '@/vivant/core';
import { after, isInstanceOf, required } from '@noeldemartin/utils';
import { getElementBounds } from '@/vivant/core/utils';
import { isInstanceOf, required } from '@noeldemartin/utils';
import { animate } from 'popmotion';

// TODO fix horizontal scroll
Expand Down Expand Up @@ -82,9 +82,9 @@ async function animateTasksListSize(
$footer.setAttribute('style', '');
}

export async function shrink(element: AnimatedElement): Promise<void> {
export async function shrink(element: Element): Promise<void> {
// TODO fix footer animation
const $child = element.element.firstElementChild;
const $child = element.firstElementChild;

if (!isInstanceOf($child, HTMLElement)) {
return;
Expand All @@ -105,9 +105,9 @@ export async function shrink(element: AnimatedElement): Promise<void> {
$child.setAttribute('style', '');
}

export async function grow(element: AnimatedElement): Promise<void> {
export async function grow(element: Element): Promise<void> {
// TODO fix footer animation
const $child = element.element.firstElementChild;
const $child = element.firstElementChild;

if (!isInstanceOf($child, HTMLElement)) {
return;
Expand All @@ -129,32 +129,32 @@ export async function grow(element: AnimatedElement): Promise<void> {
$child.setAttribute('style', '');
}

export async function freeze(element: AnimatedElement): Promise<void> {
const bounds = element.getPreviousBounds();
export async function freeze(element: Element): Promise<void> {
const [bounds] = getElementBounds(element);

if (!bounds || !(element.element instanceof HTMLElement)) {
if (!bounds || !isInstanceOf(element, HTMLElement)) {
return;
}

element.element.style.width = `${bounds.width}px`;
element.element.style.height = `${bounds.height}px`;
element.style.width = `${bounds.width}px`;
element.style.height = `${bounds.height}px`;

// TODO after animation, clear styles? maybe not, we're removing the element...
}

// TODO refactor to have same signature as native Vue hooks? (optionally return promise...)
export async function showCompletedTasks(wrapper: AnimatedElement): Promise<void> {
await animateTasksListSize(
wrapper.element as HTMLElement,
required(wrapper.getCurrentBounds()),
required(wrapper.getPreviousBounds()),
'grow',
);
export async function showCompletedTasks(wrapper: Element): Promise<void> {
const [first, last] = getElementBounds(wrapper);

await animateTasksListSize(wrapper as HTMLElement, required(last), required(first), 'grow');
}

export async function hideCompletedTasks(wrapper: AnimatedElement): Promise<void> {
export async function hideCompletedTasks(wrapper: Element): Promise<void> {
const [first, last] = getElementBounds(wrapper);

await animateTasksListSize(
wrapper.element as HTMLElement,
required(wrapper.getPreviousBounds()),
required(wrapper.getCurrentBounds()),
wrapper as HTMLElement,
required(first),
required(last),
'shrink', // TODO instead of "shink" and "grow", just use from --> to properly?
);
}
Expand Down
35 changes: 17 additions & 18 deletions src/vivant/core/AnimatedElement.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import { animatedElements } from './orchestration';
import type Animation from '@/vivant/core/Animation';
import { registerElement } from '@/vivant/core/registry';
import type Animation from '@/vivant/core/animations/Animation';

export default class AnimatedElement {

public readonly element: Element;
public readonly nativeElement: Element;
private animations: Animation[];
private previousBounds?: DOMRect;
private currentBounds?: DOMRect;
private animations: Record<string, Animation>;
private registryCleanup?: Function;

constructor(element: Element) {
this.element = element;
this.animations = {};
constructor(nativeElement: Element) {
this.nativeElement = nativeElement;
this.animations = [];
}

public getPreviousBounds(): DOMRect | undefined {
Expand All @@ -21,29 +22,27 @@ export default class AnimatedElement {
return this.currentBounds;
}

public animates(animation: string): boolean {
return animation in this.animations;
}

public useAnimation(name: string, animation: Animation): void {
this.animations[name] = animation;
public useAnimation(animation: Animation): void {
this.animations.push(animation);
}

public attach(): void {
animatedElements.add(this);
this.registryCleanup = registerElement(this.nativeElement, this);
}

public detach(): void {
animatedElements.delete(this);
this.registryCleanup?.();

delete this.registryCleanup;
}

public measure(): void {
this.previousBounds = this.currentBounds;
this.currentBounds = this.element.getBoundingClientRect();
this.currentBounds = this.nativeElement.getBoundingClientRect();
}

public async animate(animation: string): Promise<void> {
await this.animations[animation]?.run(this);
public async animate(): Promise<void> {
await Promise.all(this.animations.map((animation) => animation.run(this)));
}

}
14 changes: 0 additions & 14 deletions src/vivant/core/CustomAnimation.ts

This file was deleted.

17 changes: 0 additions & 17 deletions src/vivant/core/FreezeAnimation.ts

This file was deleted.

35 changes: 0 additions & 35 deletions src/vivant/core/LayoutAnimation.ts

This file was deleted.

File renamed without changes.
62 changes: 62 additions & 0 deletions src/vivant/core/animations/LayoutAnimation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { isInstanceOf } from '@noeldemartin/utils';

import type AnimatedElement from '@/vivant/core/AnimatedElement';

import Animation from './Animation';
import { animate } from 'popmotion';

function rectsEqual(a: DOMRect, b: DOMRect): boolean {
return (
a.x === b.x &&
a.y === b.y &&
a.width === b.width &&
a.height === b.height &&
a.top === b.top &&
a.left === b.left
);
}

export default class LayoutAnimation extends Animation {

private animating: boolean = false;

public async run(element: AnimatedElement): Promise<void> {
const first = element.getPreviousBounds();
const last = element.getCurrentBounds();

if (
!isInstanceOf(element.nativeElement, HTMLElement) ||
this.animating ||
!first ||
!last ||
rectsEqual(first, last)
) {
return;
}

this.animating = true;

await this.animate(element.nativeElement, first, last);

this.animating = false;
}

protected async animate(element: HTMLElement, first: DOMRect, last: DOMRect): Promise<void> {
if (first.y === last.y) {
return;
}

await new Promise<void>((resolve) => {
animate({
duration: 500,
onComplete: () => resolve(),
onUpdate: (progress) => {
element.style.transform = `translateY(${(1 - progress) * (first.y - last.y)}px)`;
},
});
});

element.setAttribute('style', '');
}

}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import Animation from '@/vivant/core/Animation';
import Animation from './Animation';

export default class NoopAnimation extends Animation {

Expand Down
Loading

0 comments on commit dfa52f3

Please sign in to comment.