ClockTimeline.tsx 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. import { useMemo } from "react";
  2. import Icon from "./icon";
  3. import styles from "./ClockTimeline.module.css";
  4. import { ACTION_PRESETS } from "./types";
  5. import TimelineRow from "./components/TimelineRow";
  6. import { useClockStore } from "../store/useClockStore";
  7. type Props = {};
  8. export const defaultActivationSignal = {
  9. type: "virtual-signal",
  10. name: "signal-check",
  11. subgroup: "virtual-signal",
  12. icon: "virtual-signal/signal-check.png",
  13. order: "a[checked]",
  14. };
  15. export default function ClockTimeline({}: Props) {
  16. const { duration, rowOrder, alignmentTick, addRow } = useClockStore();
  17. const majorTicks = useMemo(() => {
  18. const step = Math.max(1, Math.round(duration / 10));
  19. const out: number[] = [];
  20. for (let t = 0; t <= duration; t += step) out.push(t);
  21. return out;
  22. }, [duration]);
  23. const onPaletteDragStart = (e: React.DragEvent, presetId: string) => {
  24. e.dataTransfer.setData("text/plain", presetId);
  25. e.dataTransfer.effectAllowed = "copy";
  26. };
  27. const handleAddRow = () => {
  28. addRow({
  29. id: `row-${Date.now()}`,
  30. name: `Row ${rowOrder.length + 1}`,
  31. signals: [defaultActivationSignal],
  32. stackSize: 16,
  33. inserterCount: 1,
  34. });
  35. };
  36. return (
  37. <div className={styles.wrap}>
  38. <div className={styles.palette}>
  39. {ACTION_PRESETS.map((p) => (
  40. <div
  41. key={p.id}
  42. className={styles.chip}
  43. draggable
  44. onDragStart={(e) => onPaletteDragStart(e, p.id)}
  45. >
  46. {p.fromItem && (
  47. <Icon iconName={`item/${p.fromItem}.png`} size={20} />
  48. )}
  49. <span>{p.label}</span>
  50. <span className={styles.chipTicks}>{p.ticks}t</span>
  51. </div>
  52. ))}
  53. <span className={styles.hint}>
  54. Drag onto a row · Shift-click to multi-select · Ctrl+D to duplicate
  55. </span>
  56. </div>
  57. <div className={styles.ruler}>
  58. {majorTicks.map((t) => (
  59. <div
  60. key={t}
  61. className={styles.rulerTick}
  62. style={{ left: `${(t / duration) * 100}%` }}
  63. >
  64. <span>{t}</span>
  65. </div>
  66. ))}
  67. <div className={styles.rulerReset} />
  68. </div>
  69. <div className={styles.rows}>
  70. {alignmentTick !== null && (
  71. <div
  72. className={styles.alignGuide}
  73. style={{ left: `${(alignmentTick / duration) * 100}%` }}
  74. />
  75. )}
  76. {rowOrder.map((rowId) => (
  77. <TimelineRow key={rowId} rowId={rowId} />
  78. ))}
  79. <button className={styles.addRowBtn} onClick={handleAddRow}>
  80. + Add row
  81. </button>
  82. </div>
  83. </div>
  84. );
  85. }