浏览代码

horizontal scrolling timeline

clovis 1 月之前
父节点
当前提交
519c95461f

+ 22 - 20
src/assets/ClockTimeline/ClockTimeline.module.css

@@ -1,7 +1,7 @@
 .wrap {
   display: flex;
   flex-direction: column;
-  gap: 10px;
+  gap: 4px;
   background-color: #242324;
   border: 1px solid #646464;
   padding: 10px;
@@ -67,20 +67,40 @@
   width: 2px;
   background: #f1be64;
 }
-
+.scrollWrapper {
+  overflow-x: auto;
+  overflow-y: auto;
+  flex: 1;
+  container-type: inline-size;
+}
+.scrollContent {
+  position: relative;
+  width: max-content;
+  min-width: 100%;
+  display: flex;
+  flex-direction: column;
+  gap: 4px;
+  margin-bottom: 8px;
+}
 .rows {
   display: flex;
   flex-direction: column;
   gap: 10px;
 }
 .rowBlockWrap {
+  position: relative;
   display: flex;
   flex-direction: column;
+  width: max-content;
+  min-width: 100%;
   gap: 4px;
 }
 .rowHeader {
+  position: sticky;
+  left: 0;
   display: flex;
   align-items: center;
+  width: 100cqw;
   gap: 8px;
 }
 .rowNameInput {
@@ -142,21 +162,6 @@
   cursor: ew-resize;
   background: rgba(249, 180, 75, 0.35);
 }
-
-.addRowBtn {
-  align-self: flex-start;
-  background: none;
-  border: 1px dashed #646464;
-  color: #999;
-  border-radius: 4px;
-  padding: 6px 12px;
-  font-size: 12px;
-  cursor: pointer;
-}
-.addRowBtn:hover {
-  border-color: #f1be64;
-  color: #f1be64;
-}
 .rowThroughput {
   display: flex;
   align-items: center;
@@ -178,9 +183,6 @@
   font-family: ui-monospace, monospace;
   color: #f1be64;
 }
