mirror of
https://github.com/penpot/penpot.git
synced 2026-09-13 23:48:42 +00:00
✨ Sync Component library changes into the Ladybug graph
This commit is contained in:
parent
fc354a9334
commit
3cf6e20ba0
@ -68,11 +68,30 @@
|
|||||||
[v]
|
[v]
|
||||||
(str "json('" (escape-cypher-string (json/encode v)) "')"))
|
(str "json('" (escape-cypher-string (json/encode v)) "')"))
|
||||||
|
|
||||||
|
(defn format-timestamp
|
||||||
|
"Ladybug TIMESTAMP literal (beadpot: `timestamp('…')`)."
|
||||||
|
[v]
|
||||||
|
(let [s (cond
|
||||||
|
(instance? java.time.Instant v)
|
||||||
|
(.toString ^java.time.Instant v)
|
||||||
|
|
||||||
|
(instance? java.util.Date v)
|
||||||
|
(.toString (.toInstant ^java.util.Date v))
|
||||||
|
|
||||||
|
(string? v)
|
||||||
|
v
|
||||||
|
|
||||||
|
:else
|
||||||
|
(str v))]
|
||||||
|
(str "timestamp('" (escape-cypher-string s) "')")))
|
||||||
|
|
||||||
(defn format-value
|
(defn format-value
|
||||||
[v]
|
[v]
|
||||||
(cond
|
(cond
|
||||||
(nil? v) "NULL"
|
(nil? v) "NULL"
|
||||||
(uuid? v) (format-uuid v)
|
(uuid? v) (format-uuid v)
|
||||||
|
(instance? java.time.Instant v) (format-timestamp v)
|
||||||
|
(instance? java.util.Date v) (format-timestamp v)
|
||||||
(string? v) (format-string v)
|
(string? v) (format-string v)
|
||||||
(number? v) (format-number v)
|
(number? v) (format-number v)
|
||||||
(boolean? v) (if v "true" "false")
|
(boolean? v) (if v "true" "false")
|
||||||
@ -85,12 +104,13 @@
|
|||||||
"Format one element of a Cypher LIST literal for typed `elem-type`."
|
"Format one element of a Cypher LIST literal for typed `elem-type`."
|
||||||
[elem-type v]
|
[elem-type v]
|
||||||
(case elem-type
|
(case elem-type
|
||||||
"UUID" (format-uuid v)
|
"UUID" (format-uuid v)
|
||||||
"STRING" (format-string (str v))
|
"STRING" (format-string (str v))
|
||||||
"JSON" (format-json v)
|
"JSON" (format-json v)
|
||||||
"INT64" (format-int v)
|
"INT64" (format-int v)
|
||||||
"DOUBLE" (format-number v)
|
"DOUBLE" (format-number v)
|
||||||
"BOOLEAN" (if v "true" "false")
|
"BOOLEAN" (if v "true" "false")
|
||||||
|
"TIMESTAMP" (format-timestamp v)
|
||||||
(format-value v)))
|
(format-value v)))
|
||||||
|
|
||||||
(defn- format-list
|
(defn- format-list
|
||||||
@ -119,6 +139,9 @@
|
|||||||
(= ladybug-type "UUID")
|
(= ladybug-type "UUID")
|
||||||
(format-uuid v)
|
(format-uuid v)
|
||||||
|
|
||||||
|
(= ladybug-type "TIMESTAMP")
|
||||||
|
(format-timestamp v)
|
||||||
|
|
||||||
(and (string? ladybug-type)
|
(and (string? ladybug-type)
|
||||||
(str/ends-with? ladybug-type "[]"))
|
(str/ends-with? ladybug-type "[]"))
|
||||||
(format-list ladybug-type v)
|
(format-list ladybug-type v)
|
||||||
|
|||||||
@ -18,7 +18,10 @@
|
|||||||
(set! *warn-on-reflection* true)
|
(set! *warn-on-reflection* true)
|
||||||
|
|
||||||
(def ^:private supported-change-types
|
(def ^:private supported-change-types
|
||||||
#{:add-obj :mod-obj :del-obj :add-page :del-page :mod-page :mov-objects})
|
#{:add-obj :mod-obj :del-obj
|
||||||
|
:add-page :del-page :mod-page :mov-objects
|
||||||
|
:add-component :mod-component :del-component
|
||||||
|
:restore-component :purge-component})
|
||||||
|
|
||||||
(defn- shape-table
|
(defn- shape-table
|
||||||
[shape]
|
[shape]
|
||||||
@ -80,9 +83,22 @@
|
|||||||
[nodes]
|
[nodes]
|
||||||
(into {} (map page-index-entry (table-rows nodes "Page"))))
|
(into {} (map page-index-entry (table-rows nodes "Page"))))
|
||||||
|
|
||||||
|
(defn- component-index-entry
|
||||||
|
[attrs]
|
||||||
|
(let [id (node-attrs-id attrs)]
|
||||||
|
[id {:id id
|
||||||
|
:name (:name attrs)
|
||||||
|
:deleted (boolean (:deleted attrs))}]))
|
||||||
|
|
||||||
|
(defn- index-components
|
||||||
|
[nodes]
|
||||||
|
(into {} (map component-index-entry (table-rows nodes "Component"))))
|
||||||
|
|
||||||
(defn- shape-index-table?
|
(defn- shape-index-table?
|
||||||
[table]
|
[table]
|
||||||
(not (contains? #{"Document" "Page" :Document :Page} table)))
|
(not (contains? #{"Document" "Page" "Component"
|
||||||
|
:Document :Page :Component}
|
||||||
|
table)))
|
||||||
|
|
||||||
(defn- shape-index-entry
|
(defn- shape-index-entry
|
||||||
[table attrs parents pages edges]
|
[table attrs parents pages edges]
|
||||||
@ -111,15 +127,17 @@
|
|||||||
[file-id revn {:keys [nodes edges]}]
|
[file-id revn {:keys [nodes edges]}]
|
||||||
(let [doc-id (document-id-from-nodes nodes file-id)
|
(let [doc-id (document-id-from-nodes nodes file-id)
|
||||||
pages (index-pages nodes)
|
pages (index-pages nodes)
|
||||||
|
components (index-components nodes)
|
||||||
parents (build-parent-map edges)
|
parents (build-parent-map edges)
|
||||||
children-index (build-children-map edges)
|
children-index (build-children-map edges)
|
||||||
shapes (index-shapes nodes edges parents pages)]
|
shapes (index-shapes nodes edges parents pages)]
|
||||||
{:file-id file-id
|
{:file-id file-id
|
||||||
:doc-id doc-id
|
:doc-id doc-id
|
||||||
:revn (long revn)
|
:revn (long revn)
|
||||||
:pages pages
|
:pages pages
|
||||||
:shapes shapes
|
:components components
|
||||||
:children children-index}))
|
:shapes shapes
|
||||||
|
:children children-index}))
|
||||||
|
|
||||||
|
|
||||||
(defn- format-node-value
|
(defn- format-node-value
|
||||||
@ -196,6 +214,25 @@
|
|||||||
(str "MATCH (p:Page {id: " (ladybug/format-uuid page-id) "}) "
|
(str "MATCH (p:Page {id: " (ladybug/format-uuid page-id) "}) "
|
||||||
"SET p.name = " (ladybug/format-string name) ";"))
|
"SET p.name = " (ladybug/format-string name) ";"))
|
||||||
|
|
||||||
|
(defn- remove-node-attr-statement
|
||||||
|
"Clear a property. Ladybug has no Neo4j-style REMOVE; SET to NULL."
|
||||||
|
[table shape-id attr]
|
||||||
|
(str "MATCH (s:" (nodes/match-label table) " {id: " (ladybug/format-uuid shape-id) "}) "
|
||||||
|
"SET s." (nodes/cypher-property-key attr) " = NULL;"))
|
||||||
|
|
||||||
|
(defn- index-add-component!
|
||||||
|
[index {:keys [id name doc-id]}]
|
||||||
|
(-> index
|
||||||
|
(assoc-in [:components id] {:id id :name name :deleted false})
|
||||||
|
(update :children update doc-id (fnil conj #{}) id)))
|
||||||
|
|
||||||
|
(defn- index-remove-component!
|
||||||
|
[index component-id]
|
||||||
|
(let [doc-id (:doc-id index)]
|
||||||
|
(-> index
|
||||||
|
(update :components dissoc component-id)
|
||||||
|
(update :children update doc-id #(disj (or % #{}) component-id)))))
|
||||||
|
|
||||||
|
|
||||||
(defn- set-document-revision-statement
|
(defn- set-document-revision-statement
|
||||||
[doc-id revn]
|
[doc-id revn]
|
||||||
@ -501,16 +538,145 @@
|
|||||||
:applied? true}
|
:applied? true}
|
||||||
{:index index :statements [] :applied? false :reason :unsupported-page-change}))
|
{:index index :statements [] :applied? false :reason :unsupported-page-change}))
|
||||||
|
|
||||||
|
(defn- component-syncable-attrs
|
||||||
|
"Projected Component columns that sync may SET (everything but :id)."
|
||||||
|
[]
|
||||||
|
(disj (set (nodes/column-keys "Component")) :id))
|
||||||
|
|
||||||
|
(defn- component-attrs-from-change
|
||||||
|
"Build CREATE attrs for `:add-component` (objects are not projected)."
|
||||||
|
[{:keys [id name path main-instance-id main-instance-page
|
||||||
|
annotation variant-id variant-properties]}]
|
||||||
|
(cond-> {:id id
|
||||||
|
:name (or name "Component")
|
||||||
|
:path (or path "")
|
||||||
|
:main-instance-id main-instance-id
|
||||||
|
:main-instance-page main-instance-page}
|
||||||
|
(some? annotation) (assoc :annotation annotation)
|
||||||
|
(some? variant-id) (assoc :variant-id variant-id)
|
||||||
|
(seq variant-properties) (assoc :variant-properties variant-properties)))
|
||||||
|
|
||||||
|
(defn- apply-add-component
|
||||||
|
[index {:keys [id] :as change}]
|
||||||
|
(if (get-in index [:components id])
|
||||||
|
{:index index :statements [] :applied? true}
|
||||||
|
(let [doc-id (:doc-id index)
|
||||||
|
position (count (:components index))
|
||||||
|
attrs (nodes/project-attrs "Component" (component-attrs-from-change change))
|
||||||
|
edge {:from-table "Component"
|
||||||
|
:from-id id
|
||||||
|
:to-table "Document"
|
||||||
|
:to-id doc-id
|
||||||
|
:position position}]
|
||||||
|
{:index (index-add-component! index
|
||||||
|
{:id id
|
||||||
|
:name (:name attrs)
|
||||||
|
:doc-id doc-id})
|
||||||
|
:statements [(create-node-statement "Component" attrs)
|
||||||
|
(create-edge-statement edge)]
|
||||||
|
:applied? true})))
|
||||||
|
|
||||||
|
(defn- apply-mod-component
|
||||||
|
"Update projected Component attrs from a `:mod-component` change.
|
||||||
|
|
||||||
|
Nil optional values clear the property (Penpot dissocs them). `:objects`
|
||||||
|
is never projected — shape trees live on pages."
|
||||||
|
[index {:keys [id] :as change}]
|
||||||
|
(let [syncable (component-syncable-attrs)
|
||||||
|
sets (into {}
|
||||||
|
(keep (fn [[k v]]
|
||||||
|
(when (and (contains? syncable k) (some? v))
|
||||||
|
[k v])))
|
||||||
|
(dissoc change :type :id :objects))
|
||||||
|
removes (into []
|
||||||
|
(keep (fn [[k v]]
|
||||||
|
(when (and (contains? syncable k) (nil? v))
|
||||||
|
k)))
|
||||||
|
(dissoc change :type :id :objects))
|
||||||
|
stmts (into (mapv (fn [[k v]]
|
||||||
|
(set-node-attr-statement "Component" id k v))
|
||||||
|
sets)
|
||||||
|
(map #(remove-node-attr-statement "Component" id %) removes))
|
||||||
|
index' (if (get-in index [:components id])
|
||||||
|
(cond-> index
|
||||||
|
(contains? sets :name)
|
||||||
|
(assoc-in [:components id :name] (:name sets)))
|
||||||
|
(assoc-in index [:components id]
|
||||||
|
{:id id
|
||||||
|
:name (:name sets)
|
||||||
|
:deleted false}))]
|
||||||
|
(if (empty? stmts)
|
||||||
|
{:index index :statements [] :applied? true}
|
||||||
|
{:index index' :statements stmts :applied? true})))
|
||||||
|
|
||||||
|
(defn- apply-del-component
|
||||||
|
[index {:keys [id skip-undelete?]}]
|
||||||
|
(cond
|
||||||
|
(not (get-in index [:components id]))
|
||||||
|
{:index index :statements [] :applied? true}
|
||||||
|
|
||||||
|
skip-undelete?
|
||||||
|
{:index (index-remove-component! index id)
|
||||||
|
:statements [(delete-edge-statement {:from-table "Component"
|
||||||
|
:from-id id
|
||||||
|
:to-table "Document"
|
||||||
|
:to-id (:doc-id index)})
|
||||||
|
(delete-node-statement "Component" id)]
|
||||||
|
:applied? true}
|
||||||
|
|
||||||
|
:else
|
||||||
|
{:index (assoc-in index [:components id :deleted] true)
|
||||||
|
:statements [(set-node-attr-statement "Component" id :deleted true)]
|
||||||
|
:applied? true}))
|
||||||
|
|
||||||
|
(defn- apply-restore-component
|
||||||
|
[index {:keys [id page-id]}]
|
||||||
|
(let [stmts (cond-> [(set-node-attr-statement "Component" id :deleted false)]
|
||||||
|
page-id
|
||||||
|
(conj (set-node-attr-statement "Component" id :main-instance-page page-id)))
|
||||||
|
index (if (get-in index [:components id])
|
||||||
|
(-> index
|
||||||
|
(assoc-in [:components id :deleted] false)
|
||||||
|
(cond-> page-id
|
||||||
|
(assoc-in [:components id :main-instance-page] page-id)))
|
||||||
|
(assoc-in index [:components id]
|
||||||
|
{:id id :name nil :deleted false}))]
|
||||||
|
{:index index :statements stmts :applied? true}))
|
||||||
|
|
||||||
|
(defn- apply-purge-component
|
||||||
|
[index {:keys [id]}]
|
||||||
|
(if-not (get-in index [:components id])
|
||||||
|
;; Still attempt delete in case the node exists but was not indexed.
|
||||||
|
{:index index
|
||||||
|
:statements [(delete-edge-statement {:from-table "Component"
|
||||||
|
:from-id id
|
||||||
|
:to-table "Document"
|
||||||
|
:to-id (:doc-id index)})
|
||||||
|
(delete-node-statement "Component" id)]
|
||||||
|
:applied? true}
|
||||||
|
{:index (index-remove-component! index id)
|
||||||
|
:statements [(delete-edge-statement {:from-table "Component"
|
||||||
|
:from-id id
|
||||||
|
:to-table "Document"
|
||||||
|
:to-id (:doc-id index)})
|
||||||
|
(delete-node-statement "Component" id)]
|
||||||
|
:applied? true}))
|
||||||
|
|
||||||
(defn- apply-change
|
(defn- apply-change
|
||||||
[index change]
|
[index change]
|
||||||
(case (:type change)
|
(case (:type change)
|
||||||
:add-obj (apply-add-obj index change)
|
:add-obj (apply-add-obj index change)
|
||||||
:mod-obj (apply-mod-obj index change)
|
:mod-obj (apply-mod-obj index change)
|
||||||
:del-obj (apply-del-obj index change)
|
:del-obj (apply-del-obj index change)
|
||||||
:add-page (apply-add-page index change)
|
:add-page (apply-add-page index change)
|
||||||
:del-page (apply-del-page index change)
|
:del-page (apply-del-page index change)
|
||||||
:mod-page (apply-mod-page index change)
|
:mod-page (apply-mod-page index change)
|
||||||
:mov-objects (apply-mov-objects index change)
|
:mov-objects (apply-mov-objects index change)
|
||||||
|
:add-component (apply-add-component index change)
|
||||||
|
:mod-component (apply-mod-component index change)
|
||||||
|
:del-component (apply-del-component index change)
|
||||||
|
:restore-component (apply-restore-component index change)
|
||||||
|
:purge-component (apply-purge-component index change)
|
||||||
{:index index :statements [] :applied? false :reason :unsupported-type}))
|
{:index index :statements [] :applied? false :reason :unsupported-type}))
|
||||||
|
|
||||||
(defn apply-changes!
|
(defn apply-changes!
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user