From 9950c5dc0ff0b6d7e8d66bb9291d265feec7754e Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 9 Aug 2022 19:27:19 +0200 Subject: [PATCH] :tada: Add shared state hook and broadcast channel api --- frontend/src/app/main/broadcast.cljs | 52 +++++++++++++++++++ .../src/app/main/data/workspace/colors.cljs | 23 +++----- frontend/src/app/main/ui/hooks.cljs | 24 +++++++-- .../app/main/ui/workspace/colorpalette.cljs | 3 +- .../ui/workspace/colorpicker/libraries.cljs | 2 +- 5 files changed, 81 insertions(+), 23 deletions(-) create mode 100644 frontend/src/app/main/broadcast.cljs diff --git a/frontend/src/app/main/broadcast.cljs b/frontend/src/app/main/broadcast.cljs new file mode 100644 index 0000000000..ef50e4b31b --- /dev/null +++ b/frontend/src/app/main/broadcast.cljs @@ -0,0 +1,52 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) UXBOX Labs SL + +(ns app.main.broadcast + "BroadcastChannel API." + (:require + [app.common.transit :as t] + [beicon.core :as rx] + [potok.core :as ptk])) + +(defrecord BroadcastMessage [id type data] + cljs.core/IDeref + (-deref [_] data)) + +(def ^:const default-topic "penpot") + +;; The main broadcast channel instance, used for emit data +(defonce default-channel + (js/BroadcastChannel. default-topic)) + +(defonce stream + (->> (rx/create (fn [subs] + (let [chan (js/BroadcastChannel. default-topic)] + (unchecked-set chan "onmessage" #(rx/push! subs (unchecked-get % "data"))) + (fn [] (.close ^js chan))))) + (rx/map t/decode-str) + (rx/map map->BroadcastMessage) + (rx/share))) + +(defn emit! + ([type data] + (.postMessage ^js default-channel (t/encode-str {:id nil :type type :data data})) + nil) + ([id type data] + (.postMessage ^js default-channel (t/encode-str {:id id :type type :data data})) + nil)) + +(defn type? + ([type] + (fn [obj] (= (:type obj) type))) + ([obj type] + (= (:type obj) type))) + +(defn event + [type data] + (ptk/reify ::event + ptk/EffectEvent + (effect [_ _ _] + (emit! type data)))) diff --git a/frontend/src/app/main/data/workspace/colors.cljs b/frontend/src/app/main/data/workspace/colors.cljs index 94e35f2058..61f91a062e 100644 --- a/frontend/src/app/main/data/workspace/colors.cljs +++ b/frontend/src/app/main/data/workspace/colors.cljs @@ -9,6 +9,7 @@ [app.common.colors :as colors] [app.common.data :as d] [app.common.pages.helpers :as cph] + [app.main.broadcast :as mbc] [app.main.data.modal :as md] [app.main.data.workspace.changes :as dch] [app.main.data.workspace.layout :as layout] @@ -19,30 +20,18 @@ [beicon.core :as rx] [potok.core :as ptk])) -(defn change-palette-selected - "Change the library used by the general palette tool" - [selected] - (ptk/reify ::change-palette-selected - ptk/UpdateEvent - (update [_ state] - (assoc-in state [:workspace-global :selected-palette] selected)) - - ptk/EffectEvent - (effect [_ state _] - (let [wglobal (:workspace-global state)] - (layout/persist-layout-state! wglobal))))) +;; A set of keys that are used for shared state identifiers +(def ^:const colorpicker-selected-broadcast-key ::colorpicker-selected) +(def ^:const colorpalette-selected-broadcast-key ::colorpalette-selected) (defn show-palette "Show the palette tool and change the library it uses" [selected] (ptk/reify ::show-palette - ptk/UpdateEvent - (update [_ state] - (assoc-in state [:workspace-global :selected-palette] selected)) - ptk/WatchEvent (watch [_ _ _] - (rx/of (layout/toggle-layout-flag :colorpalette :force? true))) + (rx/of (layout/toggle-layout-flag :colorpalette :force? true) + (mbc/event colorpalette-selected-broadcast-key selected))) ptk/EffectEvent (effect [_ state _] diff --git a/frontend/src/app/main/ui/hooks.cljs b/frontend/src/app/main/ui/hooks.cljs index de73064935..6fc2c6850b 100644 --- a/frontend/src/app/main/ui/hooks.cljs +++ b/frontend/src/app/main/ui/hooks.cljs @@ -7,9 +7,10 @@ (ns app.main.ui.hooks "A collection of general purpose react hooks." (:require - [app.common.uuid :as uuid] [app.common.data.macros :as dm] [app.common.pages :as cp] + [app.common.uuid :as uuid] + [app.main.broadcast :as mbc] [app.main.data.shortcuts :as dsc] [app.main.refs :as refs] [app.main.store :as st] @@ -289,9 +290,26 @@ (update-fn value)) state)) -(defn use-persistent-state +(defn use-shared-state + "A specialized hook that adds persistence and inter-context reactivity + to the default mf/use-state hook. + + The state is automatically persisted under the provided key on + localStorage. And it will keep watching events with type equals to + `key` for new values." [key default] - (let [state (mf/use-state (get @storage key default))] + (let [id (use-id) + state (mf/use-state (get @storage key default)) + stream (mf/with-memo [] + (->> mbc/stream + (rx/filter #(= (:type %) key)) + (rx/filter #(not= (:id %) id)) + (rx/map deref)))] + (mf/with-effect [@state key] + (mbc/emit! id key @state) (swap! storage assoc key @state)) + + (use-stream stream (partial reset! state)) + state)) diff --git a/frontend/src/app/main/ui/workspace/colorpalette.cljs b/frontend/src/app/main/ui/workspace/colorpalette.cljs index 1de4161ab0..39b0e17bbb 100644 --- a/frontend/src/app/main/ui/workspace/colorpalette.cljs +++ b/frontend/src/app/main/ui/workspace/colorpalette.cljs @@ -21,7 +21,6 @@ [app.util.object :as obj] [cuerdas.core :as str] [goog.events :as events] - [okulary.core :as l] [rumext.alpha :as mf])) ;; --- Components @@ -178,7 +177,7 @@ (let [recent-colors (mf/deref refs/workspace-recent-colors) file-colors (mf/deref refs/workspace-file-colors) shared-libs (mf/deref refs/workspace-libraries) - selected (h/use-persistent-state ::selected :recent) + selected (h/use-shared-state mdc/colorpalette-selected-broadcast-key :recent) colors (mf/use-state []) on-select (mf/use-fn #(reset! selected %))] diff --git a/frontend/src/app/main/ui/workspace/colorpicker/libraries.cljs b/frontend/src/app/main/ui/workspace/colorpicker/libraries.cljs index fd39aa7398..8e3b6e3925 100644 --- a/frontend/src/app/main/ui/workspace/colorpicker/libraries.cljs +++ b/frontend/src/app/main/ui/workspace/colorpicker/libraries.cljs @@ -19,7 +19,7 @@ (mf/defc libraries [{:keys [on-select-color on-add-library-color disable-gradient disable-opacity]}] - (let [selected (h/use-persistent-state ::selected :recent) + (let [selected (h/use-shared-state dc/colorpicker-selected-broadcast-key :recent) current-colors (mf/use-state []) shared-libs (mf/deref refs/workspace-libraries)