Skip to content

Commit

Permalink
feat: repeat
Browse files Browse the repository at this point in the history
  • Loading branch information
sheepbox8646 committed Jan 4, 2025
1 parent e919280 commit b0e8b55
Show file tree
Hide file tree
Showing 9 changed files with 423 additions and 66 deletions.
6 changes: 3 additions & 3 deletions packages/app/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ import Widgets from "./components/Widgets.vue";
<TopMenubar />
<div class="flex-grow flex flex-col overflow-hidden">
<Preview :width="1600" :height="900" />
<Tools class="flex-shrink-0" />
<div class="flex-grow flex flex-row overflow-hidden">
<div class="w-1/4 overflow-y-auto">
<Tools class="flex-shrink-0 border-t" />
<div class="h-[300px] flex flex-row overflow-hidden border-t">
<div class="w-1/4 border-r overflow-y-auto">
<Widgets
:widgets="[{ type: 'Arc' }, { type: 'Arc' }, { type: 'Arc' }]"
/>
Expand Down
81 changes: 58 additions & 23 deletions packages/app/src/components/Preview.vue
Original file line number Diff line number Diff line change
@@ -1,48 +1,83 @@
<script setup lang="ts">
import { Motion } from "@vue-motion/lib";
/** @ts-expect-error virtual-import */

Check failure on line 3 in packages/app/src/components/Preview.vue

View workflow job for this annotation

GitHub Actions / type-check

Unused '@ts-expect-error' directive.
import Animation from "virtual:user-main";
import { ref, watch } from "vue";
// import TestAnimation from './__test__/TestAnimation.vue'
// import Animation from "virtual:user-main";
import { ref, watch, onMounted, onUnmounted } from "vue";

Check warning on line 5 in packages/app/src/components/Preview.vue

View workflow job for this annotation

GitHub Actions / eslint

'watch' is defined but never used
import TestAnimation from "./__test__/TestAnimation.vue";
const { width, height } = defineProps<{
width: number;
height: number;
}>();
const zoom = ref(((window.innerHeight - 40) * 0.6) / height);
const zoom = ref(1);
const container = ref<HTMLDivElement | null>(null);
function updateZoom() {
zoom.value =
((window.innerWidth - 40) * 0.6) / width <
((window.innerHeight - 40) * 0.6) / height
? ((window.innerHeight - 40) * 0.6) / height
: ((window.innerWidth - 40) * 0.6) / width;
if (!container.value) return;
const rect = container.value.getBoundingClientRect();
const containerWidth = rect.width;
const containerHeight = rect.height;
// Calculate aspect ratios
const videoRatio = width / height;
const containerRatio = containerWidth / containerHeight;
// Determine scaling based on aspect ratio comparison
if (containerRatio > videoRatio) {
// Container is wider than video - use height as constraint
zoom.value = Math.min(containerHeight / height, 1);
} else {
// Container is taller than video - use width as constraint
zoom.value = Math.min(containerWidth / width, 1);
}
}
window.addEventListener("resize", updateZoom);
// Update zoom when container is resized
const resizeObserver = new ResizeObserver(() => {
updateZoom();
});
onMounted(() => {
if (container.value) {
resizeObserver.observe(container.value);
}
});
watch(() => window.innerHeight, updateZoom);
onUnmounted(() => {
resizeObserver.disconnect();
});
let dev: boolean;
if (__D__) dev = true;
else dev = false;

Check warning on line 54 in packages/app/src/components/Preview.vue

View workflow job for this annotation

GitHub Actions / eslint

'dev' is assigned a value but never used
</script>

