mirror of
https://github.com/penpot/penpot.git
synced 2026-11-07 10:30:40 +00:00
161 lines
5.9 KiB
Clojure
161 lines
5.9 KiB
Clojure
;; 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 SUBSIDIARY SL
|
|
|
|
(ns app.worker.index
|
|
"Page index management within the worker."
|
|
(:require
|
|
[app.common.data.macros :as dm]
|
|
[app.common.files.changes :as ch]
|
|
[app.common.geom.rect :as grc]
|
|
[app.common.logging :as log]
|
|
[app.common.time :as ct]
|
|
[app.common.transit :as t]
|
|
[app.worker.impl :as impl]
|
|
[app.worker.selection :as selection]
|
|
[app.worker.snap :as snap]
|
|
[app.worker.trim :as trim]
|
|
[okulary.core :as l]))
|
|
|
|
(log/set-level! :info)
|
|
|
|
(defonce state (l/atom {:pages-index {}}))
|
|
|
|
;; Changes that edit what the indexes read. Replaying one on a trimmed page
|
|
;; needs the attributes it reads in `app.worker.trim`.
|
|
(def index-change-types
|
|
#{:add-obj :mod-obj :del-obj :fix-obj :mov-objects :reg-objects
|
|
:reorder-children :set-guide :del-page})
|
|
|
|
;; Changes that leave the indexes alone.
|
|
(def skipped-change-types
|
|
#{:set-comment-thread-position :set-flow :set-default-grid
|
|
:add-page :mod-page :mov-page :set-plugin-data
|
|
:add-color :mod-color :del-color
|
|
:add-media :mod-media :del-media
|
|
:add-component :mod-component :del-component :restore-component :purge-component
|
|
:add-typography :mod-typography :del-typography
|
|
:set-tokens-lib :set-token :set-token-set :set-token-theme :set-tokens-status
|
|
:set-own-tokens-status :rename-token-set-group :move-token-set
|
|
:move-token-set-group :set-base-font-size :set-tokens-source})
|
|
|
|
(defn- index-change?
|
|
[{:keys [type]}]
|
|
(cond
|
|
(contains? index-change-types type)
|
|
true
|
|
|
|
(contains? skipped-change-types type)
|
|
false
|
|
|
|
:else
|
|
(do (log/wrn :hint "change type unknown to the worker index, skipped" :type type)
|
|
false)))
|
|
|
|
(defn- changed-object?
|
|
[old-object new-object]
|
|
(and (not (identical? old-object new-object))
|
|
(not= old-object new-object)))
|
|
|
|
(defmethod impl/handler :index/initialize
|
|
[{:keys [page] :as message}]
|
|
(let [tpoint (ct/tpoint-ms)
|
|
page (trim/trim-page page)]
|
|
(try
|
|
(swap! state update :pages-index assoc (:id page) page)
|
|
(swap! state update ::selection selection/add-page page)
|
|
(swap! state update ::snap snap/add-page page)
|
|
|
|
(finally
|
|
(let [elapsed (tpoint)]
|
|
(log/dbg :hint "page indexed" :id (:id page) :elapsed elapsed ::log/sync? true))))
|
|
nil))
|
|
|
|
(defmethod impl/handler :index/finalize
|
|
[{:keys [page-id]}]
|
|
(swap! state #(-> %
|
|
(update :pages-index dissoc page-id)
|
|
(update ::selection dissoc page-id)
|
|
(update ::snap dissoc page-id)))
|
|
(t/clear-uuid-cache)
|
|
(log/dbg :hint "page index dropped" :id page-id ::log/sync? true)
|
|
nil)
|
|
|
|
(defmethod impl/handler :index/update
|
|
[{:keys [page-id changes] :as message}]
|
|
(let [tpoint (ct/tpoint-ms)]
|
|
(try
|
|
(if-let [old-page (dm/get-in @state [:pages-index page-id])]
|
|
(let [changes (filterv index-change? changes)
|
|
new-page (-> state
|
|
(swap! ch/process-changes changes false)
|
|
(dm/get-in [:pages-index page-id]))]
|
|
(if (nil? new-page)
|
|
;; The changes deleted the page
|
|
(swap! state #(-> %
|
|
(update ::snap dissoc page-id)
|
|
(update ::selection dissoc page-id)))
|
|
|
|
(let [old-objects (:objects old-page)
|
|
ids (ch/get-candidate-object-ids old-objects (:objects new-page) page-id changes)
|
|
new-page (update new-page :objects #(trim/retrim-objects old-objects % ids))
|
|
new-objects (:objects new-page)
|
|
ids (into #{}
|
|
(filter #(changed-object? (get old-objects %) (get new-objects %)))
|
|
ids)
|
|
guides? (not (identical? (:guides old-page) (:guides new-page)))]
|
|
(swap! state #(cond-> (assoc-in % [:pages-index page-id] new-page)
|
|
(or (seq ids) guides?)
|
|
(update ::snap snap/update-page old-page new-page ids)
|
|
|
|
(seq ids)
|
|
(update ::selection selection/update-page old-page new-page ids))))))
|
|
|
|
;; A later `:index/initialize` sends the whole page again
|
|
(log/dbg :hint "page not indexed, skipping update" :id page-id ::log/sync? true))
|
|
(catch :default cause
|
|
(log/error :hint "error updating page index" :id page-id :cause cause))
|
|
(finally
|
|
(let [elapsed (tpoint)]
|
|
(log/dbg :hint "page index updated" :id page-id :elapsed elapsed ::log/sync? true))))
|
|
nil))
|
|
|
|
(defn- run-query-snap
|
|
[index page-id frame-id axis ranges bounds]
|
|
(let [match-bounds?
|
|
(fn [[_ data]]
|
|
(some #(or (= :guide (:type %))
|
|
(= :layout (:type %))
|
|
(grc/contains-point? bounds (:pt %))) data))
|
|
|
|
xform
|
|
(comp (mapcat #(snap/query index page-id frame-id axis %))
|
|
(distinct)
|
|
(filter match-bounds?))]
|
|
(into [] xform ranges)))
|
|
|
|
;; FIXME: schema
|
|
(defmethod impl/handler :index/query-snap
|
|
[{:keys [page-id frame-id axis ranges bounds]}]
|
|
(if-let [index (get @state ::snap)]
|
|
(run-query-snap index page-id frame-id axis ranges bounds)
|
|
[]))
|
|
|
|
;; Single round-trip for X+Y snap used by `app.main.snap/closest-snap` (e.g. shape drag).
|
|
(defmethod impl/handler :index/query-snap-xy
|
|
[{:keys [page-id frame-id bounds x-ranges y-ranges]}]
|
|
(if-let [index (get @state ::snap)]
|
|
{:x (run-query-snap index page-id frame-id :x x-ranges bounds)
|
|
:y (run-query-snap index page-id frame-id :y y-ranges bounds)}
|
|
{:x [] :y []}))
|
|
|
|
;; FIXME: schema
|
|
|
|
(defmethod impl/handler :index/query-selection
|
|
[message]
|
|
(if-let [index (get @state ::selection)]
|
|
(selection/query index message)
|
|
[]))
|