summaryrefslogtreecommitdiff
path: root/apps/web/src/stores
diff options
context:
space:
mode:
Diffstat (limited to 'apps/web/src/stores')
-rw-r--r--apps/web/src/stores/playback-store.ts20
-rw-r--r--apps/web/src/stores/project-store.ts126
-rw-r--r--apps/web/src/stores/timeline-store.ts19
3 files changed, 159 insertions, 6 deletions
diff --git a/apps/web/src/stores/playback-store.ts b/apps/web/src/stores/playback-store.ts
index 70ff490d..feea0f46 100644
--- a/apps/web/src/stores/playback-store.ts
+++ b/apps/web/src/stores/playback-store.ts
@@ -1,5 +1,7 @@
import { create } from "zustand";
import type { PlaybackState, PlaybackControls } from "@/types/playback";
+import { useTimelineStore } from "@/stores/timeline-store";
+import { useProjectStore } from "./project-store";
interface PlaybackStore extends PlaybackState, PlaybackControls {
setDuration: (duration: number) => void;
@@ -22,7 +24,6 @@ const startTimer = (store: () => PlaybackStore) => {
const newTime = state.currentTime + delta * state.speed;
// Get actual content duration from timeline store
- const { useTimelineStore } = require("@/stores/timeline-store");
const actualContentDuration = useTimelineStore
.getState()
.getTotalDuration();
@@ -33,12 +34,21 @@ const startTimer = (store: () => PlaybackStore) => {
actualContentDuration > 0 ? actualContentDuration : state.duration;
if (newTime >= effectiveDuration) {
- // When content completes, pause and reset playhead to start
+ // When content completes, pause just before the end so we can see the last frame
+ const projectFps = useProjectStore.getState().activeProject?.fps;
+ if (!projectFps)
+ console.error("Project FPS is not set, assuming 30fps");
+
+ const frameOffset = 1 / (projectFps ?? 30); // Stop 1 frame before end based on project FPS
+ const stopTime = Math.max(0, effectiveDuration - frameOffset);
+
state.pause();
- state.setCurrentTime(0);
- // Notify video elements to sync with reset
+ state.setCurrentTime(stopTime);
+ // Notify video elements to sync with end position
window.dispatchEvent(
- new CustomEvent("playback-seek", { detail: { time: 0 } })
+ new CustomEvent("playback-seek", {
+ detail: { time: stopTime },
+ })
);
} else {
state.setCurrentTime(newTime);
diff --git a/apps/web/src/stores/project-store.ts b/apps/web/src/stores/project-store.ts
index 7bb38039..5c617dff 100644
--- a/apps/web/src/stores/project-store.ts
+++ b/apps/web/src/stores/project-store.ts
@@ -11,6 +11,7 @@ interface ProjectStore {
savedProjects: TProject[];
isLoading: boolean;
isInitialized: boolean;
+ invalidProjectIds?: Set<string>;
// Actions
createNewProject: (name: string) => Promise<string>;
@@ -28,10 +29,20 @@ interface ProjectStore {
) => Promise<void>;
updateProjectFps: (fps: number) => Promise<void>;
+ // Bookmark methods
+ toggleBookmark: (time: number) => Promise<void>;
+ isBookmarked: (time: number) => boolean;
+ removeBookmark: (time: number) => Promise<void>;
+
getFilteredAndSortedProjects: (
searchQuery: string,
sortOption: string
) => TProject[];
+
+ // Global invalid project ID tracking
+ isInvalidProjectId: (id: string) => boolean;
+ markProjectIdAsInvalid: (id: string) => void;
+ clearInvalidProjectIds: () => void;
}
export const useProjectStore = create<ProjectStore>((set, get) => ({
@@ -39,6 +50,98 @@ export const useProjectStore = create<ProjectStore>((set, get) => ({
savedProjects: [],
isLoading: true,
isInitialized: false,
+ invalidProjectIds: new Set<string>(),
+
+ // Implementation of bookmark methods
+ toggleBookmark: async (time: number) => {
+ const { activeProject } = get();
+ if (!activeProject) return;
+
+ // Round time to the nearest frame
+ const fps = activeProject.fps || 30;
+ const frameTime = Math.round(time * fps) / fps;
+
+ const bookmarks = activeProject.bookmarks || [];
+ let updatedBookmarks: number[];
+
+ // Check if already bookmarked
+ const bookmarkIndex = bookmarks.findIndex(
+ (bookmark) => Math.abs(bookmark - frameTime) < 0.001
+ );
+
+ if (bookmarkIndex !== -1) {
+ // Remove bookmark
+ updatedBookmarks = bookmarks.filter((_, i) => i !== bookmarkIndex);
+ } else {
+ // Add bookmark
+ updatedBookmarks = [...bookmarks, frameTime].sort((a, b) => a - b);
+ }
+
+ const updatedProject = {
+ ...activeProject,
+ bookmarks: updatedBookmarks,
+ updatedAt: new Date(),
+ };
+
+ try {
+ await storageService.saveProject(updatedProject);
+ set({ activeProject: updatedProject });
+ await get().loadAllProjects(); // Refresh the list
+ } catch (error) {
+ console.error("Failed to update project bookmarks:", error);
+ toast.error("Failed to update bookmarks", {
+ description: "Please try again",
+ });
+ }
+ },
+
+ isBookmarked: (time: number) => {
+ const { activeProject } = get();
+ if (!activeProject || !activeProject.bookmarks) return false;
+
+ // Round time to the nearest frame
+ const fps = activeProject.fps || 30;
+ const frameTime = Math.round(time * fps) / fps;
+
+ return activeProject.bookmarks.some(
+ (bookmark) => Math.abs(bookmark - frameTime) < 0.001
+ );
+ },
+
+ removeBookmark: async (time: number) => {
+ const { activeProject } = get();
+ if (!activeProject || !activeProject.bookmarks) return;
+
+ // Round time to the nearest frame
+ const fps = activeProject.fps || 30;
+ const frameTime = Math.round(time * fps) / fps;
+
+ const updatedBookmarks = activeProject.bookmarks.filter(
+ (bookmark) => Math.abs(bookmark - frameTime) >= 0.001
+ );
+
+ if (updatedBookmarks.length === activeProject.bookmarks.length) {
+ // No bookmark found to remove
+ return;
+ }
+
+ const updatedProject = {
+ ...activeProject,
+ bookmarks: updatedBookmarks,
+ updatedAt: new Date(),
+ };
+
+ try {
+ await storageService.saveProject(updatedProject);
+ set({ activeProject: updatedProject });
+ await get().loadAllProjects(); // Refresh the list
+ } catch (error) {
+ console.error("Failed to update project bookmarks:", error);
+ toast.error("Failed to remove bookmark", {
+ description: "Please try again",
+ });
+ }
+ },
createNewProject: async (name: string) => {
const newProject: TProject = {
@@ -50,6 +153,8 @@ export const useProjectStore = create<ProjectStore>((set, get) => ({
backgroundColor: "#000000",
backgroundType: "color",
blurIntensity: 8,
+ bookmarks: [],
+ fps: 30,
};
set({ activeProject: newProject });
@@ -230,9 +335,9 @@ export const useProjectStore = create<ProjectStore>((set, get) => ({
existingNumbers.length > 0 ? Math.max(...existingNumbers) + 1 : 1;
const newProject: TProject = {
+ ...project, // Copy all properties from the original project
id: generateUUID(),
name: `(${nextNumber}) ${baseName}`,
- thumbnail: project.thumbnail,
createdAt: new Date(),
updatedAt: new Date(),
};
@@ -357,4 +462,23 @@ export const useProjectStore = create<ProjectStore>((set, get) => ({
return sortedProjects;
},
+
+ // Global invalid project ID tracking implementation
+ isInvalidProjectId: (id: string) => {
+ const invalidIds = get().invalidProjectIds || new Set();
+ return invalidIds.has(id);
+ },
+
+ markProjectIdAsInvalid: (id: string) => {
+ set((state) => ({
+ invalidProjectIds: new Set([
+ ...(state.invalidProjectIds || new Set()),
+ id,
+ ]),
+ }));
+ },
+
+ clearInvalidProjectIds: () => {
+ set({ invalidProjectIds: new Set() });
+ },
}));
diff --git a/apps/web/src/stores/timeline-store.ts b/apps/web/src/stores/timeline-store.ts
index f3497be9..0f441176 100644
--- a/apps/web/src/stores/timeline-store.ts
+++ b/apps/web/src/stores/timeline-store.ts
@@ -126,6 +126,7 @@ interface TimelineStore {
pushHistory?: boolean
) => void;
toggleTrackMute: (trackId: string) => void;
+ toggleElementHidden: (trackId: string, elementId: string) => void;
// Split operations for elements
splitElement: (
@@ -868,6 +869,24 @@ export const useTimelineStore = create<TimelineStore>((set, get) => {
);
},
+ toggleElementHidden: (trackId, elementId) => {
+ get().pushHistory();
+ updateTracksAndSave(
+ get()._tracks.map((track) =>
+ track.id === trackId
+ ? {
+ ...track,
+ elements: track.elements.map((element) =>
+ element.id === elementId
+ ? { ...element, hidden: !element.hidden }
+ : element
+ ),
+ }
+ : track
+ )
+ );
+ },
+
updateTextElement: (trackId, elementId, updates) => {
get().pushHistory();
updateTracksAndSave(