<template>
<div class="w-full h-[60%] flex overflow-hidden">
<div class="w-full h-full flex items-center justify-center">
<Motion
id="motion"
:width="width"
:height="height"
:scale="dev ? zoom : (null as any)"
:min-width="dev ? width * zoom : (null as any)"
:min-height="dev ? height * zoom : (null as any)"
<div class="flex-grow flex overflow-hidden">
<div
ref="container"
class="w-full h-full flex items-center justify-center p-4"
>
<div
:style="{
width: `${width}px`,
height: `${height}px`,
transform: `scale(${zoom})`,
transformOrigin: 'center',
}"
class="relative"
>
<Animation />
<!-- <TestAnimation /> -->
</Motion>
<Motion
id="motion"
:width="width"
:height="height"
class="max-w-full max-h-full"
>
<TestAnimation />
</Motion>
</div>
</div>
</div>
</template>
126 changes: 120 additions & 6 deletions packages/app/src/components/Timeline.vue
Original file line number Diff line number Diff line change
@@ -1,23 +1,118 @@
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from "vue";
import { usePlayer } from "@vue-motion/core";
const player = usePlayer();
const isDragging = ref(false);
const currentTime = ref(0);
const { widget } = defineProps<{
widget: {
duration: number;
};
}>();
function handleMouseDown(e: MouseEvent) {
isDragging.value = true;
updateTimeFromMouseEvent(e);
// Prevent text selection while dragging
e.preventDefault();
}
function handleMouseMove(e: MouseEvent) {
if (!isDragging.value) return;
// Find the SVG element and its container
const svg = document.querySelector("svg");
const container = document.querySelector(".overflow-auto");
if (!svg || !container) return;
const containerRect = container.getBoundingClientRect();
const scrollLeft = container.scrollLeft;
// Calculate x position considering scroll and container offset
// Allow negative values for smooth dragging experience
const rawX = e.clientX - containerRect.left - 40 + scrollLeft;
const x = Math.max(0, Math.min(widget.duration * 100, rawX)); // Clamp to timeline width
const totalWidth = widget.duration * 100; // 100px per second
// Auto scroll when dragging near edges or outside
const scrollSpeed = 10;
if (e.clientX <= containerRect.left) {
// Mouse is left of the container - scroll left faster based on distance
const distance = containerRect.left - e.clientX;
container.scrollLeft = Math.max(
0,
scrollLeft - scrollSpeed * (1 + distance / 50),
);
} else if (e.clientX >= containerRect.right) {
// Mouse is right of the container - scroll right faster based on distance
const distance = e.clientX - containerRect.right;
container.scrollLeft = scrollLeft + scrollSpeed * (1 + distance / 50);
}
// Calculate time in seconds (clamp between 0 and duration)
const newTime = Math.max(
0,
Math.min(widget.duration, (x / totalWidth) * widget.duration),
);
currentTime.value = newTime;
player.seek(newTime);
}
function handleMouseUp() {
isDragging.value = false;
}
function updateTimeFromMouseEvent(e: MouseEvent) {
const svg = e.currentTarget as SVGElement;
const container = svg.closest(".overflow-auto");
if (!container) return;
const containerRect = container.getBoundingClientRect();
const scrollLeft = container.scrollLeft;
const rawX = e.clientX - containerRect.left - 40 + scrollLeft;
const x = Math.max(0, Math.min(widget.duration * 100, rawX));
const totalWidth = widget.duration * 100;
const newTime = Math.max(
0,
Math.min(widget.duration, (x / totalWidth) * widget.duration),
);
currentTime.value = newTime;
player.seek(newTime);
}
// Add global mouse event listeners
onMounted(() => {
window.addEventListener("mousemove", handleMouseMove);
window.addEventListener("mouseup", handleMouseUp);
});
onUnmounted(() => {
window.removeEventListener("mousemove", handleMouseMove);
window.removeEventListener("mouseup", handleMouseUp);
});
</script>

<template>
<div class="w-full h-full overflow-scroll">
<div class="mx-10">
<div class="w-full h-full overflow-auto">
<div class="mx-10 relative">
<svg
@mousedown="handleMouseDown"
:style="{
width: `${widget.duration * 10}px`,
textAlign: 'center',
width: `${widget.duration * 100}px`,
height: '60px',
}"
class="cursor-col-resize"
>
<!-- Timeline marks - 10 marks per second -->
<line :x1="0" :y1="0" :x2="0" :y2="35" stroke="grey" stroke-width="1" />
<line
v-for="i in widget.duration * 10 - 10"
v-for="i in widget.duration * 10"
:key="i"
:x1="i * 10"
:y1="0"
Expand All @@ -26,11 +121,30 @@ const { widget } = defineProps<{
stroke="grey"
stroke-width="1"
/>

<!-- Time labels - one per second -->
<text :x="0" :y="50">0</text>
<text v-for="i in widget.duration - 1" :key="i" :x="i * 100" :y="50">
<text v-for="i in widget.duration" :key="i" :x="i * 100 - 10" :y="50">
{{ i }}
</text>

<!-- Current time indicator -->
<line
:x1="currentTime * 100"
:y1="0"
:x2="currentTime * 100"
:y2="50"
stroke="#2196f3"
stroke-width="2"
/>
<circle :cx="currentTime * 100" :cy="0" r="4" fill="#2196f3" />
</svg>
</div>
</div>
</template>

<style scoped>
.cursor-col-resize {
cursor: col-resize;
}
</style>
18 changes: 15 additions & 3 deletions packages/app/src/components/__test__/TestAnimation.vue
Original file line number Diff line number Diff line change
@@ -1,12 +1,24 @@
<script setup lang="ts">
import { Arc } from "@vue-motion/lib";
import { useMotion } from "@vue-motion/core";
import { Arc, Circle } from "@vue-motion/lib";

Check warning on line 2 in packages/app/src/components/__test__/TestAnimation.vue

View workflow job for this annotation

GitHub Actions / eslint

'Arc' is defined but never used
import { useMotion, usePlayer, useWidget } from "@vue-motion/core";
import { onMounted } from "vue";
import { CircleIns } from "@vue-motion/lib";
const motion = useMotion();
motion.width.value = 1600;
motion.height.value = 900;
const { play } = usePlayer();
const arc = useWidget<CircleIns>();
onMounted(() => {
arc.move(200, 300, {
duration: 10,
});
play();
});
</script>

<template>
<Arc :radius="100" />
<Circle :radius="100" :widget="arc" />
</template>
9 changes: 8 additions & 1 deletion packages/app/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,20 @@ import "./output.css";
import App from "./App.vue";
import Preview from "./components/Preview.vue";
import "font-awesome/css/font-awesome.css";
import { createPlayer } from "@vue-motion/core";
// /**@ts-ignore */
// import router from 'virtual:router'
// /**@ts-ignore */
// import player from 'virtual:player'

if (__D__) {
createApp(App).mount("#app");
createApp(App)
.use(
createPlayer({
studio: true,
}),
)
.mount("#app");
} else {
createApp(Preview).mount("#app");
}
Loading

0 comments on commit b0e8b55

Please sign in to comment.