From b92eb73160c45517be0ada49cbb02dc05a7d967b Mon Sep 17 00:00:00 2001 From: Kelly Jerrell Date: Tue, 26 May 2026 14:42:52 -0700 Subject: [PATCH 1/2] clean up exports and bug with video context not being cleared --- src/components/measureGrid/MeasureGrid.tsx | 2 +- src/dtProjects/dialog/DialogPresenter.tsx | 5 +- .../dialog/clipExport/FramesExportDialog.tsx | 2 +- .../settingsPanel/SettingsPanel.tsx | 3 +- src/dtProjects/state/types.ts | 8 +- src/dtProjects/state/watchFolders.ts | 2 +- src/dtProjects/util/resourceHandle.ts | 15 +- src/hooks/useGetContext.tsx | 11 +- src/metadata/toolbar/PinnedIcon.tsx | 142 +++++++++--------- 9 files changed, 97 insertions(+), 93 deletions(-) diff --git a/src/components/measureGrid/MeasureGrid.tsx b/src/components/measureGrid/MeasureGrid.tsx index fc184a67..5bfad176 100644 --- a/src/components/measureGrid/MeasureGrid.tsx +++ b/src/components/measureGrid/MeasureGrid.tsx @@ -7,7 +7,7 @@ export interface MeasureGridProps extends SimpleGridProps { maxItemLines?: number } -export function MeasureGrid(props: PropsWithChildren) { +function MeasureGrid(props: PropsWithChildren) { const { columns = 1, children, maxItemLines = 4, ...restProps } = props const sizerRef = useRef(null) diff --git a/src/dtProjects/dialog/DialogPresenter.tsx b/src/dtProjects/dialog/DialogPresenter.tsx index 6f4f37af..8939b78f 100644 --- a/src/dtProjects/dialog/DialogPresenter.tsx +++ b/src/dtProjects/dialog/DialogPresenter.tsx @@ -53,7 +53,9 @@ function DialogPresenter(props: DialogPresenterComponentProps) { const { uiState } = useDTP() const uiSnap = uiState.useSnap() - const { Dialog, dialogProps, panelProps, containerProps } = getDialogComponent(uiSnap.dialog) + const { Dialog, dialogProps, panelProps, containerProps } = getDialogComponent( + uiSnap.dialog as DialogState, + ) if (!Dialog) return null return ( @@ -68,6 +70,7 @@ function DialogPresenter(props: DialogPresenterComponentProps) { uiState.hideDialog() }} overflow={"hidden"} + {...restProps} > ) { - const { onClose, historyNode, ...restProps } = props + const { onClose, historyNode, image, ...restProps } = props const defaultWidth = historyNode.data.start_width * 64 const defaultHeight = historyNode.data.start_height * 64 diff --git a/src/dtProjects/settingsPanel/SettingsPanel.tsx b/src/dtProjects/settingsPanel/SettingsPanel.tsx index ede6b5da..d9e323b9 100644 --- a/src/dtProjects/settingsPanel/SettingsPanel.tsx +++ b/src/dtProjects/settingsPanel/SettingsPanel.tsx @@ -16,7 +16,8 @@ import { useSetting } from "@/state/settings" import type { ICommand } from "@/types" import type { DialogProps, SettingsDialogState } from "../dialog/types" import { useDTP } from "../state/context" -import type { WatchFolderState, WatchFoldersController } from "../state/watchFolders" +import type WatchFoldersController from "../state/watchFolders" +import type { WatchFolderState } from "../state/watchFolders" import GrantAccess from "./GrantAccess" function useCommands(watchFolders: WatchFoldersController): ICommand[] { diff --git a/src/dtProjects/state/types.ts b/src/dtProjects/state/types.ts index 702394ef..f49b9ceb 100644 --- a/src/dtProjects/state/types.ts +++ b/src/dtProjects/state/types.ts @@ -9,12 +9,8 @@ import type ModelsController from "./models" import type ProjectsController from "./projects" import type SearchController from "./search" import type { UIController } from "./uiState" -import type { - ListModelInfoFilesResult, - ProjectFileStats, - WatchFolderState, - WatchFoldersController, -} from "./watchFolders" +import type WatchFoldersController from "./watchFolders" +import type { ListModelInfoFilesResult, ProjectFileStats, WatchFolderState } from "./watchFolders" export type DTProjectsJobs = { "models-refresh": { diff --git a/src/dtProjects/state/watchFolders.ts b/src/dtProjects/state/watchFolders.ts index b37d2865..1da7f5f9 100644 --- a/src/dtProjects/state/watchFolders.ts +++ b/src/dtProjects/state/watchFolders.ts @@ -60,7 +60,7 @@ export type ListFilesResult = { * Takes a handler for when a full scan is required. * useDTP() will be responsible for assigning the handler */ -export class WatchFoldersController extends DTPStateController { +class WatchFoldersController extends DTPStateController { state = proxy({ folders: [] as WatchFolderState[], isDtFolderAdded: false, diff --git a/src/dtProjects/util/resourceHandle.ts b/src/dtProjects/util/resourceHandle.ts index 85f3f104..c04d3b0d 100644 --- a/src/dtProjects/util/resourceHandle.ts +++ b/src/dtProjects/util/resourceHandle.ts @@ -75,9 +75,11 @@ export class ResourceHandle { return this.history } - async getClip() { + async getFrameTensorId(frame: number) { const history = await this.getHistory() - return history?.clip + if (!history?.clip || !this.image?.id) return undefined + const clipFrames = await DtpService.getClip(this.image.id, history.clip.clip_id) + return clipFrames?.frames.find((f) => f.indexInAClip === frame)?.tensorId } async getPngData(frame?: number) { @@ -87,13 +89,8 @@ export class ResourceHandle { if (this.isCanvasStack) { return await this.renderCanvas() } - let tensorId: Nullable - if (frame !== undefined) { - const clip = await this.getClip() - tensorId = clip?.frames.find((f) => f.indexInAClip === frame)?.tensorId - } else { - tensorId = await this.getTensorId() - } + const tensorId = + frame !== undefined ? await this.getFrameTensorId(frame) : await this.getTensorId() if (!tensorId) throw new Error("No tensor id") const data = await DtpService.decodeTensor(this.projectId, tensorId, true, this.nodeId) diff --git a/src/hooks/useGetContext.tsx b/src/hooks/useGetContext.tsx index 0e8155c9..4483112e 100644 --- a/src/hooks/useGetContext.tsx +++ b/src/hooks/useGetContext.tsx @@ -1,4 +1,4 @@ -import { useContext, useRef } from "react" +import { useContext, useEffect, useRef } from "react" /** * This is a somewhat hacky solution to allow a component to access a context @@ -6,7 +6,7 @@ import { useContext, useRef } from "react" * of the component you want to access the context from. Either provide the ref as * an arg, or use the ref returned */ -export function useGetContext(context: React.Context, ref?: React.RefObject) { +export function useGetContext(context: React.Context, ref?: React.RefObject) { const contextRef = useRef(null) const Extractor = () => { @@ -14,6 +14,13 @@ export function useGetContext(context: React.Context, ref?: React.RefObjec contextRef.current = ctx if (ref) ref.current = ctx + useEffect(() => { + return () => { + contextRef.current = null + if (ref) ref.current = null + } + }, []) + return null } diff --git a/src/metadata/toolbar/PinnedIcon.tsx b/src/metadata/toolbar/PinnedIcon.tsx index ae6c0af8..159974f4 100644 --- a/src/metadata/toolbar/PinnedIcon.tsx +++ b/src/metadata/toolbar/PinnedIcon.tsx @@ -1,80 +1,80 @@ import { motion } from "motion/react" import { useEffect, useState } from "react" -export const PinnedIcon = ({ pin }: { pin?: number | null }) => { - const [isPinnedDisplay, setIsPinnedDisplay] = useState(false) +const PinnedIcon = ({ pin }: { pin?: number | null }) => { + const [isPinnedDisplay, setIsPinnedDisplay] = useState(false) - useEffect(() => { - if (pin === undefined) return - setIsPinnedDisplay(pin !== null) - }, [pin]) + useEffect(() => { + if (pin === undefined) return + setIsPinnedDisplay(pin !== null) + }, [pin]) - return ( - - {/* */} - - - - - - - - ) + return ( + + {/* */} + + + + + + + + ) - // return + // return } export default PinnedIcon From ad497dbc080c83c4651cc0d2b08676febf8244ca Mon Sep 17 00:00:00 2001 From: Kelly Jerrell Date: Tue, 26 May 2026 15:09:11 -0700 Subject: [PATCH 2/2] remove unused code --- src/components/itemsList/index.tsx | 0 src/components/ui/provider.tsx | 12 - src/components/video/FpsButton.tsx | 24 - .../virtualizedList/VirtualizedList2.tsx | 229 -------- .../virtualizedList/usePagedItemSource.ts | 183 ------- .../detailsOverlay/DetailsImages.tsx | 125 ----- src/hooks/useColor.tsx | 52 -- src/library/Library.tsx | 66 --- src/metadata/history/Scrollbar.tsx | 19 - src/metadata/state/context.tsx | 33 -- src/metadata/toolbar/ToolbarButton.tsx | 32 -- src/metadata/toolbar/ToolbarItem.tsx | 83 --- src/scratch/Coffee.tsx | 77 --- src/scratch/FilmStrip.tsx | 85 --- src/scratch/FourthDot.tsx | 84 --- src/scratch/Scratch.tsx | 504 ------------------ src/scratch/Scratch4.tsx | 218 -------- src/scratch/Tensordata.tsx | 109 ---- src/scratch/UpgradeSpinner.tsx | 158 ------ src/scratch/VList.tsx | 192 ------- src/scratch/Vid.tsx | 73 --- src/test/setup.ts | 1 - src/theme/tabs.ts | 258 --------- src/utils/DebounceMap.ts | 20 - src/utils/PagedItemSource.ts | 147 ----- src/utils/handler.ts | 23 - src/utils/metadata.ts | 13 - src/utils/pagedItemSourceF.ts | 141 ----- src/vid/Vid.tsx | 394 -------------- src/vid/ffmpegCommand.ts | 62 --- src/vid/types.ts | 10 - src/views.tsx | 1 - 32 files changed, 3428 deletions(-) delete mode 100644 src/components/itemsList/index.tsx delete mode 100644 src/components/ui/provider.tsx delete mode 100644 src/components/video/FpsButton.tsx delete mode 100644 src/components/virtualizedList/VirtualizedList2.tsx delete mode 100644 src/components/virtualizedList/usePagedItemSource.ts delete mode 100644 src/dtProjects/detailsOverlay/DetailsImages.tsx delete mode 100644 src/hooks/useColor.tsx delete mode 100644 src/library/Library.tsx delete mode 100644 src/metadata/history/Scrollbar.tsx delete mode 100644 src/metadata/state/context.tsx delete mode 100644 src/metadata/toolbar/ToolbarButton.tsx delete mode 100644 src/metadata/toolbar/ToolbarItem.tsx delete mode 100644 src/scratch/Coffee.tsx delete mode 100644 src/scratch/FilmStrip.tsx delete mode 100644 src/scratch/FourthDot.tsx delete mode 100644 src/scratch/Scratch.tsx delete mode 100644 src/scratch/Scratch4.tsx delete mode 100644 src/scratch/Tensordata.tsx delete mode 100644 src/scratch/UpgradeSpinner.tsx delete mode 100644 src/scratch/VList.tsx delete mode 100644 src/scratch/Vid.tsx delete mode 100644 src/test/setup.ts delete mode 100644 src/theme/tabs.ts delete mode 100644 src/utils/DebounceMap.ts delete mode 100644 src/utils/PagedItemSource.ts delete mode 100644 src/utils/handler.ts delete mode 100644 src/utils/metadata.ts delete mode 100644 src/utils/pagedItemSourceF.ts delete mode 100644 src/vid/Vid.tsx delete mode 100644 src/vid/ffmpegCommand.ts delete mode 100644 src/vid/types.ts diff --git a/src/components/itemsList/index.tsx b/src/components/itemsList/index.tsx deleted file mode 100644 index e69de29b..00000000 diff --git a/src/components/ui/provider.tsx b/src/components/ui/provider.tsx deleted file mode 100644 index 8c112468..00000000 --- a/src/components/ui/provider.tsx +++ /dev/null @@ -1,12 +0,0 @@ -"use client" - -import { ChakraProvider, defaultSystem } from "@chakra-ui/react" -import { ColorModeProvider, type ColorModeProviderProps } from "./color-mode" - -export function Provider(props: ColorModeProviderProps) { - return ( - - - - ) -} diff --git a/src/components/video/FpsButton.tsx b/src/components/video/FpsButton.tsx deleted file mode 100644 index b7e1096c..00000000 --- a/src/components/video/FpsButton.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import type { ComponentProps } from "react" -import { PanelButton } from ".." -import { useVideoContext } from "./context" - -interface FpsButtonProps extends ComponentProps {} - -function FpsButton(props: FpsButtonProps) { - const { ...restProps } = props - - const { fps, setFps, state } = useVideoContext() - - return ( - setFps(state.fps === 16 ? 20 : state.fps === 20 ? 24 : 16)} - {...restProps} - > - {fps} - - ) -} - -export default FpsButton diff --git a/src/components/virtualizedList/VirtualizedList2.tsx b/src/components/virtualizedList/VirtualizedList2.tsx deleted file mode 100644 index 871f2b02..00000000 --- a/src/components/virtualizedList/VirtualizedList2.tsx +++ /dev/null @@ -1,229 +0,0 @@ -import { Box, chakra } from "@chakra-ui/react" -import { type JSX, useCallback, useEffect, useRef } from "react" -import { createPortal } from "react-dom" -import { proxy, useSnapshot } from "valtio" -import { proxyMap } from "valtio/utils" - -interface VirtualizedListProps extends ChakraProps { - overscan?: number - itemComponent: (props: VirtualizedListItemProps) => JSX.Element - items: T[] | Readonly - keyFn?: (item: T) => string | number - estimatedItemSize: number - initialRenderCount?: number - itemProps?: unknown -} - -export interface VirtualizedListItemProps extends Record { - value?: T - index?: number - itemProps?: unknown -} - -type StateProxy = { - currentItem: number - updates: number - firstIndex: number - lastIndex: number - visibleHeight: number - expanded: ReturnType> -} -let renders = 0 -export default function VirtualizedList(props: VirtualizedListProps) { - const { - itemComponent: Item, - items, - keyFn, - estimatedItemSize, - initialRenderCount = 20, - overscan = 5, - itemProps, - ...restProps - } = props - - const stateRef = useRef(null) - if (!stateRef.current) { - stateRef.current = proxy({ - currentItem: 0, - updates: 0, - firstIndex: 0, - lastIndex: initialRenderCount, - visibleHeight: 1, - expanded: proxyMap(), - }) - } - const state = stateRef.current - const snap = useSnapshot(state) - - const containerRef = useRef(null) - const heights = useRef(new Map()) // <---- new height map - - // safe cumulative offset calculator - const getOffsetBefore = useCallback( - (index: number) => { - if (!Number.isFinite(index) || index <= 0) return 0 - index = Math.min(index, items.length) - - let sum = 0 - for (let i = 0; i < index; i++) { - const h = heights.current.get(i) - // ensure numeric fallback - const safe = typeof h === "number" && h > 0 && Number.isFinite(h) ? h : estimatedItemSize - sum += safe - } - return sum - }, - [items.length, estimatedItemSize], - ) - - // compute which items should be visible based on scroll position - const CHUNK_SIZE = 10 - - const recomputeVisibleRange = useCallback(() => { - const container = containerRef.current - if (!container) return - - const scrollTop = container.scrollTop - const viewHeight = container.clientHeight - - // find rough starting index - let start = 0 - let offset = 0 - while ( - start < items.length && - offset + (heights.current.get(start) ?? estimatedItemSize) < scrollTop - ) { - offset += heights.current.get(start) ?? estimatedItemSize - start++ - } - - let end = start - let heightSoFar = 0 - while ( - end < items.length && - heightSoFar < viewHeight * 2 // overscan - ) { - heightSoFar += heights.current.get(end) ?? estimatedItemSize - end++ - } - - // snap to chunk boundaries - const chunkStart = Math.floor(start / CHUNK_SIZE) * CHUNK_SIZE - const chunkEnd = Math.ceil(end / CHUNK_SIZE) * CHUNK_SIZE - - const nextFirst = Math.max(0, chunkStart - overscan) - const nextLast = Math.min(items.length, chunkEnd + overscan) - - // only update if changed (prevents useless renders) - if (nextFirst !== state.firstIndex || nextLast !== state.lastIndex) { - state.firstIndex = nextFirst - state.lastIndex = nextLast - state.updates++ - } - }, [items.length, estimatedItemSize, overscan, state]) - - // handle scroll event - const handleScroll = useCallback(() => { - recomputeVisibleRange() - }, [recomputeVisibleRange]) - - // handle resize of container - useEffect(() => { - if (!containerRef.current) return - const ro = new ResizeObserver(() => { - if (!containerRef.current) return - state.visibleHeight = containerRef.current.clientHeight - recomputeVisibleRange() - }) - ro.observe(containerRef.current) - return () => ro.disconnect() - }, [state, recomputeVisibleRange]) - - // measure each item dynamically - const registerItem = useCallback( - (index: number, el: HTMLDivElement | null) => { - if (!el) return - let lastHeight = 0 - - const measure = () => { - const h = el.offsetHeight - if (h > 0 && h !== lastHeight) { - lastHeight = h - heights.current.set(index, h) - // defer recompute to next frame to avoid ResizeObserver loop warnings - requestAnimationFrame(() => recomputeVisibleRange()) - } - } - - const ro = new ResizeObserver(measure) - ro.observe(el) - measure() - return () => ro.disconnect() - }, - [recomputeVisibleRange], - ) - - const safeIndex = (n: number) => Math.max(0, Math.min(items.length, n)) - - const totalHeight = getOffsetBefore(items.length) - const topSpacerHeight = getOffsetBefore(safeIndex(snap.firstIndex)) - const bottomSpacerHeight = Math.max(0, totalHeight - getOffsetBefore(safeIndex(snap.lastIndex))) - - if (!Number.isFinite(topSpacerHeight) || !Number.isFinite(bottomSpacerHeight)) { - console.warn("Spacer NaN detected", { - topSpacerHeight, - bottomSpacerHeight, - snap, - totalHeight, - }) - } - - return ( - - - - - {items.slice(snap.firstIndex, snap.lastIndex).map((item, i) => { - const index = i + snap.firstIndex - return ( -
registerItem(index, el)}> - -
- ) - })} - - -
- - {createPortal( - - Items: {items.length} - {"\n"} - Current: {snap.currentItem} - {"\n"} - Rendered: {snap.firstIndex}–{snap.lastIndex} - {"\n"} - Visible H: {snap.visibleHeight} - {"\n"} - Renders: {renders++} - , - document.getElementById("root")!, - )} -
- ) -} - -// Chakra wrappers -const Container = chakra("div", { - base: { overflowY: "auto", position: "relative", height: "100%" }, -}) - -const Content = chakra("div", { - base: { - display: "grid", - gridTemplateColumns: "1fr", - alignItems: "stretch", - justifyItems: "stretch", - minHeight: "100%", - }, -}) diff --git a/src/components/virtualizedList/usePagedItemSource.ts b/src/components/virtualizedList/usePagedItemSource.ts deleted file mode 100644 index b5b595b0..00000000 --- a/src/components/virtualizedList/usePagedItemSource.ts +++ /dev/null @@ -1,183 +0,0 @@ -import { Mutex } from "async-mutex" -import { useCallback, useEffect, useRef } from "react" -import { proxy, useSnapshot } from "valtio" -import { useInit } from "@/hooks/useInitRef" - -type PagedItem = T | null | undefined - -type Page = { - /** inclusive */ - from: number - /** inclusive */ - to: number - /** null indicates the item hasn't loaded yet - * undefined indicates the item doesn't exist, or the request failed */ - items: PagedItem[] -} - -type UsePagedItemSourceOpts = { - getItems: (skip: number, take: number) => Promise - /** use a fixed page size */ - pageSize: number - /** any items beyond this number will be ignored */ - getCount: () => Promise -} - -export function usePagedItemSource(opts: UsePagedItemSourceOpts) { - const { getItems, pageSize, getCount } = opts - - const state = useInit(() => - proxy({ - pages: [] as Page[], - renderItems: [] as PagedItem[], - firstIndex: 0, - lastIndex: 0, - pageSize, - totalCount: 0, - }), - ) - - const getterRef = useRef({ getItems, getCount }) - - useEffect(() => { - const getters = getterRef.current - if (getters.getItems !== getItems || getters.getCount !== getCount) { - getterRef.current = { getItems, getCount } - state.pages = [] - state.renderItems = [] - state.firstIndex = 0 - state.lastIndex = 0 - state.totalCount = 0 - } - getterRef.current = { getItems, getCount } - }, [getItems, getCount, state]) - - useEffect(() => { - getCount().then((count) => { - state.totalCount = count - }) - }, [getCount, state]) - - const pageLoader = useRef(new Mutex()) - const loadersWaiting = useRef(0) - - /** returns true if a new page was loaded */ - const loadPage = useCallback( - async (index: number, refresh = false) => { - if (state.pages[index] && !refresh) return false - const page = await getItems(index * pageSize, pageSize) - if (!page || page.length === 0) return false - state.pages[index] = { - from: index * pageSize, - to: (index + 1) * pageSize - 1, - items: [...page], - } - return true - }, - [getItems, pageSize, state], - ) - - const updateRenderItems = useCallback(() => { - state.renderItems = getRenderItems( - state.firstIndex, - Math.min(state.lastIndex, state.totalCount - 1), - state.pages, - ) - }, [state]) - - const ensurePages = useCallback( - async (refresh = false, clearOthers = false) => { - if (!state.totalCount) return - loadersWaiting.current++ - // mutex is used to prevent spamming multiple requests for the same page - // if this turns out to be slow, we mark individual pages as loading - // allowing for simultaneous requests for different pages - await pageLoader.current.runExclusive(async () => { - loadersWaiting.current-- - const { firstIndex, lastIndex } = state - - const firstPage = Math.floor(firstIndex / pageSize) - const lastPage = Math.floor(lastIndex / pageSize) - - if (clearOthers) { - state.pages = state.pages.filter( - (p) => p.from >= firstPage * pageSize && p.to <= lastPage * pageSize, - ) - } - - let pagesLoaded = 0 - - for (let i = firstPage; i <= lastPage; i++) { - if (await loadPage(i, refresh)) pagesLoaded++ - } - - if (pagesLoaded) updateRenderItems() - - if (loadersWaiting.current) return - - // if not, let's check for the neighboring pages - const nextPage = Math.min(lastPage + 1, Math.ceil(state.totalCount / pageSize) - 1) - await loadPage(nextPage) - - if (loadersWaiting.current) return - - const prevPage = Math.max(firstPage - 1, 0) - await loadPage(prevPage) - }) - }, - [pageSize, state, loadPage, updateRenderItems], - ) - - const setRenderWindow = useCallback( - (first: number, last: number) => { - state.firstIndex = first - state.lastIndex = last - - updateRenderItems() - ensurePages() - }, - [state, ensurePages, updateRenderItems], - ) - - // refresh items when the source may have changed (e.g. items were added/removed) - // if the source has changed significantly, use a new item source - // this attempts to keep the UI stable when the source changes - const refreshItems = useCallback(async () => { - state.totalCount = await getCount() - - const windowSize = state.lastIndex - state.firstIndex + 1 - state.lastIndex = Math.min(state.totalCount - 1, state.lastIndex) - state.firstIndex = Math.max(0, state.lastIndex - windowSize + 1) - - ensurePages(true, true) - }, [state, getCount, ensurePages]) - - const { renderItems, totalCount } = useSnapshot(state) - - return { - renderItems, - totalCount, - clearItems: refreshItems, - setRenderWindow, - } -} - -function getRenderItems(firstIndex: number, lastIndex: number, pages: Page[]) { - function* getItems(): Generator { - let index = firstIndex - let page: Page | undefined - while (index <= lastIndex) { - // assign the current page - if (!page || page.to < index) { - page = pages.find((p) => p && p.from <= index && p.to >= index) - } - - const item = page?.items[index - page?.from] ?? null - yield item - - index++ - } - } - - return [...getItems()] -} diff --git a/src/dtProjects/detailsOverlay/DetailsImages.tsx b/src/dtProjects/detailsOverlay/DetailsImages.tsx deleted file mode 100644 index 1278aa79..00000000 --- a/src/dtProjects/detailsOverlay/DetailsImages.tsx +++ /dev/null @@ -1,125 +0,0 @@ -import { Grid, HStack, Spinner } from "@chakra-ui/react" -import type { Snapshot } from "valtio" -import type { DTImageFull, ImageExtra } from "@/commands" -import urls from "@/commands/urls" -import { SpinnerRoot } from "@/components/common" -import VideoAudio from "@/components/video/Audio" -import { VideoContext, type VideoContextType } from "@/components/video/context" -import MuteButton from "@/components/video/MuteButton" -import PlayPauseButton from "@/components/video/PlayPauseButton" -import Seekbar from "@/components/video/Seekbar" -import Video from "@/components/video/Video" -import { VideoImage } from "@/components/video/VideoImage" -import { useGetContext } from "@/hooks/useGetContext" -import type { UIControllerState } from "../state/uiState" -import type { CanvasStack, SubItem } from "../types" -import DetailsImage from "./DetailsImage" -import ImageFallback from "./ImageFallback" -import SubItemWrapper from "./SubItemWrapper" - -interface DetailsImagesProps { - item: ImageExtra - itemDetails?: Snapshot - subItem?: Snapshot - showSpinner: boolean - videoRef?: React.RefObject -} - -function DetailsImages(props: DetailsImagesProps) { - const { item, itemDetails, subItem, showSpinner, videoRef } = props - - const { Extractor } = useGetContext(VideoContext, videoRef) - - if (!item) return null - if (!item.is_ready) return - - const srcHalf = urls.thumbHalf(item) - const srcFull = urls.thumb(item) - - const { width, height } = getSize(item, itemDetails, subItem) - - return ( - <> - {(itemDetails?.node.clip_id ?? -1) >= 0 ? ( - e.stopPropagation()} - templateRows={"1fr auto auto"} - // width={"100%"} - // height={"100%"} - maxHeight={"100%"} - overflow={"hidden"} - gridArea={"image"} - > - - - ) : ( - - )} - {(showSpinner || subItem?.isLoading) && ( - - - - )} - {subItem && ( - - )} - - ) -} - -export default DetailsImages - -function getSize( - item: ImageExtra, - itemDetails: Snapshot | undefined, - subItem: Snapshot | undefined, -): { width: number; height: number } { - if (subItem && "width" in subItem && subItem.width && "height" in subItem && subItem.height) { - return { width: subItem.width, height: subItem.height } - } - - const width = itemDetails?.node?.start_width ?? item.start_width * 64 - const height = itemDetails?.node?.start_height ?? item.start_height * 64 - return { width, height } -} diff --git a/src/hooks/useColor.tsx b/src/hooks/useColor.tsx deleted file mode 100644 index bf3284dd..00000000 --- a/src/hooks/useColor.tsx +++ /dev/null @@ -1,52 +0,0 @@ -export function useColor() { - const color = [ - "rose.500/20", - "pink.500/20", - "fuchsia.500/20", - "purple.500/20", - "violet.500/20", - "indigo.500/20", - "blue.500/20", - "sky.500/20", - "cyan.500/20", - "teal.500/20", - "emerald.500/20", - "green.500/20", - "lime.500/20", - "yellow.500/20", - "amber.500/20", - "orange.500/20", - "red.500/20", - "neutral.500/20", - "stone.500/20", - "zinc.500/20", - "gray.500/20", - "slate.500/20", - ] - let i = 0 - - return () => - `${color[i++ % color.length]}.500` as - | "rose.500/20" - | "pink.500/20" - | "fuchsia.500/20" - | "purple.500/20" - | "violet.500/20" - | "indigo.500/20" - | "blue.500/20" - | "sky.500/20" - | "cyan.500/20" - | "teal.500/20" - | "emerald.500/20" - | "green.500/20" - | "lime.500/20" - | "yellow.500/20" - | "amber.500/20" - | "orange.500/20" - | "red.500/20" - | "neutral.500/20" - | "stone.500/20" - | "zinc.500/20" - | "gray.500/20" - | "slate.500/20" -} diff --git a/src/library/Library.tsx b/src/library/Library.tsx deleted file mode 100644 index 46fd9412..00000000 --- a/src/library/Library.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import { Box, Button, VStack } from "@chakra-ui/react" -import { appDataDir, join } from "@tauri-apps/api/path" -import { open } from "@tauri-apps/plugin-dialog" -import { readDir, writeTextFile } from "@tauri-apps/plugin-fs" -import { proxy, useSnapshot } from "valtio" -import { getDrawThingsDataFromExif } from "@/metadata/helpers" -import { getExif } from "@/metadata/state/metadataStore" -import type { DrawThingsMetaData } from "@/types" - -const store = proxy({ - files: [] as string[], - status: "idle" as "idle" | "scanning", - progress: 0, -}) - -function Library() { - const snap = useSnapshot(store) - - return ( - - - {snap.files.length} files selected - - {snap.progress}% - - ) -} - -async function scanFiles(files: Readonly, update: (done: number, total: number) => void) { - const data = [] as { path: string; data: DrawThingsMetaData }[] - let done = 0 - for (const file of files) { - const exif = await getExif(file) - const dt = getDrawThingsDataFromExif(exif) - - if (dt) data.push({ path: file, data: dt }) - done++ - - if (done % 50 === 0) update(done, files.length) - } - return data -} - -export default Library diff --git a/src/metadata/history/Scrollbar.tsx b/src/metadata/history/Scrollbar.tsx deleted file mode 100644 index 2857b674..00000000 --- a/src/metadata/history/Scrollbar.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { chakra } from '@chakra-ui/react' - -interface ScrollbarProps extends ChakraProps {} - -function Scrollbar(props: ScrollbarProps) { - const { children, ...rest } = props - - return ( - - {children} - - ) -} - -const ScrollbarSomething = chakra("div", { - base: {} -}) - -export default Scrollbar \ No newline at end of file diff --git a/src/metadata/state/context.tsx b/src/metadata/state/context.tsx deleted file mode 100644 index 246cc958..00000000 --- a/src/metadata/state/context.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { createContext, type PropsWithChildren, useContext } from "react" -import type { ImageSource } from "@/types" -import type { ImageItem } from "./ImageItem" -import type { ExifType, getMetadataStore, MediaItemParam } from "./metadataStore" - -export type MetadataStoreContextType = { - state: ReturnType - selectImage(image?: MediaItemParam | null): void - pinImage(image: MediaItemParam, value: number | boolean | null): void - pinImage(useCurrent: true, value: number | boolean | null): void - clearAll(keepTabs: boolean): Promise - clearCurrent(): Promise - createImageItem( - imageData: Uint8Array, - type: string, - source: ImageSource, - ): Promise - getExif(imagePath: string): Promise - getExif(imageDataBuffer: ArrayBuffer): Promise - initialized: boolean -} - -const MetadataStoreContext = createContext>({ - initialized: false, -}) - -export function useMetadataStore() { - const context = useContext(MetadataStoreContext) - if (!context) throw new Error("useMetadataStore must be used within a MetadataStoreProvider") - return context -} - -export function MetadataStoreProvider(props: PropsWithChildren) {} diff --git a/src/metadata/toolbar/ToolbarButton.tsx b/src/metadata/toolbar/ToolbarButton.tsx deleted file mode 100644 index deafa984..00000000 --- a/src/metadata/toolbar/ToolbarButton.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import type { ComponentProps, ComponentType, PropsWithChildren, SVGProps } from "react" -import { IconButton } from "@/components" -import type { IconType } from "@/components/icons/icons" -import Tooltip from "@/components/Tooltip" - -type ToolbarButtonProps = ComponentProps & { - icon?: IconType | ComponentType> - tip?: string -} - -function ToolbarButton(props: PropsWithChildren) { - const { icon: Icon, children, onClick, tip, ...restProps } = props - - const content = Icon ? : children - - return ( - - - {content} - - - ) -} - -export default ToolbarButton diff --git a/src/metadata/toolbar/ToolbarItem.tsx b/src/metadata/toolbar/ToolbarItem.tsx deleted file mode 100644 index 79408695..00000000 --- a/src/metadata/toolbar/ToolbarItem.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import { useSnapshot } from "valtio" -import { MotionBox } from "@/components/common" -import { getMetadataStore } from "../state/metadataStore" -import type { ToolbarCommand } from "./commands" -import ToolbarButton from "./ToolbarButton" - -const separatorProps: ChakraProps["_before"] = { - content: '""', - width: "1px", - height: "1rem", - bgColor: "fg.2/20", - alignSelf: "center", - marginInline: "-1px", -} - -interface ToolbarItemProps { - command: ToolbarCommand> - showSeparator?: boolean - state: "hide" | "show" -} - -export function ToolbarItem(props: ToolbarItemProps) { - const { command, showSeparator, state } = props - const snap = useSnapshot(getMetadataStore()) as ReadonlyState< - ReturnType - > - - const tip = command.tip ?? command.getTip?.(snap) - const Icon = command.icon - const content = Icon ? : command.getIcon?.(snap) - - // const hDelay = 0.5 * (order ?? 0) - // const vDelay = 0.5 * (changedCount ?? 0) - - return ( - - command.action(getMetadataStore())} - > - {content} - - - ) -} - -export default ToolbarItem diff --git a/src/scratch/Coffee.tsx b/src/scratch/Coffee.tsx deleted file mode 100644 index a5beb899..00000000 --- a/src/scratch/Coffee.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import { motion, useAnimate } from "motion/react" -import { useEffect, useRef } from "react" -import { CheckRoot, Panel } from "@/components" - -function Empty() { - const svgRef = useRef(null) - - const [scope, anim] = useAnimate() - - useEffect(() => { - anim( - [ - [scope.current, { rotate: [0, 0] }], - [scope.current, { y: [0, -20] }], - ["#s1", { y: [0, 8] }], - ["#s2", { y: [0, 8] }], - ["#s3", { y: [0, 8] }], - ], - { - repeat: Infinity, - repeatType: "loop", - duration: 2, - }, - ) - }, [anim, scope.current]) - - return ( - - - - - - - - - - - ) -} - -export default Empty diff --git a/src/scratch/FilmStrip.tsx b/src/scratch/FilmStrip.tsx deleted file mode 100644 index 7c0c00f1..00000000 --- a/src/scratch/FilmStrip.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import { Fragment } from "react/jsx-runtime" -import { CheckRoot, Panel } from "@/components" - -const topEdge = 35 -const bottomEdge = 165 -const width = 220 -const height = 200 -const thickness = 12 - -function Empty() { - return ( - - - - - - - - {Array.from({ length: 4 }).map((_, i) => ( - - - - - ))} - - 81 - - - - - ) -} - -export default Empty diff --git a/src/scratch/FourthDot.tsx b/src/scratch/FourthDot.tsx deleted file mode 100644 index 8c60e1ad..00000000 --- a/src/scratch/FourthDot.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import { Box, Portal, VStack } from "@chakra-ui/react" -import { proxy, useSnapshot } from "valtio" -import { CheckRoot } from "@/components" -import { Panel } from "@/components/common" -import { useRootElement } from "@/hooks/useRootElement" -import { motion } from "motion/react" - -const store = proxy({ - isOpen: false, -}) - -function FourthDot(props) { - const snap = useSnapshot(store) - - const root = useRootElement("app") - - return ( - - - { - store.isOpen = !store.isOpen - }} - > - - - - - - - - - {snap.someState} - - - ) -} - -export default FourthDot diff --git a/src/scratch/Scratch.tsx b/src/scratch/Scratch.tsx deleted file mode 100644 index fef7b334..00000000 --- a/src/scratch/Scratch.tsx +++ /dev/null @@ -1,504 +0,0 @@ -import { SliderWithInput } from '@/components' -import { clipboardTextTypes, parseText } from "@/metadata/state/imageLoaders" -import { getClipboardTypes, getClipboardText } from "@/utils/clipboard" -import { shuffle } from "@/utils/helpers" -import { Box, Center, chakra, HStack, SimpleGrid, VStack } from "@chakra-ui/react" -import { invoke } from "@tauri-apps/api/core" -import { - cubicBezier, - motion, - MotionProps, - MotionValue, - useMotionValue, - useTransform, - transform, - mapValue, - transformValue, - useSpring, - useMotionTemplate, -} from "motion/react" -import { PropsWithChildren, useEffect, useMemo, useState } from "react" -import { proxy, useSnapshot } from "valtio" - -const store = proxy({ - file: "", - // sourceX: 34, - // sourceY: 55, - angle: 45, - depth: 5, - bgOpacity: 0.3, - shadOpacity: 0.2, - focus: 2, - n: 2, - d: 16, - z1: 0, - z2: 0, - p: 300, -}) - -const params = [ - // ["sourceX", 0, 100, 1], - // ["sourceY", 0, 100, 1], - ["angle", 0, 360, 1], - ["depth", 0, 20, 1], - ["focus", 0, 16, 1], - ["bgOpacity", 0, 1, 0.01], - ["shadOpacity", 0, 1, 0.01], - ["n", 1, 16, 1], - ["d", 1, 64, 1], - ["z1", -100, 100, 1], - ["z2", -100, 100, 1], - ["p", 0, 4000, 1], -] as [string, number, number, number][] - -const params2 = [ - ["curve_a", 0, 10, 0.01, new MotionValue(1)], - ["curve_b", 0, 10, 0.01, new MotionValue(1)], - ["curve_c", 0, 1, 0.01, new MotionValue(1)], - ["curve_d", 0, 1, 0.01, new MotionValue(1)], -] as const - -const params2p = proxy({ - curve_a: 1, - curve_b: 1, - curve_c: 1, - curve_d: 1, -}) - -function mapA(input_a, minA = 0.1, maxA = 10) { - // logarithmic interpolation - const logMin = Math.log(minA) - const logMax = Math.log(maxA) - const logA = logMin + (logMax - logMin) * input_a - return Math.exp(logA) -} - -const mvA = transformValue(() => params2[0][4].get()) //mapA(params2[0][4].get())) -const mvB = transformValue(() => params2[1][4].get()) //mapA(params2[1][4].get())) - -const easeB = () => (x) => { - const a = mvA.get() - const b = mvB.get() - return x ** a / (x ** a + (1 - x) ** b) -} - -function DPoint(props) { - const { x, width, height } = props - // single param - // const y = useTransform(mvA, (a) => x ** a / (x ** a + (1 - x) ** a)) - // two params - const y = useTransform([mvA, mvB], ([a, b]) => x ** a / (x ** a + (1 - x) ** b)) - const px = x * width - const py = useTransform(y, [0, 1], [height, 0]) - const psy = useSpring(py) - - return ( - - ) -} - -function Scratch(props: ChakraProps) { - const snap = useSnapshot(store) - const handlers = useMemo( - () => ({ - onDrop: async (e: React.DragEvent) => { - e.preventDefault() - const types = await getClipboardTypes("drag") - const cliptext = await getClipboardText( - clipboardTextTypes.filter((t) => types.includes(t)), - "drag", - ) - for (const [type, text] of Object.entries(cliptext)) { - const { files } = parseText(text, type) - if (files.length > 0) { - store.file = files[0] - invoke("load_metadata", { filepath: files[0] }) - return - } - } - }, - onDragOver: (e: React.DragEvent) => { - e.preventDefault() - }, - }), - [], - ) - - const { items } = getMotionItems(512, 512, 32) - - const boxShadow = shadow({ ...snap }) - - const mx = useSpring(0, {}) - const my = useSpring(0, {}) - const mxb = useSpring(0, {}) - const myb = useSpring(0, {}) - - const [tempA, setTempA] = useState(0) - const [tempB, setTempB] = useState(0) - - const tx = useTransform(mx, [0, 1], [-15, 15]) - const ty = useTransform(my, [0, 1], [-15, 15]) - - const po = useMotionTemplate`${mx.get() * 100}px ${my.get() * 100}px` - - return ( - - Hello - {snap.file} - {/* - check - - - - - - bg - - {items.map((item) => { - return - })} - */} - - -
{ - const box = e.currentTarget.getBoundingClientRect() - const px = (e.clientX - box.left) / box.width - const py = (e.clientY - box.top) / box.height - if (e.shiftKey) { - setTempA(px * 150 - 75) - setTempB(py * 80 - 40) - } else { - mx.set(px) - my.set(py) - } - }} - perspective={`${snap.p}px`} - transformStyle={"preserve-3d"} - > - - - - - - {/* {Array.from({ length: 120 }).map((_, i, arr) => { - const x = i / (arr.length - 1) - return */} -
- - - -
- - {params.map(([key, min, max, step]) => ( - { - store[key] = v - }} - min={min} - max={max} - step={step} - immediate={true} - /> - ))} - {params2.map(([key, min, max, step, mv]) => ( - { - mv.set(v) - params2p[key] = v - }} - min={min} - max={max} - step={step} - immediate={true} - /> - ))} - -
- ) -} - -// times will be normalized -// we'll do a checkerboard of dots popping in in random order -// then they'll expand into squares -// t=0 nothing -// t=0-1 all dots fade in -// t=1-2 dots become squares -// (the duration of 0-1 and 1-2 can be adjusted, and a gap can be added) - -type SequenceItem = { - index: number - initial: MotionProps["initial"] - animate: MotionProps["animate"] - transition: MotionProps["transition"] - style: MotionProps["style"] -} -const curves = [cubicBezier(0.74, 0.19, 0.87, 0.29), cubicBezier(0.1, 0.91, 0.87, 0.29)] -const totalDuration = 5 -const phases = [ - [0, 2], - [3, 4], -] - -const norm = (times: number[], phaseIndex: number) => { - const phase = phases[phaseIndex] - const duration = phase[1] - phase[0] - - return times.map((t) => (curves[phaseIndex](t) * duration + phase[0]) / totalDuration) -} - -function getMotionItems(width: number, height: number, size: number): { items: SequenceItem[] } { - const nCols = Math.ceil(width / size) - const nRows = Math.ceil(height / size) - const nItems = nCols * nRows - - const radiusSequence = [size / 8, size / 8, 0, 0] - const sizeSequence = [size / 4, size / 4, size, size] - // const sizeTimes = [0, 1, 2] - const sizeDuration = 0.5 - const sizeTimes = (ti) => [ - 0, - (ti / nItems) * (1 - sizeDuration / totalDuration), - (ti / nItems + sizeDuration / totalDuration) * (1 - sizeDuration / totalDuration), - 1, - ] - - const getX = (i) => Math.floor(i / nRows) - const xSequence = (x) => [ - x * size + (size * 3) / 8, - x * size + (size * 3) / 8, - x * size, - x * size, - ] - - const getY = (i) => i % nRows - const ySequence = (y) => [ - y * size + (size * 3) / 8, - y * size + (size * 3) / 8, - y * size, - y * size, - ] - - const getColorIndex = (i) => ((getX(i) % 2) + (getY(i) % 2)) % 2 - - const opacityDuration = 0.1 - const opacitySequence = [0, 0, 1, 1] - const opacityTimes = (ti) => [ - 0, - (ti / nItems) * (1 - opacityDuration / totalDuration), - (ti / nItems + opacityDuration / totalDuration) * (1 - opacityDuration / totalDuration), - 1, - ] - - const sizeTrans = (ti) => - ({ - times: norm(sizeTimes(ti), 1), - duration: 4, - repeat: Infinity, - repeatType: "loop", - }) as MotionProps["transition"] - - const indexes = shuffle(Array.from({ length: nItems }).map((_, i) => i)) - - const items = indexes.map((index, ti) => { - const style: MotionProps["style"] = { - fill: `var(--chakra-colors-check-${getColorIndex(index) + 1})`, - } - const initial: MotionProps["initial"] = { - opacity: 0, - } - const animate: MotionProps["animate"] = { - opacity: opacitySequence, - x: xSequence(getX(index)), - y: ySequence(getY(index)), - rx: radiusSequence, - ry: radiusSequence, - width: sizeSequence, - height: sizeSequence, - } - - const si = ((getX(index) + getY(index)) / (nCols + nRows)) * nItems - const transition: MotionProps["transition"] = { - opacity: { - duration: 4, - repeat: Infinity, - repeatType: "loop", - times: norm(opacityTimes(ti), 0), - }, - x: sizeTrans(si), - y: sizeTrans(si), - rx: sizeTrans(si), - ry: sizeTrans(si), - width: sizeTrans(si), - height: sizeTrans(si), - } - - return { index, initial, animate, transition, style } - }) - - return { items } -} - -function shadow(opts: typeof store) { - const { angle, depth, bgOpacity, shadOpacity, focus, n, d } = opts - - const getXY = (dist: number) => { - const rad = (angle * Math.PI) / 180 - const x = dist * Math.cos(rad) - const y = dist * Math.sin(rad) - return `${x.toFixed(2)}px ${y.toFixed(2)}px` - } - - const scale = 1 + depth / 80 - - const bs = Array.from({ length: n }, (_, i) => { - const p = n > 1 ? i / (n - 1) : 0 - const dist = (d * p * depth) / 5 - - return `${getXY(dist)} ${focus + depth * p ** 2}px ${-1}px rgba(0, 0, 0, ${shadOpacity})` - }).join(", ") - - return { - boxShadow: bs, - // filter: `drop-shadow(${bs})`, - backdropFilter: `blur(${depth / 5}px)`, - backgroundColor: `rgba(237, 238, 240, ${bgOpacity})`, - scale: scale, - } -} - -export default Scratch - -// 64, 0 - 64 -// 16 24 - 40 - -const MotionBox = chakra(motion.div, { - base: {}, -}) -type MBProps = Parameters[0] & { - mx: MotionValue - my: MotionValue -} - -const Surface = (props: MBProps) => { - const { mx, my, ...rest } = props - const { outer, inner } = splitProps(rest) - - const skewY = useTransform(mx, [0, 1], [0, 0]) - const rotateY = useTransform(mx, [0, 1], [35, -35]) - const skewX = useTransform(mx, [0, 1], [0, 0]) - const rotateX = useTransform(my, [0, 1], [-35, 35]) - - return ( - - - {props.children} - - - ) -} - -function splitProps(props: Partial) { - const { width, height, animate, initial, transition, top, left, bottom, right, ...rest } = props - const outer = { width, height, animate, initial, transition, top, left, bottom, right } - - return { outer, inner: rest } -} diff --git a/src/scratch/Scratch4.tsx b/src/scratch/Scratch4.tsx deleted file mode 100644 index 5c49b27e..00000000 --- a/src/scratch/Scratch4.tsx +++ /dev/null @@ -1,218 +0,0 @@ -import { Box, Button, HStack, VStack } from "@chakra-ui/react" -import { proxy, useSnapshot } from "valtio" -import { CheckRoot } from "@/components" -import { MotionBox, Panel } from "@/components/common" -import { motion } from "motion/react" -import PVList from "@/components/virtualizedList/PVLIst" -import { useEffect, useMemo } from "react" -import PVGrid from "@/components/virtualizedList/PVGrid" -import { dtProject, pdb } from "@/commands" -import { chunk } from "@/utils/helpers" - -const store = proxy({ - someState: "Hello", - // data: [] as { r: number; g: number; b: number }[], - data: [] as { h: number; s: number; l: number }[], - palette: [] as ReturnType, - width: 32, - height: 32, -}) - -function Empty(props) { - const snap = useSnapshot(store) - - useEffect(() => { - dtProject - .decodeTensor(1, "tensor_history_117557853", false) - // .decodeTensor(80, "color_palette_1222765681", false) - .then((data) => { - const buffer = new Uint8Array(data) - - const sized = resize(buffer, 768, 768, 32, 32) - const pixels = Array(sized?.byteLength / 4) - console.log(sized?.byteLength, "byte length") - for (let i = 0; i < sized.byteLength; i += 4) { - const r = sized[i] - const g = sized[i + 1] - const b = sized[i + 2] - pixels[i / 4] = { r, g, b } - } - store.data = pixels.map((p) => rgbToHsl(p)) - store.palette = arrangePalette(store.data, 32, 32) - }) - .catch(console.error) - }, []) - - return ( - - - - {snap.palette.map((col, i, { length }) => { - const [xa, ya] = [col.x1, col.y1] - const [xb, yb] = [(snap.width * col.h) / 360, (snap.height * col.l) / 100] - const [xc, yc] = [col.x2, col.y2] - return ( - - ) - })} - - - - ) -} - -export default Empty - -function Item(props) { - if (props.value === null) return Loading... - return ( - - {props.index} {props.value} - - ) -} - -async function getItems(skip: number, take: number) { - console.log("loading page", skip, take) - await new Promise((r) => setTimeout(r, 500)) - - return Array(take) - .fill(null) - .map((_, i) => i + skip) -} - -function arrangePalette( - colors: { h: number; s: number; l: number }[], - width: number = 16, - height: number = 16, -) { - const out = colors.map((c, i) => ({ - ...c, - x1: i % width, - y1: Math.floor(i / height), - x2: 0, - y2: 0, - })) - console.log(out.length, out.filter((c) => !c).length) - const lSort = out.toSorted((a, b) => a.h - b.h) - const lChunks = chunk(lSort, width) - - for (let y = 0; y < height; y++) { - const hSort = lChunks[y].sort((a, b) => a.s - b.s) - console.log(hSort) - for (let x = 0; x < height; x++) { - const col = hSort[x] - if (!col) { - // console.warn(y, x, hSort) - continue - } - col.x2 = x - col.y2 = y - } - } - - return out -} - -function rgbToCss(color: { r: number; g: number; b: number }) { - return `rgb(${color.r},${color.g},${color.b})` -} - -function hslToCss(color: { h: number; s: number; l: number }) { - return `hsl(${color.h},${color.s}%,${color.l}%)` -} - -function rgbToHsl(p: { r: number; g: number; b: number }) { - const r = p.r / 255 - const g = p.g / 255 - const b = p.b / 255 - - const max = Math.max(r, g, b) - const min = Math.min(r, g, b) - const delta = max - min - - let h = 0 - let s = 0 - const l = (max + min) / 2 - - if (delta !== 0) { - if (max === r) { - h = ((g - b) / delta) % 6 - } else if (max === g) { - h = (b - r) / delta + 2 - } else { - h = (r - g) / delta + 4 - } - h = Math.round(h * 60) - if (h < 0) h += 360 - - s = delta / (1 - Math.abs(2 * l - 1)) - } - - // return h in degrees, s and l as percentage strings to fit hsl(...) CSS usage - return { - h, - s: Math.round(s * 100), - l: Math.round(l * 100), - } -} - -function resize( - data: Uint8Array, - oldWidth: number, - oldHeight: number, - newWidth: number, - newHeight: number, -) { - const canvas = document.createElement("canvas") - canvas.width = Math.max(newWidth, oldWidth) - canvas.height = Math.max(newHeight, oldHeight) - const ctx = canvas.getContext("2d") - if (!ctx) return - - const imageData = ctx.createImageData(oldWidth, oldHeight) - for (let i = 0; i < oldHeight * oldWidth; i += 1) { - imageData.data[i * 4 + 0] = data[i * 3 + 0] - imageData.data[i * 4 + 1] = data[i * 3 + 1] - imageData.data[i * 4 + 2] = data[i * 3 + 2] - imageData.data[i * 4 + 3] = 255 - } - ctx.putImageData(imageData, 0, 0) - - ctx.drawImage(canvas, 0, 0, oldWidth, oldHeight, 0, 0, newWidth, newHeight) - - return ctx.getImageData(0, 0, newWidth, newHeight).data -} diff --git a/src/scratch/Tensordata.tsx b/src/scratch/Tensordata.tsx deleted file mode 100644 index fe5185c4..00000000 --- a/src/scratch/Tensordata.tsx +++ /dev/null @@ -1,109 +0,0 @@ -import { Box, Button, Grid, Menu, Portal, useMenu } from "@chakra-ui/react" -import { proxy, useSnapshot } from "valtio" -import { CheckRoot, Panel } from "@/components" - -const store = proxy({ - a: 0, - b: 0, - c: 0, -}) - -function Empty() { - const snap = useSnapshot(store) - const menu = useMenu() - - return ( - - - - e.stopPropagation()} - // onContextMenuCapture={(e) => e.stopPropagation()} - > - { - return - e.preventDefault() - e.stopPropagation() - const target = e.currentTarget - console.log(target) - menu.api.setOpen(true) - menu.api.reposition({ - // getAnchorElement: () => { - // console.log("getAnchorElement", target) - // return target - // }, - getAnchorRect: () => ({ - x: e.clientX, - y: e.clientY, - width: 0, - height: 0, - }), - offset: { mainAxis: 0, crossAxis: 0 }, - strategy: "fixed", - placement: "bottom-start", - }) - }} - > - - {/* */} - {snap.a} - - {/* */} - - {/* */} - {snap.b} - - {/* */} - - {snap.c} - - - - - - - New Text File {snap.a} - New File... - New Window - Open File... - Export - - - - - - - ) -} - -export default Empty diff --git a/src/scratch/UpgradeSpinner.tsx b/src/scratch/UpgradeSpinner.tsx deleted file mode 100644 index 00e578ae..00000000 --- a/src/scratch/UpgradeSpinner.tsx +++ /dev/null @@ -1,158 +0,0 @@ -import { motion } from "motion/react" -import { CheckRoot, Panel } from "@/components" -import { ColorMode, useColorMode } from "@/components/ui/color-mode" -import { Icon } from "@chakra-ui/react" -import { ComponentProps } from "react" - -function Empty() { - return ( - - - - - - ) -} -const ball = ` - M -8 -20 - Q -18 -22 -26 -10 - Q -34 0 -26 10 - Q -18 22 -8 20 - Q 2 22 10 10 - Q 18 0 10 -10 - Q 2 -22 -8 -20 Z` -const UpgradeIcon = (props: ComponentProps) => { - const { colorMode } = useColorMode() - - return ( - - - - - - - - - - - - - ) -} - -const UpgradePath = ({ - d, - d2, - tx, - ty, - i, - colorMode, -}: { - d: string - d2: string - tx: [number, number] - ty: [number, number] - i: number - colorMode: ColorMode -}) => { - const colorDuration = 5 - const fg = colorMode === "light" ? "#565e67" : "#8e97a2" - const fgb = colorMode === "light" ? "#476d53ff" : "#66a676ff" - return ( - - ) -} - -export default Empty - -type Ts = (x: number, y: number) => [number, number] -type P = [number, number] -function getPath(width = 180, steep = 100, thickness = 40) { - const height = steep + thickness - const p = ([x, y]: P) => `${x} ${y}` - const q = ([cx, cy]: P, [x, y]: P) => `Q ${cx} ${cy} ${x} ${y}` - const mid = ([x1, y1]: P, [x2, y2]: P): P => [(x1 + x2) / 2, (y1 + y2) / 2] - const ts: P[] = [ - [width / 2, 0], - [0, height - thickness], - [0, height], - [width / 2, thickness], - [width, height], - [width, height - thickness], - [width / 2, 0], - ] - const output = [`M ${p(ts[0])}`] - - for (let i = 1; i < 7; i++) { - const prev = ts[i - 1] - const curr = ts[i] - output.push(q(mid(prev, curr), curr)) - } - - output.push("Z") - - return output.join(" ") -} diff --git a/src/scratch/VList.tsx b/src/scratch/VList.tsx deleted file mode 100644 index c0bcfb94..00000000 --- a/src/scratch/VList.tsx +++ /dev/null @@ -1,192 +0,0 @@ -import { Box, Button, chakra, HStack, VStack } from "@chakra-ui/react" -import { proxy, useSnapshot } from "valtio" -import { CheckRoot } from "@/components" -import { Panel } from "@/components/common" -import VirtualizedList from "@/components/virtualizedList/VirtualizedList" -import { useEffect, useState } from "react" -import { DotSpinner } from "@/components/preview" - -const store = proxy({ - someState: "Hello", - items: Array.from({ length: 2000 }, () => randWord(10)), - selected: false, -}) - -function randWord(length: number) { - return Array.from({ length }, () => - String.fromCharCode((Math.random() < 0.5 ? 65 : 97) + Math.floor(Math.random() * 26)), - ).join("") -} - -function VList(props) { - const snap = useSnapshot(store) - - const bsa = - "0px 1px 4px -2px #00000033, 2px 4px 6px -2px #00000022, -1px 4px 6px -2px #00000022, 0px 3px 12px -3px #00000033" - const bsb = - "0px 1px 4px -1px #00000044, 2px 6px 10px -4px #00000022, -1px 6px 10px -4px #00000022, 0px 4px 16px -4px #00000044" - - return ( - - - - - - - - - - - {/* item} - /> */} - - - - {/* - - - - Content - - - - */} - - A List Of Things - - Content First Item - { - store.selected = !store.selected - }} - > - Content Some Item - - - Content Last Item - - - - Add - Remove - - An explanation of a thing with some text - - - - ) -} - -const PanelButton = chakra( - Button, - { - base: { - bgColor: "bg.1/20", - color: "fg.2", - height: "min-content", - paddingY: 2, - _hover: { - border: "1px solid {colors.fg.2/20}", - boxShadow: "0px 1px 5px -3px #00000055", - bgColor: "bg.2/50", - } - }, - }, - { defaultProps: { size: "sm", variant: "subtle" } }, -) - -const PanelListItem = chakra( - "div", - { - base: { - bgColor: "bg.2", - color: "fg.2", - paddingX: 2, - paddingY: 1, - borderRadius: 0, - borderBlock: "1px solid #00000033", - boxShadow: "0px 0px 18px -8px #00000022", - transition: "all 0.2s ease-out", - _focusVisible: { - outline: "2px solid {colors.blue.400/70} !important", - }, - }, - variants: { - selectable: { - true: { - _hover: { - // boxShadow: "0px 0px 18px -8px #00000022, 0px 2px 8px -2px #00000033", - transform: "scale(1.01)", - bgColor: "bg.3", - transition: "all 0.1s ease-out", - }, - }, - }, - selected: { - true: { - bgImage: "linear-gradient(to left, {colors.blue.500/30}, {colors.blue.500/40})", - // bgColor: "bg.1" - color: "fg.1", - }, - }, - }, - }, - { defaultProps: { tabIndex: 0 } }, -) - -const ListContainer = (props) => { - const { children, ...restProps } = props - return ( - - {children} - - ) -} - -const ListWrapper = chakra("div", { - base: { - position: "relative", - }, -}) - -const PaneListContainer = chakra("div", { - base: { - bgColor: "bg.deep", - height: "100%", - width: "100%", - color: "fg.2", - paddingY: 1, - paddingX: 1, - // borderInline:"2px solid {colors.bg.deep}", - borderRadius: 0, - gap: 1, - display: "flex", - justifyContent: "flex-start", - alignItems: "stretch", - flexDirection: "column", - // boxShadow: "0px 0px 30px 0px #00000088 inset", - // border: "1px solid #00000055", - }, -}) - -const PanelSectionHeader = chakra("h3", { - base: { - paddingX: 2, - fontWeight: "600", - color: "fg.2", - }, -}) - -export default VList diff --git a/src/scratch/Vid.tsx b/src/scratch/Vid.tsx deleted file mode 100644 index b6d89f13..00000000 --- a/src/scratch/Vid.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import { Box, Button, Grid } from "@chakra-ui/react" -import { listen } from "@tauri-apps/api/event" -import { useEffect } from "react" -import { proxy, useSnapshot } from "valtio" -import * as vid from "@/commands/vid" -import { CheckRoot, Panel } from "@/components" - -const store = proxy({ - a: undefined as boolean | undefined, - progress: 0, - total: 0, - received: 0, - downloadResult: undefined, - callResult: "", -}) - -function Empty() { - const snap = useSnapshot(store) - - useEffect(() => { - listen("ffmpeg_download_progress", (e) => { - store.progress = e.payload.progress - store.total = e.payload.total - store.received = e.payload.received - }) - }, []) - - return ( - - - - - {`Check: ${snap.a}`} - - - {`Progress: ${snap.progress}, Total: ${snap.total}, Received: ${snap.received}`} - {`Result: ${snap.downloadResult}`} - - {snap.callResult} - - - - - ) -} - -export default Empty diff --git a/src/test/setup.ts b/src/test/setup.ts deleted file mode 100644 index c44951a6..00000000 --- a/src/test/setup.ts +++ /dev/null @@ -1 +0,0 @@ -import '@testing-library/jest-dom' diff --git a/src/theme/tabs.ts b/src/theme/tabs.ts deleted file mode 100644 index 8d146b0b..00000000 --- a/src/theme/tabs.ts +++ /dev/null @@ -1,258 +0,0 @@ -import { defineSlotRecipe } from "@chakra-ui/react" - -export const tabsSlotRecipe = defineSlotRecipe({ - slots: ["root", "trigger", "list", "content", "contentGroup", "indicator"], - className: "chakra-tabs", - base: { - root: { - position: "relative", - display: "block", - }, - list: { - display: "flex", - position: "relative", - isolation: "isolate", - flexDirection: "row", - bgColor: "bg.2", - }, - trigger: { - outline: "0", - // minW: "var(--tabs-height)", - // height: "min-content", - py: "0", - px: "spacing.8", - // transform: "rotate(20deg)", - display: "flex", - alignItems: "center", - fontWeight: "medium", - position: "relative", - // cursor: "button", - // gap: "0", - - _focusVisible: { - zIndex: 1, - outline: "2px solid", - outlineColor: "colorPalette.focusRing", - }, - _disabled: { - // cursor: "not-allowed", - opacity: 0.5, - }, - }, - content: { - bgColor: "bg.1", - focusVisibleRing: "inside", - _horizontal: { - width: "100%", - pt: "var(--tabs-content-padding)", - }, - _vertical: { - height: "100%", - ps: "var(--tabs-content-padding)", - }, - }, - indicator: { - width: "var(--width)", - height: "var(--height)", - borderRadius: "var(--tabs-indicator-radius)", - bg: "var(--tabs-indicator-bg)", - zIndex: 2, - }, - }, - variants: { - fitted: { - true: { - list: { - display: "flex", - }, - trigger: { - flex: 1, - textAlign: "center", - justifyContent: "center", - }, - }, - }, - justify: { - start: { - list: { - justifyContent: "flex-start", - }, - }, - center: { - list: { - justifyContent: "center", - }, - }, - end: { - list: { - justifyContent: "flex-end", - }, - }, - }, - size: { - sm: { - root: { - // "--tabs-height": "sizes.9", - // "--tabs-content-padding": "0", - }, - trigger: { - // py: "1", - // px: "0", - textStyle: "sm", - }, - }, - md: { - root: { - "--tabs-height": "sizes.10", - "--tabs-content-padding": "spacing.4", - }, - trigger: { - py: "2", - px: "4", - textStyle: "sm", - }, - }, - lg: { - root: { - "--tabs-height": "sizes.11", - "--tabs-content-padding": "spacing.4.5", - }, - trigger: { - py: "2", - px: "4.5", - textStyle: "md", - }, - }, - }, - variant: { - line: { - list: {}, - trigger: { - color: "fg.muted", - _disabled: { - _active: { - bg: "initial", - }, - }, - _selected: { - color: "fg", - _horizontal: { - layerStyle: "indicator.bottom", - "--indicator-offset-y": "-1px", - "--indicator-color": "{colors.highlight}", - }, - _vertical: { - layerStyle: "indicator.end", - "--indicator-offset-x": "-1px", - }, - }, - }, - }, - subtle: { - trigger: { - borderRadius: "var(--tabs-trigger-radius)", - color: "fg.muted", - _selected: { - bg: "colorPalette.subtle", - color: "colorPalette.fg", - }, - }, - }, - enclosed: { - list: { - bg: "bg.muted", - padding: "1", - borderRadius: "l3", - minH: "calc(var(--tabs-height) - 4px)", - }, - trigger: { - justifyContent: "center", - color: "fg.muted", - borderRadius: "var(--tabs-trigger-radius)", - _selected: { - bg: "bg", - color: "colorPalette.fg", - shadow: "xs", - }, - }, - }, - outline: { - list: { - "--line-thickness": "1px", - "--line-offset": "calc(var(--line-thickness) * -1)", - borderColor: "border", - display: "flex", - _horizontal: { - _before: { - content: '""', - position: "absolute", - bottom: "0px", - width: "100%", - borderBottomWidth: "var(--line-thickness)", - borderBottomColor: "border", - }, - }, - _vertical: { - _before: { - content: '""', - position: "absolute", - insetInline: "var(--line-offset)", - height: "calc(100% - calc(var(--line-thickness) * 2))", - borderEndWidth: "var(--line-thickness)", - borderEndColor: "border", - }, - }, - }, - trigger: { - color: "fg.muted", - borderWidth: "1px", - borderColor: "transparent", - _selected: { - bg: "currentBg", - color: "colorPalette.fg", - }, - _horizontal: { - borderTopRadius: "var(--tabs-trigger-radius)", - marginBottom: "var(--line-offset)", - marginEnd: { - _notLast: "var(--line-offset)", - }, - _selected: { - borderColor: "border", - borderBottomColor: "transparent", - }, - }, - _vertical: { - borderStartRadius: "var(--tabs-trigger-radius)", - marginEnd: "var(--line-offset)", - marginBottom: { - _notLast: "var(--line-offset)", - }, - _selected: { - borderColor: "border", - borderEndColor: "transparent", - }, - }, - }, - }, - plain: { - trigger: { - color: "fg.muted", - _selected: { - color: "colorPalette.fg", - }, - borderRadius: "var(--tabs-trigger-radius)", - "&[data-selected][data-ssr]": { - bg: "var(--tabs-indicator-bg)", - shadow: "var(--tabs-indicator-shadow)", - borderRadius: "var(--tabs-indicator-radius)", - }, - }, - }, - }, - }, - defaultVariants: { - size: "sm", - variant: "plain", - }, -}) diff --git a/src/utils/DebounceMap.ts b/src/utils/DebounceMap.ts deleted file mode 100644 index 1eb0880d..00000000 --- a/src/utils/DebounceMap.ts +++ /dev/null @@ -1,20 +0,0 @@ -export class DebounceMap { - delay: number - map = new Map() - - constructor(delay: number) { - this.delay = delay - } - - set(key: K, callback: () => void) { - const existing = this.map.get(key) - if (existing) { - clearTimeout(existing) - } - const timeout = setTimeout(() => { - this.map.delete(key) - callback() - }, this.delay) - this.map.set(key, timeout) - } -} \ No newline at end of file diff --git a/src/utils/PagedItemSource.ts b/src/utils/PagedItemSource.ts deleted file mode 100644 index 0362792a..00000000 --- a/src/utils/PagedItemSource.ts +++ /dev/null @@ -1,147 +0,0 @@ -import { Mutex } from "async-mutex" -import { proxy, useSnapshot } from "valtio" -import va from "./valtio" - -export interface IVirtualItemSource { - // useRenderItems(): Snapshot - renderItems: PagedItem[] - setRenderWindow(first: number, last: number): void - totalCount: number -} - -export type PagedItem = T | null | undefined - -type Page = { - /** inclusive */ - from: number - /** inclusive */ - to: number - /** null indicates the item hasn't loaded yet - * undefined indicates the item doesn't exist, or the request failed */ - items: PagedItem[] -} - -type PagedItemSourceState = { - pages: Page[] - renderItems: PagedItem[] - firstIndex: number - lastIndex: number -} - -type PagedItemsGetter = (skip: number, take: number) => Promise[] | undefined> - -class PagedItemSource { - state = proxy>({ - pages: [], - renderItems: [], - firstIndex: 0, - lastIndex: 0, - }) - - getItems: PagedItemsGetter - pageSize: number - totalCount: number - - private pageLoader = new Mutex() - private loadersWaiting = 0 - - constructor(getItems: PagedItemsGetter, pageSize: number, totalCount: number) { - this.getItems = getItems - this.pageSize = pageSize - this.totalCount = totalCount - } - - setTotalCount(totalCount: number) { - this.totalCount = totalCount - } - - private async loadPage(index: number) { - if (this.state.pages[index]) return false - const page = await this.getItems(index * this.pageSize, this.pageSize) - if (!page || page.length === 0) return false - this.state.pages[index] = { - from: index * this.pageSize, - to: (index + 1) * this.pageSize - 1, - items: [...page], - } - return true - } - - private getRenderItems(firstIndex: number, lastIndex: number) { - const pages = this.state.pages - - function* getItems(): Generator { - let index = firstIndex - let page: Page | undefined - while (index <= lastIndex) { - // assign the current page - if (!page || page.to < index) { - page = pages.find((p) => p && p.from <= index && p.to >= index) - } - - const item = page?.items[index - page?.from] ?? null - yield item - - index++ - } - } - - return [...getItems()] - } - - private updateRenderItems() { - va.set( - this.state.renderItems, - this.getRenderItems( - this.state.firstIndex, - Math.min(this.state.lastIndex, this.totalCount - 1), - ), - ) - } - - private async ensurePages() { - if (!this.totalCount) return - - this.loadersWaiting++ - - await this.pageLoader.runExclusive(async () => { - this.loadersWaiting-- - const { firstIndex, lastIndex } = this.state - - const firstPage = Math.floor(firstIndex / this.pageSize) - const lastPage = Math.floor(lastIndex / this.pageSize) - - let pagesLoaded = 0 - - for (let i = firstPage; i <= lastPage; i++) { - if (await this.loadPage(i)) pagesLoaded++ - } - - if (pagesLoaded) this.updateRenderItems() - - if (this.loadersWaiting) return - - const nextPage = Math.min(lastPage + 1, Math.ceil(this.totalCount / this.pageSize) - 1) - await this.loadPage(nextPage) - - if (this.loadersWaiting) return - - const prevPage = Math.max(firstPage - 1, 0) - await this.loadPage(prevPage) - }) - } - - /** this is a react hook, and must follow the rules of hooks */ - useRenderItems() { - return useSnapshot(this.state).renderItems - } - - setRenderWindow(first: number, last: number) { - this.state.firstIndex = first - this.state.lastIndex = last - this.updateRenderItems() - this.ensurePages() - } -} - -export default PagedItemSource diff --git a/src/utils/handler.ts b/src/utils/handler.ts deleted file mode 100644 index bcb65be3..00000000 --- a/src/utils/handler.ts +++ /dev/null @@ -1,23 +0,0 @@ -export function eventCallback() { - let handlers: ((payload: T) => void)[] = [] - - function raise(payload: T) { - for (const handler of handlers) { - handler(payload) - } - } - - function addHandler(handler: (payload: T) => void) { - handlers.push(handler) - return () => removeHandler(handler) - } - - function removeHandler(handler: (payload: T) => void) { - handlers = handlers.filter((h) => h !== handler) - } - - raise.addHandler = addHandler - raise.removeHandler = removeHandler - - return raise -} \ No newline at end of file diff --git a/src/utils/metadata.ts b/src/utils/metadata.ts deleted file mode 100644 index 23526d59..00000000 --- a/src/utils/metadata.ts +++ /dev/null @@ -1,13 +0,0 @@ -import ExifReader from "exifreader" - -export async function getMetaDataFromBuffer( - buffer: Uint8Array, -): Promise { - try { - const exif = await ExifReader.load(buffer.buffer) - return exif - } catch (e) { - console.warn(e) - return undefined - } -} diff --git a/src/utils/pagedItemSourceF.ts b/src/utils/pagedItemSourceF.ts deleted file mode 100644 index 95fad820..00000000 --- a/src/utils/pagedItemSourceF.ts +++ /dev/null @@ -1,141 +0,0 @@ -import { Mutex } from "async-mutex" -import { proxy } from "valtio" -import va from "./valtio" - -export type PagedItem = T | null | undefined - -type Page = { - /** inclusive */ - from: number - /** inclusive */ - to: number - /** null indicates the item hasn't loaded yet - * undefined indicates the item doesn't exist, or the request failed */ - items: PagedItem[] -} - -type PagedItemSourceState = { - pages: Page[] - renderItems: PagedItem[] - firstIndex: number - lastIndex: number - totalCount?: number -} - -type PagedItemsGetter = (skip: number, take: number) => Promise[] | undefined> - -export type PagedItemSource = { - state: PagedItemSourceState - setRenderWindow: (first: number, last: number) => void -} - -export function pagedItemSource( - getItems: PagedItemsGetter, - getCount: () => Promise, - pageSize = 250, -): PagedItemSource { - const state = proxy>({ - pages: [], - renderItems: [], - firstIndex: 0, - lastIndex: 0, - totalCount: 0, - }) - - getCount().then((count) => { - state.totalCount = count - }) - - const pageLoader = new Mutex() - let loadersWaiting = 0 - - const loadPage = async (index: number) => { - if (state.pages[index]) return false - const page = await getItems(index * pageSize, pageSize) - if (!page || page.length === 0) return false - state.pages[index] = { - from: index * pageSize, - to: (index + 1) * pageSize - 1, - items: [...page], - } - return true - } - - const getRenderItems = (firstIndex: number, lastIndex: number) => { - const pages = state.pages - - function* getItems(): Generator { - let index = firstIndex - let page: Page | undefined - while (index <= lastIndex) { - // assign the current page - if (!page || page.to < index) { - page = pages.find((p) => p && p.from <= index && p.to >= index) - } - - const item = page?.items[index - page?.from] ?? null - yield item - - index++ - } - } - - return [...getItems()] - } - - const updateRenderItems = () => { - va.set( - state.renderItems, - getRenderItems( - state.firstIndex, - Math.min(state.lastIndex, (state.totalCount ?? 0) - 1), - ), - ) - } - - const ensurePages = () => { - if (!state.totalCount) return - loadersWaiting++ - - pageLoader.runExclusive(async () => { - loadersWaiting-- - const { firstIndex, lastIndex } = state - - const firstPage = Math.floor(firstIndex / pageSize) - const lastPage = Math.floor(lastIndex / pageSize) - - let pagesLoaded = 0 - - for (let i = firstPage; i <= lastPage; i++) { - if (await loadPage(i)) pagesLoaded++ - } - - if (pagesLoaded) updateRenderItems() - - if (loadersWaiting) return - - const nextPage = Math.min( - lastPage + 1, - Math.ceil((state.totalCount ?? 0) / pageSize) - 1, - ) - await loadPage(nextPage) - - if (loadersWaiting) return - - const prevPage = Math.max(firstPage - 1, 0) - await loadPage(prevPage) - }) - } - - const setRenderWindow = (first: number, last: number) => { - state.firstIndex = first - state.lastIndex = last - updateRenderItems() - ensurePages() - } - - return { - state, - setRenderWindow, - } -} diff --git a/src/vid/Vid.tsx b/src/vid/Vid.tsx deleted file mode 100644 index 15c80e3b..00000000 --- a/src/vid/Vid.tsx +++ /dev/null @@ -1,394 +0,0 @@ -// @ts-nocheck -import { CheckRoot } from "@/components" -import { clipboardTextTypes, parseText } from "@/metadata/state/imageLoaders" -import { getClipboardText, getClipboardTypes } from "@/utils/clipboard" -import { - Box, - Button, - Center, - createListCollection, - HStack, - Input, - Select, - VStack, -} from "@chakra-ui/react" -import { useCallback, useMemo, useRef, useState } from "react" -import { proxy, useSnapshot } from "valtio" -import { Command } from "@tauri-apps/plugin-shell" -import { writeTextFile } from "@tauri-apps/plugin-fs" -import { appCacheDir, appDataDir } from "@tauri-apps/api/path" -import { convertFileSrc } from "@tauri-apps/api/core" - -const interpolationCollection = createListCollection({ - items: ["none", "simple", "blend", "motion", "interpolate"], -}) - -type StoreType = { - types: string[] - clips: string[] - stdout: string[] - stderr: string[] - output: string - mode: string - fps: number - status: string -} - -function Vid(props: ChakraProps) { - const storeRef = useRef(null) - if (!storeRef.current) { - storeRef.current = proxy({ - types: [] as string[], - clips: [] as string[], - stdout: [] as string[], - stderr: [] as string[], - output: "output.mp4", - mode: "none", - fps: 0, - status: "", - }) - } - const store = storeRef.current - const snap = useSnapshot(store) - - const [output, setOutput] = useState("output.mp4") - const [fps, setFps] = useState(60) - - const handlers = useMemo( - () => ({ - onDrop: async (e: React.DragEvent) => { - e.preventDefault() - const types = await getClipboardTypes("drag") - const cliptext = await getClipboardText( - clipboardTextTypes.filter((t) => types.includes(t)), - "drag", - ) - for (const [type, text] of Object.entries(cliptext)) { - const { files } = parseText(text, type) - const video = files.find((f) => f.endsWith(".mp4") || f.endsWith(".mov")) - if (video) { - store.clips.push(video) - return - } - } - }, - onDragOver: (e: React.DragEvent) => { - e.preventDefault() - }, - }), - [store.clips], - ) - - console.log(snap) - - return ( - - - - {snap.clips.map((t) => ( - // biome-ignore lint/a11y/useMediaCaption: na - - {/* */} - - - - Output Filename - { - setOutput(e.target.value) - }} - /> - - - FPS - { - setFps(parseInt(e.target.value, 10)) - }} - /> - - - Mode - { - console.log(e) - store.mode = e.value[0] - }} - > - - - - - - - - - - - - {interpolationCollection.items.map((item) => ( - - {item} - - - ))} - - - - - - - - {snap.status} - {/* - - {snap.stdout.map((t, i) => ( - {t} - ))} - - - {snap.stderr.map((t, i) => ( - {t} - ))} - - */} - - - ) -} - -export default Vid - -export type ConcatVideoOpts = { - videoPaths: string[] - outputPath: string - mode: "none" | "simple" | "blend" | "motion" - fps: number -} - -export async function concatVideo( - opts: ConcatVideoOpts, - callback?: (stdout: string | null, stderr: string | null) => void, -): Promise { - const { videoPaths, outputPath, mode, fps } = opts - - if (videoPaths.length === 0) { - throw new Error("No video paths provided ❗") - } - - // Build FFmpeg concat file content - const concatFileContent = videoPaths - .map((path) => `file '${path.replace(/'/g, "'\\''")}'`) - .join("\n") - - // Write it to a temp location inside Tauri cache dir - const cacheDir = await appDataDir() - const concatFilePath = `${cacheDir}/ffmpeg_concat_list.txt` - - await writeTextFile(concatFilePath, concatFileContent) - - // Base args - const args = ["-f", "concat", "-y", "-safe", "0", "-i", concatFilePath] - - // Mode-specific filters and encoding - switch (mode) { - case "none": - // Just copy streams and set container FPS - args.push("-c", "copy", "-r", String(fps), outputPath) - break - - case "simple": - args.push( - "-vf", - `fps=${fps}`, - "-c:v", - "libx264", - "-preset", - "medium", - "-crf", - "18", - "-c:a", - "aac", - "-b:a", - "192k", - outputPath, - ) - break - - case "blend": - args.push( - "-vf", - `minterpolate=fps=${fps}:mi_mode=blend`, - "-c:v", - "libx264", - "-preset", - "medium", - "-crf", - "18", - "-c:a", - "aac", - "-b:a", - "192k", - outputPath, - ) - break - - case "motion": - args.push( - "-vf", - `minterpolate=fps=${fps}:mi_mode=mci:me_mode=bidir`, - "-c:v", - "libx264", - "-preset", - "medium", - "-crf", - "18", - "-c:a", - "aac", - "-b:a", - "192k", - outputPath, - ) - break - } - - console.log(["ffmpeg", ...args].join(" ")) - - const command = Command.create("ffmpeg", args) - - return new Promise((resolve, reject) => { - command.on("close", (data) => { - if (data.code === 0) { - resolve() - } else { - console.error(data) - reject(new Error(`FFmpeg failed with exit code ${data.code}`)) - } - }) - - command.on("error", (error) => { - reject(error) - }) - - if (callback) { - command.stdout.on("data", (data) => callback(data, null)) - command.stderr.on("data", (data) => callback(null, data)) - } - - command.spawn().catch(reject) - }) -} - -async function concatVideosX( - videoPaths: string[], - outputPath: string, - callback?: (stdout: string | null, stderr: string | null) => void, -): Promise { - if (videoPaths.length === 0) { - throw new Error("No video paths provided ❗") - } - - // Build FFmpeg concat file content - const concatFileContent = videoPaths - .map((path) => `file '${path.replace(/'/g, "'\\''")}'`) - .join("\n") - - // Write it to a temp location inside Tauri cache dir - const cacheDir = await appDataDir() - const concatFilePath = `${cacheDir}/ffmpeg_concat_list.txt` - - await writeTextFile(concatFilePath, concatFileContent) - - // Build ffmpeg command - const args = [ - "-f", - "concat", - "-y", - "-safe", - "0", - "-i", - concatFilePath, - "-c", - "copy", - outputPath, - ] - console.log(["ffmpeg", ...args].join(" ")) - const command = Command.create("ffmpeg", args) - - return new Promise((resolve, reject) => { - command.on("close", (data) => { - if (data.code === 0) { - resolve() - } else { - console.error(data) - reject(new Error(`FFmpeg failed with exit code ${data.code}`)) - } - }) - - command.on("error", (error) => { - reject(error) - }) - - if (callback) { - command.stdout.on("data", (data) => callback(data, null)) - command.stderr.on("data", (data) => callback(null, data)) - } - - command.spawn().catch(reject) - }) -} diff --git a/src/vid/ffmpegCommand.ts b/src/vid/ffmpegCommand.ts deleted file mode 100644 index 78c27da7..00000000 --- a/src/vid/ffmpegCommand.ts +++ /dev/null @@ -1,62 +0,0 @@ -import type { AudioCodec, Filter, Format, StreamSpecifier, VideoCodec } from "./types" - -export class FFMpegCommand { - private args: string[] = [] - - input(file: string): this { - this.args.push("-i", file) - return this - } - - output(file: string): this { - this.args.push(file) - return this - } - - videoCodec(codec: VideoCodec): this { - this.args.push("-c:v", codec) - return this - } - - audioCodec(codec: AudioCodec): this { - this.args.push("-c:a", codec) - return this - } - - outputFormat(format: Format): this { - this.args.push("-f", format) - return this - } - - map(stream: StreamSpecifier): this { - this.args.push("-map", stream) - return this - } - - addFlag(flag: string): this { - this.args.push(flag) - return this - } - - addOption(key: string, value?: string): this { - this.args.push(key) - if (value) this.args.push(value) - return this - } - - addFilter(stream: "v" | "a", filter: Filter): this { - const opts = filter.options - ? Object.entries(filter.options) - .map(([k, v]) => `${k}=${v}`) - .join(":") - : "" - this.args.push(`-filter:${stream}`, `${filter.name}${opts ? `=${opts}` : ""}`) - return this - } - - build(): string { - return ["ffmpeg", ...this.args].join(" ") - } -} - -const cmd = new FFMpegCommand() \ No newline at end of file diff --git a/src/vid/types.ts b/src/vid/types.ts deleted file mode 100644 index d7ba4f70..00000000 --- a/src/vid/types.ts +++ /dev/null @@ -1,10 +0,0 @@ -export type VideoCodec = "libx264" | "libx265" | "vp9" | "mpeg4" | "copy" -export type AudioCodec = "aac" | "mp3" | "opus" | "pcm_s16le" | "copy" -export type Format = "mp4" | "mkv" | "webm" | "mov" | "avi" | "flv" - -export type Filter = { - name: string - options?: Record -} - -export type StreamSpecifier = string diff --git a/src/views.tsx b/src/views.tsx index e9d016aa..3f90c1d3 100644 --- a/src/views.tsx +++ b/src/views.tsx @@ -32,7 +32,6 @@ export const viewDescription = [ export const views = { metadata: lazy(() => import("./metadata/Metadata")), - vid: lazy(() => import("./vid/Vid")), projects: lazy(() => import("./dtProjects/DTProjects")), scratch: lazy(() => import("./scratch/Scratch3")), }