-.rows {
-  position: relative; /* anchors the alignment guide across all rows */
-}
 .alignGuide {
   position: absolute;
   top: 0;

+ 21 - 19
src/assets/ClockTimeline/ClockTimeline.tsx

@@ -16,8 +16,8 @@ export const defaultActivationSignal = {
 };
 
 export default function ClockTimeline({}: Props) {
-  const { duration, rowOrder, alignmentTick, addRow } = useClockStore();
-
+  const { duration, pixelsPerTick, rowOrder, alignmentTick, addRow } = useClockStore();
+  const timelineWidth = duration * pixelsPerTick;
   const majorTicks = useMemo(() => {
     const step = Math.max(1, Math.round(duration / 10));
     const out: number[] = [];
@@ -51,26 +51,28 @@ export default function ClockTimeline({}: Props) {
         ))}
         <span className={styles.hint}>Drag onto a row · Shift-click to multi-select · Ctrl+D to duplicate</span>
       </div>
-
-      <div className={styles.ruler}>
-        {majorTicks.map((t) => (
-          <div key={t} className={styles.rulerTick} style={{ left: `${(t / duration) * 100}%` }}>
-            <span>{t}</span>
+      <div className={styles.scrollWrapper}>
+        <div className={styles.scrollContent}>
+          <div className={styles.ruler} style={{ width: timelineWidth }}>
+            {majorTicks.map((t) => (
+              <div key={t} className={styles.rulerTick} style={{ left: `${(t / duration) * 100}%` }}>
+                <span>{t}</span>
+              </div>
+            ))}
+            <div className={styles.rulerReset} />
           </div>
-        ))}
-        <div className={styles.rulerReset} />
-      </div>
 
-      <div className={styles.rows}>
-        {alignmentTick !== null && (
-          <div className={styles.alignGuide} style={{ left: `${(alignmentTick / duration) * 100}%` }} />
-        )}
+          {alignmentTick !== null && (
+            <div className={styles.alignGuide} style={{ left: `${(alignmentTick / duration) * 100}%` }} />
+          )}
 
-        {rowOrder.map((rowId) => (
-          <TimelineRow key={rowId} rowId={rowId} />
-        ))}
-
-        <button className={styles.addRowBtn} onClick={handleAddRow}>
+          {rowOrder.map((rowId) => (
+            <TimelineRow key={rowId} rowId={rowId} />
+          ))}
+        </div>
+      </div>
+      <div>
+        <button className={"button"} onClick={handleAddRow}>
           + Add row
         </button>
       </div>

+ 0 - 1
src/assets/ClockTimeline/TimelineRow.tsx

@@ -1,7 +1,6 @@
 import { useRef } from "react";
 import { useClockStore } from "../../store/useClockStore";
 import { useTimelineDrag } from "../../hooks/useTimelineDrag";
-import SelectSignal from "../Selector/SelectSignal";
 import ExpressionInput from "../components/ExpressionInpux";
 import Icon from "../icon";
 import styles from "./ClockTimeline.module.css";

+ 10 - 13
src/store/useClockStore.ts

@@ -1,6 +1,5 @@
 import { create } from "zustand";
-import type { ClockBlock, ClockRow } from "../assets/types";
-import type { Signal } from "../assets/SelectSignal";
+import type { ClockBlock, ClockRow, Signal } from "../assets/types";
 
 export const defaultClockSignal: Signal = {
   type: "virtual-signal",
@@ -12,8 +11,11 @@ export const defaultClockSignal: Signal = {
 interface ClockState {
   // Global Settings
   duration: number;
-  clockSignal: Signal;
+  pixelsPerTick: number;
   setDuration: (duration: number) => void;
+  setZoom: (val: number) => void;
+
+  clockSignal: Signal;
   setClockSignal: (signal: Signal | null) => void;
 
   // Normalized Data Models
@@ -42,14 +44,13 @@ interface ClockState {
   removeBlocks: (ids: string[]) => void;
 
   // Selection
-  selectBlocks: (
-    ids: Set<string> | ((prev: Set<string>) => Set<string>),
-  ) => void;
+  selectBlocks: (ids: Set<string> | ((prev: Set<string>) => Set<string>)) => void;
   clearSelection: () => void;
 }
 
 export const useClockStore = create<ClockState>((set) => ({
   duration: 256,
+  pixelsPerTick: 4,
   clockSignal: defaultClockSignal, // Set your default signal here
 
   rows: {},
@@ -62,6 +63,7 @@ export const useClockStore = create<ClockState>((set) => ({
   loadState: (state: Partial<ClockState>) => set(() => ({ ...state })),
 
   setDuration: (duration) => set({ duration }),
+  setZoom: (val) => set({ pixelsPerTick: val }),
   setClockSignal: (clockSignal) => {
     if (clockSignal) set({ clockSignal });
   },
@@ -87,9 +89,7 @@ export const useClockStore = create<ClockState>((set) => ({
       delete newRows[id];
 
       // Also clean up orphan blocks
-      const newBlocks = Object.fromEntries(
-        Object.entries(state.blocks).filter(([_, block]) => block.rowId !== id),
-      );
+      const newBlocks = Object.fromEntries(Object.entries(state.blocks).filter(([_, block]) => block.rowId !== id));
 
       return {
         rows: newRows,
@@ -139,10 +139,7 @@ export const useClockStore = create<ClockState>((set) => ({
 
   selectBlocks: (idsOrUpdater) =>
     set((state) => ({
-      selectedBlockIds:
-        typeof idsOrUpdater === "function"
-          ? idsOrUpdater(state.selectedBlockIds)
-          : idsOrUpdater,
+      selectedBlockIds: typeof idsOrUpdater === "function" ? idsOrUpdater(state.selectedBlockIds) : idsOrUpdater,
     })),
 
   clearSelection: () => set({ selectedBlockIds: new Set() }),