summaryrefslogtreecommitdiff
path: root/tools/timeline_editor/index.html
diff options
context:
space:
mode:
Diffstat (limited to 'tools/timeline_editor/index.html')
-rw-r--r--tools/timeline_editor/index.html528
1 files changed, 443 insertions, 85 deletions
diff --git a/tools/timeline_editor/index.html b/tools/timeline_editor/index.html
index d1c759d..c5e0264 100644
--- a/tools/timeline_editor/index.html
+++ b/tools/timeline_editor/index.html
@@ -4,107 +4,466 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Timeline Editor - timeline.seq</title>
+ <link rel="stylesheet" href="../common/style.css">
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><rect width='100' height='100' fill='%231e1e1e'/><rect x='10' y='30' width='15' height='40' fill='%234ec9b0'/><rect x='30' y='20' width='15' height='60' fill='%234ec9b0'/><rect x='50' y='35' width='15' height='30' fill='%234ec9b0'/><rect x='70' y='15' width='15' height='70' fill='%234ec9b0'/></svg>">
<style>
- :root {
- --bg-dark: #1e1e1e;
- --bg-medium: #252526;
- --bg-light: #3c3c3c;
- --text-primary: #d4d4d4;
- --text-muted: #858585;
- --accent-blue: #0e639c;
- --accent-blue-hover: #1177bb;
- --accent-green: #4ec9b0;
- --accent-orange: #ce9178;
- --accent-red: #f48771;
- --border-color: #858585;
- --gap: 10px;
- --radius: 4px;
+ body {
+ padding: 20px;
+ min-height: 100vh;
}
- * { margin: 0; padding: 0; box-sizing: border-box; }
- body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: var(--bg-dark); color: var(--text-primary); padding: 20px; min-height: 100vh; }
- .container { max-width: 100%; width: 100%; margin: 0 auto; }
+ .container {
+ max-width: 100%;
+ width: 100%;
+ margin: 0 auto;
+ }
+
+ header {
+ background: var(--bg-medium);
+ padding: 20px;
+ border-radius: 8px;
+ margin-bottom: 20px;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 20px;
+ flex-wrap: wrap;
+ }
+
+ .zoom-controls {
+ display: flex;
+ gap: var(--gap);
+ flex-wrap: wrap;
+ align-items: center;
+ margin-bottom: var(--gap);
+ }
+
+ .checkbox-label {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ cursor: pointer;
+ user-select: none;
+ }
- header { background: var(--bg-medium); padding: 20px; border-radius: 8px; margin-bottom: 20px; display: flex; align-items: center; justify-content: space-between; gap: 20px; flex-wrap: wrap; }
- h1 { color: var(--accent-green); white-space: nowrap; }
- .controls { display: flex; gap: var(--gap); flex-wrap: wrap; align-items: center; }
- .zoom-controls { display: flex; gap: var(--gap); flex-wrap: wrap; align-items: center; margin-bottom: var(--gap); }
+ .checkbox-label input[type="checkbox"] {
+ cursor: pointer;
+ }
- button, .file-label { background: var(--accent-blue); color: white; border: none; padding: 10px 20px; border-radius: var(--radius); cursor: pointer; font-size: 14px; display: inline-block; }
- button:hover, .file-label:hover { background: var(--accent-blue-hover); }
- button:disabled { background: var(--bg-light); cursor: not-allowed; }
- input[type="file"] { display: none; }
+ .timeline-container {
+ background: var(--bg-medium);
+ border-radius: 8px;
+ position: relative;
+ height: calc(100vh - 280px);
+ min-height: 500px;
+ display: flex;
+ flex-direction: column;
+ }
- .checkbox-label { display: flex; align-items: center; gap: 8px; cursor: pointer; user-select: none; }
- .checkbox-label input[type="checkbox"] { cursor: pointer; }
+ .timeline-content {
+ flex: 1;
+ overflow: auto;
+ position: relative;
+ padding: 0 20px 20px 20px;
+ scrollbar-width: none;
+ -ms-overflow-style: none;
+ }
- .timeline-container { background: var(--bg-medium); border-radius: 8px; position: relative; height: calc(100vh - 280px); min-height: 500px; display: flex; flex-direction: column; }
- .timeline-content { flex: 1; overflow: auto; position: relative; padding: 0 20px 20px 20px; scrollbar-width: none; -ms-overflow-style: none; }
- .timeline-content::-webkit-scrollbar { display: none; }
- .timeline { position: relative; min-height: 100%; }
+ .timeline-content::-webkit-scrollbar {
+ display: none;
+ }
- .sticky-header { position: sticky; top: 0; background: var(--bg-medium); z-index: 100; padding: 20px 20px 10px 20px; border-bottom: 2px solid var(--bg-light); flex-shrink: 0; }
- .waveform-container { position: relative; height: 80px; overflow: hidden; background: rgba(0, 0, 0, 0.3); border-radius: var(--radius); cursor: crosshair; }
- #cpuLoadCanvas { position: absolute; left: 0; bottom: 0; height: 10px; display: block; z-index: 1; }
- #waveformCanvas { position: absolute; left: 0; top: 0; height: 80px; display: block; z-index: 2; }
- .waveform-cursor { position: absolute; top: 0; bottom: 0; width: 1px; background: rgba(78, 201, 176, 0.6); pointer-events: none; z-index: 3; display: none; }
- .waveform-tooltip { position: absolute; background: rgba(30, 30, 30, 0.95); color: var(--text-primary); padding: 6px 10px; border-radius: 4px; font-size: 12px; pointer-events: none; z-index: 4; display: none; white-space: nowrap; border: 1px solid var(--border-color); box-shadow: 0 2px 8px rgba(0,0,0,0.3); }
+ .timeline {
+ position: relative;
+ min-height: 100%;
+ }
- .playback-indicator { position: absolute; top: 0; bottom: 0; left: 20px; width: 2px; background: var(--accent-red); box-shadow: 0 0 4px rgba(244, 135, 113, 0.8); pointer-events: none; z-index: 110; display: none; }
+ .sticky-header {
+ position: sticky;
+ top: 0;
+ background: var(--bg-medium);
+ z-index: 100;
+ padding: 20px 20px 10px 20px;
+ border-bottom: 2px solid var(--bg-light);
+ flex-shrink: 0;
+ }
- .time-markers { position: relative; height: 30px; margin-top: var(--gap); border-bottom: 1px solid var(--bg-light); }
- .time-marker { position: absolute; top: 0; font-size: 12px; color: var(--text-muted); }
- .time-marker::before { content: ''; position: absolute; left: 0; top: 20px; width: 1px; height: 10px; background: var(--bg-light); }
- .time-marker::after { content: ''; position: absolute; left: 0; top: 30px; width: 1px; height: 10000px; background: rgba(60, 60, 60, 0.2); pointer-events: none; }
+ .waveform-container {
+ position: relative;
+ height: 80px;
+ overflow: hidden;
+ background: rgba(0, 0, 0, 0.5);
+ border-radius: var(--radius);
+ cursor: crosshair;
+ }
+
+ #cpuLoadCanvas {
+ position: absolute;
+ left: 0;
+ bottom: 0;
+ height: 10px;
+ display: block;
+ z-index: 1;
+ }
+
+ #waveformCanvas {
+ position: absolute;
+ left: 0;
+ top: 0;
+ height: 80px;
+ display: block;
+ z-index: 2;
+ }
+
+ .waveform-cursor {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ width: 1px;
+ background: rgba(78, 201, 176, 0.6);
+ pointer-events: none;
+ z-index: 3;
+ display: none;
+ }
+
+ .waveform-tooltip {
+ position: absolute;
+ background: rgba(30, 30, 30, 0.95);
+ color: var(--text-primary);
+ padding: 6px 10px;
+ border-radius: 4px;
+ font-size: 12px;
+ pointer-events: none;
+ z-index: 4;
+ display: none;
+ white-space: nowrap;
+ border: 1px solid var(--border-color);
+ box-shadow: 0 2px 8px rgba(0,0,0,0.3);
+ }
+
+ .playback-indicator {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 20px;
+ width: 2px;
+ background: var(--accent-red);
+ box-shadow: 0 0 4px rgba(244, 135, 113, 0.8);
+ pointer-events: none;
+ z-index: 110;
+ display: none;
+ }
+
+ .time-markers {
+ position: relative;
+ height: 30px;
+ margin-top: var(--gap);
+ border-bottom: 1px solid var(--bg-light);
+ }
+
+ .time-marker {
+ position: absolute;
+ top: 0;
+ font-size: 12px;
+ color: var(--text-muted);
+ }
+
+ .time-marker::before {
+ content: '';
+ position: absolute;
+ left: 0;
+ top: 20px;
+ width: 1px;
+ height: 10px;
+ background: var(--bg-light);
+ }
+
+ .time-marker::after {
+ content: '';
+ position: absolute;
+ left: 0;
+ top: 30px;
+ width: 1px;
+ height: 10000px;
+ background: rgba(100, 100, 60, 0.9);
+ pointer-events: none;
+ }
+
+ .sequence {
+ position: absolute;
+ background: #264f78;
+ border: 2px solid var(--accent-blue);
+ border-radius: var(--radius);
+ padding: 8px;
+ cursor: move;
+ min-height: 40px;
+ transition: box-shadow 0.2s;
+ }
+
+ .sequence:hover {
+ box-shadow: 0 0 10px rgba(14, 99, 156, 0.5);
+ }
+
+ .sequence.selected {
+ border-color: var(--accent-green);
+ box-shadow: 0 0 10px rgba(78, 201, 176, 0.5);
+ }
+
+ .sequence.collapsed {
+ overflow: hidden !important;
+ background: #1a3a4a !important;
+ }
+
+ .sequence.collapsed .sequence-name {
+ display: none !important;
+ }
+
+ .sequence.active-playing {
+ border-color: var(--accent-green);
+ background: #2a5f4a;
+ }
+
+ .sequence.active-flash {
+ animation: sequenceFlash 0.6s ease-out;
+ }
- .sequence { position: absolute; background: #264f78; border: 2px solid var(--accent-blue); border-radius: var(--radius); padding: 8px; cursor: move; min-height: 40px; transition: box-shadow 0.2s; }
- .sequence:hover { box-shadow: 0 0 10px rgba(14, 99, 156, 0.5); }
- .sequence.selected { border-color: var(--accent-green); box-shadow: 0 0 10px rgba(78, 201, 176, 0.5); }
- .sequence.collapsed { overflow: hidden !important; background: #1a3a4a !important; }
- .sequence.collapsed .sequence-name { display: none !important; }
- .sequence.active-playing { border-color: var(--accent-green); background: #2a5f4a; }
- .sequence.active-flash { animation: sequenceFlash 0.6s ease-out; }
@keyframes sequenceFlash {
- 0% { box-shadow: 0 0 20px rgba(78, 201, 176, 0.8); border-color: var(--accent-green); }
- 100% { box-shadow: 0 0 10px rgba(14, 99, 156, 0.5); border-color: var(--accent-blue); }
+ 0% {
+ box-shadow: 0 0 20px rgba(78, 201, 176, 0.8);
+ border-color: var(--accent-green);
+ }
+ 100% {
+ box-shadow: 0 0 10px rgba(14, 99, 156, 0.5);
+ border-color: var(--accent-blue);
+ }
}
- .sequence-header { position: absolute; top: 0; left: 0; right: 0; padding: 8px; z-index: 5; cursor: move; user-select: none; }
- .sequence-header-name { font-size: 14px; font-weight: bold; color: #ffffff; }
- .sequence:not(.collapsed) .sequence-header-name { display: none; }
- .sequence-name { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); font-size: 24px; font-weight: bold; color: #ffffff; text-shadow: 2px 2px 8px rgba(0, 0, 0, 0.9), -1px -1px 4px rgba(0, 0, 0, 0.7); pointer-events: none; white-space: nowrap; opacity: 1; transition: opacity 0.3s ease; z-index: 10; }
- .sequence.hovered .sequence-name { opacity: 0; }
+ .sequence-header {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ padding: 8px;
+ z-index: 5;
+ cursor: move;
+ user-select: none;
+ }
- .effect { position: absolute; background: #3a3d41; border: 1px solid var(--border-color); border-radius: 3px; padding: 4px 8px; cursor: move; font-size: 11px; transition: box-shadow 0.2s; display: flex; align-items: center; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
- .effect:hover { box-shadow: 0 0 8px rgba(133, 133, 133, 0.5); background: #45484d; }
- .effect.selected { border-color: var(--accent-orange); box-shadow: 0 0 8px rgba(206, 145, 120, 0.5); }
- .effect.conflict { background: #4a1d1d; border-color: var(--accent-red); box-shadow: 0 0 8px rgba(244, 135, 113, 0.6); }
- .effect.conflict:hover { background: #5a2424; }
- .effect-handle { position: absolute; top: 0; width: 6px; height: 100%; background: rgba(78, 201, 176, 0.8); cursor: ew-resize; display: none; z-index: 10; }
- .effect.selected .effect-handle { display: block; }
- .effect-handle.left { left: 0; border-radius: 3px 0 0 3px; }
- .effect-handle.right { right: 0; border-radius: 0 3px 3px 0; }
- .effect-handle:hover { background: var(--accent-green); width: 8px; }
+ .sequence-header-name {
+ font-size: 14px;
+ font-weight: bold;
+ color: #ffffff;
+ }
- .properties-panel { position: fixed; bottom: 20px; left: 20px; width: 350px; max-height: 80vh; background: var(--bg-medium); padding: 15px; border-radius: 8px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5); z-index: 1000; overflow-y: auto; transition: transform 0.3s ease; }
- .properties-panel.collapsed { transform: translateY(calc(100% + 40px)); }
- .panel-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; padding-bottom: 10px; border-bottom: 1px solid var(--bg-light); }
- .panel-header h2 { margin: 0; color: var(--accent-green); font-size: 16px; }
- .panel-toggle { background: transparent; border: 1px solid var(--border-color); color: var(--text-primary); padding: 4px 8px; border-radius: 3px; cursor: pointer; font-size: 12px; }
- .panel-toggle:hover { background: var(--bg-light); }
- .panel-collapse-btn { position: fixed; bottom: 20px; left: 20px; background: var(--bg-medium); border: 1px solid var(--border-color); color: var(--text-primary); padding: 8px 12px; border-radius: var(--radius); cursor: pointer; z-index: 999; box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3); display: none; }
- .panel-collapse-btn:hover { background: var(--bg-light); }
- .panel-collapse-btn.visible { display: block; }
+ .sequence:not(.collapsed) .sequence-header-name {
+ display: none;
+ }
- .property-group { margin-bottom: 15px; }
- .property-group label { display: block; margin-bottom: 5px; color: var(--text-muted); font-size: 14px; }
- .property-group input, .property-group select { width: 100%; padding: 8px; background: var(--bg-light); border: 1px solid var(--border-color); border-radius: var(--radius); color: var(--text-primary); font-size: 14px; }
+ .sequence-name {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ font-size: 24px;
+ font-weight: bold;
+ color: #ffffff;
+ text-shadow: 2px 2px 8px rgba(0, 0, 0, 0.9), -1px -1px 4px rgba(0, 0, 0, 0.7);
+ pointer-events: none;
+ white-space: nowrap;
+ opacity: 1;
+ transition: opacity 0.3s ease;
+ z-index: 10;
+ }
- .stats { background: var(--bg-dark); padding: 10px; border-radius: var(--radius); margin-top: 10px; font-size: 12px; color: var(--text-muted); }
- #messageArea { position: fixed; top: 80px; right: 20px; z-index: 2000; max-width: 400px; }
- .error { background: #5a1d1d; color: var(--accent-red); padding: 10px; border-radius: var(--radius); box-shadow: 0 2px 8px rgba(0,0,0,0.3); }
- .success { background: #1e5231; color: #89d185; padding: 10px; border-radius: var(--radius); box-shadow: 0 2px 8px rgba(0,0,0,0.3); }
+ .sequence.hovered .sequence-name {
+ opacity: 0;
+ }
+
+ .effect {
+ position: absolute;
+ background: #3a3d41;
+ border: 1px solid var(--border-color);
+ border-radius: 3px;
+ padding: 4px 8px;
+ cursor: move;
+ font-size: 11px;
+ transition: box-shadow 0.2s;
+ display: flex;
+ align-items: center;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ .effect:hover {
+ box-shadow: 0 0 8px rgba(133, 133, 133, 0.5);
+ background: #45484d;
+ }
+
+ .effect.selected {
+ border-color: var(--accent-orange);
+ box-shadow: 0 0 8px rgba(206, 145, 120, 0.5);
+ }
+
+ .effect.conflict {
+ background: #4a1d1d;
+ border-color: var(--accent-red);
+ box-shadow: 0 0 8px rgba(244, 135, 113, 0.6);
+ }
+
+ .effect.conflict:hover {
+ background: #5a2424;
+ }
+
+ .effect-handle {
+ position: absolute;
+ top: 0;
+ width: 6px;
+ height: 100%;
+ background: rgba(78, 201, 176, 0.8);
+ cursor: ew-resize;
+ display: none;
+ z-index: 10;
+ }
+
+ .effect.selected .effect-handle {
+ display: block;
+ }
+
+ .effect-handle.left {
+ left: 0;
+ border-radius: 3px 0 0 3px;
+ }
+
+ .effect-handle.right {
+ right: 0;
+ border-radius: 0 3px 3px 0;
+ }
+
+ .effect-handle:hover {
+ background: var(--accent-green);
+ width: 8px;
+ }
+
+ .properties-panel {
+ position: fixed;
+ bottom: 20px;
+ left: 20px;
+ width: 350px;
+ max-height: 80vh;
+ background: var(--bg-medium);
+ padding: 15px;
+ border-radius: 8px;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5);
+ z-index: 1000;
+ overflow-y: auto;
+ transition: transform 0.3s ease;
+ }
+
+ .properties-panel.collapsed {
+ transform: translateY(calc(100% + 40px));
+ }
+
+ .panel-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 15px;
+ padding-bottom: 10px;
+ border-bottom: 1px solid var(--bg-light);
+ }
+
+ .panel-header h2 {
+ margin: 0;
+ color: var(--accent-green);
+ font-size: 16px;
+ }
+
+ .panel-toggle {
+ background: transparent;
+ border: 1px solid var(--border-color);
+ color: var(--text-primary);
+ padding: 4px 8px;
+ border-radius: 3px;
+ cursor: pointer;
+ font-size: 12px;
+ }
+
+ .panel-toggle:hover {
+ background: var(--bg-light);
+ }
+
+ .panel-collapse-btn {
+ position: fixed;
+ bottom: 20px;
+ left: 20px;
+ background: var(--bg-medium);
+ border: 1px solid var(--border-color);
+ color: var(--text-primary);
+ padding: 8px 12px;
+ border-radius: var(--radius);
+ cursor: pointer;
+ z-index: 999;
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
+ display: none;
+ }
+
+ .panel-collapse-btn:hover {
+ background: var(--bg-light);
+ }
+
+ .panel-collapse-btn.visible {
+ display: block;
+ }
+
+ .property-group {
+ margin-bottom: 15px;
+ }
+
+ .property-group label {
+ display: block;
+ margin-bottom: 5px;
+ color: var(--text-muted);
+ font-size: 14px;
+ }
+
+ .property-group input,
+ .property-group select {
+ width: 100%;
+ }
+
+ .stats {
+ background: var(--bg-dark);
+ padding: 10px;
+ border-radius: var(--radius);
+ margin-top: 10px;
+ font-size: 12px;
+ color: var(--text-muted);
+ }
+
+ #messageArea {
+ position: fixed;
+ top: 80px;
+ right: 20px;
+ z-index: 2000;
+ max-width: 400px;
+ }
+
+ .error {
+ background: #5a1d1d;
+ color: var(--accent-red);
+ padding: 10px;
+ border-radius: var(--radius);
+ box-shadow: 0 2px 8px rgba(0,0,0,0.3);
+ }
+
+ .success {
+ background: #1e5231;
+ color: #89d185;
+ padding: 10px;
+ border-radius: var(--radius);
+ box-shadow: 0 2px 8px rgba(0,0,0,0.3);
+ }
</style>
</head>
<body>
@@ -151,7 +510,7 @@
<div id="messageArea"></div>
- <div class="timeline-container">
+ <div class="timeline-container" id="timelineContainer">
<div class="playback-indicator" id="playbackIndicator"></div>
<div class="sticky-header">
<div class="waveform-container" id="waveformContainer">
@@ -220,6 +579,7 @@
// DOM
const dom = {
timeline: document.getElementById('timeline'),
+ timelineContainer: document.getElementById('timelineContainer'),
timelineContent: document.getElementById('timelineContent'),
fileInput: document.getElementById('fileInput'),
saveBtn: document.getElementById('saveBtn'),
@@ -503,7 +863,6 @@
seqDiv.addEventListener('mousedown', e => startDrag(e, 'sequence', seqIndex));
seqDiv.addEventListener('click', e => { e.stopPropagation(); selectItem('sequence', seqIndex); });
seqDiv.addEventListener('dblclick', e => { e.stopPropagation(); e.preventDefault(); seq._collapsed = !seq._collapsed; renderTimeline(); });
- seqDiv.addEventListener('wheel', e => viewportController.handleWheel(e), { passive: false });
dom.timeline.appendChild(seqDiv);
if (!seq._collapsed) {
const conflicts = detectConflicts(seq);
@@ -534,7 +893,6 @@
if (!e.target.classList.contains('effect-handle')) { e.stopPropagation(); startDrag(e, 'effect', seqIndex, effectIndex); }
});
effectDiv.addEventListener('click', e => { e.stopPropagation(); selectItem('effect', seqIndex, effectIndex); });
- effectDiv.addEventListener('wheel', e => viewportController.handleWheel(e), { passive: false });
dom.timeline.appendChild(effectDiv);
});
}