diff --git a/deps/db/src/logseq/db/rules.cljc b/deps/db/src/logseq/db/rules.cljc index a6d5391d33..46acf45212 100644 --- a/deps/db/src/logseq/db/rules.cljc +++ b/deps/db/src/logseq/db/rules.cljc @@ -134,6 +134,7 @@ [(str ?val) ?str-val] (or [(= ?v ?val)] [(contains? ?v ?val)] + ;; For integer pages that aren't strings [(contains? ?v ?str-val)])] :page-ref diff --git a/e2e-tests/whiteboards.spec.ts b/e2e-tests/whiteboards.spec.ts index 97cafb2f0d..61482c110d 100644 --- a/e2e-tests/whiteboards.spec.ts +++ b/e2e-tests/whiteboards.spec.ts @@ -159,6 +159,18 @@ test('undo the delete action', async ({ page }) => { await expect(page.locator('.logseq-tldraw .tl-line-container')).toHaveCount(1) }) +test('locked elements should not be removed', async ({ page }) => { + await page.keyboard.press('Escape') + await page.waitForTimeout(1000) + await page.click('.logseq-tldraw .tl-box-container:first-of-type') + await page.keyboard.press(`${modKey}+l`) + await page.keyboard.press('Delete') + await page.keyboard.press(`${modKey}+Shift+l`) + + await expect(page.locator('.logseq-tldraw .tl-box-container')).toHaveCount(2) + +}) + test('move arrow to back', async ({ page }) => { await page.keyboard.press('Escape') await page.waitForTimeout(1000) diff --git a/package.json b/package.json index a31454c74b..990c977dd2 100644 --- a/package.json +++ b/package.json @@ -90,7 +90,7 @@ "@capawesome/capacitor-background-task": "^2.0.0", "@excalidraw/excalidraw": "0.12.0", "@hugotomazi/capacitor-navigation-bar": "^2.0.0", - "@logseq/capacitor-file-sync": "0.0.22", + "@logseq/capacitor-file-sync": "0.0.24", "@logseq/diff-merge": "^0.0.2", "@logseq/react-tweet-embed": "1.3.1-1", "@sentry/react": "^6.18.2", diff --git a/src/main/frontend/components/block.cljs b/src/main/frontend/components/block.cljs index abe6677973..7a74a6c4b4 100644 --- a/src/main/frontend/components/block.cljs +++ b/src/main/frontend/components/block.cljs @@ -2932,7 +2932,8 @@ custom-query? (boolean (:custom-query? config))] (if (and (or ref? custom-query?) (not (:ref-query-child? config))) (ui/lazy-visible - (fn [] (block-container-inner state repo config block))) + (fn [] (block-container-inner state repo config block)) + {:debug-id (str "block-container-ref " (:db/id block))}) (block-container-inner state repo config block)))) (defn divide-lists @@ -3360,7 +3361,7 @@ "More" @*loading? - (ui/lazy-loading-placeholder) + (ui/lazy-loading-placeholder 88) :else "")})])))) @@ -3452,8 +3453,7 @@ (rum/with-key (breadcrumb-with-container blocks (assoc config :top-level? top-level?)) (:db/id parent))))) - {:debug-id page - :trigger-once? false})])))))] + {:debug-id page})])))))] (and (:ref? config) (:group-by-page? config)) [:div.flex.flex-col diff --git a/src/main/frontend/components/encryption.cljs b/src/main/frontend/components/encryption.cljs index fc108f4fd7..0c7d59bc3c 100644 --- a/src/main/frontend/components/encryption.cljs +++ b/src/main/frontend/components/encryption.cljs @@ -209,7 +209,7 @@ [:span.px-3.flex (ui/icon "key")] [:p.dark:text-gray-100 [:span "Please make sure you "] - "remember the password you have set, " + "remember the password you have set, as we are unable to reset or retrieve it in case you forget it, " [:span "and we recommend you "] "keep a secure backup " [:span "of the password."]]] diff --git a/src/main/frontend/components/journal.cljs b/src/main/frontend/components/journal.cljs index 9582c28504..9aefd88c50 100644 --- a/src/main/frontend/components/journal.cljs +++ b/src/main/frontend/components/journal.cljs @@ -59,8 +59,7 @@ (blocks-cp repo page) (ui/lazy-visible (fn [] (blocks-cp repo page)) - {:trigger-once? false - :debug-id (str "journal-blocks " page)})) + {:debug-id (str "journal-blocks " page)})) {}) diff --git a/src/main/frontend/components/query.cljs b/src/main/frontend/components/query.cljs index 0e541f7d6a..68413e4c9f 100644 --- a/src/main/frontend/components/query.cljs +++ b/src/main/frontend/components/query.cljs @@ -253,5 +253,4 @@ (ui/lazy-visible (fn [] (custom-query* config q (::query-triggered? state))) - {:debug-id q - :trigger-once? false}))) + {:debug-id q}))) diff --git a/src/main/frontend/components/query_table.cljs b/src/main/frontend/components/query_table.cljs index 41f30a1d26..a5b6324f50 100644 --- a/src/main/frontend/components/query_table.cljs +++ b/src/main/frontend/components/query_table.cljs @@ -12,6 +12,7 @@ [frontend.format.block :as block] [medley.core :as medley] [rum.core :as rum] + [logseq.graph-parser.text :as text] [frontend.modules.outliner.tree :as tree])) ;; Util fns @@ -27,7 +28,7 @@ (map #(medley/dissoc-in % ks) result) result))) -(defn- sort-by-fn [sort-by-column item] +(defn- sort-by-fn [sort-by-column item {:keys [page?]}] (case sort-by-column :created-at (:block/created-at item) @@ -36,7 +37,7 @@ :block (:block/content item) :page - (:block/name item) + (if page? (:block/name item) (get-in item [:block/page :block/name])) (get-in item [:block/properties sort-by-column]))) (defn- locale-compare @@ -46,11 +47,12 @@ (< x y) (.localeCompare (str x) (str y) (state/sub :preferred-language) #js {:numeric true}))) -(defn- sort-result [result {:keys [sort-by-column sort-desc? sort-nlp-date?]}] +(defn- sort-result [result {:keys [sort-by-column sort-desc? sort-nlp-date? page?]}] (if (some? sort-by-column) (let [comp-fn (if sort-desc? #(locale-compare %2 %1) locale-compare)] (sort-by (fn [item] - (block/normalize-block (sort-by-fn sort-by-column item) sort-nlp-date?)) + (block/normalize-block (sort-by-fn sort-by-column item {:page? page?}) + sort-nlp-date?)) comp-fn result)) result)) @@ -115,7 +117,7 @@ (defn- build-column-value "Builds a column's tuple value for a query table given a row, column and options" - [row column {:keys [page? ->elem map-inline config]}] + [row column {:keys [page? ->elem map-inline config comma-separated-property?]}] (case column :page [:string (if page? @@ -143,9 +145,13 @@ [:string (when-let [updated-at (:block/updated-at row)] (date/int->local-time-2 updated-at))] - [:string (or (get-in row [:block/properties-text-values column]) - ;; Fallback to property relationships for page blocks - (get-in row [:block/properties column]))])) + [:string (if comma-separated-property? + ;; Return original properties since comma properties need to + ;; return collections for display purposes + (get-in row [:block/properties column]) + (or (get-in row [:block/properties-text-values column]) + ;; Fallback to original properties for page blocks + (get-in row [:block/properties column])))])) (rum/defcs result-table < rum/reactive (rum/local false ::select?) @@ -165,7 +171,8 @@ ;; Sort state needs to be in sync between final result and sortable title ;; as user needs to know if there result is sorted sort-state (get-sort-state current-block) - result' (sort-result result sort-state)] + result' (sort-result result (assoc sort-state :page? page?)) + property-separated-by-commas? (partial text/separated-by-commas? (state/get-config))] [:div.overflow-x-auto {:on-mouse-down (fn [e] (.stopPropagation e)) :style {:width "100%"} :class (when-not page? "query-table")} @@ -188,7 +195,8 @@ {:page? page? :->elem ->elem :map-inline map-inline - :config config})] + :config config + :comma-separated-property? (property-separated-by-commas? column)})] [:td.whitespace-nowrap {:on-mouse-down (fn [] (reset! *mouse-down? true) (reset! select? false)) diff --git a/src/main/frontend/components/scheduled_deadlines.cljs b/src/main/frontend/components/scheduled_deadlines.cljs index 905bcc9c17..a1d68e4e87 100644 --- a/src/main/frontend/components/scheduled_deadlines.cljs +++ b/src/main/frontend/components/scheduled_deadlines.cljs @@ -16,7 +16,7 @@ (not (true? (state/scheduled-deadlines-disabled?))) (= (string/lower-case page-name) (string/lower-case (date/journal-name))))) -(rum/defc scheduled-and-deadlines < rum/reactive db-mixins/query +(rum/defc scheduled-and-deadlines-inner < rum/reactive db-mixins/query [page-name] (let [scheduled-or-deadlines (when (scheduled-or-deadlines? page-name) (db/get-date-scheduled-or-deadlines (string/capitalize page-name)))] @@ -33,3 +33,9 @@ {})] (content/content page-name {:hiccup ref-hiccup}))] {:title-trigger? true})]))) + +(rum/defc scheduled-and-deadlines + [page-name] + (ui/lazy-visible + (fn [] (scheduled-and-deadlines-inner page-name)) + {:debug-id "scheduled-and-deadlines"})) diff --git a/src/main/frontend/db/react.cljs b/src/main/frontend/db/react.cljs index 9210107a16..0af0431e5b 100644 --- a/src/main/frontend/db/react.cljs +++ b/src/main/frontend/db/react.cljs @@ -51,8 +51,12 @@ (defonce query-state (atom {})) +;; Current dynamic component (def ^:dynamic *query-component*) +;; Which reactive queries are triggered by the current component +(def ^:dynamic *reactive-queries*) + ;; component -> query-key (defonce query-components (atom {})) @@ -156,11 +160,14 @@ transform-fn identity}} query & inputs] {:pre [(s/valid? ::react-query-keys k)]} (let [kv? (and (vector? k) (= :kv (first k))) + origin-key k k (vec (cons repo k))] (when-let [db (conn/get-db repo)] (let [result-atom (get-query-cached-result k)] (when-let [component *query-component*] (add-query-component! k component)) + (when-let [queries *reactive-queries*] + (swap! queries conj origin-key)) (if (and use-cache? result-atom) result-atom (let [{:keys [result time]} (util/with-time diff --git a/src/main/frontend/db_mixins.cljs b/src/main/frontend/db_mixins.cljs index 1a04360331..bd3d8b551f 100644 --- a/src/main/frontend/db_mixins.cljs +++ b/src/main/frontend/db_mixins.cljs @@ -3,10 +3,14 @@ (:require [frontend.db.react :as react])) (def query - {:wrap-render + {:init + (fn [state] + (assoc state :reactive-queries (atom #{}))) + :wrap-render (fn [render-fn] (fn [state] - (binding [react/*query-component* (:rum/react-component state)] + (binding [react/*query-component* (:rum/react-component state) + react/*reactive-queries* (:reactive-queries state)] (render-fn state)))) :will-unmount (fn [state] diff --git a/src/main/frontend/fs/capacitor_fs.cljs b/src/main/frontend/fs/capacitor_fs.cljs index 1b8d665b79..8b87e0954c 100644 --- a/src/main/frontend/fs/capacitor_fs.cljs +++ b/src/main/frontend/fs/capacitor_fs.cljs @@ -148,7 +148,7 @@ "") (string/replace (re-pattern (str "(?i)" (gstring/regExpEscape (str "." ext)) "$")) ""))] - (util/safe-path-join repo-dir (str bak-dir "/" relative-path)))) + (path/path-join repo-dir bak-dir relative-path))) (defn- truncate-old-versioned-files! "reserve the latest 6 version files" @@ -174,7 +174,8 @@ dir (case dir :backup-dir (get-backup-dir repo-dir path backup-dir ext) :version-file-dir (get-backup-dir repo-dir path version-file-dir ext)) - new-path (util/safe-path-join dir (str (string/replace (.toISOString (js/Date.)) ":" "_") "." (mobile-util/platform) "." ext))] + new-path (path/path-join dir (str (string/replace (.toISOString (js/Date.)) ":" "_") "." (mobile-util/platform) "." ext))] + ( self-top 0))) - (set-visible! in-view?))))}) - ref (.-ref inViewState)] - (lazy-visible-inner visible? content-fn ref))))) + :or {trigger-once? false}}] + (let [[visible? set-visible!] (rum/use-state false) + root-margin 100 + inViewState (useInView #js {:rootMargin (str root-margin "px") + :triggerOnce trigger-once? + :onChange (fn [in-view? entry] + (let [self-top (.-top (.-boundingClientRect entry))] + (when (or (and (not visible?) in-view?) + ;; hide only the components below the current top for better ux + (and visible? (not in-view?) (> self-top root-margin))) + (set-visible! in-view?))))}) + ref (.-ref inViewState)] + (lazy-visible-inner visible? content-fn ref)))) (rum/defc portal ([children] diff --git a/src/main/frontend/util.cljc b/src/main/frontend/util.cljc index 29a66dd5a9..5a2d8ebf79 100644 --- a/src/main/frontend/util.cljc +++ b/src/main/frontend/util.cljc @@ -528,13 +528,6 @@ (if (string? s) (string/lower-case s) s)) -#?(:cljs - (defn safe-path-join [prefix & paths] - (let [path (apply node-path.join (cons prefix paths))] - (if (and (electron?) (gstring/caseInsensitiveStartsWith path "file://")) - (gp-util/safe-decode-uri-component (subs path 7)) - path)))) - (defn trim-safe [s] (when s diff --git a/src/test/frontend/components/query_table_test.cljs b/src/test/frontend/components/query_table_test.cljs index 38889e6d7b..d6692a59d3 100644 --- a/src/test/frontend/components/query_table_test.cljs +++ b/src/test/frontend/components/query_table_test.cljs @@ -16,11 +16,11 @@ (when-let [result (query-dsl/query test-helper/test-db s)] (map first (deref result)))) -(deftest sort-result +(deftest sort-result-for-standard-columns ;; Define since it's not defined (state/set-preferred-language! "en") - (testing "sort by block content" + (testing "sort by block column" (are [sort-state result sorted-result] (= (mapv #(hash-map :block/content %) sorted-result) (#'query-table/sort-result (mapv #(hash-map :block/content %) result) sort-state)) @@ -30,6 +30,24 @@ {:sort-desc? false :sort-by-column :block} ["abc" "cde"] ["abc" "cde"])) + (testing "sort by page column" + (are [sort-options result sorted-result] + (= sorted-result + (#'query-table/sort-result result sort-options)) + ;; on page queries + {:sort-desc? true :sort-by-column :page :page? true} + (map #(hash-map :block/name %) ["abc" "cde"]) + (map #(hash-map :block/name %) ["cde" "abc"]) + + ;; on block queries + {:sort-desc? true :sort-by-column :page :page? false} + (map #(hash-map :block/page {:block/name %}) ["abc" "cde"]) + (map #(hash-map :block/page {:block/name %}) ["cde" "abc"])))) + +(deftest sort-result-for-property-columns + ;; Define since it's not defined + (state/set-preferred-language! "en") + (testing "sort by integer block property" (are [sort-state result sorted-result] (= (mapv #(hash-map :block/properties %) sorted-result) @@ -86,17 +104,17 @@ [{:title 1.1} {:title 1.2} {:title 1.11}] [{:title 1.1} {:title 1.11} {:title 1.2}])) - (testing "sort by semver-style string block property" - (are [sort-state result sorted-result] - (= (mapv #(hash-map :block/properties %) sorted-result) - (#'query-table/sort-result (mapv #(hash-map :block/properties %) result) sort-state)) - {:sort-desc? true :sort-by-column :title} - [{:title "1.1.0"} {:title "1.2.0"} {:title "1.11.0"} {:title "1.1.1"} {:title "1.11.1"} {:title "1.2.1"}] - [{:title "1.11.1"} {:title "1.11.0"} {:title "1.2.1"} {:title "1.2.0"} {:title "1.1.1"} {:title "1.1.0"}] + (testing "sort by semver-style string block property" + (are [sort-state result sorted-result] + (= (mapv #(hash-map :block/properties %) sorted-result) + (#'query-table/sort-result (mapv #(hash-map :block/properties %) result) sort-state)) + {:sort-desc? true :sort-by-column :title} + [{:title "1.1.0"} {:title "1.2.0"} {:title "1.11.0"} {:title "1.1.1"} {:title "1.11.1"} {:title "1.2.1"}] + [{:title "1.11.1"} {:title "1.11.0"} {:title "1.2.1"} {:title "1.2.0"} {:title "1.1.1"} {:title "1.1.0"}] - {:sort-desc? false :sort-by-column :title} - [{:title "1.1.0"} {:title "1.2.0"} {:title "1.11.0"} {:title "1.1.1"} {:title "1.11.1"} {:title "1.2.1"}] - [{:title "1.1.0"} {:title "1.1.1"} {:title "1.2.0"} {:title "1.2.1"} {:title "1.11.0"} {:title "1.11.1"}])) + {:sort-desc? false :sort-by-column :title} + [{:title "1.1.0"} {:title "1.2.0"} {:title "1.11.0"} {:title "1.1.1"} {:title "1.11.1"} {:title "1.2.1"}] + [{:title "1.1.0"} {:title "1.1.1"} {:title "1.2.0"} {:title "1.2.1"} {:title "1.11.0"} {:title "1.11.1"}])) (testing "sort by string block property for specific locale" (state/set-preferred-language! "zh-CN") diff --git a/src/test/frontend/db/query_dsl_test.cljs b/src/test/frontend/db/query_dsl_test.cljs index 80a9cdcd04..ebbda9a6ba 100644 --- a/src/test/frontend/db/query_dsl_test.cljs +++ b/src/test/frontend/db/query_dsl_test.cljs @@ -1,7 +1,9 @@ (ns frontend.db.query-dsl-test (:require [cljs.test :refer [are deftest testing use-fixtures is]] [clojure.string :as str] + [logseq.graph-parser.util.page-ref :as page-ref] [frontend.db :as db] + [frontend.util :as util] [frontend.db.query-dsl :as query-dsl] [frontend.test.helper :as test-helper :include-macros true :refer [load-test-files]])) @@ -142,6 +144,21 @@ prop-d:: nada"}]) (test-helper/with-config {} (block-property-queries-test)))) +(deftest block-property-query-performance + (let [pages (->> (repeat 10 {:tags ["tag1" "tag2"]}) + (map-indexed (fn [idx {:keys [tags]}] + {:file/path (str "pages/page" idx ".md") + :file/content (if (seq tags) + (str "tags:: " (str/join ", " (map page-ref/->page-ref tags))) + "")}))) + _ (load-test-files pages) + {:keys [result time]} + (util/with-time (dsl-query "(and (property tags tag1) (property tags tag2))"))] + ;; Specific number isn't as important as ensuring query doesn't take orders + ;; of magnitude longer + (is (> 25.0 time) "multi property query perf is reasonable") + (is (= 10 (count result))))) + (defn- page-property-queries-test [] (load-test-files [{:file/path "pages/page1.md" diff --git a/src/test/frontend/util_test.cljs b/src/test/frontend/util_test.cljs index 5233366876..be44f33eaa 100644 --- a/src/test/frontend/util_test.cljs +++ b/src/test/frontend/util_test.cljs @@ -22,17 +22,6 @@ (is (= 2 (util/safe-inc-current-pos-from-start "abcde" 1))) (is (= 1 (util/safe-inc-current-pos-from-start "中文" 0))))) -(deftest test-safe-path-join - (testing "safe path join with custom schema" - (is (= (util/node-path.join "a/b" "c/d.md") "a/b/c/d.md")) - (is (= (util/node-path.join "a/b/c" "../../d.md") "a/d.md")) - (is (= (util/node-path.join "file:///a/b" "c/d.md") "file:///a/b/c/d.md")) - (is (= (util/node-path.join "file:///a/b" "../d.md") "file:///a/d.md")) - (is (= (util/node-path.join "file:///a a2/b" "c/d.md") "file:///a a2/b/c/d.md")) - (is (= (util/node-path.join "C:/a2/b" "c/d.md") "C:/a2/b/c/d.md")) - (is (= (util/node-path.join "content://a/b" "../d.md") "content://a/d.md")) - (is (= (util/node-path.join "https://logseq.com/a/b" "c/d.md") "https://logseq.com/a/b/c/d.md")))) - (deftest test-memoize-last (testing "memoize-last add test" (let [actual-ops (atom 0) @@ -120,4 +109,4 @@ (is (= (config/ext-of-video? "file.mp3") false)) (is (= (config/ext-of-image? "file.svg") true)) (is (= (config/ext-of-image? "a.file.png") true)) - (is (= (config/ext-of-image? "file.tiff") false)))) \ No newline at end of file + (is (= (config/ext-of-image? "file.tiff") false)))) diff --git a/tldraw/apps/tldraw-logseq/src/components/Button/CircleButton.tsx b/tldraw/apps/tldraw-logseq/src/components/Button/CircleButton.tsx index b836b75f25..31a7d99b60 100644 --- a/tldraw/apps/tldraw-logseq/src/components/Button/CircleButton.tsx +++ b/tldraw/apps/tldraw-logseq/src/components/Button/CircleButton.tsx @@ -1,11 +1,8 @@ -import React from 'react' import { TablerIcon } from '../icons' export const CircleButton = ({ - active, style, icon, - otherIcon, onClick, }: { active?: boolean @@ -14,27 +11,14 @@ export const CircleButton = ({ otherIcon?: string onClick: () => void }) => { - const [recentlyChanged, setRecentlyChanged] = React.useState(false) - - React.useEffect(() => { - setRecentlyChanged(true) - const timer = setTimeout(() => { - setRecentlyChanged(false) - }, 500) - return () => clearTimeout(timer) - }, [active]) - return ( diff --git a/tldraw/apps/tldraw-logseq/src/components/ContextBar/contextBarActionFactory.tsx b/tldraw/apps/tldraw-logseq/src/components/ContextBar/contextBarActionFactory.tsx index 326da7aeaa..42eb0f8fe4 100644 --- a/tldraw/apps/tldraw-logseq/src/components/ContextBar/contextBarActionFactory.tsx +++ b/tldraw/apps/tldraw-logseq/src/components/ContextBar/contextBarActionFactory.tsx @@ -28,11 +28,13 @@ import { type ToggleGroupInputOption, } from '../inputs/ToggleGroupInput' import { ToggleInput } from '../inputs/ToggleInput' +import { GeometryTools } from '../GeometryTools' import { LogseqContext } from '../../lib/logseq-context' export const contextBarActionTypes = [ // Order matters - 'Edit', + 'LogseqPortalViewMode', + 'Geometry', 'AutoResizing', 'Swatch', 'NoFill', @@ -42,14 +44,12 @@ export const contextBarActionTypes = [ 'YoutubeLink', 'TwitterLink', 'IFrameSource', - 'LogseqPortalViewMode', 'ArrowMode', 'Links', ] as const type ContextBarActionType = typeof contextBarActionTypes[number] const singleShapeActions: ContextBarActionType[] = [ - 'Edit', 'YoutubeLink', 'TwitterLink', 'IFrameSource', @@ -63,7 +63,6 @@ type ShapeType = Shape['props']['type'] export const shapeMapping: Record = { 'logseq-portal': [ 'Swatch', - 'Edit', 'LogseqPortalViewMode', 'ScaleLevel', 'AutoResizing', @@ -72,13 +71,13 @@ export const shapeMapping: Record = { youtube: ['YoutubeLink', 'Links'], tweet: ['TwitterLink', 'Links'], iframe: ['IFrameSource', 'Links'], - box: ['Edit', 'TextStyle', 'Swatch', 'ScaleLevel', 'NoFill', 'StrokeType', 'Links'], - ellipse: ['Edit', 'TextStyle', 'Swatch', 'ScaleLevel', 'NoFill', 'StrokeType', 'Links'], - polygon: ['Edit', 'TextStyle', 'Swatch', 'ScaleLevel', 'NoFill', 'StrokeType', 'Links'], - line: ['Edit', 'TextStyle', 'Swatch', 'ScaleLevel', 'ArrowMode', 'Links'], + box: ['Geometry', 'TextStyle', 'Swatch', 'ScaleLevel', 'NoFill', 'StrokeType', 'Links'], + ellipse: ['Geometry', 'TextStyle', 'Swatch', 'ScaleLevel', 'NoFill', 'StrokeType', 'Links'], + polygon: ['Geometry', 'TextStyle', 'Swatch', 'ScaleLevel', 'NoFill', 'StrokeType', 'Links'], + line: ['TextStyle', 'Swatch', 'ScaleLevel', 'ArrowMode', 'Links'], pencil: ['Swatch', 'Links', 'ScaleLevel'], highlighter: ['Swatch', 'Links', 'ScaleLevel'], - text: ['Edit', 'TextStyle', 'Swatch', 'ScaleLevel', 'AutoResizing', 'Links'], + text: ['TextStyle', 'Swatch', 'ScaleLevel', 'AutoResizing', 'Links'], html: ['ScaleLevel', 'AutoResizing', 'Links'], image: ['Links'], video: ['Links'], @@ -90,64 +89,15 @@ export const withFillShapes = Object.entries(shapeMapping) }) .map(([key]) => key) as ShapeType[] -function filterShapeByAction(shapes: Shape[], type: ContextBarActionType): S[] { - return shapes.filter(shape => shapeMapping[shape.props.type]?.includes(type)) as S[] -} - -const EditAction = observer(() => { - const { - handlers: { isWhiteboardPage, redirectToPage, getRedirectPageName, insertFirstPageBlock }, - } = React.useContext(LogseqContext) - +function filterShapeByAction(type: ContextBarActionType) { const app = useApp() - const shape = filterShapeByAction(app.selectedShapesArray, 'Edit')[0] - - const iconName = - ('label' in shape.props && shape.props.label) || ('text' in shape.props && shape.props.text) - ? 'forms' - : 'text' - - return ( - - ) -}) + const unlockedSelectedShapes = app.selectedShapesArray.filter(s => !s.props.isLocked) + return unlockedSelectedShapes.filter(shape => shapeMapping[shape.props.type]?.includes(type)) +} const AutoResizingAction = observer(() => { const app = useApp() - const shapes = filterShapeByAction( - app.selectedShapesArray, - 'AutoResizing' - ) + const shapes = filterShapeByAction('AutoResizing') const pressed = shapes.every(s => s.props.isAutoResizing) @@ -177,36 +127,23 @@ const AutoResizingAction = observer(() => { const LogseqPortalViewModeAction = observer(() => { const app = useApp() - const shapes = filterShapeByAction( - app.selectedShapesArray, - 'LogseqPortalViewMode' - ) + const shapes = filterShapeByAction('LogseqPortalViewMode') const collapsed = shapes.every(s => s.collapsed) - const ViewModeOptions: ToggleGroupInputOption[] = [ - { - value: '1', - icon: 'object-compact', - tooltip: 'Collapse', - }, - { - value: '0', - icon: 'object-expanded', - tooltip: 'Expand', - }, - ] + if (!collapsed && !shapes.every(s => !s.collapsed)) { + return null + } + return ( - { - shapes.forEach(shape => { - shape.toggleCollapsed() - }) - app.persist() - }} - /> + s.props.type === 'logseq-portal')} + className="tl-button" + pressed={collapsed} + onPressedChange={() => app.api.setCollapsed(!collapsed) } + > + + ) }) @@ -215,8 +152,7 @@ const ScaleLevelAction = observer(() => { handlers: { isMobile }, } = React.useContext(LogseqContext) - const app = useApp() - const shapes = filterShapeByAction(app.selectedShapesArray, 'ScaleLevel') + const shapes = filterShapeByAction('ScaleLevel') const scaleLevel = new Set(shapes.map(s => s.scaleLevel)).size > 1 ? '' : shapes[0].scaleLevel return @@ -224,7 +160,7 @@ const ScaleLevelAction = observer(() => { const IFrameSourceAction = observer(() => { const app = useApp() - const shape = filterShapeByAction(app.selectedShapesArray, 'IFrameSource')[0] + const shape = filterShapeByAction('IFrameSource')[0] const handleChange = React.useCallback((e: React.ChangeEvent) => { shape.onIFrameSourceChange(e.target.value.trim().toLowerCase()) @@ -255,7 +191,7 @@ const IFrameSourceAction = observer(() => { const YoutubeLinkAction = observer(() => { const app = useApp() - const shape = filterShapeByAction(app.selectedShapesArray, 'YoutubeLink')[0] + const shape = filterShapeByAction('YoutubeLink')[0] const handleChange = React.useCallback((e: React.ChangeEvent) => { shape.onYoutubeLinkChange(e.target.value) app.persist() @@ -282,7 +218,7 @@ const YoutubeLinkAction = observer(() => { const TwitterLinkAction = observer(() => { const app = useApp() - const shape = filterShapeByAction(app.selectedShapesArray, 'TwitterLink')[0] + const shape = filterShapeByAction('TwitterLink')[0] const handleChange = React.useCallback((e: React.ChangeEvent) => { shape.onTwitterLinkChange(e.target.value) app.persist() @@ -309,10 +245,7 @@ const TwitterLinkAction = observer(() => { const NoFillAction = observer(() => { const app = useApp() - const shapes = filterShapeByAction( - app.selectedShapesArray, - 'NoFill' - ) + const shapes = filterShapeByAction('NoFill') const handleChange = React.useCallback((v: boolean) => { shapes.forEach(s => s.update({ noFill: v })) app.persist() @@ -337,7 +270,7 @@ const SwatchAction = observer(() => { // Placeholder const shapes = filterShapeByAction< BoxShape | PolygonShape | EllipseShape | LineShape | PencilShape | TextShape - >(app.selectedShapesArray, 'Swatch') + >('Swatch') const handleSetColor = React.useCallback((color: string) => { shapes.forEach(s => { @@ -365,11 +298,27 @@ const SwatchAction = observer(() => { ) }) +const GeometryAction = observer(() => { + const app = useApp() + + const handleSetGeometry = React.useCallback((e: React.MouseEvent) => { + const type = e.currentTarget.dataset.tool + app.api.convertShapes(type) + }, []) + + return ( + + ) +}) + const StrokeTypeAction = observer(() => { const app = useApp() const shapes = filterShapeByAction< BoxShape | PolygonShape | EllipseShape | LineShape | PencilShape - >(app.selectedShapesArray, 'StrokeType') + >('StrokeType') const StrokeTypeOptions: ToggleGroupInputOption[] = [ { @@ -409,7 +358,7 @@ const StrokeTypeAction = observer(() => { const ArrowModeAction = observer(() => { const app = useApp() - const shapes = filterShapeByAction(app.selectedShapesArray, 'ArrowMode') + const shapes = filterShapeByAction('ArrowMode') const StrokeTypeOptions: ToggleGroupInputOption[] = [ { @@ -453,7 +402,7 @@ const ArrowModeAction = observer(() => { const TextStyleAction = observer(() => { const app = useApp() - const shapes = filterShapeByAction(app.selectedShapesArray, 'TextStyle') + const shapes = filterShapeByAction('TextStyle') const bold = shapes.every(s => s.props.fontWeight > 500) const italic = shapes.every(s => s.props.italic) @@ -517,7 +466,7 @@ const LinksAction = observer(() => { ) }) -contextBarActionMapping.set('Edit', EditAction) +contextBarActionMapping.set('Geometry', GeometryAction) contextBarActionMapping.set('AutoResizing', AutoResizingAction) contextBarActionMapping.set('LogseqPortalViewMode', LogseqPortalViewModeAction) contextBarActionMapping.set('ScaleLevel', ScaleLevelAction) diff --git a/tldraw/apps/tldraw-logseq/src/components/ContextMenu/ContextMenu.tsx b/tldraw/apps/tldraw-logseq/src/components/ContextMenu/ContextMenu.tsx index 11386d6594..11b9dc3a9a 100644 --- a/tldraw/apps/tldraw-logseq/src/components/ContextMenu/ContextMenu.tsx +++ b/tldraw/apps/tldraw-logseq/src/components/ContextMenu/ContextMenu.tsx @@ -56,77 +56,81 @@ export const ContextMenu = observer(function ContextMenu({ tabIndex={-1} >
- {app.selectedShapes?.size > 1 && !app.readOnly && ( - <> - -
- - - - - -
-
- - - - - -
-
- - runAndTransition(app.packIntoRectangle)} - > - - Pack into rectangle - - - - )} + {app.selectedShapes?.size > 1 && + !app.readOnly && + app.selectedShapesArray?.some(s => !s.props.isLocked) && ( + <> + +
+ + + + + +
+
+ + + + + +
+
+ + runAndTransition(app.packIntoRectangle)} + > + + Pack into rectangle + + + + )} {app.selectedShapes?.size > 0 && ( <> - {MOD_KEY} 1 + 2
@@ -145,6 +149,7 @@ export const ContextMenu = observer(function ContextMenu({ )} {(app.selectedShapesArray.some(s => s.type === 'group' || app.getParentGroup(s)) || app.selectedShapesArray.length > 1) && + app.selectedShapesArray?.some(s => !s.props.isLocked) && !app.readOnly && ( <> {app.selectedShapesArray.some(s => s.type === 'group' || app.getParentGroup(s)) && ( @@ -161,24 +166,25 @@ export const ContextMenu = observer(function ContextMenu({ )} - {app.selectedShapesArray.length > 1 && ( - runAndTransition(app.api.doGroup)} - > - - Group -
- - {MOD_KEY} G - -
-
- )} + {app.selectedShapesArray.length > 1 && + app.selectedShapesArray?.some(s => !s.props.isLocked) && ( + runAndTransition(app.api.doGroup)} + > + + Group +
+ + {MOD_KEY} G + +
+
+ )} )} - {app.selectedShapes?.size > 0 && ( + {app.selectedShapes?.size > 0 && app.selectedShapesArray?.some(s => !s.props.isLocked) && ( <> {!app.readOnly && ( )} - - - runAndTransition(() => - handlers.exportToImage(app.currentPageId, { - x: app.selectionBounds.minX + app.viewport.camera.point[0] - EXPORT_PADDING, - y: app.selectionBounds.minY + app.viewport.camera.point[1] - EXPORT_PADDING, - width: app.selectionBounds?.width + EXPORT_PADDING * 2, - height: app.selectionBounds?.height + EXPORT_PADDING * 2, - zoom: app.viewport.camera.zoom, - }) - ) - } - > - - Export -
- -
-
+ {app.selectedShapes?.size > 0 && ( + <> + + + runAndTransition(() => + handlers.exportToImage(app.currentPageId, { + x: app.selectionBounds.minX + app.viewport.camera.point[0] - EXPORT_PADDING, + y: app.selectionBounds.minY + app.viewport.camera.point[1] - EXPORT_PADDING, + width: app.selectionBounds?.width + EXPORT_PADDING * 2, + height: app.selectionBounds?.height + EXPORT_PADDING * 2, + zoom: app.viewport.camera.zoom, + }) + ) + } + > + + Export +
+ +
+
+ + )} )} - {app.selectedShapes?.size > 0 && !app.readOnly && ( - <> - runAndTransition(app.api.deleteShapes)} - > - - Delete -
- - Del - -
-
- {app.selectedShapes?.size > 1 && !app.readOnly && ( - <> - - runAndTransition(app.flipHorizontal)} - > - - Flip horizontally - - runAndTransition(app.flipVertical)} - > - - Flip vertically - - - )} - {!app.readOnly && ( - <> - - runAndTransition(app.bringToFront)} - > - Move to front -
- - ] - -
-
- runAndTransition(app.sendToBack)} - > - Move to back -
- - [ - -
-
- - )} - - {developerMode && ( + {app.selectedShapes?.size > 0 && app.selectedShapesArray?.some(s => !s.props.isLocked) && ( + runAndTransition(() => app.setLocked(true))} + > + + Lock +
+ + {MOD_KEY} L + +
+
+ )} + {app.selectedShapes?.size > 0 && app.selectedShapesArray?.some(s => s.props.isLocked) && ( + runAndTransition(() => app.setLocked(false))} + > + + Unlock +
+ + {MOD_KEY} L + +
+
+ )} + {app.selectedShapes?.size > 0 && + !app.readOnly && + app.selectedShapesArray?.some(s => !s.props.isLocked) && ( + <> { - if (app.selectedShapesArray.length === 1) { - console.log(toJS(app.selectedShapesArray[0].serialized)) - } else { - console.log(app.selectedShapesArray.map(s => toJS(s.serialized))) - } - }} + onClick={() => runAndTransition(app.api.deleteShapes)} > - (Dev) Print shape props + + Delete +
+ + Del + +
- )} - - )} + {app.selectedShapes?.size > 1 && !app.readOnly && ( + <> + + runAndTransition(app.flipHorizontal)} + > + + Flip horizontally + + runAndTransition(app.flipVertical)} + > + + Flip vertically + + + )} + {!app.readOnly && ( + <> + + runAndTransition(app.bringToFront)} + > + Move to front +
+ + ] + +
+
+ runAndTransition(app.sendToBack)} + > + Move to back +
+ + [ + +
+
+ + )} + + {developerMode && ( + { + if (app.selectedShapesArray.length === 1) { + console.log(toJS(app.selectedShapesArray[0].serialized)) + } else { + console.log(app.selectedShapesArray.map(s => toJS(s.serialized))) + } + }} + > + (Dev) Print shape props + + )} + + )} diff --git a/tldraw/apps/tldraw-logseq/src/components/Devtools/Devtools.tsx b/tldraw/apps/tldraw-logseq/src/components/Devtools/Devtools.tsx index fd97e1857c..08031fcd07 100644 --- a/tldraw/apps/tldraw-logseq/src/components/Devtools/Devtools.tsx +++ b/tldraw/apps/tldraw-logseq/src/components/Devtools/Devtools.tsx @@ -1,59 +1,12 @@ -import { useApp, useRendererContext } from '@tldraw/react' -import { autorun } from 'mobx' +import { useRendererContext } from '@tldraw/react' import { observer } from 'mobx-react-lite' import React from 'react' import ReactDOM from 'react-dom' -import type { Shape } from '../../lib' const printPoint = (point: number[]) => { return `[${point.map(d => d?.toFixed(2) ?? '-').join(', ')}]` } -const HistoryStack = observer(function HistoryStack() { - const app = useApp() - const anchorRef = React.useRef() - const [_, setTick] = React.useState(0) - - React.useEffect(() => { - anchorRef.current = document.createElement('div') - anchorRef.current.style.display = 'contents' - document.body.append(anchorRef.current) - setTick(tick => tick + 1) - return () => { - anchorRef.current?.remove() - } - }, []) - - React.useEffect(() => { - requestAnimationFrame(() => { - anchorRef.current - ?.querySelector(`[data-item-index="${app.history.pointer}"]`) - ?.scrollIntoView() - }) - }, [app.history.pointer]) - - return anchorRef.current - ? ReactDOM.createPortal( -
- {app.history.stack.map((item, i) => ( -
app.history.setPointer(i)} - className="flex items-center rounded-lg px-2 h-[32px] whitespace-nowrap" - > - {item.pages[0].nonce} -
- ))} -
, - anchorRef.current - ) - : null -}) - export const DevTools = observer(() => { const { viewport: { @@ -63,13 +16,9 @@ export const DevTools = observer(() => { inputs, } = useRendererContext() - const canvasAnchorRef = React.useRef() const statusbarAnchorRef = React.useRef() React.useEffect(() => { - const canvasAnchor = document.getElementById('tl-dev-tools-canvas-anchor') - canvasAnchorRef.current = canvasAnchor - const statusbarAnchor = document.getElementById('tl-statusbar-anchor') statusbarAnchorRef.current = statusbarAnchor }, []) @@ -84,15 +33,6 @@ export const DevTools = observer(() => { .map(p => p.join('')) .join('|') - const originPoint = canvasAnchorRef.current - ? ReactDOM.createPortal( - - - , - canvasAnchorRef.current - ) - : null - const rendererStatus = statusbarAnchorRef.current ? ReactDOM.createPortal(
{ return ( <> - {originPoint} {rendererStatus} ) diff --git a/tldraw/apps/tldraw-logseq/src/components/GeometryTools/GeometryTools.tsx b/tldraw/apps/tldraw-logseq/src/components/GeometryTools/GeometryTools.tsx index 682f681529..9f93e028b1 100644 --- a/tldraw/apps/tldraw-logseq/src/components/GeometryTools/GeometryTools.tsx +++ b/tldraw/apps/tldraw-logseq/src/components/GeometryTools/GeometryTools.tsx @@ -1,11 +1,22 @@ -import { useApp } from '@tldraw/react' import { observer } from 'mobx-react-lite' -import * as React from 'react' +import type { Side } from '@radix-ui/react-popper' import { ToolButton } from '../ToolButton' import * as Popover from '@radix-ui/react-popover' import { TablerIcon } from '../icons' -export const GeometryTools = observer(function GeometryTools() { +interface GeometryToolsProps extends React.HTMLAttributes { + popoverSide?: Side + activeGeometry?: string + setGeometry: (e: React.MouseEvent) => void + chevron?: boolean +} + +export const GeometryTools = observer(function GeometryTools({ + popoverSide = "left", + setGeometry, + activeGeometry, + chevron = true, + ...rest}: GeometryToolsProps) { const geometries = [ { id: 'box', @@ -24,34 +35,33 @@ export const GeometryTools = observer(function GeometryTools() { }, ] - const app = useApp() - const [activeGeomId, setActiveGeomId] = React.useState( - () => (geometries.find(geo => geo.id === app.selectedTool.id) ?? geometries[0]).id - ) + const shapes = { + id: 'shapes', + icon: 'triangle-square-circle', + tooltip: 'Shape', + } - React.useEffect(() => { - setActiveGeomId(prevId => { - return geometries.find(geo => geo.id === app.selectedTool.id)?.id ?? prevId - }) - }, [app.selectedTool.id]) + const activeTool = activeGeometry ? geometries.find(geo => geo.id === activeGeometry) : shapes return ( - -
- geo.id === activeGeomId)!} /> - geo.id === app.selectedTool.id)} - className="tl-popover-indicator" - name="chevron-down-left" - /> + +
+ + {chevron && + + }
- -
+ +
{geometries.map(props => ( - + ))}
diff --git a/tldraw/apps/tldraw-logseq/src/components/PrimaryTools/PrimaryTools.tsx b/tldraw/apps/tldraw-logseq/src/components/PrimaryTools/PrimaryTools.tsx index cd16e4d83d..7f7cd066c6 100644 --- a/tldraw/apps/tldraw-logseq/src/components/PrimaryTools/PrimaryTools.tsx +++ b/tldraw/apps/tldraw-logseq/src/components/PrimaryTools/PrimaryTools.tsx @@ -1,4 +1,5 @@ import { useApp } from '@tldraw/react' +import { Geometry } from '@tldraw/core' import { observer } from 'mobx-react-lite' import * as React from 'react' import { ToolButton } from '../ToolButton' @@ -14,23 +15,43 @@ export const PrimaryTools = observer(function PrimaryTools() { app.api.setColor(color) }, []) + const handleToolClick = React.useCallback( + (e: React.MouseEvent) => { + const tool = e.currentTarget.dataset.tool + if (tool) app.selectTool(tool) + }, + [] + ) + + const [activeGeomId, setActiveGeomId] = React.useState( + () => (Object.values(Geometry).find((geo: string) => geo === app.selectedTool.id) ?? Object.values(Geometry)[0]) + ) + + React.useEffect(() => { + setActiveGeomId((prevId: Geometry) => { + return Object.values(Geometry).find((geo: string) => geo === app.selectedTool.id) ?? prevId + }) + }, [app.selectedTool.id]) + + return (
- + app.selectTool("select")} tooltip="Select" id="select" icon="select-cursor" /> app.selectTool("move")} tooltip="Move" id="move" icon={app.isIn('move.panning') ? 'hand-grab' : 'hand-stop'} /> - - - - - - - + app.selectTool("logseq-portal")} tooltip="Add block or page" id="logseq-portal" icon="circle-plus" /> + app.selectTool("pencil")} tooltip="Draw" id="pencil" icon="ballpen" /> + app.selectTool("highlighter")} tooltip="Highlight" id="highlighter" icon="highlight" /> + app.selectTool("erase")} tooltip="Eraser" id="erase" icon="eraser" /> + app.selectTool("line")} tooltip="Connector" id="line" icon="connector" /> + app.selectTool("text")} tooltip="Text" id="text" icon="text" /> + ) => void } -export const ToolButton = observer(({ id, icon, tooltip, ...props }: ToolButtonProps) => { +export const ToolButton = observer(({ id, icon, tooltip, tooltipSide = "left", handleClick, ...props }: ToolButtonProps) => { const app = useApp() - const handleToolClick = React.useCallback( - (e: React.MouseEvent) => { - const tool = e.currentTarget.dataset.tool - if (tool) app.selectTool(tool) - }, - [app] - ) - // Tool must exist const Tool = [...app.Tools, TLSelectTool, TLMoveTool]?.find(T => T.id === id) - const shortcuts = (Tool as any)['shortcut'] + const shortcuts = (Tool as any)?.['shortcut'] - const tooltipContent = shortcuts ? ( + const tooltipContent = shortcuts && tooltip ? ( <> {tooltip} @@ -43,11 +38,11 @@ export const ToolButton = observer(({ id, icon, tooltip, ...props }: ToolButtonP return ( diff --git a/tldraw/apps/tldraw-logseq/src/components/ZoomMenu/ZoomMenu.tsx b/tldraw/apps/tldraw-logseq/src/components/ZoomMenu/ZoomMenu.tsx index a97fd410e2..09fa5aad8e 100644 --- a/tldraw/apps/tldraw-logseq/src/components/ZoomMenu/ZoomMenu.tsx +++ b/tldraw/apps/tldraw-logseq/src/components/ZoomMenu/ZoomMenu.tsx @@ -41,7 +41,7 @@ export const ZoomMenu = observer(function ZoomMenu(): JSX.Element { Zoom to fit selection
- {MOD_KEY} 1 + 2
diff --git a/tldraw/apps/tldraw-logseq/src/hooks/usePaste.ts b/tldraw/apps/tldraw-logseq/src/hooks/usePaste.ts index e52134149e..1c7d2ac570 100644 --- a/tldraw/apps/tldraw-logseq/src/hooks/usePaste.ts +++ b/tldraw/apps/tldraw-logseq/src/hooks/usePaste.ts @@ -399,6 +399,7 @@ const handleCreatingShapes = async ( return { ...shape, parentId: app.currentPageId, + isLocked: false, id: validUUID(shape.id) ? shape.id : uniqueId(), } }) diff --git a/tldraw/apps/tldraw-logseq/src/lib/shapes/BoxShape.tsx b/tldraw/apps/tldraw-logseq/src/lib/shapes/BoxShape.tsx index 012605cf9d..876068ca33 100644 --- a/tldraw/apps/tldraw-logseq/src/lib/shapes/BoxShape.tsx +++ b/tldraw/apps/tldraw-logseq/src/lib/shapes/BoxShape.tsx @@ -166,12 +166,20 @@ export class BoxShape extends TLBoxShape { props: { size: [w, h], borderRadius, + isLocked, }, } = this return ( - + ) }) diff --git a/tldraw/apps/tldraw-logseq/src/lib/shapes/DotShape.tsx b/tldraw/apps/tldraw-logseq/src/lib/shapes/DotShape.tsx index 8cd6fd2951..26edd9a9da 100644 --- a/tldraw/apps/tldraw-logseq/src/lib/shapes/DotShape.tsx +++ b/tldraw/apps/tldraw-logseq/src/lib/shapes/DotShape.tsx @@ -43,8 +43,16 @@ export class DotShape extends TLDotShape { }) ReactIndicator = observer(() => { - const { radius } = this.props - return + const { radius, isLocked } = this.props + return ( + + ) }) validateProps = (props: Partial) => { diff --git a/tldraw/apps/tldraw-logseq/src/lib/shapes/EllipseShape.tsx b/tldraw/apps/tldraw-logseq/src/lib/shapes/EllipseShape.tsx index 965c8bcc7c..3fb4a3c512 100644 --- a/tldraw/apps/tldraw-logseq/src/lib/shapes/EllipseShape.tsx +++ b/tldraw/apps/tldraw-logseq/src/lib/shapes/EllipseShape.tsx @@ -154,11 +154,20 @@ export class EllipseShape extends TLEllipseShape { ReactIndicator = observer(() => { const { size: [w, h], + isLocked, } = this.props return ( - + ) }) diff --git a/tldraw/apps/tldraw-logseq/src/lib/shapes/HTMLShape.tsx b/tldraw/apps/tldraw-logseq/src/lib/shapes/HTMLShape.tsx index 15a0471749..90af1dcd4b 100644 --- a/tldraw/apps/tldraw-logseq/src/lib/shapes/HTMLShape.tsx +++ b/tldraw/apps/tldraw-logseq/src/lib/shapes/HTMLShape.tsx @@ -136,9 +136,17 @@ export class HTMLShape extends TLBoxShape { const { props: { size: [w, h], + isLocked, }, } = this - return + return ( + + ) }) validateProps = (props: Partial) => { diff --git a/tldraw/apps/tldraw-logseq/src/lib/shapes/HighlighterShape.tsx b/tldraw/apps/tldraw-logseq/src/lib/shapes/HighlighterShape.tsx index a428be44e6..968263fae1 100644 --- a/tldraw/apps/tldraw-logseq/src/lib/shapes/HighlighterShape.tsx +++ b/tldraw/apps/tldraw-logseq/src/lib/shapes/HighlighterShape.tsx @@ -83,8 +83,10 @@ export class HighlighterShape extends TLDrawShape { } ReactIndicator = observer(() => { - const { pointsPath } = this - return + const { pointsPath, props } = this + return ( + + ) }) validateProps = (props: Partial) => { diff --git a/tldraw/apps/tldraw-logseq/src/lib/shapes/IFrameShape.tsx b/tldraw/apps/tldraw-logseq/src/lib/shapes/IFrameShape.tsx index f448b3804a..939f8da63e 100644 --- a/tldraw/apps/tldraw-logseq/src/lib/shapes/IFrameShape.tsx +++ b/tldraw/apps/tldraw-logseq/src/lib/shapes/IFrameShape.tsx @@ -82,8 +82,18 @@ export class IFrameShape extends TLBoxShape { const { props: { size: [w, h], + isLocked, }, } = this - return + return ( + + ) }) } diff --git a/tldraw/apps/tldraw-logseq/src/lib/shapes/ImageShape.tsx b/tldraw/apps/tldraw-logseq/src/lib/shapes/ImageShape.tsx index c3dc31a5a5..0a6f970e41 100644 --- a/tldraw/apps/tldraw-logseq/src/lib/shapes/ImageShape.tsx +++ b/tldraw/apps/tldraw-logseq/src/lib/shapes/ImageShape.tsx @@ -74,9 +74,17 @@ export class ImageShape extends TLImageShape { const { props: { size: [w, h], + isLocked, }, } = this - return + return ( + + ) }) getShapeSVGJsx({ assets }: { assets: TLAsset[] }) { diff --git a/tldraw/apps/tldraw-logseq/src/lib/shapes/LineShape.tsx b/tldraw/apps/tldraw-logseq/src/lib/shapes/LineShape.tsx index f113d870e7..9f3f0ec84d 100644 --- a/tldraw/apps/tldraw-logseq/src/lib/shapes/LineShape.tsx +++ b/tldraw/apps/tldraw-logseq/src/lib/shapes/LineShape.tsx @@ -77,7 +77,7 @@ export class LineShape extends TLLineShape { const labelSize = label || isEditing ? getTextLabelSize( - label || "Enter text", + label || 'Enter text', { fontFamily: 'var(--ls-font-family)', fontSize, lineHeight: 1, fontWeight }, 6 ) @@ -99,7 +99,11 @@ export class LineShape extends TLLineShape { [label] ) return ( -
+
{ fontSize, fontWeight, handles: { start, end }, + isLocked, } = this.props const bounds = this.getBounds() const labelSize = @@ -176,6 +181,7 @@ export class LineShape extends TLLineShape { decorations?.start, decorations?.end )} + strokeDasharray={isLocked ? '8 2' : 'undefined'} /> {label && !isEditing && (
{children}
@@ -148,8 +148,7 @@ export class LogseqPortalShape extends TLBoxShape { return this.props.blockType === 'B' ? this.props.compact : this.props.collapsed } - @action toggleCollapsed = async () => { - const collapsed = !this.collapsed + @action setCollapsed = async (collapsed: boolean) => { if (this.props.blockType === 'B') { this.update({ compact: collapsed }) this.canResize[1] = !collapsed @@ -191,28 +190,30 @@ export class LogseqPortalShape extends TLBoxShape { const [size, setSize] = React.useState<[number, number]>([0, 0]) const app = useApp() React.useEffect(() => { - if (ref?.current) { - const el = selector ? ref.current.querySelector(selector) : ref.current - if (el) { - const updateSize = () => { - const { width, height } = el.getBoundingClientRect() - const bound = Vec.div([width, height], app.viewport.camera.zoom) as [number, number] - setSize(bound) - return bound - } - updateSize() - // Hacky, I know 🤨 - this.getInnerHeight = () => updateSize()[1] - const resizeObserver = new ResizeObserver(() => { + setTimeout(() => { + if (ref?.current) { + const el = selector ? ref.current.querySelector(selector) : ref.current + if (el) { + const updateSize = () => { + const { width, height } = el.getBoundingClientRect() + const bound = Vec.div([width, height], app.viewport.camera.zoom) as [number, number] + setSize(bound) + return bound + } updateSize() - }) - resizeObserver.observe(el) - return () => { - resizeObserver.disconnect() + // Hacky, I know 🤨 + this.getInnerHeight = () => updateSize()[1] + const resizeObserver = new ResizeObserver(() => { + updateSize() + }) + resizeObserver.observe(el) + return () => { + resizeObserver.disconnect() + } } } - } - return () => {} + return () => {} + }, 10); }, [ref, selector]) return size } @@ -349,7 +350,7 @@ export class LogseqPortalShape extends TLBoxShape { ReactComponent = observer((componentProps: TLComponentProps) => { const { events, isErasing, isEditing, isBinding } = componentProps const { - props: { opacity, pageId, fill, scaleLevel, strokeWidth, size }, + props: { opacity, pageId, fill, scaleLevel, strokeWidth, size, isLocked }, } = this const app = useApp() @@ -493,41 +494,30 @@ export class LogseqPortalShape extends TLBoxShape { placeholder="Create or search your graph..." /> ) : ( - <> -
- {!this.props.compact && !targetNotFound && ( - - {this.props.blockType === 'P' ? ( - - ) : ( - - )} - - )} - {targetNotFound &&
Target not found
} - {showingPortal && } -
- {!app.readOnly && ( - +
+ {!this.props.compact && !targetNotFound && ( + + {this.props.blockType === 'P' ? ( + + ) : ( + + )} + )} - + {targetNotFound &&
Target not found
} + {showingPortal && } +
)}
@@ -536,7 +526,16 @@ export class LogseqPortalShape extends TLBoxShape { ReactIndicator = observer(() => { const bounds = this.getBounds() - return + return ( + + ) }) validateProps = (props: Partial) => { diff --git a/tldraw/apps/tldraw-logseq/src/lib/shapes/PenShape.tsx b/tldraw/apps/tldraw-logseq/src/lib/shapes/PenShape.tsx index f00e0788a7..31126ac08c 100644 --- a/tldraw/apps/tldraw-logseq/src/lib/shapes/PenShape.tsx +++ b/tldraw/apps/tldraw-logseq/src/lib/shapes/PenShape.tsx @@ -66,7 +66,7 @@ export class PenShape extends TLDrawShape { ReactIndicator = observer(() => { const { pointsPath } = this - return + return }) validateProps = (props: Partial) => { diff --git a/tldraw/apps/tldraw-logseq/src/lib/shapes/PencilShape.tsx b/tldraw/apps/tldraw-logseq/src/lib/shapes/PencilShape.tsx index bf30699c9e..f59afe7b65 100644 --- a/tldraw/apps/tldraw-logseq/src/lib/shapes/PencilShape.tsx +++ b/tldraw/apps/tldraw-logseq/src/lib/shapes/PencilShape.tsx @@ -133,7 +133,7 @@ export class PencilShape extends TLDrawShape { ReactIndicator = observer(() => { const { pointsPath } = this - return + return }) validateProps = (props: Partial) => { diff --git a/tldraw/apps/tldraw-logseq/src/lib/shapes/PolygonShape.tsx b/tldraw/apps/tldraw-logseq/src/lib/shapes/PolygonShape.tsx index a3931b0e8c..b5a36565d0 100644 --- a/tldraw/apps/tldraw-logseq/src/lib/shapes/PolygonShape.tsx +++ b/tldraw/apps/tldraw-logseq/src/lib/shapes/PolygonShape.tsx @@ -168,7 +168,7 @@ export class PolygonShape extends TLPolygonShape { ReactIndicator = observer(() => { const { offset: [x, y], - props: { strokeWidth }, + props: { strokeWidth, isLocked }, } = this return ( @@ -176,6 +176,7 @@ export class PolygonShape extends TLPolygonShape { ) diff --git a/tldraw/apps/tldraw-logseq/src/lib/shapes/TextShape.tsx b/tldraw/apps/tldraw-logseq/src/lib/shapes/TextShape.tsx index 1f94e82e98..25f64a0955 100644 --- a/tldraw/apps/tldraw-logseq/src/lib/shapes/TextShape.tsx +++ b/tldraw/apps/tldraw-logseq/src/lib/shapes/TextShape.tsx @@ -240,7 +240,7 @@ export class TextShape extends TLTextShape { ReactIndicator = observer(({ isEditing }: TLComponentProps) => { const { - props: { borderRadius }, + props: { borderRadius, isLocked }, bounds, } = this return isEditing ? null : ( @@ -250,6 +250,7 @@ export class TextShape extends TLTextShape { rx={borderRadius} ry={borderRadius} fill="transparent" + strokeDasharray={isLocked ? '8 2' : 'undefined'} /> ) }) diff --git a/tldraw/apps/tldraw-logseq/src/lib/shapes/TweetShape.tsx b/tldraw/apps/tldraw-logseq/src/lib/shapes/TweetShape.tsx index 620fdf0026..77d560ef5b 100644 --- a/tldraw/apps/tldraw-logseq/src/lib/shapes/TweetShape.tsx +++ b/tldraw/apps/tldraw-logseq/src/lib/shapes/TweetShape.tsx @@ -90,10 +90,10 @@ export class TweetShape extends TLBoxShape { }} > {this.embedId ? ( -
- -
- ) : (null)} +
+ +
+ ) : null}
) @@ -103,9 +103,19 @@ export class TweetShape extends TLBoxShape { const { props: { size: [w, h], + isLocked, }, } = this - return + return ( + + ) }) useComponentSize(ref: React.RefObject | null, selector = '') { @@ -191,12 +201,7 @@ export class TweetShape extends TLBoxShape { if (embedId) { return ( - + { const { props: { size: [w, h], + isLocked, }, } = this - return + return ( + + ) }) } diff --git a/tldraw/apps/tldraw-logseq/src/lib/shapes/YouTubeShape.tsx b/tldraw/apps/tldraw-logseq/src/lib/shapes/YouTubeShape.tsx index 6088d0a1b9..0d636fd5ca 100644 --- a/tldraw/apps/tldraw-logseq/src/lib/shapes/YouTubeShape.tsx +++ b/tldraw/apps/tldraw-logseq/src/lib/shapes/YouTubeShape.tsx @@ -119,9 +119,19 @@ export class YouTubeShape extends TLBoxShape { const { props: { size: [w, h], + isLocked, }, } = this - return + return ( + + ) }) validateProps = (props: Partial) => { diff --git a/tldraw/apps/tldraw-logseq/src/styles.css b/tldraw/apps/tldraw-logseq/src/styles.css index 5c41519888..798d48aeb9 100644 --- a/tldraw/apps/tldraw-logseq/src/styles.css +++ b/tldraw/apps/tldraw-logseq/src/styles.css @@ -246,6 +246,10 @@ html[data-theme='light'] { outline: none; } } + + .tl-geometry-tools-pane-anchor > .tl-button { + border: 1px solid var(--ls-secondary-border-color); + } } .tl-statusbar { @@ -318,7 +322,7 @@ button.tl-select-input-trigger { @apply flex items-center px-3; box-shadow: 0 0 0 1px var(--ls-secondary-border-color); background-color: var(--ls-secondary-background-color); - border-radius: 8px; + border-radius: 0.25rem; font-size: 16px; height: 100%; color: var(--ls-secondary-text-color); @@ -543,7 +547,7 @@ button.tl-select-input-trigger { } .tl-circle-button { - @apply absolute flex items-center justify-center transition-all rounded-full shadow; + @apply absolute flex items-center justify-center rounded-full shadow; color: var(--ls-primary-text-color); background-color: var(--ls-secondary-background-color); @@ -553,60 +557,14 @@ button.tl-select-input-trigger { width: 34px; border: 2px solid var(--ls-secondary-background-color); top: 2px; - transition-delay: 0; .tie { transform: translateY(-100%); } - &[data-active='false']:hover:not([data-recently-changed='true']) { - .tie { - transform: translateY(0); - - &:first-of-type { - opacity: 0.6; - } - } - } - - &[data-active='true'] { - background-color: var(--ls-active-primary-color); - color: var(--ls-block-highlight-color); - border: 2px solid var(--ls-active-primary-color); - - .tie { - transform: translateY(0); - - &:last-of-type { - opacity: 0.6; - } - } - - &:hover:not([data-recently-changed='true']) { - color: var(--ls-primary-text-color); - background-color: var(--ls-secondary-background-color); - - .tie { - transform: translateY(-100%); - } - } - } - .tl-circle-button-icons-wrapper { @apply flex flex-col; } - - i.tie { - transition: transform 0.2s ease-in-out; - transition-delay: 0; - } - - .tl-circle-button-icons-wrapper[data-icons-count='2'] { - position: relative; - width: 22px; - height: 22px; - overflow: hidden; - } } .tl-quick-search-input-container { @@ -957,7 +915,6 @@ html[data-theme='dark'] { .tl-geometry-toolbar { box-shadow: none; - flex-flow: column; } .tl-popover-arrow { @@ -1214,6 +1171,10 @@ button.tl-shape-links-panel-item-remove-button { fill: var(--ls-secondary-background-color); } +.tl-locked { + stroke-dasharray: calc(3px * var(--tl-scale)) calc(3px * var(--tl-scale)); +} + @keyframes fadeIn { from { opacity: 0; diff --git a/tldraw/packages/core/src/lib/TLApi/TLApi.ts b/tldraw/packages/core/src/lib/TLApi/TLApi.ts index 2c42770391..38c1f9f7e9 100644 --- a/tldraw/packages/core/src/lib/TLApi/TLApi.ts +++ b/tldraw/packages/core/src/lib/TLApi/TLApi.ts @@ -11,8 +11,10 @@ export class TLApi { - this.app.transition('select').selectedTool.transition('editingShape', { shape }) + editShape = (shape: S | undefined): this => { + if (!shape?.props.isLocked) + this.app.transition('select').selectedTool.transition('editingShape', { shape }) + return this } @@ -41,7 +43,7 @@ export class TLApi(...shapes: ({ id: string } & Partial)[]): this => { + updateShapes = (...shapes: ({ id: string, type: string } & Partial)[]): this => { this.app.updateShapes(shapes) return this } @@ -174,7 +176,7 @@ export class TLApi { - s.update({ fill: color, stroke: color }) + if (!s.props.isLocked) s.update({ fill: color, stroke: color }) }) this.app.persist() @@ -187,7 +189,7 @@ export class TLApi { - shape.setScaleLevel(scaleLevel) + if (!shape.props.isLocked) shape.setScaleLevel(scaleLevel) }) this.app.persist() @@ -425,4 +427,27 @@ export class TLApi { + const ShapeClass = this.app.getShapeClass(type) + + this.app.currentPage.removeShapes(...shapes) + const clones = shapes.map(s => { + return new ShapeClass({ + ...s.serialized, + type: type, + }) + }) + this.app.currentPage.addShapes(...clones) + this.app.persist() + this.app.setSelectedShapes(clones) + } + + setCollapsed = (collapsed: boolean, shapes: S[] = this.app.allSelectedShapesArray) => { + shapes.forEach(shape => { + if (shape.props.type === 'logseq-portal') + shape.setCollapsed(collapsed) + }) + this.app.persist() + } } diff --git a/tldraw/packages/core/src/lib/TLApp/TLApp.ts b/tldraw/packages/core/src/lib/TLApp/TLApp.ts index 4b4e4fee48..c662297dca 100644 --- a/tldraw/packages/core/src/lib/TLApp/TLApp.ts +++ b/tldraw/packages/core/src/lib/TLApp/TLApp.ts @@ -113,9 +113,17 @@ export class TLApp< fn: () => this.api.zoomToFit(), }, { - keys: 'mod+shift+1', + keys: 'shift+2', fn: () => this.api.zoomToSelection(), }, + { + keys: 'mod+up', + fn: () => this.api.setCollapsed(true), + }, + { + keys: 'mod+down', + fn: () => this.api.setCollapsed(false), + }, { keys: 'mod+-', fn: () => this.api.zoomOut(), @@ -197,6 +205,18 @@ export class TLApp< this.api.toggleGrid() }, }, + { + keys: 'mod+l', + fn: () => { + this.setLocked(true) + }, + }, + { + keys: 'mod+shift+l', + fn: () => { + this.setLocked(false) + }, + }, ] // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore @@ -335,10 +355,16 @@ export class TLApp< return this } - @action updateShapes = (shapes: ({ id: string } & Partial)[]): this => { + @action updateShapes = (shapes: ({ id: string, type: string } & Partial)[]): this => { if (this.readOnly) return this - shapes.forEach(shape => this.getShapeById(shape.id)?.update(shape)) + shapes.forEach(shape => { + const oldShape = this.getShapeById(shape.id) + oldShape?.update(shape) + if (shape.type !== oldShape?.type) { + this.api.convertShapes(shape.type , [oldShape]) + } + }) this.persist() return this } @@ -348,6 +374,7 @@ export class TLApp< const normalizedShapes: S[] = shapes .map(shape => (typeof shape === 'string' ? this.getShapeById(shape) : shape)) .filter(isNonNullable) + .filter(s => !s.props.isLocked) // delete a group shape should also delete its children const shapesInGroups = this.shapesInGroups(normalizedShapes) @@ -381,6 +408,7 @@ export class TLApp< } this.currentPage.shapes + .filter(s => !s.props.isLocked) .flatMap(s => Object.values(s.props.handles ?? {})) .flatMap(h => h.bindingId) .filter(isNonNullable) @@ -519,6 +547,17 @@ export class TLApp< return this } + setLocked = (locked: boolean): this => { + if (this.selectedShapesArray.length === 0 || this.readOnly) return this + + this.selectedShapesArray.forEach(shape => { + shape.update({ isLocked: locked }) + }) + + this.persist() + return this + } + /* --------------------- Assets --------------------- */ @observable assets: Record = {} diff --git a/tldraw/packages/core/src/lib/tools/TLSelectTool/states/HoveringSelectionHandleState.ts b/tldraw/packages/core/src/lib/tools/TLSelectTool/states/HoveringSelectionHandleState.ts index b7cfc41991..1ab601d879 100644 --- a/tldraw/packages/core/src/lib/tools/TLSelectTool/states/HoveringSelectionHandleState.ts +++ b/tldraw/packages/core/src/lib/tools/TLSelectTool/states/HoveringSelectionHandleState.ts @@ -74,7 +74,7 @@ export class HoveringSelectionHandleState< if (!isSingle) return const selectedShape = getFirstFromSet(this.app.selectedShapes) - if (selectedShape.canEdit && !this.app.readOnly) { + if (selectedShape.canEdit && !this.app.readOnly && !selectedShape.props.isLocked) { switch (info.type) { case TLTargetType.Shape: { this.tool.transition('editingShape', info) diff --git a/tldraw/packages/core/src/lib/tools/TLSelectTool/states/IdleState.ts b/tldraw/packages/core/src/lib/tools/TLSelectTool/states/IdleState.ts index bc00031c64..58c42cc7c7 100644 --- a/tldraw/packages/core/src/lib/tools/TLSelectTool/states/IdleState.ts +++ b/tldraw/packages/core/src/lib/tools/TLSelectTool/states/IdleState.ts @@ -124,7 +124,7 @@ export class IdleState< if (info.order || this.app.selectedShapesArray.length !== 1 || this.app.readOnly) return const selectedShape = this.app.selectedShapesArray[0] - if (!selectedShape.canEdit) return + if (!selectedShape.canEdit || selectedShape.props.isLocked) return switch (info.type) { case TLTargetType.Shape: { diff --git a/tldraw/packages/core/src/lib/tools/TLSelectTool/states/PointingSelectedShapeState.ts b/tldraw/packages/core/src/lib/tools/TLSelectTool/states/PointingSelectedShapeState.ts index 80b379c00f..3e12764104 100644 --- a/tldraw/packages/core/src/lib/tools/TLSelectTool/states/PointingSelectedShapeState.ts +++ b/tldraw/packages/core/src/lib/tools/TLSelectTool/states/PointingSelectedShapeState.ts @@ -49,6 +49,7 @@ export class PointingSelectedShapeState< selectedShapesArray.length === 1 && this.pointedSelectedShape.canEdit && !this.app.readOnly && + !this.pointedSelectedShape.props.isLocked && this.pointedSelectedShape instanceof TLBoxShape && PointUtils.pointInBounds(currentPoint, this.pointedSelectedShape.bounds) ) { diff --git a/tldraw/packages/core/src/lib/tools/TLSelectTool/states/TranslatingState.ts b/tldraw/packages/core/src/lib/tools/TLSelectTool/states/TranslatingState.ts index 4e8ae316f4..b5d49cb096 100644 --- a/tldraw/packages/core/src/lib/tools/TLSelectTool/states/TranslatingState.ts +++ b/tldraw/packages/core/src/lib/tools/TLSelectTool/states/TranslatingState.ts @@ -30,7 +30,6 @@ export class TranslatingState< private moveSelectedShapesToPointer() { const { - selectedShapes, inputs: { shiftKey, originPoint, currentPoint }, } = this.app @@ -47,8 +46,8 @@ export class TranslatingState< } transaction(() => { - this.app.allSelectedShapes.forEach(shape => { - shape.update({ point: Vec.add(initialPoints[shape.id], delta) }) + this.app.allSelectedShapesArray.forEach(shape => { + if (!shape.props.isLocked) shape.update({ point: Vec.add(initialPoints[shape.id], delta) }) }) }) } @@ -66,6 +65,7 @@ export class TranslatingState< type: shape.type, point: this.initialPoints[shape.id], rotation: shape.props.rotation, + isLocked: false, }) return clone }) diff --git a/tldraw/packages/core/src/types/types.ts b/tldraw/packages/core/src/types/types.ts index 58d0b4289d..72253792fe 100644 --- a/tldraw/packages/core/src/types/types.ts +++ b/tldraw/packages/core/src/types/types.ts @@ -14,6 +14,12 @@ export enum Color { Default = '', } +export enum Geometry { + Box = 'box', + Ellipse = 'ellipse', + Polygon = 'polygon', +} + export enum AlignType { Top = 'top', CenterVertical = 'centerVertical', diff --git a/tldraw/packages/react/src/components/Canvas/Canvas.tsx b/tldraw/packages/react/src/components/Canvas/Canvas.tsx index 403f875ec7..5969563c62 100644 --- a/tldraw/packages/react/src/components/Canvas/Canvas.tsx +++ b/tldraw/packages/react/src/components/Canvas/Canvas.tsx @@ -168,6 +168,7 @@ export const Canvas = observer(function Renderer({ isHovered={false} isBinding={false} isSelected={true} + isLocked={shape.props.isLocked} /> ))} {hoveredShapes.map( @@ -194,7 +195,7 @@ export const Canvas = observer(function Renderer({ zIndex={editingShape && selectedShapes.includes(editingShape) ? 1002 : 10002} > !shape.props.isLocked)} bounds={selectionBounds} showResizeHandles={showResizeHandles} showRotateHandles={showRotateHandles} @@ -251,7 +252,7 @@ export const Canvas = observer(function Renderer({ {selectedShapes && components.ContextBar && ( shape.id).join('')} - shapes={selectedShapes} + shapes={selectedShapes.filter(s => !s.props.isLocked)} hidden={!showContextBar} bounds={singleSelectedShape ? singleSelectedShape.bounds : selectionBounds} rotation={singleSelectedShape ? singleSelectedShape.props.rotation : 0} diff --git a/tldraw/packages/react/src/components/Indicator/Indicator.tsx b/tldraw/packages/react/src/components/Indicator/Indicator.tsx index 61c68e9f05..c40b28e2ef 100644 --- a/tldraw/packages/react/src/components/Indicator/Indicator.tsx +++ b/tldraw/packages/react/src/components/Indicator/Indicator.tsx @@ -10,6 +10,7 @@ interface IndicatorProps { isSelected?: boolean isBinding?: boolean isEditing?: boolean + isLocked?: boolean meta?: any } @@ -19,6 +20,7 @@ export const Indicator = observer(function Shape({ isSelected = false, isBinding = false, isEditing = false, + isLocked = false, meta, }: IndicatorProps) { const { @@ -38,11 +40,16 @@ export const Indicator = observer(function Shape({ zIndex={isEditing ? 1000 : 10000} > - + () const events = useShapeEvents(shape) + const parentGroup = app.getParentGroup(shape) + const isParentGrpupSelected = app.selectedIds.has(parentGroup?.id) + const ignoreExport = !isSelected && !isParentGrpupSelected && app.selectedShapes.size !== 0 || null + return ( - {!app.editingShape && ()} - - - - - - - - - {canResize?.every(r => r) && ( - <> - + {shapes.length > 0 && ( + + {!app.editingShape && ( + + )} + + + + + - - - - + {canResize?.every(r => r) && ( + <> + + + + + + )} + )} - + ) }) diff --git a/tldraw/packages/react/src/lib/TLReactShape.tsx b/tldraw/packages/react/src/lib/TLReactShape.tsx index 04c0e0bb08..4b528e93d3 100644 --- a/tldraw/packages/react/src/lib/TLReactShape.tsx +++ b/tldraw/packages/react/src/lib/TLReactShape.tsx @@ -8,6 +8,7 @@ export interface TLCommonShapeProps { isHovered: boolean isSelected: boolean isErasing: boolean + isLocked: boolean asset?: TLAsset } diff --git a/yarn.lock b/yarn.lock index b442b60f1f..15de9e6ce5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -487,10 +487,10 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" -"@logseq/capacitor-file-sync@0.0.22": - version "0.0.22" - resolved "https://registry.yarnpkg.com/@logseq/capacitor-file-sync/-/capacitor-file-sync-0.0.22.tgz#3fa94d40e5c44c70a12537ce17cf3089ff72f93b" - integrity sha512-lb0+43YAaWy0umBCP2mPKyAPlIr2YHrLBfqGkCJUGAbrhTCAj37KZzb3snwSqeLA8dUSks9PcAN3jSgS74VMMw== +"@logseq/capacitor-file-sync@0.0.24": + version "0.0.24" + resolved "https://registry.yarnpkg.com/@logseq/capacitor-file-sync/-/capacitor-file-sync-0.0.24.tgz#be7b69492b92df9c4e899502c632deebe179746b" + integrity sha512-CBIXEPYVp1ddjyYN+Z2dTQ9gwF0KYwZwlvwnl+zmR0Q3ODXxg75BExh5vAU8khXkSNZjZXgZT/J61/kn9xN11w== "@logseq/diff-merge@^0.0.2": version "0.0.2"