feat: upload a script PDF from the script pane
Backend: POST /api/projects/<id>/script/ (owner/collaborator) saves the script FileField and returns the refreshed detail. Frontend: when a project has no script, the script pane shows a PDF file picker that uploads on selection; the returned detail carries the new URL so the pane renders it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
9c4b56dbc6
commit
b05b440922
6 changed files with 46 additions and 2 deletions
|
|
@ -9,5 +9,6 @@ urlpatterns = [
|
||||||
path("projects/", views.projects),
|
path("projects/", views.projects),
|
||||||
path("projects/<int:pk>/", views.project),
|
path("projects/<int:pk>/", views.project),
|
||||||
path("projects/<int:pk>/scene/", views.scene),
|
path("projects/<int:pk>/scene/", views.scene),
|
||||||
|
path("projects/<int:pk>/script/", views.script),
|
||||||
path("projects/<int:pk>/revisions/", views.revisions),
|
path("projects/<int:pk>/revisions/", views.revisions),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -174,6 +174,22 @@ def scene(request, pk):
|
||||||
return HttpResponseNotAllowed(["GET", "PUT"])
|
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):
|
def revisions(request, pk):
|
||||||
"""GET /api/projects/<pk>/revisions/ — the save history (who/when/what)."""
|
"""GET /api/projects/<pk>/revisions/ — the save history (who/when/what)."""
|
||||||
if (resp := _auth(request)):
|
if (resp := _auth(request)):
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
:net-error nil ; backend unreachable (fetch-me / logout)
|
:net-error nil ; backend unreachable (fetch-me / logout)
|
||||||
:projects-error nil ; project-list fetch failure
|
:projects-error nil ; project-list fetch failure
|
||||||
:save-error nil ; annotation save/delete failure
|
:save-error nil ; annotation save/delete failure
|
||||||
|
:script-error nil ; script PDF upload failure
|
||||||
:fps (/ 24000 1001)
|
:fps (/ 24000 1001)
|
||||||
|
|
||||||
;; the whole scene graph (see tl.scene). Seeded from OTIO at load.
|
;; the whole scene graph (see tl.scene). Seeded from OTIO at load.
|
||||||
|
|
|
||||||
|
|
@ -253,6 +253,22 @@
|
||||||
|
|
||||||
(rf/reg-event-db ::set-pane (fn [db [_ p]] (assoc-in db [:view :pane] p)))
|
(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
|
;; 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
|
;; 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
|
;; rects land on the draft's :script and ride its save. ✓ keeps, ✕ restores the
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@
|
||||||
(rf/reg-sub ::net-error (fn [db] (:net-error db)))
|
(rf/reg-sub ::net-error (fn [db] (:net-error db)))
|
||||||
(rf/reg-sub ::projects-error (fn [db] (:projects-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 ::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 ::load-error (fn [db] (get-in db [:load :error])))
|
||||||
(rf/reg-sub ::authed? (fn [db] (boolean (get-in db [:auth :authenticated]))))
|
(rf/reg-sub ::authed? (fn [db] (boolean (get-in db [:auth :authenticated]))))
|
||||||
(rf/reg-sub ::username (fn [db] (get-in db [:auth :username])))
|
(rf/reg-sub ::username (fn [db] (get-in db [:auth :username])))
|
||||||
|
|
|
||||||
|
|
@ -851,6 +851,8 @@
|
||||||
anns @(rf/subscribe [::subs/annotations])
|
anns @(rf/subscribe [::subs/annotations])
|
||||||
hl @(rf/subscribe [::subs/highlighting])
|
hl @(rf/subscribe [::subs/highlighting])
|
||||||
active @(rf/subscribe [::subs/active-annotation])
|
active @(rf/subscribe [::subs/active-annotation])
|
||||||
|
authed? @(rf/subscribe [::subs/authed?])
|
||||||
|
script-err @(rf/subscribe [::subs/script-error])
|
||||||
armed (:gid hl)
|
armed (:gid hl)
|
||||||
hl-name (some #(when (= (:id %) armed) (:name %)) anns)]
|
hl-name (some #(when (= (:id %) armed) (:name %)) anns)]
|
||||||
(when (and url (exists? js/pdfjsLib) (not= url @loaded))
|
(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-ok {:title "Done" :on-click #(rf/dispatch [::events/finish-highlighting])} "✓"]
|
||||||
[:button.hl-cancel {:title "Cancel" :on-click #(rf/dispatch [::events/cancel-highlighting])} "✕"]]]
|
[:button.hl-cancel {:title "Cancel" :on-click #(rf/dispatch [::events/cancel-highlighting])} "✕"]]]
|
||||||
[:div.script-hint
|
[:div.script-hint
|
||||||
(if url "Playing follows the script to each note. Edit an annotation → “Add script annotation” to link a passage."
|
(cond
|
||||||
"No script uploaded for this project.")])
|
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 %)}
|
[:div.script-pages {:ref #(reset! el %)}
|
||||||
(if (seq @pages)
|
(if (seq @pages)
|
||||||
(doall (for [[n {:keys [pg w h]}] @pages]
|
(doall (for [[n {:keys [pg w h]}] @pages]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue