diff --git a/packages/tldraw/apps/tldraw-logseq/src/lib/shapes/HTMLShape.tsx b/packages/tldraw/apps/tldraw-logseq/src/lib/shapes/HTMLShape.tsx index 90af1dcd4..babef5a4a 100644 --- a/packages/tldraw/apps/tldraw-logseq/src/lib/shapes/HTMLShape.tsx +++ b/packages/tldraw/apps/tldraw-logseq/src/lib/shapes/HTMLShape.tsx @@ -96,7 +96,7 @@ export class HTMLShape extends TLBoxShape { React.useEffect(() => { if (this.props.size[1] === 0) { this.onResetBounds({ zoom: app.viewport.camera.zoom }) - app.persist(true) + app.persist({replace: true}) } }, []) diff --git a/packages/tldraw/apps/tldraw-logseq/src/lib/shapes/LogseqPortalShape.tsx b/packages/tldraw/apps/tldraw-logseq/src/lib/shapes/LogseqPortalShape.tsx index 79b1f557f..1c7275952 100644 --- a/packages/tldraw/apps/tldraw-logseq/src/lib/shapes/LogseqPortalShape.tsx +++ b/packages/tldraw/apps/tldraw-logseq/src/lib/shapes/LogseqPortalShape.tsx @@ -296,7 +296,7 @@ export class LogseqPortalShape extends TLBoxShape { size: [this.props.size[0], newHeight], }) - if (loaded) app.persist(true) + if (loaded) app.persist({replace: true}) } } }, [innerHeight, this.props.isAutoResizing]) @@ -305,7 +305,7 @@ export class LogseqPortalShape extends TLBoxShape { if (!this.initialHeightCalculated) { setTimeout(() => { this.onResetBounds() - app.persist(true) + app.persist({replace: true}) }) } }, [this.initialHeightCalculated]) diff --git a/packages/tldraw/apps/tldraw-logseq/src/lib/shapes/TweetShape.tsx b/packages/tldraw/apps/tldraw-logseq/src/lib/shapes/TweetShape.tsx index 68330890a..53bc890f0 100644 --- a/packages/tldraw/apps/tldraw-logseq/src/lib/shapes/TweetShape.tsx +++ b/packages/tldraw/apps/tldraw-logseq/src/lib/shapes/TweetShape.tsx @@ -61,7 +61,7 @@ export class TweetShape extends TLBoxShape { this.update({ size: [this.props.size[0], newHeight], }) - app.persist(true) + app.persist({replace: true}) } }, [innerHeight]) @@ -69,7 +69,7 @@ export class TweetShape extends TLBoxShape { if (!this.initialHeightCalculated) { setTimeout(() => { this.onResetBounds() - app.persist(true) + app.persist({replace: true}) }) } }, [this.initialHeightCalculated]) diff --git a/packages/tldraw/packages/core/src/lib/TLApi/TLApi.ts b/packages/tldraw/packages/core/src/lib/TLApi/TLApi.ts index b83b158d6..29e72eb14 100644 --- a/packages/tldraw/packages/core/src/lib/TLApi/TLApi.ts +++ b/packages/tldraw/packages/core/src/lib/TLApi/TLApi.ts @@ -450,7 +450,7 @@ export class TLApi s.type !== 'group') + let selectedShapes = shapes.filter(s => s.type !== 'group') if (selectedShapes.length > 1) { const ShapeGroup = this.app.getShapeClass('group') const group = new ShapeGroup({ @@ -461,11 +461,12 @@ export class TLApi { diff --git a/packages/tldraw/packages/core/src/lib/TLHistory.ts b/packages/tldraw/packages/core/src/lib/TLHistory.ts index abfd612aa..7c19f8ab1 100644 --- a/packages/tldraw/packages/core/src/lib/TLHistory.ts +++ b/packages/tldraw/packages/core/src/lib/TLHistory.ts @@ -28,9 +28,9 @@ export class TLHistory { + @action persist = (info) => { if (this.isPaused || this.creating) return - this.app.notify('persist', { replace }) + this.app.notify('persist', info) } @action undo = () => { diff --git a/packages/tldraw/packages/core/src/lib/TLPage/TLPage.ts b/packages/tldraw/packages/core/src/lib/TLPage/TLPage.ts index b2265010c..ac043904a 100644 --- a/packages/tldraw/packages/core/src/lib/TLPage/TLPage.ts +++ b/packages/tldraw/packages/core/src/lib/TLPage/TLPage.ts @@ -33,6 +33,7 @@ export class TLPage { - const shapesToMove = this.parseShapesArg(shapes) - shapesToMove - .sort((a, b) => this.shapes.indexOf(b) - this.shapes.indexOf(a)) - .map(shape => this.shapes.indexOf(shape)) - .forEach(index => { - if (index === this.shapes.length - 1) return - const next = this.shapes[index + 1] - if (shapesToMove.includes(next)) return - const t = this.shapes[index] - this.shapes[index] = this.shapes[index + 1] - this.shapes[index + 1] = t - }) - this.app.persist() + this.bringToFront(shapes) return this } @action sendBackward = (shapes: S[] | string[]): this => { - const shapesToMove = this.parseShapesArg(shapes) - shapesToMove - .sort((a, b) => this.shapes.indexOf(a) - this.shapes.indexOf(b)) - .map(shape => this.shapes.indexOf(shape)) - .forEach(index => { - if (index === 0) return - const next = this.shapes[index - 1] - if (shapesToMove.includes(next)) return - const t = this.shapes[index] - this.shapes[index] = this.shapes[index - 1] - this.shapes[index - 1] = t - }) - this.app.persist() + this.sendToBack(shapes) return this } @action bringToFront = (shapes: S[] | string[]): this => { const shapesToMove = this.parseShapesArg(shapes) - this.shapes = this.shapes.filter(shape => !shapesToMove.includes(shape)).concat(shapesToMove) - this.app.persist() + let others = this.shapes.filter(shape => !shapesToMove.includes(shape)) + this.shapes = others.concat(shapesToMove) + const info = {op: "bringToFront", shapes: shapesToMove, before: others[others.length - 1]} + this.app.persist(info) + this.persistInfo = info return this } @action sendToBack = (shapes: S[] | string[]): this => { const shapesToMove = this.parseShapesArg(shapes) - this.shapes = shapesToMove.concat(this.shapes.filter(shape => !shapesToMove.includes(shape))) - this.app.persist() + let others = this.shapes.filter(shape => !shapesToMove.includes(shape)) + this.shapes = shapesToMove.concat(others) + this.app.persist({op: "sendToBack", shapes: shapesToMove, next: others[0]}) return this } diff --git a/src/main/frontend/extensions/tldraw.cljs b/src/main/frontend/extensions/tldraw.cljs index 7a7425188..b67eeb8b5 100644 --- a/src/main/frontend/extensions/tldraw.cljs +++ b/src/main/frontend/extensions/tldraw.cljs @@ -178,7 +178,7 @@ (p/let [_ @*transact-result result (p/do! (state/set-state! [:whiteboard/last-persisted-at (state/get-current-repo)] (util/time-ms)) - (whiteboard-handler/clj-keywordize [obj] @@ -29,16 +31,12 @@ (gp-whiteboard/shape->block repo shape page-id))) (defn- build-shapes - [page-block blocks] - (let [page-metadata (pu/get-block-property-value page-block :logseq.property.tldraw/page) - shapes-index (:shapes-index page-metadata) - shape-id->index (zipmap shapes-index (range 0 (count shapes-index)))] + [blocks] + (let [blocks (db/sort-by-order blocks)] (->> blocks - (map (fn [block] - (assoc block :index (get shape-id->index (str (:block/uuid block)) 0)))) + (db/sort-by-order blocks) (filter pu/shape-block?) (map pu/block->shape) - (sort-by :index) (map (fn [shape] (if-let [page-id (:pageId shape)] (let [page (db/get-page page-id)] @@ -48,7 +46,7 @@ (defn- whiteboard-clj->tldr [page-block blocks] (let [id (str (:block/uuid page-block)) - shapes (build-shapes page-block blocks) + shapes (build-shapes blocks) tldr-page (pu/page-block->tldr-page page-block) assets (:assets tldr-page) tldr-page (dissoc tldr-page :assets)] @@ -61,14 +59,13 @@ :shapes shapes})]}))) (defn db-build-page-block - [page-entity page-name tldraw-page assets shapes-index] + [page-entity page-name tldraw-page assets] (let [get-k #(gobj/get tldraw-page %) tldraw-page {:id (get-k "id") :name (get-k "name") :bindings (js->clj-keywordize (get-k "bindings")) :nonce (get-k "nonce") - :assets (js->clj-keywordize assets) - :shapes-index shapes-index}] + :assets (js->clj-keywordize assets)}] {:db/id (:db/id page-entity) :block/original-name page-name :block/name (util/page-name-sanity-lc page-name) @@ -81,7 +78,7 @@ (util/time-ms))})) (defn file-build-page-block - [page-entity page-name tldraw-page assets shapes-index] + [page-entity page-name tldraw-page assets] (let [get-k #(gobj/get tldraw-page %)] {:block/original-name page-name :block/name (util/page-name-sanity-lc page-name) @@ -94,37 +91,36 @@ :name (get-k "name") :bindings (js->clj-keywordize (get-k "bindings")) :nonce (get-k "nonce") - :assets (js->clj-keywordize assets) - :shapes-index shapes-index}} + :assets (js->clj-keywordize assets)}} :block/updated-at (util/time-ms) :block/created-at (or (:block/created-at page-entity) (util/time-ms))})) (defn build-page-block - [page-entity page-name tldraw-page assets shapes-index] + [page-entity page-name tldraw-page assets] (let [f (if (config/db-based-graph? (state/get-current-repo)) db-build-page-block file-build-page-block)] - (f page-entity page-name tldraw-page assets shapes-index))) + (f page-entity page-name tldraw-page assets))) (defn- compute-tx [^js app ^js tl-page new-id-nonces db-id-nonces page-uuid replace?] (let [page-entity (db/get-page page-uuid) assets (js->clj-keywordize (.getCleanUpAssets app)) - new-shapes (.-shapes tl-page) - shapes-index (map #(gobj/get % "id") new-shapes) - shape-id->index (zipmap shapes-index (range (.-length new-shapes))) upsert-shapes (->> (set/difference new-id-nonces db-id-nonces) (map (fn [{:keys [id]}] (-> (.-serialized ^js (.getShapeById tl-page id)) - js->clj-keywordize - (assoc :index (get shape-id->index id))))) + js->clj-keywordize))) (set)) old-ids (set (map :id db-id-nonces)) new-ids (set (map :id new-id-nonces)) created-ids (->> (set/difference new-ids old-ids) (remove string/blank?) (set)) + new-orders (when (seq created-ids) + (let [max-key (last (sort (map :block/order (:block/_page page-entity))))] + (db-order/gen-n-keys (count created-ids) max-key nil))) + new-id->order (when (seq created-ids) (zipmap created-ids new-orders)) created-shapes (set (filter #(created-ids (:id %)) upsert-shapes)) deleted-ids (->> (set/difference old-ids new-ids) (remove string/blank?)) @@ -136,9 +132,13 @@ deleted-shapes-tx (mapv (fn [id] [:db/retractEntity [:block/uuid (uuid id)]]) deleted-ids) upserted-blocks (->> upsert-shapes (map #(shape->block % (:db/id page-entity))) - (map sqlite-util/block-with-timestamps)) + (map sqlite-util/block-with-timestamps) + (map (fn [block] + (if-let [new-order (when new-id->order (get new-id->order (str (:block/uuid block))))] + (assoc block :block/order new-order) + block)))) page-name (or (:block/original-name page-entity) (str page-uuid)) - page-block (build-page-block page-entity page-name tl-page assets shapes-index)] + page-block (build-page-block page-entity page-name tl-page assets)] (when (or (seq upserted-blocks) (seq deleted-shapes-tx) (not= (:block/properties page-block) @@ -153,12 +153,51 @@ (defonce *last-shapes-nonce (atom {})) +(defn- get-shape-block-id + [^js shape] + (uuid (.-id shape))) + +(defn- handle-order-update! + [page info] + (let [op (:op info) + moved-shapes (:shapes info) + shape-ids (mapv get-shape-block-id moved-shapes)] + (case op + "sendToBack" + (let [next-order (when-let [id (get-shape-block-id (:next info))] + (:block/order (db/entity [:block/uuid id]))) + new-orders (db-order/gen-n-keys (count shape-ids) nil next-order) + tx-data (conj + (map-indexed (fn [idx id] + {:block/uuid id + :block/order (nth new-orders idx)}) shape-ids) + (outliner-core/block-with-updated-at {:db/id (:db/id page)}))] + tx-data) + + "bringToFront" + (let [before-order (when-let [id (get-shape-block-id (:before info))] + (:block/order (db/entity [:block/uuid id]))) + new-orders (db-order/gen-n-keys (count shape-ids) before-order nil) + tx-data (conj + (->> + (map-indexed (fn [idx id] + (when (db/entity [:block/uuid id]) + {:block/uuid id + :block/order (nth new-orders idx)})) shape-ids) + (remove nil?)) + (outliner-core/block-with-updated-at {:db/id (:db/id page)}))] + tx-data)))) + ;; FIXME: it seems that nonce for the page block will not be updated with new updates for the whiteboard (defn clj info*) + replace? (:replace info) + tl-page ^js (second (first (.-pages app))) page-block (model/get-page page-uuid) + order-tx-data (when (contains? #{"bringToFront" "sendToBack"} (:op info)) + (handle-order-update! page-block info)) + shapes (.-shapes ^js tl-page) new-id-nonces (set (map-indexed (fn [_idx shape] (let [id (.-id shape)] {:id id @@ -170,8 +209,8 @@ (map #(update % :id str))))) {:keys [page-block new-shapes deleted-shapes upserted-blocks delete-blocks metadata] :as result} (compute-tx app tl-page new-id-nonces db-id-nonces page-uuid replace?)] - (when (seq result) - (let [tx-data (concat delete-blocks [page-block] upserted-blocks) + (when (or (seq result) (seq order-tx-data)) + (let [tx-data (concat delete-blocks [page-block] upserted-blocks order-tx-data) metadata' (cond ;; group (some #(= "group" (:type %)) new-shapes) @@ -313,7 +352,7 @@ ([{:keys [pages blocks]} api] (let [page-block (first pages) ;; FIXME: should also clone normal blocks - shapes (build-shapes page-block blocks) + shapes (build-shapes blocks) tldr-page (pu/page-block->tldr-page page-block) assets (:assets tldr-page) bindings (:bindings tldr-page)] @@ -342,8 +381,8 @@ (let [tl-page ^js (second (first (.-pages app)))] (when tl-page (when-let [page (db/get-page page-uuid)] - (let [page-metadata (pu/get-block-property-value page :logseq.property.tldraw/page) - shapes-index (:shapes-index page-metadata)] + (let [shapes-index (->> (db/sort-by-order (:block/_page page)) + (map (comp str :block/uuid)))] (when (seq shapes-index) (.updateShapesIndex tl-page (bean/->js shapes-index)))))))))