From 60d1a641ee85e40badbb37d5d8a97d27f3c4615d Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 28 Jun 2026 09:12:28 -0400 Subject: [PATCH] feat: broken fixes lol --- tl/dev/media_server.py | 9 ++ tl/resources/public/css/app.css | 5 + tl/shadow-cljs.edn | 7 +- tl/src/tl/db.cljs | 5 +- tl/src/tl/events.cljs | 70 +++++++++-- tl/src/tl/marks.cljs | 68 +++++++++++ tl/src/tl/subs.cljs | 70 ++++++----- tl/src/tl/views.cljs | 203 +++++++++++++++++++------------- tl/test/tl/marks_test.cljs | 96 +++++++++++++++ 9 files changed, 412 insertions(+), 121 deletions(-) create mode 100644 tl/test/tl/marks_test.cljs diff --git a/tl/dev/media_server.py b/tl/dev/media_server.py index 71ff5d6..ec1f6c8 100755 --- a/tl/dev/media_server.py +++ b/tl/dev/media_server.py @@ -20,15 +20,23 @@ import time PORT = int(os.environ.get("MEDIA_PORT", "8281")) ROOT = os.path.join(os.path.dirname(__file__), "..", "resources", "public") +MEDIA_CACHE_CONTROL = os.environ.get("MEDIA_CACHE_CONTROL", "public, max-age=604800") +MEDIA_EXTENSIONS = {".mp4", ".mov", ".m4v", ".webm"} class RangeHandler(http.server.SimpleHTTPRequestHandler): def __init__(self, *args, **kwargs): super().__init__(*args, directory=os.path.abspath(ROOT), **kwargs) + def is_cacheable_media(self, path=None): + path = path or self.translate_path(self.path) + return os.path.splitext(path)[1].lower() in MEDIA_EXTENSIONS + def end_headers(self): self.send_header("Access-Control-Allow-Origin", "*") self.send_header("Accept-Ranges", "bytes") + if self.is_cacheable_media(): + self.send_header("Cache-Control", MEDIA_CACHE_CONTROL) super().end_headers() def send_head(self): @@ -59,6 +67,7 @@ class RangeHandler(http.server.SimpleHTTPRequestHandler): self.send_header("Content-Type", self.guess_type(path)) self.send_header("Content-Range", f"bytes {start}-{end}/{size}") self.send_header("Content-Length", str(end - start + 1)) + self.send_header("Last-Modified", self.date_time_string(os.fstat(f.fileno()).st_mtime)) self.end_headers() f.seek(start) self._range = (f, start, end) # consumed by copyfile override diff --git a/tl/resources/public/css/app.css b/tl/resources/public/css/app.css index 9939258..aef0fea 100644 --- a/tl/resources/public/css/app.css +++ b/tl/resources/public/css/app.css @@ -131,6 +131,11 @@ body { overflow: hidden; } .row-x:hover { color: #e66; border-color: #a44; } .add-mark { align-self: flex-start; color: #cfe3f5; border-color: #3a6ea5; } +/* the point a clip-click will fill — make it obvious where focus is */ +.pt-input.active .pt-text, .pt-chip.active { + outline: 2px solid #5ab07a; outline-offset: 0; +} + /* point editor + autocomplete */ .pt-input { position: relative; flex: 1 1 130px; display: inline-block; } .pt-text { diff --git a/tl/shadow-cljs.edn b/tl/shadow-cljs.edn index 532f0bc..14a7c6c 100644 --- a/tl/shadow-cljs.edn +++ b/tl/shadow-cljs.edn @@ -15,7 +15,12 @@ 8290 "target/browser-test"} :builds - {:app + {:test + {:target :node-test + :output-to "target/node-tests.js" + :ns-regexp "-test$"} + + :app {:target :browser :output-dir "resources/public/js/compiled" :asset-path "/js/compiled" diff --git a/tl/src/tl/db.cljs b/tl/src/tl/db.cljs index d7c5745..4c49812 100644 --- a/tl/src/tl/db.cljs +++ b/tl/src/tl/db.cljs @@ -67,7 +67,10 @@ ;; transient view state (mutates while scrubbing/zooming) :view {:zoom 50 ; X zoom: pixels per SECOND (px/frame = zoom/fps) :row-h 28 ; Y zoom: track row height in px - :playhead 0.0 ; frames (media/display coordinate) + ;; playhead per timeline context (frames): keyed by the timeline-stack + ;; top, so each (sub)timeline remembers where its playhead was. :root + ;; is the main timeline. + :playheads {} :playing? false :selected-clip nil ;; resizable layout (relative): top region height as vh, video pane diff --git a/tl/src/tl/events.cljs b/tl/src/tl/events.cljs index 8d10caf..002eb0b 100644 --- a/tl/src/tl/events.cljs +++ b/tl/src/tl/events.cljs @@ -3,6 +3,7 @@ [re-frame.core :as rf] [tl.db :as db] [tl.otio :as otio] + [tl.marks :as marks] [tl.storage :as storage] [ajax.core :as ajax])) @@ -47,10 +48,14 @@ ;; The