diff --git a/resources/package.json b/resources/package.json index c25f5bcf7..2d1b36567 100644 --- a/resources/package.json +++ b/resources/package.json @@ -37,7 +37,7 @@ "https-proxy-agent": "5.0.0", "@sentry/electron": "2.5.1", "posthog-js": "1.10.2", - "@logseq/rsapi": "0.0.38", + "@logseq/rsapi": "0.0.44", "electron-deeplink": "1.0.10", "abort-controller": "3.0.0" }, diff --git a/src/electron/electron/file_sync_rsapi.cljs b/src/electron/electron/file_sync_rsapi.cljs index f81dfef09..5513cb7ed 100644 --- a/src/electron/electron/file_sync_rsapi.cljs +++ b/src/electron/electron/file_sync_rsapi.cljs @@ -3,8 +3,8 @@ (defn key-gen [] (rsapi/keygen)) -(defn set-env [env private-key public-key] - (rsapi/setEnv env private-key public-key)) +(defn set-env [graph-uuid env private-key public-key] + (rsapi/setEnv graph-uuid env private-key public-key)) (defn get-local-files-meta [graph-uuid base-path file-paths] (rsapi/getLocalFilesMeta graph-uuid base-path (clj->js file-paths))) @@ -27,17 +27,14 @@ (defn delete-remote-files [graph-uuid base-path file-paths txid token] (rsapi/deleteRemoteFiles graph-uuid base-path (clj->js file-paths) txid token)) -(defn update-remote-file [graph-uuid base-path file-path txid token] - (rsapi/updateRemoteFile graph-uuid base-path file-path txid token)) - (defn update-remote-files [graph-uuid base-path file-paths txid token] (rsapi/updateRemoteFiles graph-uuid base-path (clj->js file-paths) txid token true)) -(defn encrypt-fnames [fnames] - (mapv rsapi/encryptFname fnames)) +(defn encrypt-fnames [graph-uuid fnames] + (rsapi/encryptFnames graph-uuid (clj->js fnames))) -(defn decrypt-fnames [fnames] - (mapv rsapi/decryptFname fnames)) +(defn decrypt-fnames [graph-uuid fnames] + (rsapi/decryptFnames graph-uuid (clj->js fnames))) (defn encrypt-with-passphrase [passphrase data] (rsapi/ageEncryptWithPassphrase passphrase data)) diff --git a/src/electron/electron/handler.cljs b/src/electron/electron/handler.cljs index 3628f5d43..c0a2dfc5c 100644 --- a/src/electron/electron/handler.cljs +++ b/src/electron/electron/handler.cljs @@ -564,9 +564,6 @@ (defmethod handle :delete-remote-files [_ args] (apply rsapi/delete-remote-files (rest args))) -(defmethod handle :update-remote-file [_ args] - (apply rsapi/update-remote-file (rest args))) - (defmethod handle :update-remote-files [_ args] (apply rsapi/update-remote-files (rest args))) diff --git a/src/main/frontend/dicts.cljc b/src/main/frontend/dicts.cljc index b53776eac..529ae8da2 100644 --- a/src/main/frontend/dicts.cljc +++ b/src/main/frontend/dicts.cljc @@ -227,7 +227,7 @@ :open-new-window "New window" :sync-from-local-files "Refresh" :sync-from-local-files-detail "Import changes from local files" - :sync-from-local-changes-detected "Refresh detects and processes files modified on your disk and diverged from the actual Logseq page content. Continue?" + :sync-from-local-changes-detected "Refresh detects and processes files modified on your disk that have diverged from the current Logseq page content. Continue?" :unlink "unlink" :search/publishing "Search" diff --git a/src/main/frontend/fs/capacitor_fs.cljs b/src/main/frontend/fs/capacitor_fs.cljs index 115a201c5..5c8f23ad4 100644 --- a/src/main/frontend/fs/capacitor_fs.cljs +++ b/src/main/frontend/fs/capacitor_fs.cljs @@ -62,12 +62,7 @@ (defn- (p/chain (.stat Filesystem (clj->js {:path path})) - #(js->clj % :keywordize-keys true) - #(update % :type (fn [v] - (case v - "NSFileTypeDirectory" "directory" - "NSFileTypeRegular" "file" - v)))) + #(js->clj % :keywordize-keys true)) (p/catch (fn [error] (js/console.error "stat Error: " path ": " error) nil)))) diff --git a/src/main/frontend/fs/sync.cljs b/src/main/frontend/fs/sync.cljs index 2efd36a79..083b610b6 100644 --- a/src/main/frontend/fs/sync.cljs +++ b/src/main/frontend/fs/sync.cljs @@ -634,7 +634,7 @@ (defprotocol IRSAPI (rsapi-ready? [this graph-uuid] "return true when rsapi ready") ( remote, return err or txid") ( r first :path (not= filepath)) (-> r first :path))))) (defn clj (c (ipc/ipc "key-gen"))) :keywordize-keys true))) - (c (ipc/ipc "set-env" (if prod? "prod" "dev") private-key public-key))) + (p->c (ipc/ipc "set-env" graph-uuid (if prod? "prod" "dev") private-key public-key))) (c (ipc/ipc "get-local-all-files-meta" graph-uuid base-path))))] @@ -781,9 +781,9 @@ (c (ipc/ipc "delete-remote-files" graph-uuid base-path filepaths local-txid token))))))) - (clj (c (ipc/ipc "encrypt-fnames" fnames)))))) - (c (ipc/ipc "decrypt-fnames" fnames)))] + (clj (c (ipc/ipc "encrypt-fnames" graph-uuid fnames)))))) + (c (ipc/ipc "decrypt-fnames" graph-uuid fnames)))] (if (instance? ExceptionInfo r) (ex-info "decrypt-failed" {:fnames fnames} (ex-cause r)) (js->clj r)))))) @@ -806,7 +806,7 @@ (go (let [r (c (.keygen mobile-util/file-sync #js {})))] (-> r (js->clj :keywordize-keys true))))) - (clj r) "txid"))))) - (c (.encryptFnames mobile-util/file-sync (clj->js {:filePaths fnames}))))] (if (instance? ExceptionInfo r) (.-cause r) (get (js->clj r) "value"))))) - (c (.decryptFnames mobile-util/file-sync (clj->js {:filePaths fnames}))))] (if (instance? ExceptionInfo r) @@ -1107,7 +1107,7 @@ exp-r (let [file-meta-list* (persistent! file-meta-list) encrypted-path-list* (persistent! encrypted-path-list) - path-list-or-exp (path-map (zipmap encrypted-path-list* path-list-or-exp)] @@ -1124,12 +1124,12 @@ (path-map (zipmap encrypted-paths paths-or-exp)] @@ -1152,7 +1152,7 @@ (path-map (zipmap encrypted-paths - (path-map %))) @@ -1205,7 +1205,7 @@ encrypted-path->path-map (zipmap encrypted-paths - (remote-files sync-state--add-current-remote->local-files @@ -1406,7 +1406,8 @@ (remove nil?))] (doseq [relative-p (map relative-path filetxns)] - (when-some [relative-p* (local-file-item {:remote->local-type :delete :checksum nil :path relative-p*}] @@ -1433,7 +1434,7 @@ (.-deleted? (first filetxns)) (let [filetxn (first filetxns)] (assert (= 1 (count filetxns))) - (if (recent-remote->local-file-item - [^FileChangeEvent e] + [graph-uuid ^FileChangeEvent e] (go (let [tp (case (.-type e) ("add" "change") :update @@ -1555,7 +1556,7 @@ path (relative-path e)] {:remote->local-type tp :checksum (if (= tp :delete) nil - (val (first ( files-meta first :etag))] (>! local-changes-chan (->FileChangeEvent type dir path stat checksum)))))))))) @@ -1853,7 +1856,7 @@ (let [{:keys [private-key public-key]} (get @pwd-map graph-uuid)] (assert (and private-key public-key) (pr-str :private-key private-key :public-key public-key :pwd-map @pwd-map)) - (remote ::remote->local ::local->remote-full-sync ::remote->local-full-sync} + (:state sync-state))) + + ;;; ### remote->local syncer & local->remote syncer (defprotocol IRemote->LocalSync @@ -2159,18 +2169,18 @@ (case (.-type e) "unlink" ;; keep this e when it's not found - (local-files @*sync-state) - (recent-remote->local-file-item e))))] + (recent-remote->local-file-item + graph-uuid e))))] (when (and (true? r) (seq (:recent-remote->local-files @*sync-state))) (println :debug (:recent-remote->local-files @*sync-state) e)) @@ -2291,7 +2303,7 @@ (map #(relative-path %)) (remove ignored?))] (go - (let [es* (local-file-item {:remote->local-type :delete :checksum nil :path relative-p}] diff --git a/src/main/frontend/handler/whiteboard.cljs b/src/main/frontend/handler/whiteboard.cljs index 264b449d0..c0d5881fd 100644 --- a/src/main/frontend/handler/whiteboard.cljs +++ b/src/main/frontend/handler/whiteboard.cljs @@ -2,7 +2,6 @@ (:require [datascript.core :as d] [frontend.db.model :as model] [frontend.db.utils :as db-utils] - [frontend.handler.editor :as editor-handler] [frontend.handler.route :as route-handler] [frontend.modules.outliner.core :as outliner] [frontend.modules.outliner.file :as outliner-file] @@ -30,17 +29,6 @@ ;; (and (.-classList el) (.. el -classList (contains "whiteboard"))) true ;; :else (recur (.-parentElement el))))) -(defn get-tldr-app - [] - js/window.tln) - -(defn tldraw-idle? - "return true when tldraw is active and idle. nil when tldraw is - not active." - [] - (when-let [^js app (get-tldr-app)] - (.. app -selectedTool (isIn "idle")))) - (defn- block->shape [block] (:block/properties block)) diff --git a/src/main/frontend/modules/outliner/file.cljs b/src/main/frontend/modules/outliner/file.cljs index 2f6f60fec..f004f9f79 100644 --- a/src/main/frontend/modules/outliner/file.cljs +++ b/src/main/frontend/modules/outliner/file.cljs @@ -40,12 +40,14 @@ [repo page-db-id] (let [page-block (db/pull repo '[*] page-db-id) page-db-id (:db/id page-block) + whiteboard? (:block/whiteboard? page-block) blocks-count (model/get-page-blocks-count repo page-db-id)] - (if (and (> blocks-count 500) - (not (state/input-idle? repo :diff 3000))) ; long page + (if (or (and (> blocks-count 500) + (not (state/input-idle? repo :diff 3000))) ;; long page + ;; when this whiteboard page is just being updated + (and whiteboard? (state/whiteboard-page-idle? repo page-block 3000))) (async/put! (state/get-file-write-chan) [repo page-db-id]) - (let [whiteboard? (:block/whiteboard? page-block) - pull-keys (if whiteboard? whiteboard-blocks-pull-keys-with-persisted-ids '[*]) + (let [pull-keys (if whiteboard? whiteboard-blocks-pull-keys-with-persisted-ids '[*]) blocks (model/get-page-blocks-no-cache repo (:block/name page-block) {:pull-keys pull-keys}) blocks (if whiteboard? (map cleanup-whiteboard-block blocks) blocks)] (when-not (and (= 1 (count blocks)) diff --git a/src/main/frontend/modules/shortcut/before.cljs b/src/main/frontend/modules/shortcut/before.cljs index 62cd6b3d4..bc9b5a1a2 100644 --- a/src/main/frontend/modules/shortcut/before.cljs +++ b/src/main/frontend/modules/shortcut/before.cljs @@ -1,8 +1,7 @@ (ns frontend.modules.shortcut.before - (:require [frontend.state :as state] - [frontend.util :as util] - [frontend.mobile.util :as mobile-util] - [frontend.handler.whiteboard :as whiteboard])) + (:require [frontend.mobile.util :as mobile-util] + [frontend.state :as state] + [frontend.util :as util])) ;; before function (defn prevent-default-behavior @@ -35,6 +34,7 @@ (fn [e] (when (and (or (contains? #{:srs :page-histories} (state/get-modal-id)) (not (state/block-component-editing?))) - (not (and (whiteboard/tldraw-idle?) - (not (state/editing?))))) + ;; should not enable when in whiteboard mode, but not editing a logseq block + (not (and (state/active-tldraw-app) + (not (state/tldraw-editing-logseq-block?))))) (f e)))) diff --git a/src/main/frontend/state.cljs b/src/main/frontend/state.cljs index ae048b186..a64258d7b 100644 --- a/src/main/frontend/state.cljs +++ b/src/main/frontend/state.cljs @@ -1,20 +1,20 @@ (ns frontend.state (:require [cljs-bean.core :as bean] [cljs.core.async :as async] - [clojure.string :as string] [cljs.spec.alpha :as s] + [clojure.string :as string] [dommy.core :as dom] - [medley.core :as medley] [electron.ipc :as ipc] + [frontend.mobile.util :as mobile-util] [frontend.storage :as storage] [frontend.util :as util] [frontend.util.cursor :as cursor] [goog.dom :as gdom] [goog.object :as gobj] - [promesa.core :as p] - [rum.core :as rum] [logseq.graph-parser.config :as gp-config] - [frontend.mobile.util :as mobile-util])) + [medley.core :as medley] + [promesa.core :as p] + [rum.core :as rum])) ;; Stores main application state (defonce ^:large-vars/data-var state @@ -1396,6 +1396,15 @@ Similar to re-frame subscriptions" (set-state! [:plugin/installed-hooks hook-or-all] (disj coll pid)))) true)) +(defn active-tldraw-app + [] + ^js js/window.tln) + +(defn tldraw-editing-logseq-block? + [] + (when-let [app (active-tldraw-app)] + (and (= 1 (.. app -selectedShapesArray -length)) + (= (.. app -editingShape) (.. app -selectedShapesArray (at 0)))))) (defn set-graph-syncing? [value] @@ -1445,11 +1454,24 @@ Similar to re-frame subscriptions" :or {diff 1000}}] (when repo (or - (when-let [last-time (get-in @state [:editor/last-input-time repo])] - (let [now (util/time-ms)] - (>= (- now last-time) diff))) - ;; not in editing mode - (not (get-edit-input-id))))) + (when-let [last-time (get-in @state [:editor/last-input-time repo])] + (let [now (util/time-ms)] + (>= (- now last-time) diff))) + ;; not in editing mode + ;; Is this a good idea to put whiteboard check here? + (not (get-edit-input-id))))) + +(defn whiteboard-page-idle? + [repo whiteboard-page & {:keys [diff] + :or {diff 1000}}] + (when repo + (or + (when-let [last-time (:block/updated-at whiteboard-page)] + (let [now (util/time-ms)] + (>= (- now last-time) diff))) + ;; not in idle mode + (not (when-let [tldraw-app (active-tldraw-app)] + (.. tldraw-app -selectedTool (isIn "idle"))))))) (defn set-nfs-refreshing! [value] diff --git a/tldraw/apps/tldraw-logseq/src/lib/shapes/LogseqPortalShape.tsx b/tldraw/apps/tldraw-logseq/src/lib/shapes/LogseqPortalShape.tsx index 2600d031a..8cca46a4b 100644 --- a/tldraw/apps/tldraw-logseq/src/lib/shapes/LogseqPortalShape.tsx +++ b/tldraw/apps/tldraw-logseq/src/lib/shapes/LogseqPortalShape.tsx @@ -30,6 +30,7 @@ export interface LogseqPortalShapeProps extends TLBoxShapeProps, CustomStyleProp blockType?: 'P' | 'B' collapsed?: boolean compact?: boolean + borderRadius?: number collapsedHeight?: number scaleLevel?: SizeLevel } @@ -186,6 +187,7 @@ export class LogseqPortalShape extends TLBoxShape { stroke: 'var(--ls-primary-text-color)', fill: 'var(--ls-secondary-background-color)', noFill: false, + borderRadius: 8, strokeWidth: 2, strokeType: 'line', opacity: 1, diff --git a/tldraw/apps/tldraw-logseq/src/lib/shapes/PencilShape.tsx b/tldraw/apps/tldraw-logseq/src/lib/shapes/PencilShape.tsx index 68381c7be..91a98834b 100644 --- a/tldraw/apps/tldraw-logseq/src/lib/shapes/PencilShape.tsx +++ b/tldraw/apps/tldraw-logseq/src/lib/shapes/PencilShape.tsx @@ -3,13 +3,56 @@ import { SvgPathUtils, TLDrawShape, TLDrawShapeProps } from '@tldraw/core' import { SVGContainer, TLComponentProps } from '@tldraw/react' import { computed, makeObservable } from 'mobx' import { observer } from 'mobx-react-lite' -import getStroke from 'perfect-freehand' +import getStroke, { getStrokeOutlinePoints, getStrokePoints, StrokeOptions } from 'perfect-freehand' import { CustomStyleProps, withClampedStyles } from './style-props' export interface PencilShapeProps extends TLDrawShapeProps, CustomStyleProps { type: 'pencil' } +const simulatePressureSettings: StrokeOptions = { + easing: t => Math.sin((t * Math.PI) / 2), + simulatePressure: true, +} + +const realPressureSettings: StrokeOptions = { + easing: t => t * t, + simulatePressure: false, +} + +function getFreehandOptions(shape: PencilShapeProps) { + const options: StrokeOptions = { + size: 1 + shape.strokeWidth * 1.5, + thinning: 0.65, + streamline: 0.65, + smoothing: 0.65, + ...(shape.points[1][2] === 0.5 ? simulatePressureSettings : realPressureSettings), + last: shape.isComplete, + } + + return options +} + +function getFillPath(shape: PencilShapeProps) { + if (shape.points.length < 2) return '' + + return SvgPathUtils.getSvgPathFromStroke( + getStrokePoints(shape.points, getFreehandOptions(shape)).map(pt => pt.point) + ) +} + +function getDrawStrokePoints(shape: PencilShapeProps, options: StrokeOptions) { + return getStrokePoints(shape.points, options) +} + +function getDrawStrokePathTDSnapshot(shape: PencilShapeProps) { + if (shape.points.length < 2) return '' + const options = getFreehandOptions(shape) + const strokePoints = getDrawStrokePoints(shape, options) + const path = SvgPathUtils.getSvgPathFromStroke(getStrokeOutlinePoints(strokePoints, options)) + return path +} + export class PencilShape extends TLDrawShape { constructor(props = {} as Partial) { super(props) @@ -34,16 +77,7 @@ export class PencilShape extends TLDrawShape { } @computed get pointsPath() { - const { - props: { points, isComplete, strokeWidth }, - } = this - if (points.length < 2) { - return `M -4, 0 - a 4,4 0 1,0 8,0 - a 4,4 0 1,0 -8,0` - } - const stroke = getStroke(points, { size: 4 + strokeWidth * 2, last: isComplete }) - return SvgPathUtils.getCurvedPathForPolygon(stroke) + return getDrawStrokePathTDSnapshot(this.props) } ReactComponent = observer(({ events, isErasing }: TLComponentProps) => { diff --git a/tldraw/apps/tldraw-logseq/src/styles.css b/tldraw/apps/tldraw-logseq/src/styles.css index d1e7432bd..e6f3e58f2 100644 --- a/tldraw/apps/tldraw-logseq/src/styles.css +++ b/tldraw/apps/tldraw-logseq/src/styles.css @@ -540,7 +540,6 @@ button.tl-select-input-trigger { } &[data-recently-changed=true] { - transition-delay: 0.5s; i.tie { transition-delay: 0.5s; } diff --git a/tldraw/packages/core/src/utils/SvgPathUtils.ts b/tldraw/packages/core/src/utils/SvgPathUtils.ts index cd23ad4f2..344fc0ba2 100644 --- a/tldraw/packages/core/src/utils/SvgPathUtils.ts +++ b/tldraw/packages/core/src/utils/SvgPathUtils.ts @@ -1,4 +1,3 @@ -import Vec from '@tldraw/vec' export class SvgPathUtils { static getCurvedPathForPolygon(points: number[][]) { @@ -45,23 +44,34 @@ export class SvgPathUtils { * @param stroke ; */ static getSvgPathFromStroke(points: number[][], closed = true): string { - if (!points.length) { - return '' + const len = points.length + + if (len < 4) { + return `` } - const max = points.length - 1 + let a = points[0] + let b = points[1] + const c = points[2] - return points - .reduce( - (acc, point, i, arr) => { - if (i === max) { - if (closed) acc.push('Z') - } else acc.push(point, Vec.med(point, arr[i + 1])) - return acc - }, - ['M', points[0], 'Q'] - ) - .join(' ') - .replaceAll(this.TRIM_NUMBERS, '$1') + let result = `M${a[0].toFixed(2)},${a[1].toFixed(2)} Q${b[0].toFixed(2)},${b[1].toFixed( + 2 + )} ${average(b[0], c[0]).toFixed(2)},${average(b[1], c[1]).toFixed(2)} T` + + for (let i = 2, max = len - 1; i < max; i++) { + a = points[i] + b = points[i + 1] + result += `${average(a[0], b[0]).toFixed(2)},${average(a[1], b[1]).toFixed(2)} ` + } + + if (closed) { + result += 'Z' + } + + return result } } + +function average(a: number, b: number): number { + return (a + b) / 2 +} diff --git a/tldraw/packages/react/src/components/ui/SelectionForeground/SelectionForeground.tsx b/tldraw/packages/react/src/components/ui/SelectionForeground/SelectionForeground.tsx index 38660c4aa..cb6b7af0f 100644 --- a/tldraw/packages/react/src/components/ui/SelectionForeground/SelectionForeground.tsx +++ b/tldraw/packages/react/src/components/ui/SelectionForeground/SelectionForeground.tsx @@ -22,7 +22,8 @@ export const SelectionForeground = observer(function SelectionForeground @@ -30,8 +31,8 @@ export const SelectionForeground = observer(function SelectionForeground ): if (!elm) return () => void null - elm.addEventListener('touchstart', preventGestureNavigation) + elm.addEventListener('touchstart', preventGestureNavigation, { + passive: true, + }) // @ts-ignore - elm.addEventListener('gestureend', preventGestureNavigation) + elm.addEventListener('gestureend', preventGestureNavigation, { + passive: true, + }) // @ts-ignore - elm.addEventListener('gesturechange', preventGestureNavigation) + elm.addEventListener('gesturechange', preventGestureNavigation, { + passive: true, + }) // @ts-ignore - elm.addEventListener('gesturestart', preventGestureNavigation) + elm.addEventListener('gesturestart', preventGestureNavigation, { + passive: true, + }) // @ts-ignore - elm.addEventListener('touchstart', preventNavigation) + elm.addEventListener('touchstart', preventNavigation, { + passive: true, + }) return () => { if (elm) {