From 73d85b988401604bb2f4a5e654dd3307c31744be Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Wed, 6 Nov 2024 10:55:35 +0100 Subject: [PATCH 1/6] :bug: Fix incorrect behavior of ::sm/vec and ::sm/set decoder --- common/src/app/common/schema.cljc | 63 ++++++----------------- common/test/common_tests/schema_test.cljc | 60 +++++++++++++++++++++ 2 files changed, 77 insertions(+), 46 deletions(-) diff --git a/common/src/app/common/schema.cljc b/common/src/app/common/schema.cljc index 5ffd035f6..9398f4986 100644 --- a/common/src/app/common/schema.cljc +++ b/common/src/app/common/schema.cljc @@ -275,7 +275,7 @@ (= :set (:type s)) (m/-collection-schema s) - (= :vec (:type s)) + (= :vector (:type s)) (m/-collection-schema s) :else @@ -449,24 +449,12 @@ (fn [value] (every? pred value))) - - decode-string-child - (decoder kind string-transformer) - - decode-string + decode (fn [v] - (let [v (if (string? v) (str/split v #"[\s,]+") v) - x (comp xf:filter-word-strings (map decode-string-child))] - (into #{} x v))) - - decode-json-child - (decoder kind json-transformer) - - decode-json - (fn [v] - (let [v (if (string? v) (str/split v #"[\s,]+") v) - x (comp xf:filter-word-strings (map decode-json-child))] - (into #{} x v))) + (if (string? v) + (let [v (str/split v #"[\s,]+")] + (into #{} xf:filter-word-strings v)) + v)) encode-string-child (encoder kind string-transformer) @@ -475,15 +463,8 @@ (fn [o] (if (set? o) (str/join ", " (map encode-string-child o)) - o)) - - encode-json - (fn [o] - (if (set? o) - (vec o) o))] - {:pred pred :empty #{} :type-properties @@ -491,10 +472,10 @@ :description "Set of Strings" :error/message "should be a set of strings" :gen/gen (-> kind sg/generator sg/set) - :decode/string decode-string - :decode/json decode-json + :decode/string decode + :decode/json decode :encode/string encode-string - :encode/json encode-json + :encode/json identity ::oapi/type "array" ::oapi/format "set" ::oapi/items {:type "string"} @@ -542,23 +523,12 @@ (fn [value] (every? pred value))) - decode-string-child - (decoder kind string-transformer) - - decode-json-child - (decoder kind json-transformer) - - decode-string + decode (fn [v] - (let [v (if (string? v) (str/split v #"[\s,]+") v) - x (comp xf:filter-word-strings (map decode-string-child))] - (into #{} x v))) - - decode-json - (fn [v] - (let [v (if (string? v) (str/split v #"[\s,]+") v) - x (comp xf:filter-word-strings (map decode-json-child))] - (into #{} x v))) + (if (string? v) + (let [v (str/split v #"[\s,]+")] + (into #{} xf:filter-word-strings v)) + v)) encode-string-child (encoder kind string-transformer) @@ -575,9 +545,10 @@ :description "Set of Strings" :error/message "should be a set of strings" :gen/gen (-> kind sg/generator sg/set) - :decode/string decode-string - :decode/json decode-json + :decode/string decode + :decode/json decode :encode/string encode-string + :encode/json identity ::oapi/type "array" ::oapi/format "set" ::oapi/items {:type "string"} diff --git a/common/test/common_tests/schema_test.cljc b/common/test/common_tests/schema_test.cljc index 05b2c2ae6..bbb97bbcd 100644 --- a/common/test/common_tests/schema_test.cljc +++ b/common/test/common_tests/schema_test.cljc @@ -39,3 +39,63 @@ (let [schema [::sm/set ::sm/email] value (sg/generate schema)] (t/is (true? (sm/validate schema (sg/generate schema))))))) + + +(t/deftest test-set-1 + (let [candidate-1 "cff4b058-ca31-8197-8005-32aeb2377d83, cff4b058-ca31-8197-8005-32aeb2377d82" + candidate-2 ["cff4b058-ca31-8197-8005-32aeb2377d82", + "cff4b058-ca31-8197-8005-32aeb2377d83"] + candidate-3 #{"cff4b058-ca31-8197-8005-32aeb2377d82", "cff4b058-ca31-8197-8005-32aeb2377d83"} + candidate-4 [#uuid "cff4b058-ca31-8197-8005-32aeb2377d82" + #uuid "cff4b058-ca31-8197-8005-32aeb2377d83"] + candidate-5 #{#uuid "cff4b058-ca31-8197-8005-32aeb2377d82" + #uuid "cff4b058-ca31-8197-8005-32aeb2377d83"} + + expected candidate-5 + + schema [::sm/set ::sm/uuid] + decode-s (sm/decoder schema sm/string-transformer) + decode-j (sm/decoder schema sm/json-transformer) + encode-s (sm/encoder schema sm/string-transformer) + encode-j (sm/encoder schema sm/json-transformer)] + + + (t/is (= expected (decode-s candidate-1))) + (t/is (= expected (decode-s candidate-2))) + (t/is (= expected (decode-s candidate-3))) + (t/is (= expected (decode-s candidate-4))) + (t/is (= expected (decode-s candidate-5))) + + (t/is (= candidate-1 (encode-s expected))) + (t/is (= candidate-3 (encode-j expected))))) + + +(t/deftest test-vec-1 + (let [candidate-1 "cff4b058-ca31-8197-8005-32aeb2377d83, cff4b058-ca31-8197-8005-32aeb2377d82" + candidate-2 ["cff4b058-ca31-8197-8005-32aeb2377d83", + "cff4b058-ca31-8197-8005-32aeb2377d82"] + candidate-3 #{"cff4b058-ca31-8197-8005-32aeb2377d82", "cff4b058-ca31-8197-8005-32aeb2377d83"} + candidate-4 [#uuid "cff4b058-ca31-8197-8005-32aeb2377d83" + #uuid "cff4b058-ca31-8197-8005-32aeb2377d82"] + candidate-5 #{#uuid "cff4b058-ca31-8197-8005-32aeb2377d82" + #uuid "cff4b058-ca31-8197-8005-32aeb2377d83"} + + expected candidate-4 + + schema [::sm/vec ::sm/uuid] + decode-s (sm/decoder schema sm/string-transformer) + decode-j (sm/decoder schema sm/json-transformer) + encode-s (sm/encoder schema sm/string-transformer) + encode-j (sm/encoder schema sm/json-transformer)] + + + (t/is (= expected (decode-s candidate-1))) + (t/is (= expected (decode-s candidate-2))) + (t/is (= expected (decode-s candidate-3))) + (t/is (= expected (decode-s candidate-4))) + (t/is (= expected (decode-s candidate-5))) + + (t/is (= candidate-1 (encode-s expected))) + (t/is (= candidate-2 (encode-j expected))))) + + From b27edb4259471b2cda1a90098b6a49e2a66c4af6 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Wed, 6 Nov 2024 10:56:39 +0100 Subject: [PATCH 2/6] :bug: Use proper schema for move-file rpc method --- backend/src/app/rpc/commands/management.clj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/app/rpc/commands/management.clj b/backend/src/app/rpc/commands/management.clj index 223c5cb56..cad94b019 100644 --- a/backend/src/app/rpc/commands/management.clj +++ b/backend/src/app/rpc/commands/management.clj @@ -326,7 +326,7 @@ (def ^:private schema:move-files [:map {:title "move-files"} - [:ids ::sm/set-of-uuid] + [:ids [::sm/set {:min 1} ::sm/uuid]] [:project-id ::sm/uuid]]) (sv/defmethod ::move-files @@ -335,7 +335,7 @@ ::webhooks/event? true ::sm/params schema:move-files} [cfg {:keys [::rpc/profile-id] :as params}] - (db/tx-run! cfg #(move-files % (assoc params :profile-id profile-id)))) + (db/tx-run! cfg move-files (assoc params :profile-id profile-id))) ;; --- COMMAND: Move project From 23d3661ea59a23f5b586a310ff6b3a9ecbc48244 Mon Sep 17 00:00:00 2001 From: AzazelN28 Date: Wed, 30 Oct 2024 15:37:15 +0100 Subject: [PATCH 3/6] :tada: Handle WebGL context state change --- frontend/src/app/main/data/render_wasm.cljs | 17 +++++ frontend/src/app/main/refs.cljs | 6 ++ .../app/main/ui/workspace/viewport_wasm.cljs | 17 ++++- .../app/main/ui/workspace/viewport_wasm.scss | 10 +++ frontend/src/app/render_wasm.cljs | 66 +++++++++++++++---- frontend/src/app/util/debug.cljs | 5 +- render-wasm/Cargo.lock | 18 +++++ render-wasm/Cargo.toml | 2 + 8 files changed, 125 insertions(+), 16 deletions(-) create mode 100644 frontend/src/app/main/data/render_wasm.cljs diff --git a/frontend/src/app/main/data/render_wasm.cljs b/frontend/src/app/main/data/render_wasm.cljs new file mode 100644 index 000000000..e55d98754 --- /dev/null +++ b/frontend/src/app/main/data/render_wasm.cljs @@ -0,0 +1,17 @@ +(ns app.main.data.render-wasm + (:require + [potok.v2.core :as ptk])) + +(defn context-lost + [] + (ptk/reify ::context-lost + ptk/UpdateEvent + (update [_ state] + (update state :render-state #(assoc % :lost true))))) + +(defn context-restored + [] + (ptk/reify ::context-restored + ptk/UpdateEvent + (update [_ state] + (update state :render-state #(dissoc % :lost))))) diff --git a/frontend/src/app/main/refs.cljs b/frontend/src/app/main/refs.cljs index f0a8db187..9a8ac024a 100644 --- a/frontend/src/app/main/refs.cljs +++ b/frontend/src/app/main/refs.cljs @@ -114,6 +114,12 @@ ;; ---- Workspace refs +(def render-state + (l/derived :render-state st/state)) + +(def render-context-lost? + (l/derived :lost render-state)) + (def workspace-local (l/derived :workspace-local st/state)) diff --git a/frontend/src/app/main/ui/workspace/viewport_wasm.cljs b/frontend/src/app/main/ui/workspace/viewport_wasm.cljs index a6fd17f43..f5c6963cf 100644 --- a/frontend/src/app/main/ui/workspace/viewport_wasm.cljs +++ b/frontend/src/app/main/ui/workspace/viewport_wasm.cljs @@ -111,6 +111,8 @@ modifiers (mf/deref refs/workspace-modifiers) text-modifiers (mf/deref refs/workspace-text-modifier) + render-context-lost? (mf/deref refs/render-context-lost?) + objects-modified (mf/with-memo [base-objects text-modifiers modifiers] (apply-modifiers-to-selected selected base-objects text-modifiers modifiers)) @@ -175,6 +177,8 @@ mode-inspect? (= options-mode :inspect) + on-render-restore-context #(.reload js/location) + on-click (actions/on-click hover selected edition drawing-path? drawing-tool space? selrect z?) on-context-menu (actions/on-context-menu hover hover-ids read-only?) on-double-click (actions/on-double-click hover hover-ids hover-top-frame-id drawing-path? base-objects edition drawing-tool z? read-only?) @@ -277,9 +281,9 @@ (p/fmap (fn [ready?] (when ready? (reset! canvas-init? true) - (render.wasm/assign-canvas canvas))))) + (render.wasm/setup-canvas canvas))))) (fn [] - (render.wasm/clear-canvas)))) + (render.wasm/dispose-canvas canvas)))) (mf/with-effect [objects-modified canvas-init?] (when @canvas-init? @@ -635,4 +639,11 @@ {:objects base-objects :zoom zoom :vbox vbox - :bottom-padding (when palete-size (+ palete-size 8))}]]]]])) + :bottom-padding (when palete-size (+ palete-size 8))}]]]] + + (when render-context-lost? + [:div {:id "context-lost" :class (stl/css :context-lost)} + [:h1 "GL Error Screen"] + [:button + {:on-click on-render-restore-context} + "Restore context"]])])) diff --git a/frontend/src/app/main/ui/workspace/viewport_wasm.scss b/frontend/src/app/main/ui/workspace/viewport_wasm.scss index 727a6c529..a83fde465 100644 --- a/frontend/src/app/main/ui/workspace/viewport_wasm.scss +++ b/frontend/src/app/main/ui/workspace/viewport_wasm.scss @@ -35,3 +35,13 @@ right: 0; z-index: 10; } + +.context-lost { + position: fixed; + inset: 0; + z-index: 100; + background-color: rgba(0, 0, 0, 0.5); + display: grid; + place-items: center; + cursor: default; +} diff --git a/frontend/src/app/render_wasm.cljs b/frontend/src/app/render_wasm.cljs index 44086efd7..20c769d93 100644 --- a/frontend/src/app/render_wasm.cljs +++ b/frontend/src/app/render_wasm.cljs @@ -11,6 +11,10 @@ [app.common.files.helpers :as cfh] [app.common.types.shape.impl] [app.config :as cf] + [app.main.data.render-wasm :as drw] + [app.main.store :as st] + [app.util.debug :as dbg] + [app.util.dom :as dom] [promesa.core :as p])) (def enabled? @@ -82,29 +86,67 @@ :stencil true :alpha true}) -(defn clear-canvas - [] - ;; TODO: perform corresponding cleaning - ) +(defn init-skia + [canvas] + (let [init-fn (unchecked-get internal-module "_init") + state (init-fn (.-width ^js canvas) + (.-height ^js canvas))] + (set! internal-gpu-state state))) -(defn assign-canvas +;; NOTE: This function can be called externally +;; by the button in the context lost component (shown +;; in viewport-wasm) or called internally by +;; on-webgl-context +(defn restore-canvas + [canvas] + (st/emit! (drw/context-restored)) + ;; We need to reinitialize skia when the + ;; context is restored. + (init-skia canvas)) + +;; Handles both events: webglcontextlost and +;; webglcontextrestored +(defn on-webgl-context + [event] + (dom/prevent-default event) + (if (= (.-type event) "webglcontextlost") + (st/emit! (drw/context-lost)) + (restore-canvas (dom/get-target event)))) + +(defn dispose-canvas + [canvas] + ;; TODO: perform corresponding cleaning + (.removeEventListener canvas "webglcontextlost" on-webgl-context) + (.removeEventListener canvas "webglcontextrestored" on-webgl-context)) + +(defn init-debug-webgl-context-state + [context] + (let [context-extension (.getExtension ^js context "WEBGL_lose_context") + info-extension (.getExtension ^js context "WEBGL_debug_renderer_info")] + (set! (.-penpotGL js/window) #js {:context context-extension + :renderer info-extension}) + (js/console.log "WEBGL_lose_context" context-extension) + (js/console.log "WEBGL_debug_renderer_info" info-extension))) + +(defn setup-canvas [canvas] (let [gl (unchecked-get internal-module "GL") - init-fn (unchecked-get internal-module "_init") - context (.getContext ^js canvas "webgl2" canvas-options) ;; Register the context with emscripten handle (.registerContext ^js gl context #js {"majorVersion" 2}) - _ (.makeContextCurrent ^js gl handle) + _ (.makeContextCurrent ^js gl handle)] - ;; Initialize Skia - state (init-fn (.-width ^js canvas) - (.-height ^js canvas))] + (when (dbg/enabled? :gl-context) + (init-debug-webgl-context-state context)) + + (.addEventListener canvas "webglcontextlost" on-webgl-context) + (.addEventListener canvas "webglcontextrestored" on-webgl-context) (set! (.-width canvas) (.-clientWidth ^js canvas)) (set! (.-height canvas) (.-clientHeight ^js canvas)) - (set! internal-gpu-state state))) + + (init-skia canvas))) (defonce module (->> (js/dynamicImport "/js/render_wasm.js") diff --git a/frontend/src/app/util/debug.cljs b/frontend/src/app/util/debug.cljs index 094550cef..8342da71c 100644 --- a/frontend/src/app/util/debug.cljs +++ b/frontend/src/app/util/debug.cljs @@ -89,7 +89,10 @@ :display-touched ;; Show some visual indicators for bool shape - :bool-shapes}) + :bool-shapes + + ;; Show some information about the WebGL context. + :gl-context}) (defn enable! [option] diff --git a/render-wasm/Cargo.lock b/render-wasm/Cargo.lock index f8079959d..7595770cd 100644 --- a/render-wasm/Cargo.lock +++ b/render-wasm/Cargo.lock @@ -96,6 +96,22 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +[[package]] +name = "emscripten-functions" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62c026cc030b24957ca45d9555f9fa241d6b3a01d725cd98a25924de249b840a" +dependencies = [ + "cc", + "emscripten-functions-sys", +] + +[[package]] +name = "emscripten-functions-sys" +version = "4.1.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65715a5f07b03636d7cd5508a45d1b62486840cb7d91a66564a73f1d7aa70b79" + [[package]] name = "equivalent" version = "1.0.1" @@ -370,6 +386,8 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" name = "render" version = "0.1.0" dependencies = [ + "emscripten-functions", + "emscripten-functions-sys", "gl", "skia-safe", ] diff --git a/render-wasm/Cargo.toml b/render-wasm/Cargo.toml index 66cff051d..a0de796ea 100644 --- a/render-wasm/Cargo.toml +++ b/render-wasm/Cargo.toml @@ -11,6 +11,8 @@ name = "render_wasm" path = "src/main.rs" [dependencies] +emscripten-functions = "0.2.3" +emscripten-functions-sys = "4.1.67" gl = "0.14.0" skia-safe = { version = "0.78.2", features = ["gl"] } From 0c4b1cc4fc50787238990cbf675cdb4c26795fa9 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 7 Nov 2024 20:24:33 +0100 Subject: [PATCH 4/6] :paperclip: Update yarn.lock with text-editor dependency change --- frontend/yarn.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 0255a0346..a4afad715 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -1004,10 +1004,10 @@ __metadata: languageName: node linkType: hard -"@penpot/text-editor@penpot/penpot-text-editor#449e3322f3fa40b1318c9154afbbc7932a3cb766": +"@penpot/text-editor@penpot/penpot-text-editor#a100aad8d0efcbb070bed9144dbd2782547e78ba": version: 0.0.0 - resolution: "@penpot/text-editor@https://github.com/penpot/penpot-text-editor.git#commit=449e3322f3fa40b1318c9154afbbc7932a3cb766" - checksum: 10c0/377fbd1fccc91ce532356601a27fe11afe19f169748127884f39a6b231a037e7e1e8b401149062e39219715901933771b7d752accaa52682fb141889c22dd1d3 + resolution: "@penpot/text-editor@https://github.com/penpot/penpot-text-editor.git#commit=a100aad8d0efcbb070bed9144dbd2782547e78ba" + checksum: 10c0/328c827cd740c5e05df678083cfb1d2b6d006b56523daa0bd2a3c2936a0490a2ae4d0e69a3aec428674609a22a5fafdd5600aae1399cb3f4ed5b80e497c74a5c languageName: node linkType: hard @@ -4393,7 +4393,7 @@ __metadata: "@penpot/hljs": "portal:./vendor/hljs" "@penpot/mousetrap": "portal:./vendor/mousetrap" "@penpot/svgo": "penpot/svgo#c6fba7a4dcfbc27b643e7fc0c94fc98cf680b77b" - "@penpot/text-editor": "penpot/penpot-text-editor#449e3322f3fa40b1318c9154afbbc7932a3cb766" + "@penpot/text-editor": "penpot/penpot-text-editor#a100aad8d0efcbb070bed9144dbd2782547e78ba" "@playwright/test": "npm:1.48.1" "@storybook/addon-essentials": "npm:^8.3.6" "@storybook/addon-themes": "npm:^8.3.6" From 9eaa55b7110b58eccc92234b930a83af69b18468 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 7 Nov 2024 20:50:01 +0100 Subject: [PATCH 5/6] :sparkles: Prevent logging EOF exceptions on SSE responses They are not necessary and they are pretty common, because the user can interrupt the connection at any time. --- backend/src/app/http/sse.clj | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/backend/src/app/http/sse.clj b/backend/src/app/http/sse.clj index fb7f75e2d..f00422a27 100644 --- a/backend/src/app/http/sse.clj +++ b/backend/src/app/http/sse.clj @@ -9,6 +9,7 @@ (:refer-clojure :exclude [tap]) (:require [app.common.data :as d] + [app.common.exceptions :as ex] [app.common.logging :as l] [app.common.transit :as t] [app.http.errors :as errors] @@ -60,13 +61,10 @@ (try (let [result (handler)] (events/tap :end result)) - - (catch java.io.EOFException cause - (events/tap :error (errors/handle' cause request))) (catch Throwable cause - (l/err :hint "unexpected error on processing sse response" - :cause cause) - (events/tap :error (errors/handle' cause request))) + (events/tap :error (errors/handle' cause request)) + (when-not (ex/instance? java.io.EOFException cause) + (l/err :hint "unexpected error on processing sse response" :cause cause))) (finally (sp/close! events/*channel*) (px/await! listener)))))))})) From d9eff00a719a78f5d8c17a2d317b31392da7d289 Mon Sep 17 00:00:00 2001 From: Pablo Alba Date: Thu, 7 Nov 2024 13:42:32 +0100 Subject: [PATCH 6/6] :sparkles: Integrate viewer role with plugin menus and popup --- frontend/src/app/main/data/plugins.cljs | 70 +++++++++++++++++-- .../main/data/workspace/notifications.cljs | 4 +- frontend/src/app/main/refs.cljs | 5 ++ frontend/src/app/main/ui/workspace.cljs | 4 +- .../src/app/main/ui/workspace/main_menu.cljs | 57 ++++++++++----- .../src/app/main/ui/workspace/main_menu.scss | 18 +++++ .../src/app/main/ui/workspace/plugins.cljs | 33 +++++++-- .../src/app/main/ui/workspace/plugins.scss | 5 +- frontend/translations/en.po | 4 ++ frontend/translations/es.po | 4 ++ 10 files changed, 168 insertions(+), 36 deletions(-) diff --git a/frontend/src/app/main/data/plugins.cljs b/frontend/src/app/main/data/plugins.cljs index ba27e6a0a..0dd933651 100644 --- a/frontend/src/app/main/data/plugins.cljs +++ b/frontend/src/app/main/data/plugins.cljs @@ -8,13 +8,23 @@ (:require [app.common.data.macros :as dm] [app.main.data.modal :as modal] + [app.main.data.notifications :as ntf] [app.main.store :as st] [app.plugins.register :as preg] [app.util.globals :as ug] [app.util.http :as http] + [app.util.i18n :as i18n :refer [tr]] + [app.util.time :as dt] [beicon.v2.core :as rx] [potok.v2.core :as ptk])) +(defn save-plugin-permissions-peek + [id permissions] + (ptk/reify ::save-plugin-permissions-peek + ptk/UpdateEvent + (update [_ state] + (assoc-in state [:plugins-permissions-peek :data id] permissions)))) + (defn fetch-manifest [plugin-url] (->> (http/send! {:method :get @@ -59,15 +69,21 @@ (.error js/console "Error" e)))) (defn open-plugin! - [{:keys [url] :as manifest}] + [{:keys [url] :as manifest} user-can-edit?] (if url ;; If the saved manifest has a URL we fetch the manifest to check ;; for updates (->> (fetch-manifest url) (rx/subs! (fn [new-manifest] - (let [new-manifest (merge new-manifest (select-keys manifest [:plugin-id]))] + (let [new-manifest (merge new-manifest (select-keys manifest [:plugin-id])) + permissions (:permissions new-manifest) + is-edition-plugin? (or (contains? permissions "content:write") + (contains? permissions "library:write"))] + (st/emit! (save-plugin-permissions-peek (:plugin-id new-manifest) permissions)) (cond + (and is-edition-plugin? (not user-can-edit?)) + (st/emit! (ntf/warn (tr "workspace.plugins.error.need-editor"))) (not= (:permissions new-manifest) (:permissions manifest)) (modal/show! :plugin-permissions-update @@ -96,13 +112,21 @@ (.error js/console "Error" e)))) (defn close-current-plugin - [] + [& {:keys [close-only-edition-plugins?]}] (ptk/reify ::close-current-plugin ptk/EffectEvent (effect [_ state _] (let [ids (dm/get-in state [:workspace-local :open-plugins])] (doseq [id ids] - (close-plugin! (preg/get-plugin id))))))) + (let [plugin (preg/get-plugin id) + permissions (or (dm/get-in state [:plugins-permissions-peek :data id]) + (:permissions plugin)) + is-edition-plugin? (or (contains? permissions "content:write") + (contains? permissions "library:write"))] + + (when (or (not close-only-edition-plugins?) + is-edition-plugin?) + (close-plugin! plugin)))))))) (defn delay-open-plugin [plugin] @@ -116,6 +140,38 @@ (ptk/reify ::check-open-plugin ptk/WatchEvent (watch [_ state _] - (when-let [pid (::open-plugin state)] - (open-plugin! (preg/get-plugin pid)) - (rx/of #(dissoc % ::open-plugin)))))) + (let [user-can-edit? (dm/get-in state [:permissions :can-edit])] + (when-let [pid (::open-plugin state)] + (open-plugin! (preg/get-plugin pid) user-can-edit?) + (rx/of #(dissoc % ::open-plugin))))))) + +(defn- update-plugin-permissions-peek + [{:keys [plugin-id url]}] + (when url + ;; If the saved manifest has a URL we fetch the manifest to check + ;; for updates + (->> (fetch-manifest url) + (rx/subs! + (fn [new-manifest] + (let [permissions (:permissions new-manifest)] + (when permissions + (st/emit! (save-plugin-permissions-peek plugin-id permissions))))))))) + +(defn update-plugins-permissions-peek + [] + (ptk/reify ::update-plugins-permissions-peek + ptk/UpdateEvent + (update [_ state] + (let [now (dt/now) + expiration (dt/minus now (dt/duration {:days 1})) + updated-at (dm/get-in state [:plugins-permissions-peek :updated-at] 0) + expired? (> expiration updated-at)] + + (if expired? + (let [plugins (preg/plugins-list)] + (doseq [plugin plugins] + (update-plugin-permissions-peek plugin)) + (-> state + (assoc-in [:plugins-permissions-peek :updated-at] now))) + + state))))) diff --git a/frontend/src/app/main/data/workspace/notifications.cljs b/frontend/src/app/main/data/workspace/notifications.cljs index fc49d273f..d4b6b7ffe 100644 --- a/frontend/src/app/main/data/workspace/notifications.cljs +++ b/frontend/src/app/main/data/workspace/notifications.cljs @@ -14,6 +14,7 @@ [app.main.data.changes :as dch] [app.main.data.common :as dc] [app.main.data.modal :as modal] + [app.main.data.plugins :as dpl] [app.main.data.websocket :as dws] [app.main.data.workspace.common :as dwc] [app.main.data.workspace.edition :as dwe] @@ -117,7 +118,8 @@ (rx/delay 100)) (if (= :viewer role) (rx/of (modal/hide) - (dwly/set-options-mode :inspect)) + (dwly/set-options-mode :inspect) + (dpl/close-current-plugin {:close-only-edition-plugins? true})) (rx/of (dwly/set-options-mode :design))))))) (defn- process-message diff --git a/frontend/src/app/main/refs.cljs b/frontend/src/app/main/refs.cljs index 6f73be8dc..063bb5b79 100644 --- a/frontend/src/app/main/refs.cljs +++ b/frontend/src/app/main/refs.cljs @@ -512,6 +512,11 @@ (def workspace-selected-token-set-tokens (l/derived #(or (wtts/get-selected-token-set-tokens %) {}) st/state)) +(def plugins-permissions-peek + (l/derived (fn [state] + (dm/get-in state [:plugins-permissions-peek :data])) + st/state)) + ;; ---- Viewer refs (defn lookup-viewer-objects-by-id diff --git a/frontend/src/app/main/ui/workspace.cljs b/frontend/src/app/main/ui/workspace.cljs index 4f6360733..760269254 100644 --- a/frontend/src/app/main/ui/workspace.cljs +++ b/frontend/src/app/main/ui/workspace.cljs @@ -11,6 +11,7 @@ [app.main.data.modal :as modal] [app.main.data.notifications :as ntf] [app.main.data.persistence :as dps] + [app.main.data.plugins :as dpl] [app.main.data.workspace :as dw] [app.main.data.workspace.colors :as dc] [app.main.features :as features] @@ -185,7 +186,8 @@ background-color (:background-color wglobal)] (mf/with-effect [] - (st/emit! (dps/initialize-persistence))) + (st/emit! (dps/initialize-persistence) + (dpl/update-plugins-permissions-peek))) ;; Setting the layout preset by its name (mf/with-effect [layout-name] diff --git a/frontend/src/app/main/ui/workspace/main_menu.cljs b/frontend/src/app/main/ui/workspace/main_menu.cljs index 7fd4cddb5..d870e3ac5 100644 --- a/frontend/src/app/main/ui/workspace/main_menu.cljs +++ b/frontend/src/app/main/ui/workspace/main_menu.cljs @@ -632,7 +632,9 @@ ::mf/wrap [mf/memo]} [{:keys [open-plugins on-close]}] (when (features/active-feature? @st/state "plugins/runtime") - (let [plugins (preg/plugins-list)] + (let [plugins (preg/plugins-list) + user-can-edit? (:can-edit (deref refs/permissions)) + permissions-peek (deref refs/plugins-permissions-peek)] [:& dropdown-menu {:show true :list-class (stl/css-case :sub-menu true :plugins true) :on-close on-close} @@ -653,24 +655,41 @@ (when (d/not-empty? plugins) [:div {:class (stl/css :separator)}]) - (for [[idx {:keys [name host] :as manifest}] (d/enumerate plugins)] - [:> dropdown-menu-item* {:key (dm/str "plugins-menu-" idx) - :on-click #(do - (st/emit! (ptk/event ::ev/event {::ev/name "start-plugin" - ::ev/origin "workspace:menu" - :name name - :host host})) - (dp/open-plugin! manifest)) - :class (stl/css :submenu-item) - :on-key-down (fn [event] - (when (kbd/enter? event) - #(do - (st/emit! (ptk/event ::ev/event {::ev/name "start-plugin" - ::ev/origin "workspace:menu" - :name name - :host host})) - (dp/open-plugin! manifest))))} - [:span {:class (stl/css :item-name)} name]])]))) + (for [[idx {:keys [plugin-id name host permissions] :as manifest}] (d/enumerate plugins)] + (let [permissions (or (get permissions-peek plugin-id) permissions) + is-edition-plugin? (or (contains? permissions "content:write") + (contains? permissions "library:write")) + can-open? (or user-can-edit? + (not is-edition-plugin?)) + on-click + (mf/use-fn + (mf/deps can-open? name host manifest user-can-edit?) + (fn [event] + (if can-open? + (do + (st/emit! (ptk/event ::ev/event {::ev/name "start-plugin" + ::ev/origin "workspace:menu" + :name name + :host host})) + (dp/open-plugin! manifest user-can-edit?)) + (dom/stop-propagation event)))) + on-key-down + (mf/use-fn + (mf/deps can-open? name host manifest user-can-edit?) + (fn [event] + (when can-open? + (when (kbd/enter? event) + (st/emit! (ptk/event ::ev/event {::ev/name "start-plugin" + ::ev/origin "workspace:menu" + :name name + :host host})) + (dp/open-plugin! manifest user-can-edit?)))))] + [:> dropdown-menu-item* {:key (dm/str "plugins-menu-" idx) + :on-click on-click + :title (when-not can-open? (tr "workspace.plugins.error.need-editor")) + :class (stl/css-case :submenu-item true :menu-disabled (not can-open?)) + :on-key-down on-key-down} + [:span {:class (stl/css :item-name)} name]]))]))) (mf/defc menu {::mf/props :obj} diff --git a/frontend/src/app/main/ui/workspace/main_menu.scss b/frontend/src/app/main/ui/workspace/main_menu.scss index e74ddcba6..56ae1cd19 100644 --- a/frontend/src/app/main/ui/workspace/main_menu.scss +++ b/frontend/src/app/main/ui/workspace/main_menu.scss @@ -17,20 +17,25 @@ .menu-item { @extend .menu-item-base; cursor: pointer; + .open-arrow { @include flexCenter; + svg { @extend .button-icon; stroke: var(--icon-foreground); } } + &:hover { color: var(--menu-foreground-color-hover); + .open-arrow { svg { stroke: var(--menu-foreground-color-hover); } } + .shortcut-key { color: var(--menu-shortcut-foreground-color-hover); } @@ -46,6 +51,7 @@ .shortcut { @extend .shortcut-base; } + .shortcut-key { @extend .shortcut-key-base; } @@ -59,14 +65,26 @@ .submenu-item { @extend .menu-item-base; + &:hover { color: var(--menu-foreground-color-hover); + .shortcut-key { color: var(--menu-shortcut-foreground-color-hover); } } } + .menu-disabled { + color: var(--color-foreground-secondary); + + &:hover { + cursor: default; + color: var(--color-foreground-secondary); + background-color: var(--menu-background-color); + } + } + &.file { top: $s-48; } diff --git a/frontend/src/app/main/ui/workspace/plugins.cljs b/frontend/src/app/main/ui/workspace/plugins.cljs index b6aee18d6..c0ad1c580 100644 --- a/frontend/src/app/main/ui/workspace/plugins.cljs +++ b/frontend/src/app/main/ui/workspace/plugins.cljs @@ -13,9 +13,11 @@ [app.main.data.events :as ev] [app.main.data.modal :as modal] [app.main.data.plugins :as dp] + [app.main.refs :as refs] [app.main.store :as st] [app.main.ui.components.search-bar :refer [search-bar]] [app.main.ui.components.title-bar :refer [title-bar]] + [app.main.ui.ds.buttons.button :refer [button*]] [app.main.ui.ds.buttons.icon-button :refer [icon-button*]] [app.main.ui.icons :as i] [app.plugins.register :as preg] @@ -40,14 +42,22 @@ icon)) (mf/defc plugin-entry - [{:keys [index manifest on-open-plugin on-remove-plugin]}] + [{:keys [index manifest user-can-edit on-open-plugin on-remove-plugin]}] + + (let [{:keys [plugin-id host icon name description permissions]} manifest + plugins-permissions-peek (deref refs/plugins-permissions-peek) + permissions (or (get plugins-permissions-peek plugin-id) + permissions) + is-edition-plugin? (or (contains? permissions "content:write") + (contains? permissions "library:write")) + can-open? (or user-can-edit + (not is-edition-plugin?)) - (let [{:keys [host icon name description]} manifest handle-open-click (mf/use-callback - (mf/deps index manifest on-open-plugin) + (mf/deps index manifest on-open-plugin can-open?) (fn [] - (when on-open-plugin + (when (and can-open? on-open-plugin) (on-open-plugin manifest)))) handle-delete-click @@ -64,8 +74,14 @@ [:div {:class (stl/css :plugin-description)} [:div {:class (stl/css :plugin-title)} name] [:div {:class (stl/css :plugin-summary)} (d/nilv description "")]] - [:button {:class (stl/css :open-button) - :on-click handle-open-click} (tr "workspace.plugins.button-open")] + + + [:> button* {:class (stl/css :open-button) + :variant "secondary" + :on-click handle-open-click + :title (when-not can-open? (tr "workspace.plugins.error.need-editor")) + :disabled (not can-open?)} (tr "workspace.plugins.button-open")] + [:> icon-button* {:variant "ghost" :aria-label (tr "workspace.plugins.remove-plugin") :on-click handle-delete-click @@ -91,6 +107,8 @@ error-manifest? (= :error-manifest input-status) error? (or error-url? error-manifest?) + user-can-edit? (:can-edit (deref refs/permissions)) + handle-close-dialog (mf/use-callback (fn [] @@ -137,7 +155,7 @@ ::ev/origin "workspace:plugins" :name (:name manifest) :host (:host manifest)})) - (dp/open-plugin! manifest) + (dp/open-plugin! manifest user-can-edit?) (modal/hide!))) handle-remove-plugin @@ -204,6 +222,7 @@ [:& plugin-entry {:key (dm/str "plugin-" idx) :index idx :manifest manifest + :user-can-edit user-can-edit? :on-open-plugin handle-open-plugin :on-remove-plugin handle-remove-plugin}])]])]]])) diff --git a/frontend/src/app/main/ui/workspace/plugins.scss b/frontend/src/app/main/ui/workspace/plugins.scss index 96ad29243..82d0bb6cd 100644 --- a/frontend/src/app/main/ui/workspace/plugins.scss +++ b/frontend/src/app/main/ui/workspace/plugins.scss @@ -102,6 +102,7 @@ @include flexCenter; width: $s-20; padding: 0 0 0 $s-8; + svg { @extend .button-icon-small; stroke: var(--icon-foreground); @@ -114,7 +115,9 @@ } .open-button { - @extend .button-secondary; + display: flex; + justify-content: center; + align-items: center; width: $s-68; min-width: $s-68; height: $s-32; diff --git a/frontend/translations/en.po b/frontend/translations/en.po index 96dc22c16..e44b01166 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -5859,6 +5859,10 @@ msgstr "Plugins" msgid "workspace.plugins.remove-plugin" msgstr "Remove plugin" +#: src/app/main/data/plugins.cljs:78 +msgid "workspace.plugins.error.need-editor" +msgstr "You need to be an editor to use this plugin" + #: /src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs:1005 msgid "workspace.shape.menu.add-layout" msgstr "Add layout" diff --git a/frontend/translations/es.po b/frontend/translations/es.po index 82864350b..d50e66603 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -5837,6 +5837,10 @@ msgstr "Extensiones" msgid "workspace.plugins.remove-plugin" msgstr "Eliminar extensión" +#: src/app/main/data/plugins.cljs:78 +msgid "workspace.plugins.error.need-editor" +msgstr "Debes ser un editor para usar este plugin" + #: /src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs:1005 msgid "workspace.shape.menu.add-layout" msgstr "Añadir layout"