Merge pull request #3517 from penpot/niwinz-enhancements-push-notifications

🎉 Add the ability to send push notifications
This commit is contained in:
Alejandro 2023-08-14 12:24:43 +02:00 committed by GitHub
commit a1ac839b2a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 388 additions and 74 deletions

View file

@ -6,7 +6,6 @@
rumext.v2/defc clojure.core/defn rumext.v2/defc clojure.core/defn
rumext.v2/fnc clojure.core/fn rumext.v2/fnc clojure.core/fn
app.common.data/export clojure.core/def app.common.data/export clojure.core/def
app.db/with-atomic clojure.core/with-open
app.common.data.macros/get-in clojure.core/get-in app.common.data.macros/get-in clojure.core/get-in
app.common.data.macros/with-open clojure.core/with-open app.common.data.macros/with-open clojure.core/with-open
app.common.data.macros/select-keys clojure.core/select-keys app.common.data.macros/select-keys clojure.core/select-keys
@ -17,6 +16,7 @@
{app.common.data.macros/export hooks.export/export {app.common.data.macros/export hooks.export/export
potok.core/reify hooks.export/potok-reify potok.core/reify hooks.export/potok-reify
app.util.services/defmethod hooks.export/service-defmethod app.util.services/defmethod hooks.export/service-defmethod
app.db/with-atomic hooks.export/penpot-with-atomic
}} }}
:output :output

View file

