mirror of
https://github.com/penpot/penpot.git
synced 2026-01-03 03:48:46 -05:00
Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1eaf7b2b44 | ||
|
|
903f064e87 | ||
|
|
a23d1908e9 | ||
|
|
1e8226a3fc | ||
|
|
b7459726f5 | ||
|
|
b8179d0e35 | ||
|
|
e36b49b4f0 | ||
|
|
92ff5de538 | ||
|
|
c83d028466 | ||
|
|
56a0d522dc | ||
|
|
a3495800b5 | ||
|
|
750cf05784 | ||
|
|
1384219ae7 | ||
|
|
d2d9aeff25 | ||
|
|
95d80c9578 | ||
|
|
b523bef8ba | ||
|
|
0c5c04e58a | ||
|
|
a0973b9ddf | ||
|
|
c53b6117c0 | ||
|
|
bd3ddebcc4 | ||
|
|
0441f28880 | ||
|
|
288030888a | ||
|
|
203c0ed87d | ||
|
|
09e28076cd | ||
|
|
ad4e489312 | ||
|
|
50932dea54 | ||
|
|
da3c829b1b | ||
|
|
d4b4e6be7d |
14
CHANGES.md
14
CHANGES.md
@@ -1,5 +1,19 @@
|
||||
# CHANGELOG
|
||||
|
||||
## 1.19.3
|
||||
|
||||
### :sparkles: New features
|
||||
|
||||
- Remember last color mode in colorpicker [Taiga #5508](https://tree.taiga.io/project/penpot/issue/5508)
|
||||
- Improve layers multiselection behaviour [Github #5741](https://github.com/penpot/penpot/issues/5741)
|
||||
- Remember last active team across logouts / sessions [Github #3325](https://github.com/penpot/penpot/issues/3325)
|
||||
|
||||
### :bug: Bugs fixed
|
||||
|
||||
- List view is discarded on tab change on Workspace Assets Sidebar tab [Github #3547](https://github.com/penpot/penpot/issues/3547)
|
||||
- Fix message popup remains open when exiting workspace with browser back button [Taiga #5747](https://tree.taiga.io/project/penpot/issue/5747)
|
||||
- When editing text if font is changed, the proportions of the rendered shape are wrong [Taiga #5786](https://tree.taiga.io/project/penpot/issue/5786)
|
||||
|
||||
## 1.19.2
|
||||
|
||||
### :sparkles: New features
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
|
||||
<Logger name="app.rpc.commands.binfile" level="debug" />
|
||||
<Logger name="app.storage.tmp" level="debug" />
|
||||
<Logger name="app.worker" level="info" />
|
||||
<Logger name="app.worker" level="trace" />
|
||||
<Logger name="app.msgbus" level="info" />
|
||||
<Logger name="app.http.websocket" level="info" />
|
||||
<Logger name="app.util.websocket" level="info" />
|
||||
|
||||
@@ -8,14 +8,13 @@
|
||||
(:require
|
||||
[app.config :as cf]
|
||||
[buddy.hashers :as hashers]
|
||||
[cuerdas.core :as str]
|
||||
[promesa.exec :as px]))
|
||||
[cuerdas.core :as str]))
|
||||
|
||||
(def default-params
|
||||
{:alg :argon2id
|
||||
:memory (* 32768 2) ;; 64 MiB
|
||||
:iterations 7
|
||||
:parallelism (px/get-available-processors)})
|
||||
:memory 32768 ;; 32 MiB
|
||||
:iterations 3
|
||||
:parallelism 2})
|
||||
|
||||
(defn derive-password
|
||||
[password]
|
||||
|
||||
@@ -382,8 +382,8 @@
|
||||
|
||||
;; --- GENERAL PURPOSE DYNAMIC VARS
|
||||
|
||||
(def ^:dynamic *state*)
|
||||
(def ^:dynamic *options*)
|
||||
(def ^:dynamic *state* nil)
|
||||
(def ^:dynamic *options* nil)
|
||||
|
||||
;; --- EXPORT WRITER
|
||||
|
||||
@@ -773,7 +773,7 @@
|
||||
(defn- lookup-index
|
||||
[id]
|
||||
(let [val (get-in @*state* [:index id])]
|
||||
(l/debug :fn "lookup-index" :id id :val val ::l/sync? true)
|
||||
(l/trc :fn "lookup-index" :id id :val val ::l/sync? true)
|
||||
(when (and (not (::ignore-index-errors? *options*)) (not val))
|
||||
(ex/raise :type :validation
|
||||
:code :incomplete-index
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
[cuerdas.core :as str]
|
||||
[expound.alpha :as expound]))
|
||||
|
||||
(def ^:dynamic *conn*)
|
||||
(def ^:dynamic *conn* nil)
|
||||
|
||||
(defn reset-password!
|
||||
"Reset a password to a specific one for a concrete user or all users
|
||||
|
||||
@@ -251,53 +251,59 @@
|
||||
|
||||
(defmethod ig/init-key ::gc-deleted-task
|
||||
[_ {:keys [::db/pool ::storage ::min-age]}]
|
||||
(letfn [(retrieve-deleted-objects-chunk [conn min-age cursor]
|
||||
(let [min-age (db/interval min-age)
|
||||
rows (db/exec! conn [sql:retrieve-deleted-objects-chunk min-age cursor])]
|
||||
[(some-> rows peek :created-at)
|
||||
(letfn [(get-to-delete-chunk [cursor]
|
||||
(let [sql (str "select s.* "
|
||||
" from storage_object as s "
|
||||
" where s.deleted_at is not null "
|
||||
" and s.deleted_at < ? "
|
||||
" order by s.deleted_at desc "
|
||||
" limit 25")
|
||||
rows (db/exec! pool [sql cursor])]
|
||||
[(some-> rows peek :deleted-at)
|
||||
(some->> (seq rows) (d/group-by #(-> % :backend keyword) :id #{}) seq)]))
|
||||
|
||||
(retrieve-deleted-objects [conn min-age]
|
||||
(d/iteration (partial retrieve-deleted-objects-chunk conn min-age)
|
||||
:initk (dt/now)
|
||||
(get-to-delete-chunks [min-age]
|
||||
(d/iteration get-to-delete-chunk
|
||||
:initk (dt/minus (dt/now) min-age)
|
||||
:vf second
|
||||
:kf first))
|
||||
|
||||
(delete-in-bulk [backend-id ids]
|
||||
(let [backend (impl/resolve-backend storage backend-id)]
|
||||
(delete-in-bulk! [backend-id ids]
|
||||
(try
|
||||
(db/with-atomic [conn pool]
|
||||
(let [sql "delete from storage_object where id = ANY(?)"
|
||||
ids' (db/create-array conn "uuid" ids)
|
||||
|
||||
(doseq [id ids]
|
||||
(l/debug :hint "gc-deleted: permanently delete storage object" :backend backend-id :id id))
|
||||
total (-> (db/exec-one! conn [sql ids'])
|
||||
(db/get-update-count))]
|
||||
|
||||
(impl/del-objects-in-bulk backend ids)))]
|
||||
(-> (impl/resolve-backend storage backend-id)
|
||||
(impl/del-objects-in-bulk ids))
|
||||
|
||||
(doseq [id ids]
|
||||
(l/dbg :hint "gc-deleted: permanently delete storage object" :backend backend-id :id id))
|
||||
|
||||
total))
|
||||
|
||||
(catch Throwable cause
|
||||
(l/err :hint "gc-deleted: unexpected error on bulk deletion"
|
||||
:ids (vec ids)
|
||||
:cause cause)
|
||||
0)))]
|
||||
|
||||
(fn [params]
|
||||
(let [min-age (or (:min-age params) min-age)]
|
||||
(db/with-atomic [conn pool]
|
||||
(loop [total 0
|
||||
groups (retrieve-deleted-objects conn min-age)]
|
||||
(if-let [[backend-id ids] (first groups)]
|
||||
(do
|
||||
(delete-in-bulk backend-id ids)
|
||||
(recur (+ total (count ids))
|
||||
(rest groups)))
|
||||
(do
|
||||
(l/info :hint "gc-deleted: task finished" :min-age (dt/format-duration min-age) :total total)
|
||||
{:deleted total}))))))))
|
||||
|
||||
(def sql:retrieve-deleted-objects-chunk
|
||||
"with items_part as (
|
||||
select s.id
|
||||
from storage_object as s
|
||||
where s.deleted_at is not null
|
||||
and s.deleted_at < (now() - ?::interval)
|
||||
and s.created_at < ?
|
||||
order by s.created_at desc
|
||||
limit 25
|
||||
)
|
||||
delete from storage_object
|
||||
where id in (select id from items_part)
|
||||
returning *;")
|
||||
(let [min-age (or (some-> params :min-age dt/duration) min-age)]
|
||||
(loop [total 0
|
||||
chunks (get-to-delete-chunks min-age)]
|
||||
(if-let [[backend-id ids] (first chunks)]
|
||||
(let [deleted (delete-in-bulk! backend-id ids)]
|
||||
(recur (+ total deleted)
|
||||
(rest chunks)))
|
||||
(do
|
||||
(l/inf :hint "gc-deleted: task finished"
|
||||
:min-age (dt/format-duration min-age)
|
||||
:total total)
|
||||
{:deleted total})))))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Garbage Collection: Analyze touched objects
|
||||
|
||||
@@ -87,10 +87,10 @@
|
||||
|
||||
(defmethod ig/init-key ::registry
|
||||
[_ {:keys [::mtx/metrics ::tasks]}]
|
||||
(l/info :hint "registry initialized" :tasks (count tasks))
|
||||
(l/inf :hint "registry initialized" :tasks (count tasks))
|
||||
(reduce-kv (fn [registry k v]
|
||||
(let [tname (name k)]
|
||||
(l/trace :hint "register task" :name tname)
|
||||
(l/trc :hint "register task" :name tname)
|
||||
(assoc registry tname (wrap-task-handler metrics tname v))))
|
||||
{}
|
||||
tasks))
|
||||
@@ -141,18 +141,18 @@
|
||||
|
||||
(px/thread
|
||||
{:name "penpot/executors-monitor" :virtual true}
|
||||
(l/info :hint "monitor: started" :name name)
|
||||
(l/inf :hint "monitor: started" :name name)
|
||||
(try
|
||||
(loop [steals 0]
|
||||
(when-not (px/shutdown? executor)
|
||||
(px/sleep interval)
|
||||
(recur (long (monitor! executor steals)))))
|
||||
(catch InterruptedException _cause
|
||||
(l/debug :hint "monitor: interrupted" :name name))
|
||||
(l/trc :hint "monitor: interrupted" :name name))
|
||||
(catch Throwable cause
|
||||
(l/error :hint "monitor: unexpected error" :name name :cause cause))
|
||||
(l/err :hint "monitor: unexpected error" :name name :cause cause))
|
||||
(finally
|
||||
(l/info :hint "monitor: terminated" :name name))))))
|
||||
(l/inf :hint "monitor: terminated" :name name))))))
|
||||
|
||||
(defmethod ig/halt-key! ::monitor
|
||||
[_ thread]
|
||||
@@ -207,10 +207,10 @@
|
||||
(db/create-array conn "uuid" ids)]]
|
||||
|
||||
(db/exec-one! conn sql)
|
||||
(l/debug :hist "dispatcher: queue tasks"
|
||||
:queue queue
|
||||
:tasks (count ids)
|
||||
:queued res)))
|
||||
(l/trc :hist "dispatcher: queue tasks"
|
||||
:queue queue
|
||||
:tasks (count ids)
|
||||
:queued res)))
|
||||
|
||||
(run-batch! [rconn]
|
||||
(try
|
||||
@@ -225,35 +225,35 @@
|
||||
(cond
|
||||
(rds/exception? cause)
|
||||
(do
|
||||
(l/warn :hint "dispatcher: redis exception (will retry in an instant)" :cause cause)
|
||||
(l/wrn :hint "dispatcher: redis exception (will retry in an instant)" :cause cause)
|
||||
(px/sleep (::rds/timeout rconn)))
|
||||
|
||||
(db/sql-exception? cause)
|
||||
(do
|
||||
(l/warn :hint "dispatcher: database exception (will retry in an instant)" :cause cause)
|
||||
(l/wrn :hint "dispatcher: database exception (will retry in an instant)" :cause cause)
|
||||
(px/sleep (::rds/timeout rconn)))
|
||||
|
||||
:else
|
||||
(do
|
||||
(l/error :hint "dispatcher: unhandled exception (will retry in an instant)" :cause cause)
|
||||
(l/err :hint "dispatcher: unhandled exception (will retry in an instant)" :cause cause)
|
||||
(px/sleep (::rds/timeout rconn)))))))
|
||||
|
||||
(dispatcher []
|
||||
(l/info :hint "dispatcher: started")
|
||||
(l/inf :hint "dispatcher: started")
|
||||
(try
|
||||
(dm/with-open [rconn (rds/connect redis)]
|
||||
(loop []
|
||||
(run-batch! rconn)
|
||||
(recur)))
|
||||
(catch InterruptedException _
|
||||
(l/trace :hint "dispatcher: interrupted"))
|
||||
(l/trc :hint "dispatcher: interrupted"))
|
||||
(catch Throwable cause
|
||||
(l/error :hint "dispatcher: unexpected exception" :cause cause))
|
||||
(l/err :hint "dispatcher: unexpected exception" :cause cause))
|
||||
(finally
|
||||
(l/info :hint "dispatcher: terminated"))))]
|
||||
(l/inf :hint "dispatcher: terminated"))))]
|
||||
|
||||
(if (db/read-only? pool)
|
||||
(l/warn :hint "dispatcher: not started (db is read-only)")
|
||||
(l/wrn :hint "dispatcher: not started (db is read-only)")
|
||||
(px/fn->thread dispatcher :name "penpot/worker/dispatcher" :virtual true))))
|
||||
|
||||
(defmethod ig/halt-key! ::dispatcher
|
||||
@@ -286,7 +286,7 @@
|
||||
(let [queue (d/name queue)
|
||||
cfg (assoc cfg ::queue queue)]
|
||||
(if (db/read-only? pool)
|
||||
(l/warn :hint "worker: not started (db is read-only)" :queue queue :parallelism parallelism)
|
||||
(l/wrn :hint "worker: not started (db is read-only)" :queue queue :parallelism parallelism)
|
||||
(doall
|
||||
(->> (range parallelism)
|
||||
(map #(assoc cfg ::worker-id %))
|
||||
@@ -300,7 +300,7 @@
|
||||
[{:keys [::rds/redis ::worker-id ::queue] :as cfg}]
|
||||
(px/thread
|
||||
{:name (format "penpot/worker/runner:%s" worker-id)}
|
||||
(l/info :hint "worker: started" :worker-id worker-id :queue queue)
|
||||
(l/inf :hint "worker: started" :worker-id worker-id :queue queue)
|
||||
(try
|
||||
(dm/with-open [rconn (rds/connect redis)]
|
||||
(let [tenant (cf/get :tenant "main")
|
||||
@@ -320,14 +320,14 @@
|
||||
:worker-id worker-id
|
||||
:queue queue))
|
||||
(catch Throwable cause
|
||||
(l/error :hint "worker: unexpected exception"
|
||||
:worker-id worker-id
|
||||
:queue queue
|
||||
:cause cause))
|
||||
(l/err :hint "worker: unexpected exception"
|
||||
:worker-id worker-id
|
||||
:queue queue
|
||||
:cause cause))
|
||||
(finally
|
||||
(l/info :hint "worker: terminated"
|
||||
:worker-id worker-id
|
||||
:queue queue)))))
|
||||
(l/inf :hint "worker: terminated"
|
||||
:worker-id worker-id
|
||||
:queue queue)))))
|
||||
|
||||
(defn- run-worker-loop!
|
||||
[{:keys [::db/pool ::rds/rconn ::timeout ::queue ::registry ::worker-id]}]
|
||||
@@ -368,19 +368,19 @@
|
||||
(let [task-id (t/decode payload)]
|
||||
(if (uuid? task-id)
|
||||
task-id
|
||||
(l/error :hint "worker: received unexpected payload (uuid expected)"
|
||||
:payload task-id)))
|
||||
(l/err :hint "worker: received unexpected payload (uuid expected)"
|
||||
:payload task-id)))
|
||||
(catch Throwable cause
|
||||
(l/error :hint "worker: unable to decode payload"
|
||||
:payload payload
|
||||
:length (alength payload)
|
||||
:cause cause))))
|
||||
(l/err :hint "worker: unable to decode payload"
|
||||
:payload payload
|
||||
:length (alength payload)
|
||||
:cause cause))))
|
||||
|
||||
(handle-task [{:keys [name] :as task}]
|
||||
(let [task-fn (get registry name)]
|
||||
(if task-fn
|
||||
(task-fn task)
|
||||
(l/warn :hint "no task handler found" :name name))
|
||||
(l/wrn :hint "no task handler found" :name name))
|
||||
{:status :completed :task task}))
|
||||
|
||||
(handle-task-exception [cause task]
|
||||
@@ -395,9 +395,9 @@
|
||||
(= ::noop (:strategy edata))
|
||||
(assoc :inc-by 0))
|
||||
(do
|
||||
(l/error :hint "worker: unhandled exception on task"
|
||||
::l/context (get-error-context cause task)
|
||||
:cause cause)
|
||||
(l/err :hint "worker: unhandled exception on task"
|
||||
::l/context (get-error-context cause task)
|
||||
:cause cause)
|
||||
(if (>= (:retry-num task) (:max-retries task))
|
||||
{:status :failed :task task :error cause}
|
||||
{:status :retry :task task :error cause})))))
|
||||
@@ -414,31 +414,31 @@
|
||||
(if (or (db/connection-error? task)
|
||||
(db/serialization-error? task))
|
||||
(do
|
||||
(l/warn :hint "worker: connection error on retrieving task from database (retrying in some instants)"
|
||||
:worker-id worker-id
|
||||
:cause task)
|
||||
(l/wrn :hint "worker: connection error on retrieving task from database (retrying in some instants)"
|
||||
:worker-id worker-id
|
||||
:cause task)
|
||||
(px/sleep (::rds/timeout rconn))
|
||||
(recur (get-task task-id)))
|
||||
(do
|
||||
(l/error :hint "worker: unhandled exception on retrieving task from database (retrying in some instants)"
|
||||
:worker-id worker-id
|
||||
:cause task)
|
||||
(l/err :hint "worker: unhandled exception on retrieving task from database (retrying in some instants)"
|
||||
:worker-id worker-id
|
||||
:cause task)
|
||||
(px/sleep (::rds/timeout rconn))
|
||||
(recur (get-task task-id))))
|
||||
|
||||
(nil? task)
|
||||
(l/warn :hint "worker: no task found on the database"
|
||||
:worker-id worker-id
|
||||
:task-id task-id)
|
||||
(l/wrn :hint "worker: no task found on the database"
|
||||
:worker-id worker-id
|
||||
:task-id task-id)
|
||||
|
||||
:else
|
||||
(try
|
||||
(l/debug :hint "worker: executing task"
|
||||
:name (:name task)
|
||||
:id (:id task)
|
||||
:queue queue
|
||||
:worker-id worker-id
|
||||
:retry (:retry-num task))
|
||||
(l/trc :hint "executing task"
|
||||
:name (:name task)
|
||||
:id (str (:id task))
|
||||
:queue queue
|
||||
:worker-id worker-id
|
||||
:retry (:retry-num task))
|
||||
(handle-task task)
|
||||
(catch InterruptedException cause
|
||||
(throw cause))
|
||||
@@ -459,13 +459,13 @@
|
||||
(if (or (db/connection-error? cause)
|
||||
(db/serialization-error? cause))
|
||||
(do
|
||||
(l/warn :hint "worker: database exeption on processing task result (retrying in some instants)"
|
||||
:cause cause)
|
||||
(l/wrn :hint "worker: database exeption on processing task result (retrying in some instants)"
|
||||
:cause cause)
|
||||
(px/sleep (::rds/timeout rconn))
|
||||
(recur result))
|
||||
(do
|
||||
(l/error :hint "worker: unhandled exception on processing task result (retrying in some instants)"
|
||||
:cause cause)
|
||||
(l/err :hint "worker: unhandled exception on processing task result (retrying in some instants)"
|
||||
:cause cause)
|
||||
(px/sleep (::rds/timeout rconn))
|
||||
(recur result))))))]
|
||||
|
||||
@@ -481,12 +481,12 @@
|
||||
(catch Exception cause
|
||||
(if (rds/timeout-exception? cause)
|
||||
(do
|
||||
(l/error :hint "worker: redis pop operation timeout, consider increasing redis timeout (will retry in some instants)"
|
||||
:timeout timeout
|
||||
:cause cause)
|
||||
(l/err :hint "worker: redis pop operation timeout, consider increasing redis timeout (will retry in some instants)"
|
||||
:timeout timeout
|
||||
:cause cause)
|
||||
(px/sleep timeout))
|
||||
|
||||
(l/error :hint "worker: unhandled exception" :cause cause))))))
|
||||
(l/err :hint "worker: unhandled exception" :cause cause))))))
|
||||
|
||||
(defn- get-error-context
|
||||
[_ item]
|
||||
@@ -517,7 +517,7 @@
|
||||
(defmethod ig/init-key ::cron
|
||||
[_ {:keys [::entries ::registry ::db/pool] :as cfg}]
|
||||
(if (db/read-only? pool)
|
||||
(l/warn :hint "cron: not started (db is read-only)")
|
||||
(l/wrn :hint "cron: not started (db is read-only)")
|
||||
(let [running (atom #{})
|
||||
entries (->> entries
|
||||
(filter some?)
|
||||
@@ -540,22 +540,22 @@
|
||||
|
||||
cfg (assoc cfg ::entries entries ::running running)]
|
||||
|
||||
(l/info :hint "cron: started" :tasks (count entries))
|
||||
(synchronize-cron-entries! cfg)
|
||||
(l/inf :hint "cron: started" :tasks (count entries))
|
||||
(synchronize-cron-entries! cfg)
|
||||
|
||||
(->> (filter some? entries)
|
||||
(run! (partial schedule-cron-task cfg)))
|
||||
(->> (filter some? entries)
|
||||
(run! (partial schedule-cron-task cfg)))
|
||||
|
||||
(reify
|
||||
clojure.lang.IDeref
|
||||
(deref [_] @running)
|
||||
(reify
|
||||
clojure.lang.IDeref
|
||||
(deref [_] @running)
|
||||
|
||||
java.lang.AutoCloseable
|
||||
(close [_]
|
||||
(l/info :hint "cron: terminated")
|
||||
(doseq [item @running]
|
||||
(when-not (.isDone ^Future item)
|
||||
(.cancel ^Future item true))))))))
|
||||
java.lang.AutoCloseable
|
||||
(close [_]
|
||||
(l/inf :hint "cron: terminated")
|
||||
(doseq [item @running]
|
||||
(when-not (.isDone ^Future item)
|
||||
(.cancel ^Future item true))))))))
|
||||
|
||||
(defmethod ig/halt-key! ::cron
|
||||
[_ instance]
|
||||
@@ -571,7 +571,7 @@
|
||||
[{:keys [::db/pool ::entries]}]
|
||||
(db/with-atomic [conn pool]
|
||||
(doseq [{:keys [id cron]} entries]
|
||||
(l/trace :hint "register cron task" :id id :cron (str cron))
|
||||
(l/trc :hint "register cron task" :id id :cron (str cron))
|
||||
(db/exec-one! conn [sql:upsert-cron-task id (str cron) (str cron)]))))
|
||||
|
||||
(defn- lock-scheduled-task!
|
||||
@@ -589,7 +589,7 @@
|
||||
(db/exec-one! conn ["SET statement_timeout=0;"])
|
||||
(db/exec-one! conn ["SET idle_in_transaction_session_timeout=0;"])
|
||||
(when (lock-scheduled-task! conn id)
|
||||
(l/trace :hint "cron: execute task" :task-id id)
|
||||
(l/dbg :hint "cron: execute task" :task-id id)
|
||||
((:fn task) task))
|
||||
(db/rollback! conn))
|
||||
|
||||
@@ -598,9 +598,9 @@
|
||||
|
||||
(catch Throwable cause
|
||||
(binding [l/*context* (get-error-context cause task)]
|
||||
(l/error :hint "cron: unhandled exception on running task"
|
||||
:task-id id
|
||||
:cause cause)))
|
||||
(l/err :hint "cron: unhandled exception on running task"
|
||||
:task-id id
|
||||
:cause cause)))
|
||||
(finally
|
||||
(when-not (px/interrupted? :current)
|
||||
(schedule-cron-task cfg task))))))
|
||||
@@ -610,12 +610,16 @@
|
||||
(s/assert dt/cron? cron)
|
||||
(let [now (dt/now)
|
||||
next (dt/next-valid-instant-from cron now)]
|
||||
(inst-ms (dt/diff now next))))
|
||||
(dt/diff now next)))
|
||||
|
||||
(defn- schedule-cron-task
|
||||
[{:keys [::running] :as cfg} {:keys [cron] :as task}]
|
||||
(let [ft (px/schedule! (ms-until-valid cron)
|
||||
(partial execute-cron-task cfg task))]
|
||||
[{:keys [::running] :as cfg} {:keys [cron id] :as task}]
|
||||
(let [ts (ms-until-valid cron)
|
||||
ft (px/schedule! ts (partial execute-cron-task cfg task))]
|
||||
|
||||
(l/dbg :hint "cron: schedule task" :task-id id
|
||||
:ts (dt/format-duration ts)
|
||||
:at (dt/format-instant (dt/in-future ts)))
|
||||
(swap! running #(into #{ft} (filter p/pending?) %))))
|
||||
|
||||
|
||||
@@ -678,13 +682,13 @@
|
||||
(-> (db/exec-one! conn [sql:remove-not-started-tasks task queue label])
|
||||
:next.jdbc/update-count))]
|
||||
|
||||
(l/debug :hint "submit task"
|
||||
:name task
|
||||
:queue queue
|
||||
:label label
|
||||
:dedupe (boolean dedupe)
|
||||
:deleted (or deleted 0)
|
||||
:in (dt/format-duration duration))
|
||||
(l/trc :hint "submit task"
|
||||
:name task
|
||||
:queue queue
|
||||
:label label
|
||||
:dedupe (boolean dedupe)
|
||||
:deleted (or deleted 0)
|
||||
:in (dt/format-duration duration))
|
||||
|
||||
(db/exec-one! conn [sql:insert-new-task id task props queue
|
||||
label priority max-retries interval])
|
||||
|
||||
@@ -100,6 +100,7 @@
|
||||
(configure-storage-backend))
|
||||
content1 (sto/content "content1")
|
||||
content2 (sto/content "content2")
|
||||
content3 (sto/content "content3")
|
||||
object1 (sto/put-object! storage {::sto/content content1
|
||||
::sto/expired-at (dt/now)
|
||||
:content-type "text/plain"
|
||||
@@ -107,16 +108,20 @@
|
||||
object2 (sto/put-object! storage {::sto/content content2
|
||||
::sto/expired-at (dt/in-past {:hours 2})
|
||||
:content-type "text/plain"
|
||||
})
|
||||
object3 (sto/put-object! storage {::sto/content content3
|
||||
::sto/expired-at (dt/in-past {:hours 1})
|
||||
:content-type "text/plain"
|
||||
})]
|
||||
|
||||
|
||||
(th/sleep 200)
|
||||
|
||||
(let [task (:app.storage/gc-deleted-task th/*system*)
|
||||
res (task {})]
|
||||
(let [res (th/run-task! :storage-gc-deleted {})]
|
||||
(t/is (= 1 (:deleted res))))
|
||||
|
||||
(let [res (db/exec-one! th/*pool* ["select count(*) from storage_object;"])]
|
||||
(t/is (= 1 (:count res))))))
|
||||
(t/is (= 2 (:count res))))))
|
||||
|
||||
(t/deftest test-touched-gc-task-1
|
||||
(let [storage (-> (:app.storage/storage th/*system*)
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.geom.shapes.path :as gsp]
|
||||
[app.common.geom.shapes.text :as gsht]
|
||||
[app.common.logging :as log]
|
||||
[app.common.logging :as l]
|
||||
[app.common.math :as mth]
|
||||
[app.common.pages :as cp]
|
||||
[app.common.pages.helpers :as cph]
|
||||
@@ -25,7 +25,7 @@
|
||||
|
||||
(defmulti migrate :version)
|
||||
|
||||
(log/set-level! :info)
|
||||
#?(:cljs (l/set-level! :info))
|
||||
|
||||
(defn migrate-data
|
||||
([data] (migrate-data data cp/file-version))
|
||||
@@ -33,7 +33,7 @@
|
||||
(if (= (:version data) to-version)
|
||||
data
|
||||
(let [migrate-fn #(do
|
||||
(log/trace :hint "migrate file" :id (:id %) :version-from %2 :version-to (inc %2))
|
||||
(l/trc :hint "migrate file" :id (:id %) :version-from %2 :version-to (inc %2))
|
||||
(migrate (assoc %1 :version (inc %2))))]
|
||||
(reduce migrate-fn data (range (:version data 0) to-version))))))
|
||||
|
||||
@@ -450,11 +450,11 @@
|
||||
;; If we cannot find any we let the frame-id as it was before
|
||||
frame-id)]
|
||||
(when (not= frame-id calculated-frame-id)
|
||||
(log/info :hint "Fix wrong frame-id"
|
||||
:shape (:name object)
|
||||
:id (:id object)
|
||||
:current (dm/get-in objects [frame-id :name])
|
||||
:calculated (get-in objects [calculated-frame-id :name])))
|
||||
(l/trc :hint "Fix wrong frame-id"
|
||||
:shape (:name object)
|
||||
:id (:id object)
|
||||
:current (dm/get-in objects [frame-id :name])
|
||||
:calculated (get-in objects [calculated-frame-id :name])))
|
||||
(assoc object :frame-id calculated-frame-id)))
|
||||
|
||||
(update-container [container]
|
||||
|
||||
@@ -279,8 +279,9 @@
|
||||
(assoc-in (conj path :position) (:position comment-thread))
|
||||
(assoc-in (conj path :frame-id) (:frame-id comment-thread))))))
|
||||
(fetched [[users comments] state]
|
||||
(let [pages (get-in state [:workspace-data :pages-index])
|
||||
comments (filter #(some? (get pages (:page-id %))) comments)
|
||||
(let [pages (-> (get-in state [:workspace-data :pages])
|
||||
set)
|
||||
comments (filter #(contains? pages (:page-id %)) comments)
|
||||
state (-> state
|
||||
(assoc :comment-threads (d/index-by :id comments))
|
||||
(update :current-file-comments-users merge (d/index-by :id users)))]
|
||||
|
||||
@@ -59,10 +59,11 @@
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(rx/merge
|
||||
;;fetch teams must be first in case the team doesn't exist
|
||||
(ptk/watch (du/fetch-teams) state stream)
|
||||
(ptk/watch (df/load-team-fonts id) state stream)
|
||||
(ptk/watch (fetch-projects) state stream)
|
||||
(ptk/watch (fetch-team-members) state stream)
|
||||
(ptk/watch (du/fetch-teams) state stream)
|
||||
(ptk/watch (du/fetch-users {:team-id id}) state stream)
|
||||
|
||||
(let [stoper (rx/filter (ptk/type? ::finalize) stream)
|
||||
|
||||
@@ -125,7 +125,7 @@
|
||||
accepting invitation, or third party auth signup or singin."
|
||||
[profile]
|
||||
(letfn [(get-redirect-event []
|
||||
(let [team-id (:default-team-id profile)
|
||||
(let [team-id (get-current-team-id profile)
|
||||
redirect-url (:redirect-url @storage)]
|
||||
(if (some? redirect-url)
|
||||
(do
|
||||
@@ -247,7 +247,9 @@
|
||||
|
||||
ptk/EffectEvent
|
||||
(effect [_ _ _]
|
||||
(reset! storage {})
|
||||
;; We prefer to keek some stuff in the storage like the current-team-id
|
||||
(swap! storage dissoc :redirect-url)
|
||||
(swap! storage dissoc :profile)
|
||||
(i18n/reset-locale)))))
|
||||
|
||||
(defn logout
|
||||
|
||||
28
frontend/src/app/main/data/workspace/assets.cljs
Normal file
28
frontend/src/app/main/data/workspace/assets.cljs
Normal file
@@ -0,0 +1,28 @@
|
||||
;; 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) KALEIDOS INC
|
||||
|
||||
(ns app.main.data.workspace.assets
|
||||
"Workspace assets management events and helpers."
|
||||
(:require
|
||||
[app.util.storage :refer [storage]]))
|
||||
|
||||
(defn get-current-assets-ordering
|
||||
[]
|
||||
(let [ordering (::ordering @storage)]
|
||||
(or ordering :asc)))
|
||||
|
||||
(defn set-current-assets-ordering!
|
||||
[ordering]
|
||||
(swap! storage assoc ::ordering ordering))
|
||||
|
||||
(defn get-current-assets-list-style
|
||||
[]
|
||||
(let [list-style (::list-style @storage)]
|
||||
(or list-style :thumbs)))
|
||||
|
||||
(defn set-current-assets-list-style!
|
||||
[list-style]
|
||||
(swap! storage assoc ::list-style list-style))
|
||||
@@ -22,6 +22,7 @@
|
||||
[app.main.data.workspace.texts :as dwt]
|
||||
[app.main.data.workspace.undo :as dwu]
|
||||
[app.util.color :as uc]
|
||||
[app.util.storage :refer [storage]]
|
||||
[beicon.core :as rx]
|
||||
[potok.core :as ptk]))
|
||||
|
||||
@@ -647,3 +648,12 @@
|
||||
:position :right})
|
||||
(ptk/event ::ev/event {::ev/name "add-asset-to-library"
|
||||
:asset-type "color"}))))))
|
||||
|
||||
(defn get-active-color-tab
|
||||
[]
|
||||
(let [tab (::tab @storage)]
|
||||
(or tab :ramp)))
|
||||
|
||||
(defn set-active-color-tab!
|
||||
[tab]
|
||||
(swap! storage assoc ::tab tab))
|
||||
|
||||
@@ -122,7 +122,9 @@
|
||||
(ptk/reify ::select-shape
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update-in state [:workspace-local :selected] d/toggle-selection id toggle?))
|
||||
(-> state
|
||||
(update-in [:workspace-local :selected] d/toggle-selection id toggle?)
|
||||
(assoc-in [:workspace-local :last-selected] id)))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
@@ -185,7 +187,9 @@
|
||||
(ptk/reify ::deselect-shape
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update-in state [:workspace-local :selected] disj id))))
|
||||
(-> state
|
||||
(update-in [:workspace-local :selected] disj id)
|
||||
(update :workspace-local dissoc :last-selected)))))
|
||||
|
||||
(defn shift-select-shapes
|
||||
([id]
|
||||
@@ -196,12 +200,14 @@
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [objects (or objects (wsh/lookup-page-objects state))
|
||||
append-to-selection (cph/expand-region-selection objects (into #{} [(get-in state [:workspace-local :last-selected]) id]))
|
||||
selection (-> state
|
||||
wsh/lookup-selected
|
||||
(conj id))]
|
||||
(-> state
|
||||
(assoc-in [:workspace-local :selected]
|
||||
(cph/expand-region-selection objects selection))))))))
|
||||
(set/union selection append-to-selection))
|
||||
(update :workspace-local assoc :last-selected id)))))))
|
||||
|
||||
(defn select-shapes
|
||||
[ids]
|
||||
|
||||
@@ -153,8 +153,10 @@
|
||||
|
||||
(hooks/use-shortcuts ::dashboard sc/shortcuts)
|
||||
|
||||
(mf/with-effect [team-id]
|
||||
(st/emit! (dd/initialize {:id team-id}))
|
||||
(mf/with-effect [profile team-id]
|
||||
(when profile
|
||||
;; When doing logout we must avoid reinitializing the dashboard
|
||||
(st/emit! (dd/initialize {:id team-id})))
|
||||
(fn []
|
||||
(dd/finalize {:id team-id})))
|
||||
|
||||
|
||||
@@ -230,8 +230,8 @@
|
||||
|
||||
]
|
||||
|
||||
(mf/with-effect [collapsed]
|
||||
(when-not collapsed
|
||||
(mf/with-effect [profile collapsed]
|
||||
(when (and profile (not collapsed))
|
||||
(st/emit! (dd/fetch-builtin-templates))))
|
||||
|
||||
[:div.dashboard-templates-section
|
||||
|
||||
@@ -8,8 +8,10 @@
|
||||
(:require-macros [app.main.style :refer [css]])
|
||||
(:require
|
||||
[app.common.data.macros :as dm]
|
||||
[app.main.data.messages :as msg]
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.data.workspace.colors :as dc]
|
||||
[app.main.data.workspace.persistence :as dwp]
|
||||
[app.main.features :as features]
|
||||
[app.main.refs :as refs]
|
||||
@@ -185,6 +187,9 @@
|
||||
(st/emit! (dw/initialize-file project-id file-id))
|
||||
(fn []
|
||||
(st/emit! ::dwp/force-persist
|
||||
(dc/stop-picker)
|
||||
(modal/hide)
|
||||
msg/hide
|
||||
(dw/finalize-file project-id file-id))))
|
||||
|
||||
[:& (mf/provider ctx/current-file-id) {:value file-id}
|
||||
|
||||
@@ -56,12 +56,17 @@
|
||||
|
||||
current-color (:current-color state)
|
||||
|
||||
active-tab (mf/use-state :ramp #_:harmony #_:hsva)
|
||||
set-ramp-tab! (mf/use-fn #(reset! active-tab :ramp))
|
||||
set-harmony-tab! (mf/use-fn #(reset! active-tab :harmony))
|
||||
set-hsva-tab! (mf/use-fn #(reset! active-tab :hsva))
|
||||
|
||||
active-tab (mf/use-state (dc/get-active-color-tab))
|
||||
drag? (mf/use-state false)
|
||||
|
||||
set-tab!
|
||||
(mf/use-fn
|
||||
(fn [event]
|
||||
(let [tab (-> (dom/get-current-target event)
|
||||
(dom/get-data "tab")
|
||||
(keyword))]
|
||||
(reset! active-tab tab)
|
||||
(dc/set-active-color-tab! tab))))
|
||||
|
||||
handle-change-color
|
||||
(mf/use-fn
|
||||
@@ -81,9 +86,9 @@
|
||||
(fn []
|
||||
(if picking-color?
|
||||
(do (modal/disallow-click-outside!)
|
||||
(st/emit! (dc/stop-picker)))
|
||||
(st/emit! (dc/stop-picker)))
|
||||
(do (modal/allow-click-outside!)
|
||||
(st/emit! (dc/start-picker))))))
|
||||
(st/emit! (dc/start-picker))))))
|
||||
|
||||
handle-change-stop
|
||||
(mf/use-fn
|
||||
@@ -225,15 +230,18 @@
|
||||
[:div.colorpicker-tab.tooltip.tooltip-bottom.tooltip-expand
|
||||
{:class (when (= @active-tab :ramp) "active")
|
||||
:alt (tr "workspace.libraries.colors.rgba")
|
||||
:on-click set-ramp-tab!} i/picker-ramp]
|
||||
:on-click set-tab!
|
||||
:data-tab "ramp"} i/picker-ramp]
|
||||
[:div.colorpicker-tab.tooltip.tooltip-bottom.tooltip-expand
|
||||
{:class (when (= @active-tab :harmony) "active")
|
||||
:alt (tr "workspace.libraries.colors.rgb-complementary")
|
||||
:on-click set-harmony-tab!} i/picker-harmony]
|
||||
:on-click set-tab!
|
||||
:data-tab "harmony"} i/picker-harmony]
|
||||
[:div.colorpicker-tab.tooltip.tooltip-bottom.tooltip-expand
|
||||
{:class (when (= @active-tab :hsva) "active")
|
||||
:alt (tr "workspace.libraries.colors.hsv")
|
||||
:on-click set-hsva-tab!} i/picker-hsv]]
|
||||
:on-click set-tab!
|
||||
:data-tab "hsva"} i/picker-hsv]]
|
||||
|
||||
(if picking-color?
|
||||
[:div.picker-detail-wrapper
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
[app.main.data.exports :as de]
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.data.workspace.colors :as dc]
|
||||
[app.main.data.workspace.common :as dwc]
|
||||
[app.main.data.workspace.libraries :as dwl]
|
||||
[app.main.data.workspace.shortcuts :as sc]
|
||||
@@ -162,9 +161,7 @@
|
||||
(st/emit! (ptk/event ::ev/event {::ev/name "show-release-notes" :version version}))
|
||||
(if (and (kbd/alt? event) (kbd/mod? event))
|
||||
(st/emit! (modal/show {:type :onboarding}))
|
||||
(st/emit! (modal/show {:type :release-notes :version version}))))))
|
||||
|
||||
]
|
||||
(st/emit! (modal/show {:type :release-notes :version version}))))))]
|
||||
|
||||
[:& dropdown {:show true :on-close on-close}
|
||||
[:ul.sub-menu.help-info
|
||||
@@ -582,16 +579,10 @@
|
||||
(dom/prevent-default event)
|
||||
(reset! editing* true)))
|
||||
|
||||
close-modals
|
||||
(mf/use-fn
|
||||
#(st/emit! (dc/stop-picker)
|
||||
(modal/hide)))
|
||||
|
||||
go-back
|
||||
(mf/use-fn
|
||||
(mf/deps project)
|
||||
(fn []
|
||||
(close-modals)
|
||||
(st/emit! (dw/go-to-dashboard project))))
|
||||
|
||||
nav-to-viewer
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
[app.util.object :as obj]
|
||||
[app.util.text-editor :as ted]
|
||||
[app.util.text-svg-position :as tsp]
|
||||
[app.util.timers :as ts]
|
||||
[promesa.core :as p]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
@@ -79,25 +80,29 @@
|
||||
|
||||
(defn- update-text-modifier
|
||||
[{:keys [grow-type id] :as shape} node]
|
||||
(->> (tsp/calc-position-data id)
|
||||
(p/fmap (fn [position-data]
|
||||
(let [props {:position-data position-data}]
|
||||
(if (contains? #{:auto-height :auto-width} grow-type)
|
||||
(let [{:keys [width height]} (-> (dom/query node ".paragraph-set") (dom/get-client-size))
|
||||
width (mth/ceil width)
|
||||
height (mth/ceil height)]
|
||||
(if (and (not (mth/almost-zero? width)) (not (mth/almost-zero? height)))
|
||||
(cond-> props
|
||||
(= grow-type :auto-width)
|
||||
(assoc :width width)
|
||||
|
||||
(p/let [position-data (tsp/calc-position-data id)
|
||||
props {:position-data position-data}
|
||||
|
||||
props
|
||||
(if (contains? #{:auto-height :auto-width} grow-type)
|
||||
(let [{:keys [width height]} (-> (dom/query node ".paragraph-set") (dom/get-client-size))
|
||||
width (mth/ceil width)
|
||||
height (mth/ceil height)]
|
||||
(if (and (not (mth/almost-zero? width)) (not (mth/almost-zero? height)))
|
||||
(cond-> props
|
||||
(= grow-type :auto-width)
|
||||
(assoc :width width)
|
||||
|
||||
(or (= grow-type :auto-height) (= grow-type :auto-width))
|
||||
(assoc :height height))
|
||||
props))
|
||||
props)]
|
||||
(st/emit! (dwt/update-text-modifier id props))))
|
||||
(or (= grow-type :auto-height) (= grow-type :auto-width))
|
||||
(assoc :height height))
|
||||
props))
|
||||
props))))
|
||||
(p/fmap (fn [props]
|
||||
;; We need to wait for the text modifier to be updated before
|
||||
;; we can update the position data. Otherwise the position data
|
||||
;; will be wrong.
|
||||
;; TODO: This is a hack. We need to find a better way to do this.
|
||||
(st/emit! (dwt/update-text-modifier id props))
|
||||
(ts/schedule 30 #(update-text-shape shape node))))))
|
||||
|
||||
(mf/defc text-container
|
||||
{::mf/wrap-props false
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
[app.main.data.events :as ev]
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.data.workspace.assets :as dwa]
|
||||
[app.main.data.workspace.colors :as dc]
|
||||
[app.main.data.workspace.libraries :as dwl]
|
||||
[app.main.data.workspace.media :as dwm]
|
||||
@@ -2424,19 +2425,32 @@
|
||||
[]
|
||||
(let [components-v2 (mf/use-ctx ctx/components-v2)
|
||||
read-only? (mf/use-ctx ctx/workspace-read-only?)
|
||||
|
||||
filters* (mf/use-state
|
||||
{:term ""
|
||||
:section :all
|
||||
:ordering :asc
|
||||
:list-style :thumbs})
|
||||
:ordering (dwa/get-current-assets-ordering)
|
||||
:list-style (dwa/get-current-assets-list-style)})
|
||||
filters (deref filters*)
|
||||
term (:term filters)
|
||||
ordering (:ordering filters)
|
||||
list-style (:list-style filters)
|
||||
|
||||
toggle-ordering
|
||||
(mf/use-fn #(swap! filters* update :ordering toggle-values [:asc :desc]))
|
||||
(mf/use-fn
|
||||
(mf/deps ordering)
|
||||
(fn []
|
||||
(let [new-value (toggle-values ordering [:asc :desc])]
|
||||
(swap! filters* assoc :ordering new-value)
|
||||
(dwa/set-current-assets-ordering! new-value))))
|
||||
|
||||
toggle-list-style
|
||||
(mf/use-fn #(swap! filters* update :list-style toggle-values [:thumbs :list]))
|
||||
(mf/use-fn
|
||||
(mf/deps list-style)
|
||||
(fn []
|
||||
(let [new-value (toggle-values list-style [:thumbs :list])]
|
||||
(swap! filters* assoc :list-style new-value)
|
||||
(dwa/set-current-assets-list-style! new-value))))
|
||||
|
||||
on-search-term-change
|
||||
(mf/use-fn
|
||||
|
||||
@@ -336,10 +336,16 @@
|
||||
|
||||
[:div.element-actions {:class (when (:shapes item) "is-parent")}
|
||||
[:div.toggle-element {:class (when (:hidden item) "selected")
|
||||
:title (if (:hidden item)
|
||||
(tr "workspace.shape.menu.show")
|
||||
(tr "workspace.shape.menu.hide"))
|
||||
:on-click toggle-visibility}
|
||||
(if (:hidden item) i/eye-closed i/eye)]
|
||||
[:div.block-element {:class (when (:blocked item) "selected")
|
||||
:on-click toggle-blocking}
|
||||
:on-click toggle-blocking
|
||||
:title (if (:blocked item)
|
||||
(tr "workspace.shape.menu.unlock")
|
||||
(tr "workspace.shape.menu.lock"))}
|
||||
(if (:blocked item) i/lock i/unlock)]]
|
||||
|
||||
(when (:shapes item)
|
||||
|
||||
@@ -86,7 +86,7 @@
|
||||
(progress! context type file nil nil))
|
||||
|
||||
([context type current total]
|
||||
(keyword? type)
|
||||
(assert (keyword? type))
|
||||
(assert (number? current))
|
||||
(assert (number? total))
|
||||
(progress! context type nil current total))
|
||||
|
||||
@@ -1 +1 @@
|
||||
1.19.2
|
||||
1.19.3
|
||||
|
||||
Reference in New Issue
Block a user