Skip to content
This repository was archived by the owner on Mar 8, 2025. It is now read-only.

Commit 023a855

Browse files
committed
feat: display keyframe on the timeline with mock data
1 parent 41e2b93 commit 023a855

22 files changed

+1363
-20
lines changed

.eslintrc.js

+3-4
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,12 @@ module.exports = {
1616
rules: {
1717
"no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
1818
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
19+
"@typescript-eslint/no-explicit-any": "off",
20+
"@typescript-eslint/explicit-module-boundary-types": "off",
1921
},
2022
overrides: [
2123
{
22-
files: [
23-
"**/__tests__/*.{j,t}s?(x)",
24-
"**/tests/unit/**/*.spec.{j,t}s?(x)",
25-
],
24+
files: ["**/__tests__/*.{j,t}s?(x)", "**/tests/unit/**/*.spec.{j,t}s?(x)"],
2625
env: {
2726
jest: true,
2827
},

.prettierignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/node_modules/**
2+
/dist/**

.prettierrc.js

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
module.exports = {
2+
arrowParens: "always",
3+
bracketSpacing: true,
4+
endOfLine: "lf",
5+
htmlWhitespaceSensitivity: "strict",
6+
printWidth: 100,
7+
proseWrap: "never",
8+
quoteProps: "consistent",
9+
semi: true,
10+
singleQuote: false,
11+
tabWidth: 2,
12+
trailingComma: "all",
13+
useTabs: false,
14+
vueIndentScriptAndStyle: false,
15+
};

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
},
1212
"dependencies": {
1313
"core-js": "^3.6.5",
14-
"vue": "^3.0.0"
14+
"vue": "^3.0.0",
15+
"vuedraggable": "^4.1.0"
1516
},
1617
"devDependencies": {
1718
"@types/jest": "^24.0.19",

src/assets/logo.png

-6.69 KB
Binary file not shown.

src/components/layer/layer-item.vue

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<template>
2+
<div>
3+
<div class="va-layer__item">
4+
<i class="icon icon-ellipsis-v handle" />
5+
<span class="text">{{ modelValue.name }}</span>
6+
</div>
7+
<div v-if="modelValue.expanded" class="va-expanded__wrapper">
8+
<div v-for="property in changedProperties" :key="property" class="va-expanded__item">
9+
<span class="text">{{ getLabelFromProperty(property) }}</span>
10+
</div>
11+
</div>
12+
</div>
13+
</template>
14+
15+
<script lang="ts">
16+
import { defineComponent } from "vue";
17+
import { useTimeline } from "../useTimeline";
18+
19+
export default defineComponent({
20+
props: {
21+
modelValue: {
22+
type: Object,
23+
default: () => ({}),
24+
},
25+
},
26+
emits: ["update:modelValue"],
27+
setup(props) {
28+
const { changedProperties, getLabelFromProperty } = useTimeline(props);
29+
30+
return {
31+
changedProperties,
32+
getLabelFromProperty,
33+
};
34+
},
35+
});
36+
</script>

src/components/timeline.vue

+156-15
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,168 @@
11
<template>
2-
<div class="va-timeline__wrapper">
3-
<!-- -->
2+
<div class="va-timeline-component">
3+
<div class="va-layer__wrapper" :style="{ width: '320px' }">
4+
<div class="va-layer__toolbar"></div>
5+
<div class="va-layer__container">
6+
<draggable v-model="elements" v-bind="dragOptions" v-on="dragEventHandlers" item-key="id">
7+
<template #item="{ index }">
8+
<LayerItem v-model="elements[index]" />
9+
</template>
10+
</draggable>
11+
</div>
12+
</div>
13+
<div class="va-timeline__wrapper">
14+
<div class="va-timeline__ruler">
15+
<i
16+
class="va-timeline__indicator__caret icon icon-eject"
17+
:style="{
18+
left: currentTime * 100 + '%',
19+
}"
20+
></i>
21+
</div>
22+
<div class="va-timeline__container">
23+
<div
24+
class="va-timeline__indicator__line"
25+
:style="{
26+
left: currentTime * 100 + '%',
27+
}"
28+
></div>
29+
<TimelineItem
30+
v-for="(element, index) in elements"
31+
v-model="elements[index]"
32+
:key="element.id"
33+
/>
34+
</div>
35+
</div>
436
</div>
537
</template>
638

739
<script lang="ts">
8-
import { defineComponent } from "vue";
40+
import { computed, defineComponent, ref, watch } from "vue";
41+
import TimelineItem from "./timeline/timeline-item.vue";
42+
import LayerItem from "./layer/layer-item.vue";
43+
44+
import draggable from "vuedraggable";
45+
46+
type Element = {
47+
id: string;
48+
name: string;
49+
keyframes: string[];
50+
expanded?: boolean;
51+
stages: {
52+
[key: string]: {
53+
keyframe: number;
54+
label: string;
55+
property: string;
56+
value: any;
57+
};
58+
};
59+
};
960
1061
export default defineComponent({
1162
name: "AnimationTimeline",
12-
components: {},
13-
setup() {
14-
//
63+
components: { draggable, TimelineItem, LayerItem },
64+
props: {
65+
modelValue: {
66+
type: Array,
67+
default: () => [],
68+
},
69+
},
70+
emits: ["update:modelValue"],
71+
setup(props, { emit }) {
72+
const isDragging = ref(false);
73+
const dragOptions = computed(() => ({
74+
animation: 200,
75+
group: "description",
76+
disabled: false,
77+
ghostClass: "draggable-ghost",
78+
handle: ".handle",
79+
}));
80+
const dragEventHandlers = {
81+
start() {
82+
isDragging.value = true;
83+
},
84+
end() {
85+
isDragging.value = false;
86+
},
87+
};
88+
89+
const currentTime = ref(0.5);
90+
const elements = ref<Element[]>([
91+
{
92+
id: "1jhg1kj23jkh4kj67jh",
93+
name: "Layer 1",
94+
keyframes: ["stage-id-168rf9c8f9c88478f9c88038f", "stage-id-268rf9c8f9c88478f9c88038f"],
95+
expanded: false,
96+
stages: {
97+
"stage-id-168rf9c8f9c88478f9c88038f": {
98+
keyframe: 0,
99+
label: "Position",
100+
property: "position",
101+
value: { x: 150, y: 100 },
102+
},
103+
"stage-id-268rf9c8f9c88478f9c88038f": {
104+
keyframe: 0.5,
105+
label: "Position",
106+
property: "position",
107+
value: { x: 200, y: 150 },
108+
},
109+
},
110+
},
111+
{
112+
id: "2jhg1kj23jkh4kj67jh",
113+
name: "Layer 2",
114+
keyframes: [
115+
"stage-id-168rf9c8f9c88478f9c88038f",
116+
"stage-id-268rf9c8f9c74478f9c88038f",
117+
"stage-id-468rf9c8f9c88ỉbf9c88038f",
118+
"stage-id-568rf9c8f9c88478f9c88038f",
119+
],
120+
expanded: true,
121+
stages: {
122+
"stage-id-168rf9c8f9c88478f9c88038f": {
123+
keyframe: 0,
124+
label: "Opacity",
125+
property: "opacity",
126+
value: 0,
127+
},
128+
"stage-id-268rf9c8f9c74478f9c88038f": {
129+
keyframe: 0.3,
130+
label: "Opacity",
131+
property: "opacity",
132+
value: 0.9,
133+
},
134+
"stage-id-468rf9c8f9c88ỉbf9c88038f": {
135+
keyframe: 0.7,
136+
label: "Opacity",
137+
property: "opacity",
138+
value: 0.5,
139+
},
140+
"stage-id-568rf9c8f9c88478f9c88038f": {
141+
keyframe: 0.5,
142+
label: "Position",
143+
property: "position",
144+
value: { x: 200, y: 150 },
145+
},
146+
},
147+
},
148+
]);
149+
150+
watch(
151+
() => elements.value,
152+
() => {
153+
emit("update:modelValue", elements.value);
154+
},
155+
{ immediate: true, deep: true },
156+
);
157+
158+
return {
159+
currentTime,
160+
elements,
161+
dragOptions,
162+
dragEventHandlers,
163+
};
15164
},
16165
});
17166
</script>
18167

19-
<style lang="scss">
20-
.va-timeline {
21-
&__wrapper {
22-
width: 100%;
23-
height: 100%;
24-
background-color: rgb(32, 32, 32);
25-
}
26-
}
27-
</style>
168+
<style lang="scss" src="@/styles/main.scss"></style>
+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<template>
2+
<div>
3+
<div class="va-timeline__item">
4+
<i
5+
v-for="key in modelValue.keyframes"
6+
:key="key"
7+
class="icon icon-locate"
8+
:style="{
9+
left: modelValue.stages[key].keyframe * 100 + '%',
10+
}"
11+
/>
12+
</div>
13+
<div v-if="modelValue.expanded" class="va-expanded__wrapper">
14+
<div v-for="property in changedProperties" :key="property" class="va-expanded__item">
15+
<template v-for="key in modelValue.keyframes" :key="key">
16+
<i
17+
class="icon icon-locate"
18+
v-if="modelValue.stages[key].property === property"
19+
:style="{
20+
left: modelValue.stages[key].keyframe * 100 + '%',
21+
}"
22+
/>
23+
</template>
24+
</div>
25+
</div>
26+
</div>
27+
</template>
28+
29+
<script lang="ts">
30+
import { defineComponent } from "vue";
31+
import { useTimeline } from "../useTimeline";
32+
33+
export default defineComponent({
34+
props: {
35+
modelValue: {
36+
type: Object,
37+
default: () => ({}),
38+
},
39+
},
40+
emits: ["update:modelValue"],
41+
setup(props) {
42+
const { changedProperties } = useTimeline(props);
43+
44+
return {
45+
changedProperties,
46+
};
47+
},
48+
});
49+
</script>

src/components/useTimeline.ts

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { computed, ComputedRef } from "vue";
2+
3+
export const useTimeline = (
4+
props: any,
5+
): {
6+
changedProperties: ComputedRef<string[]>;
7+
getLabelFromProperty: (property: string) => string;
8+
} => {
9+
const changedProperties = computed(
10+
() =>
11+
Array.from(
12+
new Set(Object.values(props.modelValue.stages).map((stage: any) => stage.property)),
13+
) as string[],
14+
);
15+
16+
const getLabelFromProperty = (property: string): string => {
17+
const stage = Object.values(props.modelValue.stages).find(
18+
(stage: any) => stage.property === property,
19+
) as any;
20+
return stage ? stage.label : "";
21+
};
22+
23+
return {
24+
changedProperties,
25+
getLabelFromProperty,
26+
};
27+
};

src/styles/draggable.scss

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
.draggable-flip-list-move {
2+
transition: transform 0.5s;
3+
}
4+
.draggable-ghost {
5+
opacity: 0.5;
6+
background: #c8ebfb;
7+
}

src/styles/icons/Read Me.txt

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Open *demo.html* to see a list of all the glyphs in your font along with their codes/ligatures.
2+
3+
To use the generated font in desktop programs, you can install the TTF font. In order to copy the character associated with each icon, refer to the text box at the bottom right corner of each glyph in demo.html. The character inside this text box may be invisible; but it can still be copied. See this guide for more info: https://icomoon.io/#docs/local-fonts
4+
5+
You won't need any of the files located under the *demo-files* directory when including the generated font in your own projects.
6+
7+
You can import *selection.json* back to the IcoMoon app using the *Import Icons* button (or via Main Menu → Manage Projects) to retrieve your icon selection.

0 commit comments

Comments
 (0)