@ -39,6 +39,43 @@
other))] other))]
{:node result}))) {:node result})))
(defn penpot-with-atomic
[{:keys [node]}]
(let [[_ params & other] (:children node)
result (if (api/vector-node? params)
(api/list-node
(into [(api/token-node (symbol "clojure.core" "with-open")) params] other))
(api/list-node
(into [(api/token-node (symbol "clojure.core" "with-open"))
(api/vector-node [params params])]
other)))
]
{:node result}))
(defn penpot-defrecord
[{:keys [:node]}]
(let [[rnode rtype rparams & other] (:children node)
nodes [(api/token-node (symbol "do"))
(api/list-node
(into [(api/token-node (symbol (name (:value rnode)))) rtype rparams] other))
(api/list-node
[(api/token-node (symbol "defn"))
(api/token-node (symbol (str "pos->" (:string-value rtype))))
(api/vector-node
(->> (:children rparams)
(mapv (fn [t]
(api/token-node (symbol (str "_" (:string-value t))))))))
(api/token-node nil)])]
result (api/list-node nodes)]
;; (prn "=====>" (into {} rparams))
;; (prn (api/sexpr result))
{:node result}))
(defn clojure-specify (defn clojure-specify
[{:keys [:node]}] [{:keys [:node]}]
(let [[rnode rtype & other] (:children node) (let [[rnode rtype & other] (:children node)
@ -48,7 +85,6 @@
other))] other))]
{:node result})) {:node result}))
(defn service-defmethod (defn service-defmethod
[{:keys [:node]}] [{:keys [:node]}]
(let [[rnode rtype ?meta & other] (:children node) (let [[rnode rtype ?meta & other] (:children node)

View file

@ -7,7 +7,7 @@ Report: {{hint|abbreviate:150}} - {{id}} - Penpot Error Report (v3)
{% block content %} {% block content %}
<nav> <nav>
<div>[<a href="/dbg/error">⮜</a>]</div> <div>[<a href="/dbg/error">⮜</a>]</div>
<div>[<a href="#message">message</a>]</div> <div>[<a href="#head">head</a>]</div>
<div>[<a href="#props">props</a>]</div> <div>[<a href="#props">props</a>]</div>
<div>[<a href="#context">context</a>]</div> <div>[<a href="#context">context</a>]</div>
{% if params %} {% if params %}
@ -29,10 +29,11 @@ Report: {{hint|abbreviate:150}} - {{id}} - Penpot Error Report (v3)
<main> <main>
<div class="table"> <div class="table">
<div class="table-row multiline"> <div class="table-row multiline">
<div id="message" class="table-key">MESSAGE: </div> <div id="head" class="table-key">HEAD</div>
<div class="table-val"> <div class="table-val">
<h1>{{hint}}</h1> <h1><span class="not-important">Hint:</span> <br/> {{hint}}</h1>
<h2><span class="not-important">Reported at:</span> <br/> {{created-at}}</h2>
<h2><span class="not-important">Report ID:</span> <br/> {{id}}</h2>
</div> </div>
</div> </div>

View file

@ -36,6 +36,11 @@ small {
color: #888; color: #888;
} }
.not-important {
color: #888;
font-weight: 200;
}
small > strong { small > strong {
font-size: 9px; font-size: 9px;
} }

View file

@ -567,7 +567,7 @@
profile (get-profile cfg info)] profile (get-profile cfg info)]
(generate-redirect cfg request info profile)) (generate-redirect cfg request info profile))
(catch Throwable cause (catch Throwable cause
(l/error :hint "error on oauth process" :cause cause) (l/warn :hint "error on oauth process" :cause cause)
(generate-error-redirect cfg cause)))) (generate-error-redirect cfg cause))))
(def provider-lookup (def provider-lookup

View file

@ -5,7 +5,7 @@
;; Copyright (c) KALEIDOS INC ;; Copyright (c) KALEIDOS INC
(ns app.db (ns app.db
(:refer-clojure :exclude [get]) (:refer-clojure :exclude [get run!])
(:require (:require
[app.common.data :as d] [app.common.data :as d]
[app.common.exceptions :as ex] [app.common.exceptions :as ex]
@ -218,7 +218,13 @@
(defmacro with-atomic (defmacro with-atomic
[& args] [& args]
`(jdbc/with-transaction ~@args)) (if (symbol? (first args))
(let [cfgs (first args)
body (rest args)]
`(jdbc/with-transaction [conn# (::pool ~cfgs)]
(let [~cfgs (assoc ~cfgs ::conn conn#)]
~@body)))
`(jdbc/with-transaction ~@args)))
(defn open (defn open
[pool] [pool]
@ -293,6 +299,10 @@
:hint "database object not found")) :hint "database object not found"))
row)) row))
(defn plan
[ds sql]
(jdbc/plan ds sql sql/default-opts))
(defn get-by-id (defn get-by-id
[ds table id & {:as opts}] [ds table id & {:as opts}]
(get ds table {:id id} opts)) (get ds table {:id id} opts))
@ -381,6 +391,52 @@
([^Connection conn ^Savepoint sp] ([^Connection conn ^Savepoint sp]
(.rollback conn sp))) (.rollback conn sp)))
(defn tx-run!
[cfg f]
(cond
(connection? cfg)
(tx-run! {::conn cfg} f)
(pool? cfg)
(tx-run! {::pool cfg} f)
(::conn cfg)
(let [conn (::conn cfg)
sp (savepoint conn)]
(try
(let [result (f cfg)]
(release! conn sp)
result)
(catch Throwable cause
(rollback! sp)
(throw cause))))
(::pool cfg)
(with-atomic [conn (::pool cfg)]
(f (assoc cfg ::conn conn)))
:else
(throw (IllegalArgumentException. "invalid arguments"))))
(defn run!
[cfg f]
(cond
(connection? cfg)
(run! {::conn cfg} f)
(pool? cfg)
(run! {::pool cfg} f)
(::conn cfg)
(f cfg)
(::pool cfg)
(with-open [^Connection conn (open (::pool cfg))]
(f (assoc cfg ::conn conn)))
:else
(throw (IllegalArgumentException. "invalid arguments"))))
(defn interval (defn interval
[o] [o]
(cond (cond

View file

@ -238,9 +238,11 @@
(-> (io/resource "app/templates/error-report.v2.tmpl") (-> (io/resource "app/templates/error-report.v2.tmpl")
(tmpl/render report))) (tmpl/render report)))
(render-template-v3 [{report :content id :id}] (render-template-v3 [{:keys [content id created-at]}]
(-> (io/resource "app/templates/error-report.v3.tmpl") (-> (io/resource "app/templates/error-report.v3.tmpl")
(tmpl/render (assoc report :id id)))) (tmpl/render (-> content
(assoc :id id)
(assoc :created-at (dt/format-instant created-at :rfc1123))))))
] ]
(when-not (authorized? pool request) (when-not (authorized? pool request)

View file

@ -11,6 +11,7 @@
[app.common.logging :as l] [app.common.logging :as l]
[app.common.pprint :as pp] [app.common.pprint :as pp]
[app.common.spec :as us] [app.common.spec :as us]
[app.common.uuid :as uuid]
[app.db :as db] [app.db :as db]
[app.http.session :as session] [app.http.session :as session]
[app.metrics :as mtx] [app.metrics :as mtx]
@ -99,7 +100,10 @@
(sp/pipe ch output-ch false) (sp/pipe ch output-ch false)
;; Subscribe to the profile topic on msgbus/redis ;; Subscribe to the profile topic on msgbus/redis
(mbus/sub! msgbus :topic profile-id :chan ch))) (mbus/sub! msgbus :topic profile-id :chan ch)
;; Subscribe to the system topic on msgbus/redis
(mbus/sub! msgbus :topic (str uuid/zero) :chan ch)))
(defmethod handle-message :close (defmethod handle-message :close
[{:keys [::mbus/msgbus]} {:keys [::ws/id ::ws/state ::profile-id ::session-id]} _] [{:keys [::mbus/msgbus]} {:keys [::ws/id ::ws/state ::profile-id ::session-id]} _]

View file

@ -294,28 +294,40 @@
[output & {:keys [level] :or {level 0}}] [output & {:keys [level] :or {level 0}}]
(ZstdOutputStream. ^OutputStream output (int level))) (ZstdOutputStream. ^OutputStream output (int level)))
(defn- retrieve-file (defn- get-files
[pool file-id] [cfg ids]
(dm/with-open [conn (db/open pool)] (letfn [(get-files* [{:keys [::db/conn]}]
(binding [pmap/*load-fn* (partial files/load-pointer conn file-id)] (let [sql (str "SELECT id FROM file "
(some-> (db/get* conn :file {:id file-id}) " WHERE id = ANY(?) ")
(files/decode-row) ids (db/create-array conn "uuid" ids)]
(files/process-pointers deref))))) (->> (db/exec! conn [sql ids])
(into [] (map :id))
(not-empty))))]
(def ^:private sql:file-media-objects (db/run! cfg get-files*)))
"SELECT * FROM file_media_object WHERE id = ANY(?)")
(defn- retrieve-file-media (defn- get-file
[pool {:keys [data id] :as file}] [cfg file-id]
(letfn [(get-file* [{:keys [::db/conn]}]
(binding [pmap/*load-fn* (partial files/load-pointer conn file-id)]
(some-> (db/get* conn :file {:id file-id} {::db/remove-deleted? false})
(files/decode-row)
(files/process-pointers deref))))]
(db/run! cfg get-file*)))
(defn- get-file-media
[{:keys [::db/pool]} {:keys [data id] :as file}]
(dm/with-open [conn (db/open pool)] (dm/with-open [conn (db/open pool)]
(let [ids (app.tasks.file-gc/collect-used-media data) (let [ids (app.tasks.file-gc/collect-used-media data)
ids (db/create-array conn "uuid" ids)] ids (db/create-array conn "uuid" ids)
sql (str "SELECT * FROM file_media_object WHERE id = ANY(?)")]
;; We assoc the file-id again to the file-media-object row ;; We assoc the file-id again to the file-media-object row
;; because there are cases that used objects refer to other ;; because there are cases that used objects refer to other
;; files and we need to ensure in the exportation process that ;; files and we need to ensure in the exportation process that
;; all ids matches ;; all ids matches
(->> (db/exec! conn [sql:file-media-objects ids]) (->> (db/exec! conn [sql ids])
(mapv #(assoc % :file-id id)))))) (mapv #(assoc % :file-id id))))))
(def ^:private storage-object-id-xf (def ^:private storage-object-id-xf
@ -325,34 +337,32 @@
(def ^:private sql:file-libraries (def ^:private sql:file-libraries
"WITH RECURSIVE libs AS ( "WITH RECURSIVE libs AS (
SELECT fl.id, fl.deleted_at SELECT fl.id
FROM file AS fl FROM file AS fl
JOIN file_library_rel AS flr ON (flr.library_file_id = fl.id) JOIN file_library_rel AS flr ON (flr.library_file_id = fl.id)
WHERE flr.file_id = ANY(?) WHERE flr.file_id = ANY(?)
UNION UNION
SELECT fl.id, fl.deleted_at SELECT fl.id
FROM file AS fl FROM file AS fl
JOIN file_library_rel AS flr ON (flr.library_file_id = fl.id) JOIN file_library_rel AS flr ON (flr.library_file_id = fl.id)
JOIN libs AS l ON (flr.file_id = l.id) JOIN libs AS l ON (flr.file_id = l.id)
) )
SELECT DISTINCT l.id SELECT DISTINCT l.id
FROM libs AS l FROM libs AS l")
WHERE l.deleted_at IS NULL OR l.deleted_at > now();")
(defn- retrieve-libraries (defn- get-libraries
[pool ids] [{:keys [::db/pool]} ids]
(dm/with-open [conn (db/open pool)] (dm/with-open [conn (db/open pool)]
(let [ids (db/create-array conn "uuid" ids)] (let [ids (db/create-array conn "uuid" ids)]
(map :id (db/exec! pool [sql:file-libraries ids]))))) (map :id (db/exec! pool [sql:file-libraries ids])))))
(def ^:private sql:file-library-rels (defn- get-library-relations
"SELECT * FROM file_library_rel [cfg ids]
WHERE file_id = ANY(?)") (db/run! cfg (fn [{:keys [::db/conn]}]
(let [ids (db/create-array conn "uuid" ids)
(defn- retrieve-library-relations sql (str "SELECT flr.* FROM file_library_rel AS flr "
[pool ids] " WHERE flr.file_id = ANY(?)")]
(dm/with-open [conn (db/open pool)] (db/exec! conn [sql ids])))))
(db/exec! conn [sql:file-library-rels (db/create-array conn "uuid" ids)])))
(defn- create-or-update-file (defn- create-or-update-file
[conn params] [conn params]
@ -378,7 +388,7 @@
;; --- EXPORT WRITER ;; --- EXPORT WRITER
(defn- embed-file-assets (defn- embed-file-assets
[data conn file-id] [data cfg file-id]
(letfn [(walk-map-form [form state] (letfn [(walk-map-form [form state]
(cond (cond
(uuid? (:fill-color-ref-file form)) (uuid? (:fill-color-ref-file form))
@ -408,7 +418,7 @@
;; NOTE: there is a possibility that shape refers to an ;; NOTE: there is a possibility that shape refers to an
;; non-existant file because the file was removed. In this ;; non-existant file because the file was removed. In this
;; case we just ignore the asset. ;; case we just ignore the asset.
(if-let [lib (retrieve-file conn lib-id)] (if-let [lib (get-file cfg lib-id)]
(reduce (partial process-asset lib) data items) (reduce (partial process-asset lib) data items)
data)) data))
@ -476,28 +486,33 @@
[:v1/metadata :v1/files :v1/rels :v1/sobjects]))))) [:v1/metadata :v1/files :v1/rels :v1/sobjects])))))
(defmethod write-section :v1/metadata (defmethod write-section :v1/metadata
[{:keys [::db/pool ::output ::file-ids ::include-libraries?]}] [{:keys [::output ::file-ids ::include-libraries?] :as cfg}]
(let [libs (when include-libraries? (if-let [fids (get-files cfg file-ids)]
(retrieve-libraries pool file-ids)) (let [lids (when include-libraries?
files (into file-ids libs)] (get-libraries cfg file-ids))
(write-obj! output {:version cf/version :files files}) ids (into fids lids)]
(vswap! *state* assoc :files files))) (write-obj! output {:version cf/version :files ids})
(vswap! *state* assoc :files ids))
(ex/raise :type :not-found
:code :files-not-found
:hint "unable to retrieve files for export")))
(defmethod write-section :v1/files (defmethod write-section :v1/files
[{:keys [::db/pool ::output ::embed-assets?]}] [{:keys [::output ::embed-assets?] :as cfg}]
;; Initialize SIDS with empty vector ;; Initialize SIDS with empty vector
(vswap! *state* assoc :sids []) (vswap! *state* assoc :sids [])
(doseq [file-id (-> *state* deref :files)] (doseq [file-id (-> *state* deref :files)]
(let [file (cond-> (retrieve-file pool file-id) (let [file (cond-> (get-file cfg file-id)
embed-assets? embed-assets?
(update :data embed-file-assets pool file-id)) (update :data embed-file-assets cfg file-id))
media (retrieve-file-media pool file)] media (get-file-media cfg file)]
(l/debug :hint "write penpot file" (l/debug :hint "write penpot file"
:id file-id :id file-id
:name (:name file)
:media (count media) :media (count media)
::l/sync? true) ::l/sync? true)
@ -508,9 +523,10 @@
(vswap! *state* update :sids into storage-object-id-xf media)))) (vswap! *state* update :sids into storage-object-id-xf media))))
(defmethod write-section :v1/rels (defmethod write-section :v1/rels
[{:keys [::db/pool ::output ::include-libraries?]}] [{:keys [::output ::include-libraries?] :as cfg}]
(let [rels (when include-libraries? (let [ids (-> *state* deref :files)
(retrieve-library-relations pool (-> *state* deref :files)))] rels (when include-libraries?
(get-library-relations cfg ids))]
(l/debug :hint "found rels" :total (count rels) ::l/sync? true) (l/debug :hint "found rels" :total (count rels) ::l/sync? true)
(write-obj! output rels))) (write-obj! output rels)))
@ -518,6 +534,7 @@
[{:keys [::sto/storage ::output]}] [{:keys [::sto/storage ::output]}]
(let [sids (-> *state* deref :sids) (let [sids (-> *state* deref :sids)
storage (media/configure-assets-storage storage)] storage (media/configure-assets-storage storage)]
(l/debug :hint "found sobjects" (l/debug :hint "found sobjects"
:items (count sids) :items (count sids)
::l/sync? true) ::l/sync? true)
@ -630,6 +647,8 @@
(when (not= file-id expected-file-id) (when (not= file-id expected-file-id)
(ex/raise :type :validation (ex/raise :type :validation
:code :inconsistent-penpot-file :code :inconsistent-penpot-file
:found-id file-id
:expected-id expected-file-id
:hint "the penpot file seems corrupt, found unexpected uuid (file-id)")) :hint "the penpot file seems corrupt, found unexpected uuid (file-id)"))
;; Update index using with media ;; Update index using with media
@ -679,18 +698,27 @@
(defmethod read-section :v1/rels (defmethod read-section :v1/rels
[{:keys [::db/conn ::input ::timestamp]}] [{:keys [::db/conn ::input ::timestamp]}]
(let [rels (read-obj! input)] (let [rels (read-obj! input)
ids (into #{} (-> *state* deref :files))]
;; Insert all file relations ;; Insert all file relations
(doseq [rel rels] (doseq [{:keys [library-file-id] :as rel} rels]
(let [rel (-> rel (let [rel (-> rel
(assoc :synced-at timestamp) (assoc :synced-at timestamp)
(update :file-id lookup-index) (update :file-id lookup-index)
(update :library-file-id lookup-index))] (update :library-file-id lookup-index))]
(l/debug :hint "create file library link"
:file-id (:file-id rel) (if (contains? ids library-file-id)
:lib-id (:library-file-id rel) (do
::l/sync? true) (l/debug :hint "create file library link"
(db/insert! conn :file-library-rel rel))))) :file-id (:file-id rel)
:lib-id (:library-file-id rel)
::l/sync? true)
(db/insert! conn :file-library-rel rel))
(l/warn :hint "ignoring file library link"
:file-id (:file-id rel)
:lib-id (:library-file-id rel)
::l/sync? true))))))
(defmethod read-section :v1/sobjects (defmethod read-section :v1/sobjects
[{:keys [::sto/storage ::db/conn ::input ::overwrite?]}] [{:keys [::sto/storage ::db/conn ::input ::overwrite?]}]

View file

@ -8,10 +8,15 @@
"A collection of adhoc fixes scripts." "A collection of adhoc fixes scripts."
#_:clj-kondo/ignore #_:clj-kondo/ignore
(:require (:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.logging :as l] [app.common.logging :as l]
[app.common.pprint :as p] [app.common.pprint :as p]
[app.common.spec :as us] [app.common.spec :as us]
[app.common.uuid :as uuid]
[app.config :as cf]
[app.db :as db] [app.db :as db]
[app.msgbus :as mbus]
[app.rpc.commands.auth :as auth] [app.rpc.commands.auth :as auth]
[app.rpc.commands.profile :as profile] [app.rpc.commands.profile :as profile]
[app.srepl.fixes :as f] [app.srepl.fixes :as f]
@ -164,3 +169,106 @@
(alter-var-root var (fn [f] (alter-var-root var (fn [f]
(or (::original (meta f)) f)))) (or (::original (meta f)) f))))
(defn notify!
[{:keys [::mbus/msgbus ::db/pool]} & {:keys [dest code message level]
:or {code :generic level :info}
:as params}]
(dm/verify!
["invalid level %" level]
(contains? #{:success :error :info :warning} level))
(dm/verify!
["invalid code: %" code]
(contains? #{:generic :upgrade-version} code))
(letfn [(send [dest]
(l/inf :hint "sending notification" :dest (str dest))
(let [message {:type :notification
:code code
:level level
:version (:full cf/version)
:subs-id dest
:message message}
message (->> (dissoc params :dest :code :message :level)
(merge message))]
(mbus/pub! msgbus
:topic (str dest)
:message message)))
(resolve-profile [email]
(some-> (db/get* pool :profile {:email (str/lower email)} {:columns [:id]}) :id vector))
(resolve-team [team-id]
(->> (db/query pool :team-profile-rel
{:team-id team-id}
{:columns [:profile-id]})
(map :profile-id)))
(parse-uuid [v]
(if (uuid? v)
v
(d/parse-uuid v)))
(resolve-dest [dest]
(cond
(uuid? dest)
[dest]
(string? dest)
(some-> dest parse-uuid resolve-dest)
(nil? dest)
(resolve-dest uuid/zero)
(map? dest)
(sequence (comp
(map vec)
(mapcat resolve-dest))
dest)
(and (coll? dest)
(every? coll? dest))
(sequence (comp
(map vec)
(mapcat resolve-dest))
dest)
(vector? dest)
(let [[op param] dest]
(cond
(= op :email)
(cond
(and (coll? param)
(every? string? param))
(sequence (comp
(keep resolve-profile)
(mapcat identity))
param)
(string? param)
(resolve-profile param))
(= op :team-id)
(cond
(coll? param)
(sequence (comp
(mapcat resolve-team)
(keep parse-uuid))
param)
(uuid? param)
(resolve-team param)
(string? param)
(some-> param parse-uuid resolve-team))
(= op :profile-id)
(if (coll? param)
(sequence (keep parse-uuid) param)
(resolve-dest param))))))
]
(->> (resolve-dest dest)
(filter some?)
(into #{})
(run! send))))

View file

@ -1157,6 +1157,7 @@ input[type="range"]:focus::-ms-fill-upper {
.wrapper { .wrapper {
display: flex; display: flex;
align-items: center;
.icon { .icon {
padding: $size-2; padding: $size-2;
@ -1173,6 +1174,9 @@ input[type="range"]:focus::-ms-fill-upper {
padding: $size-2; padding: $size-2;
width: 100%; width: 100%;
align-items: center; align-items: center;
padding: 10px 15px;
min-height: 48px;
} }
} }

View file

@ -7,7 +7,10 @@
(ns app.main.data.common (ns app.main.data.common
"A general purpose events." "A general purpose events."
(:require (:require
[app.config :as cf]
[app.main.data.messages :as msg]
[app.main.repo :as rp] [app.main.repo :as rp]
[app.util.i18n :refer [tr]]
[beicon.core :as rx] [beicon.core :as rx]
[potok.core :as ptk])) [potok.core :as ptk]))
@ -43,3 +46,33 @@
(watch [_ _ _] (watch [_ _ _]
(->> (rp/cmd! :delete-share-link {:id id}) (->> (rp/cmd! :delete-share-link {:id id})
(rx/ignore))))) (rx/ignore)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; NOTIFICATIONS
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn force-reload!
[]
(.reload js/location))
(defn handle-notification
[{:keys [message code level] :as params}]
(ptk/reify ::show-notification
ptk/WatchEvent
(watch [_ _ _]
(case code
:upgrade-version
(when (or (not= (:version params) (:full cf/version))
(true? (:force params)))
(rx/of (msg/dialog
:content (tr "notifications.by-code.upgrade-version")
:controls :inline-actions
:type level
:actions [{:label "Refresh" :callback force-reload!}]
:tag :notification)))
(rx/of (msg/dialog
:content message
:controls :close
:type level
:tag :notification))))))

View file

@ -13,10 +13,12 @@
[app.common.uri :as u] [app.common.uri :as u]
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
[app.config :as cf] [app.config :as cf]
[app.main.data.common :refer [handle-notification]]
[app.main.data.events :as ev] [app.main.data.events :as ev]
[app.main.data.fonts :as df] [app.main.data.fonts :as df]
[app.main.data.media :as di] [app.main.data.media :as di]
[app.main.data.users :as du] [app.main.data.users :as du]
[app.main.data.websocket :as dws]
[app.main.features :as features] [app.main.features :as features]
[app.main.repo :as rp] [app.main.repo :as rp]
[app.util.dom :as dom] [app.util.dom :as dom]
@ -61,7 +63,23 @@
(ptk/watch (fetch-projects) state stream) (ptk/watch (fetch-projects) state stream)
(ptk/watch (fetch-team-members) state stream) (ptk/watch (fetch-team-members) state stream)
(ptk/watch (du/fetch-teams) state stream) (ptk/watch (du/fetch-teams) state stream)
(ptk/watch (du/fetch-users {:team-id id}) state stream))))) (ptk/watch (du/fetch-users {:team-id id}) state stream)
(let [stoper (rx/filter (ptk/type? ::finalize) stream)
profile-id (:profile-id state)]
(->> stream
(rx/filter (ptk/type? ::dws/message))
(rx/map deref)
(rx/filter (fn [{:keys [subs-id type] :as msg}]
(and (or (= subs-id uuid/zero)
(= subs-id profile-id))
(= :notification type))))
(rx/map handle-notification)
(rx/take-until stoper)))))))
(defn finalize
[params]
(ptk/data-event ::finalize params))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Data Fetching (context aware: current team) ;; Data Fetching (context aware: current team)

View file

@ -120,6 +120,17 @@
:position :fixed :position :fixed
:timeout timeout}))) :timeout timeout})))
(defn dialog
[& {:keys [content controls actions position tag type]
:or {controls :none position :floating type :info}}]
(show (d/without-nils
{:content content
:type type
:position position
:controls controls
:actions actions
:tag tag})))
(defn info-dialog (defn info-dialog
([content controls actions] ([content controls actions]
(info-dialog content controls actions nil)) (info-dialog content controls actions nil))

View file

@ -10,6 +10,8 @@
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.pages.changes :as cpc] [app.common.pages.changes :as cpc]
[app.common.schema :as sm] [app.common.schema :as sm]
[app.common.uuid :as uuid]
[app.main.data.common :refer [handle-notification]]
[app.main.data.websocket :as dws] [app.main.data.websocket :as dws]
[app.main.data.workspace.changes :as dch] [app.main.data.workspace.changes :as dch]
[app.main.data.workspace.libraries :as dwl] [app.main.data.workspace.libraries :as dwl]
@ -57,8 +59,9 @@
(rx/filter (ptk/type? ::dws/message)) (rx/filter (ptk/type? ::dws/message))
(rx/map deref) (rx/map deref)
(rx/filter (fn [{:keys [subs-id] :as msg}] (rx/filter (fn [{:keys [subs-id] :as msg}]
(or (= subs-id team-id) (or (= subs-id uuid/zero)
(= subs-id profile-id) (= subs-id profile-id)
(= subs-id team-id)
(= subs-id file-id)))) (= subs-id file-id))))
(rx/map process-message)) (rx/map process-message))
@ -96,6 +99,7 @@
:pointer-update (handle-pointer-update msg) :pointer-update (handle-pointer-update msg)
:file-change (handle-file-change msg) :file-change (handle-file-change msg)
:library-change (handle-library-change msg) :library-change (handle-library-change msg)
:notification (handle-notification msg)
nil)) nil))
(defn- handle-pointer-send (defn- handle-pointer-send

View file

@ -154,18 +154,18 @@
(hooks/use-shortcuts ::dashboard sc/shortcuts) (hooks/use-shortcuts ::dashboard sc/shortcuts)
(mf/with-effect [team-id] (mf/with-effect [team-id]
(st/emit! (dd/initialize {:id team-id}))) (st/emit! (dd/initialize {:id team-id}))
(fn []
(dd/finalize {:id team-id})))
(mf/use-effect (mf/with-effect []
(fn [] (let [key (events/listen goog/global "keydown"
(let [events [(events/listen goog/global "keydown" (fn [event]
(fn [event] (when (kbd/enter? event)
(when (kbd/enter? event) (dom/stop-propagation event)
(dom/stop-propagation event) (st/emit! (dd/open-selected-file)))))]
(st/emit! (dd/open-selected-file)))))]] (fn []
(fn [] (events/unlistenByKey key))))
(doseq [key events]
(events/unlistenByKey key))))))
[:& (mf/provider ctx/current-team-id) {:value team-id} [:& (mf/provider ctx/current-team-id) {:value team-id}
[:& (mf/provider ctx/current-project-id) {:value project-id} [:& (mf/provider ctx/current-project-id) {:value project-id}

View file

@ -4960,3 +4960,7 @@ msgstr "Marketing"
#: src/app/main/ui/onboarding/questions.cljs #: src/app/main/ui/onboarding/questions.cljs
msgid "questions.student-teacher" msgid "questions.student-teacher"
msgstr "Student or teacher" msgstr "Student or teacher"
#: src/app/main/data/common.cljs
msgid "notifications.by-code.upgrade-version"
msgstr "A new version is available, please refresh the page"