From 4de43b32e81e550d2a27e054d18aa2415a3f00a4 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 13 Nov 2024 16:24:55 +0100 Subject: [PATCH] :sparkles: Add versions support in plugins --- .../src/app/main/data/workspace/versions.cljs | 29 ++++ frontend/src/app/plugins/file.cljs | 147 +++++++++++++++++- frontend/src/app/plugins/user.cljs | 6 + frontend/src/app/plugins/utils.cljs | 6 + 4 files changed, 185 insertions(+), 3 deletions(-) diff --git a/frontend/src/app/main/data/workspace/versions.cljs b/frontend/src/app/main/data/workspace/versions.cljs index f8e626de8..706397f2d 100644 --- a/frontend/src/app/main/data/workspace/versions.cljs +++ b/frontend/src/app/main/data/workspace/versions.cljs @@ -72,6 +72,35 @@ (fetch-versions file-id))))) (rx/of (ptk/event ::ev/event {::ev/name "create-version"}))))))) +(defn create-version-from-plugins + [file-id label resolve reject] + (dm/assert! (uuid? file-id)) + (ptk/reify ::create-version-plugins + ptk/WatchEvent + (watch [_ _ _] + ;; Force persist before creating snapshot, otherwise we could loss changes + (->> (rx/concat + (rx/of ::dwp/force-persist) + (->> (rx/from-atom refs/persistence-state {:emit-current-value? true}) + (rx/filter #(or (nil? %) (= :saved %))) + (rx/take 1) + (rx/mapcat #(rp/cmd! :create-file-snapshot {:file-id file-id :label label})) + + (rx/mapcat + (fn [{:keys [id]}] + (->> (rp/cmd! :get-file-snapshots {:file-id file-id}) + (rx/take 1) + (rx/map (fn [versions] (d/seek #(= id (:id %)) versions)))))) + (rx/tap resolve) + (rx/ignore)) + (rx/of (ptk/event ::ev/event {::ev/origin "plugins" + ::ev/name "create-version"}))) + + ;; On error reject the promise and empty the stream + (rx/catch (fn [error] + (reject error) + (rx/empty))))))) + (defn rename-version [file-id id label] (dm/assert! (uuid? file-id)) diff --git a/frontend/src/app/plugins/file.cljs b/frontend/src/app/plugins/file.cljs index 3f777585b..8e6a83856 100644 --- a/frontend/src/app/plugins/file.cljs +++ b/frontend/src/app/plugins/file.cljs @@ -6,23 +6,115 @@ (ns app.plugins.file (:require + [app.common.data :as d] [app.common.data.macros :as dm] [app.common.record :as crc] [app.common.uuid :as uuid] [app.config :as cf] [app.main.data.exports.files :as exports.files] [app.main.data.workspace :as dw] + [app.main.data.workspace.versions :as dwv] [app.main.features :as features] + [app.main.repo :as rp] [app.main.store :as st] [app.main.worker :as uw] [app.plugins.page :as page] [app.plugins.parser :as parser] [app.plugins.register :as r] + [app.plugins.user :as user] [app.plugins.utils :as u] [app.util.http :as http] [app.util.object :as obj] + [app.util.time :as dt] [beicon.v2.core :as rx])) +(declare file-version-proxy) + +(deftype FileVersionProxy [$plugin $file $version $data] + Object + (restore + [_] + (cond + (not (r/check-permission $plugin "content:write")) + (u/display-not-valid :restore "Plugin doesn't have 'content:write' permission") + + :else + (let [project-id (:current-project-id @st/state)] + (st/emit! (dwv/restore-version project-id $file $version))))) + + (remove + [_] + (js/Promise. + (fn [resolve reject] + (cond + (not (r/check-permission $plugin "content:write")) + (u/reject-not-valid reject :remove "Plugin doesn't have 'content:write' permission") + + :else + (->> (rp/cmd! :delete-file-snapshot {:id $version}) + (rx/subs! #(resolve) reject)))))) + + (pin + [_] + (js/Promise. + (fn [resolve reject] + (cond + (not (r/check-permission $plugin "content:write")) + (u/reject-not-valid reject :pin "Plugin doesn't have 'content:write' permission") + + (not= "system" (:created-by $data)) + (u/reject-not-valid reject :pin "Only auto-saved versions can be pinned") + + :else + (let [params {:id $version + :label (dt/format (:created-at $data) :date-full)}] + (->> (rx/zip (rp/cmd! :get-team-users {:file-id $file}) + (rp/cmd! :update-file-snapshot params)) + (rx/subs! (fn [[users data]] + (let [users (d/index-by :id users)] + (resolve (file-version-proxy $plugin $file users data)))) + reject)))))))) + +(defn file-version-proxy + [plugin-id file-id users data] + (let [data (atom data)] + (crc/add-properties! + (FileVersionProxy. plugin-id file-id (:id @data) data) + {:name "$plugin" :enumerable false :get (constantly plugin-id)} + {:name "$file" :enumerable false :get (constantly file-id)} + {:name "$version" :enumerable false :get (constantly (:id @data))} + {:name "$data" :enumerable false :get (constantly @data)} + + {:name "label" + :get (fn [_] (:label @data)) + :set + (fn [_ value] + (cond + (not (r/check-permission plugin-id "content:write")) + (u/display-not-valid :label "Plugin doesn't have 'content:write' permission") + + (or (not (string? value)) (empty? value)) + (u/display-not-valid :label value) + + :else + (do (swap! data assoc :label value :created-by "user") + (->> (rp/cmd! :update-file-snapshot {:id (:id @data) :label value}) + (rx/take 1) + (rx/subs! identity)))))} + + {:name "createdBy" + :get (fn [_] + (when-let [user-data (get users (:profile-id @data))] + (user/user-proxy plugin-id user-data)))} + + {:name "createdAt" + :get (fn [_] + (.toJSDate ^js (:created-at @data)))} + + {:name "isAutosave" + :get (fn [_] + (= "system" (:created-by @data)))}))) + (deftype FileProxy [$plugin $id] Object (getPages [_] @@ -157,7 +249,58 @@ :response-type :buffer})))) (rx/take 1) (rx/map (fn [data] (js/Uint8Array. data))) - (rx/subs! resolve reject))))))))) + (rx/subs! resolve reject)))))))) + + + (findVersions + [_ criteria] + (let [user (obj/get criteria "createdBy" nil)] + (js/Promise. + (fn [resolve reject] + (cond + (not (r/check-permission $plugin "content:read")) + (u/reject-not-valid reject :findVersions "Plugin doesn't have 'content:read' permission") + + (and (not user) (not (user/user-proxy? user))) + (u/reject-not-valid reject :findVersions-user "Created by user is not a valid user object") + + :else + (->> (rx/zip (rp/cmd! :get-team-users {:file-id $id}) + (rp/cmd! :get-file-snapshots {:file-id $id})) + (rx/take 1) + (rx/subs! + (fn [[users snapshots]] + (let [users (d/index-by :id users)] + (->> snapshots + (filter #(= (dm/str (:profile-id %)) (obj/get user "id"))) + (map #(file-version-proxy $plugin $id users %)) + (sequence) + (apply array) + (resolve)))) + reject))))))) + + (saveVersion + [_ label] + (let [users-promise + (js/Promise. + (fn [resolve reject] + (->> (rp/cmd! :get-team-users {:file-id $id}) + (rx/subs! resolve reject)))) + + create-version-promise + (js/Promise. + (fn [resolve reject] + (cond + (not (r/check-permission $plugin "content:write")) + (u/reject-not-valid reject :findVersions "Plugin doesn't have 'content:write' permission") + + :else + (st/emit! (dwv/create-version-from-plugins $id label resolve reject)))))] + (-> (js/Promise.all #js [users-promise create-version-promise]) + (.then + (fn [[users data]] + (let [users (d/index-by :id users)] + (file-version-proxy $plugin $id users data)))))))) (crc/define-properties! FileProxy @@ -182,5 +325,3 @@ {:name "pages" :get #(.getPages ^js %)})) - - diff --git a/frontend/src/app/plugins/user.cljs b/frontend/src/app/plugins/user.cljs index 20af5481e..c2997818f 100644 --- a/frontend/src/app/plugins/user.cljs +++ b/frontend/src/app/plugins/user.cljs @@ -75,6 +75,12 @@ {:name "avatarUrl" :get (fn [_] (cfg/resolve-profile-photo-url data))}))) +(defn user-proxy? + [p] + (or (instance? UserProxy p) + (current-user-proxy? p) + (active-user-proxy? p))) + (defn user-proxy [plugin-id data] (-> (UserProxy. plugin-id) diff --git a/frontend/src/app/plugins/utils.cljs b/frontend/src/app/plugins/utils.cljs index c98a1b3a9..2d28301f9 100644 --- a/frontend/src/app/plugins/utils.cljs +++ b/frontend/src/app/plugins/utils.cljs @@ -187,3 +187,9 @@ (defn display-not-valid [code value] (.error js/console (dm/str "[PENPOT PLUGIN] Value not valid: " value ". Code: " code))) + +(defn reject-not-valid + [reject code value] + (let [msg (dm/str "[PENPOT PLUGIN] Value not valid: " value ". Code: " code)] + (.error js/console msg) + (reject msg)))