diff --git a/scenes/urls.py b/scenes/urls.py index 8d14276..7824f98 100644 --- a/scenes/urls.py +++ b/scenes/urls.py @@ -9,5 +9,6 @@ urlpatterns = [ path("projects/", views.projects), path("projects//", views.project), path("projects//scene/", views.scene), + path("projects//script/", views.script), path("projects//revisions/", views.revisions), ] diff --git a/scenes/views.py b/scenes/views.py index 09fbb15..1d758d5 100644 --- a/scenes/views.py +++ b/scenes/views.py @@ -174,6 +174,22 @@ def scene(request, pk): return HttpResponseNotAllowed(["GET", "PUT"]) +@csrf_exempt +def script(request, pk): + """POST (owner/collaborator) — upload or replace the project's script PDF.""" + if request.method != "POST": + return HttpResponseNotAllowed(["POST"]) + if (resp := _auth(request)): + return resp + p = _editable(request, pk) + f = request.FILES.get("script") + if not f: + return JsonResponse({"detail": "script file is required"}, status=400) + p.script = f + p.save(update_fields=["script", "updated"]) + return JsonResponse(_meta(request, p)) + + def revisions(request, pk): """GET /api/projects//revisions/ — the save history (who/when/what).""" if (resp := _auth(request)): diff --git a/tl/src/tl/db.cljs b/tl/src/tl/db.cljs index fbc9391..25d42fb 100644 --- a/tl/src/tl/db.cljs +++ b/tl/src/tl/db.cljs @@ -12,6 +12,7 @@ :net-error nil ; backend unreachable (fetch-me / logout) :projects-error nil ; project-list fetch failure :save-error nil ; annotation save/delete failure + :script-error nil ; script PDF upload failure :fps (/ 24000 1001) ;; the whole scene graph (see tl.scene). Seeded from OTIO at load. diff --git a/tl/src/tl/events.cljs b/tl/src/tl/events.cljs index 283b8b0..8fc7d7f 100644 --- a/tl/src/tl/events.cljs +++ b/tl/src/tl/events.cljs @@ -253,6 +253,22 @@ (rf/reg-event-db ::set-pane (fn [db [_ p]] (assoc-in db [:view :pane] p))) +;; upload (or replace) the project's script PDF; on success the refreshed detail +;; carries the new script URL so the script pane renders it. +(rf/reg-event-fx ::upload-script + (fn [{:keys [db]} [_ file]] + (let [id (get-in db [:project :id]) + fd (doto (js/FormData.) (.append "script" file))] + {:db (assoc db :script-error nil) + :http-xhrio (api/upload (str "/api/projects/" id "/script/") fd + {:on-success [::script-uploaded] + :on-failure [::script-upload-error]})}))) +(rf/reg-event-db ::script-uploaded + (fn [db [_ detail]] (assoc db :project detail :script-error nil))) +(rf/reg-event-db ::script-upload-error + (fn [db [_ failure]] + (assoc db :script-error (api/error-message failure "Script upload failed.")))) + ;; Script highlighting is an edit-mode sub-task: "Add script annotation" arms it ;; for the annotation being edited (the draft) and jumps to the Script tab; the ;; rects land on the draft's :script and ride its save. ✓ keeps, ✕ restores the diff --git a/tl/src/tl/subs.cljs b/tl/src/tl/subs.cljs index ba481e2..7ae9a1a 100644 --- a/tl/src/tl/subs.cljs +++ b/tl/src/tl/subs.cljs @@ -23,6 +23,7 @@ (rf/reg-sub ::net-error (fn [db] (:net-error db))) (rf/reg-sub ::projects-error (fn [db] (:projects-error db))) (rf/reg-sub ::save-error (fn [db] (:save-error db))) +(rf/reg-sub ::script-error (fn [db] (:script-error db))) (rf/reg-sub ::load-error (fn [db] (get-in db [:load :error]))) (rf/reg-sub ::authed? (fn [db] (boolean (get-in db [:auth :authenticated])))) (rf/reg-sub ::username (fn [db] (get-in db [:auth :username]))) diff --git a/tl/src/tl/views.cljs b/tl/src/tl/views.cljs index 94b8af5..889eb1a 100644 --- a/tl/src/tl/views.cljs +++ b/tl/src/tl/views.cljs @@ -851,6 +851,8 @@ anns @(rf/subscribe [::subs/annotations]) hl @(rf/subscribe [::subs/highlighting]) active @(rf/subscribe [::subs/active-annotation]) + authed? @(rf/subscribe [::subs/authed?]) + script-err @(rf/subscribe [::subs/script-error]) armed (:gid hl) hl-name (some #(when (= (:id %) armed) (:name %)) anns)] (when (and url (exists? js/pdfjsLib) (not= url @loaded)) @@ -877,8 +879,15 @@ [:button.hl-ok {:title "Done" :on-click #(rf/dispatch [::events/finish-highlighting])} "✓"] [:button.hl-cancel {:title "Cancel" :on-click #(rf/dispatch [::events/cancel-highlighting])} "✕"]]] [:div.script-hint - (if url "Playing follows the script to each note. Edit an annotation → “Add script annotation” to link a passage." - "No script uploaded for this project.")]) + (cond + url "Playing follows the script to each note. Edit an annotation → “Add script annotation” to link a passage." + authed? [:span.script-upload + [:label.file-row "Upload a script (PDF) " + [:input {:type "file" :accept ".pdf,application/pdf" + :on-change #(when-let [f (-> % .-target .-files (aget 0))] + (rf/dispatch [::events/upload-script f]))}]] + (when script-err [:span.err " " script-err])] + :else "No script uploaded for this project.")]) [:div.script-pages {:ref #(reset! el %)} (if (seq @pages) (doall (for [[n {:keys [pg w h]}] @pages]