Merge branch 'whiteboards' into feat/whiteboards-align-shapes

pull/6802/head
Konstantinos Kaloutas 2022-09-21 08:51:59 +03:00
commit 3ce1e1c8f2
16 changed files with 194 additions and 125 deletions

View File

@ -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"
},

View File

@ -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))

View File

@ -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)))

View File

@ -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"

View File

@ -62,12 +62,7 @@
(defn- <stat [path]
(-> (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))))

View File

@ -634,7 +634,7 @@
(defprotocol IRSAPI
(rsapi-ready? [this graph-uuid] "return true when rsapi ready")
(<key-gen [this] "generate public+private keys")
(<set-env [this prod? private-key public-key graph-uuid] "set environment")
(<set-env [this graph-uuid prod? private-key public-key] "set environment")
(<get-local-files-meta [this graph-uuid base-path filepaths] "get local files' metadata")
(<get-local-all-files-meta [this graph-uuid base-path] "get all local files' metadata")
(<rename-local-file [this graph-uuid base-path from to])
@ -643,8 +643,8 @@
(<delete-local-files [this graph-uuid base-path filepaths])
(<update-remote-files [this graph-uuid base-path filepaths local-txid] "local -> remote, return err or txid")
(<delete-remote-files [this graph-uuid base-path filepaths local-txid] "return err or txid")
(<encrypt-fnames [this fnames])
(<decrypt-fnames [this fnames]))
(<encrypt-fnames [this graph-uuid fnames])
(<decrypt-fnames [this graph-uuid fnames]))
(defprotocol IRemoteAPI
(<user-info [this] "user info")
@ -673,17 +673,17 @@
it happens on macos (case-insensitive fs)
return canonicalized filepath if exists"
[irsapi base-path filepath]
[graph-uuid irsapi base-path filepath]
(go
(let [r (<! (<get-local-files-meta irsapi "" base-path [filepath]))]
(let [r (<! (<get-local-files-meta irsapi graph-uuid base-path [filepath]))]
(when (some-> r first :path (not= filepath))
(-> r first :path)))))
(defn <local-file-not-exist?
[irsapi base-path filepath]
[graph-uuid irsapi base-path filepath]
(go
(let [r (<! (<get-local-files-meta irsapi "" base-path [filepath]))]
(let [r (<! (<get-local-files-meta irsapi graph-uuid base-path [filepath]))]
(or
;; not found at all
@ -720,13 +720,13 @@
(rsapi-ready? [_ graph-uuid] (and (= graph-uuid graph-uuid') private-key' public-key'))
(<key-gen [_] (go (js->clj (<! (p->c (ipc/ipc "key-gen")))
:keywordize-keys true)))
(<set-env [_ prod? private-key public-key graph-uuid]
(<set-env [_ graph-uuid prod? private-key public-key]
(when (not-empty private-key)
(print (util/format "[%s] setting sync age-encryption passphrase..." graph-uuid)))
(set! graph-uuid' graph-uuid)
(set! private-key' private-key)
(set! public-key' public-key)
(p->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)))
(<get-local-all-files-meta [_ graph-uuid base-path]
(go
(let [r (<! (<retry-rsapi #(p->c (ipc/ipc "get-local-all-files-meta" graph-uuid base-path))))]
@ -781,9 +781,9 @@
(<!
(<retry-rsapi
#(p->c (ipc/ipc "delete-remote-files" graph-uuid base-path filepaths local-txid token)))))))
(<encrypt-fnames [_ fnames] (go (js->clj (<! (p->c (ipc/ipc "encrypt-fnames" fnames))))))
(<decrypt-fnames [_ fnames] (go
(let [r (<! (p->c (ipc/ipc "decrypt-fnames" fnames)))]
(<encrypt-fnames [_ graph-uuid fnames] (go (js->clj (<! (p->c (ipc/ipc "encrypt-fnames" graph-uuid fnames))))))
(<decrypt-fnames [_ graph-uuid fnames] (go
(let [r (<! (p->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 (<! (p->c (.keygen mobile-util/file-sync #js {})))]
(-> r
(js->clj :keywordize-keys true)))))
(<set-env [_ prod? secret-key public-key graph-uuid]
(<set-env [_ graph-uuid prod? secret-key public-key]
(set! graph-uuid' graph-uuid)
(set! private-key secret-key)
(set! public-key' public-key)
@ -899,14 +899,14 @@
r
(get (js->clj r) "txid")))))
(<encrypt-fnames [_ fnames]
(<encrypt-fnames [_ _graph-uuid fnames]
(go
(let [r (<! (p->c (.encryptFnames mobile-util/file-sync
(clj->js {:filePaths fnames}))))]
(if (instance? ExceptionInfo r)
(.-cause r)
(get (js->clj r) "value")))))
(<decrypt-fnames [_ fnames]
(<decrypt-fnames [_ _graph-uuid fnames]
(go (let [r (<! (p->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 (<! (<decrypt-fnames rsapi encrypted-path-list*))]
path-list-or-exp (<! (<decrypt-fnames rsapi graph-uuid encrypted-path-list*))]
(if (instance? ExceptionInfo path-list-or-exp)
path-list-or-exp
(let [encrypted-path->path-map (zipmap encrypted-path-list* path-list-or-exp)]
@ -1124,12 +1124,12 @@
(<get-remote-files-meta [this graph-uuid filepaths]
{:pre [(coll? filepaths)]}
(go
(let [encrypted-paths* (<! (<encrypt-fnames rsapi filepaths))
(let [encrypted-paths* (<! (<encrypt-fnames rsapi graph-uuid filepaths))
r (<! (.<request this "get_files_meta" {:GraphUUID graph-uuid :Files encrypted-paths*}))]
(if (instance? ExceptionInfo r)
r
(let [encrypted-paths (mapv :FilePath r)
paths-or-exp (<! (<decrypt-fnames rsapi encrypted-paths))]
paths-or-exp (<! (<decrypt-fnames rsapi graph-uuid encrypted-paths))]
(if (instance? ExceptionInfo paths-or-exp)
paths-or-exp
(let [encrypted-path->path-map (zipmap encrypted-paths paths-or-exp)]
@ -1152,7 +1152,7 @@
(<get-remote-file-versions [this graph-uuid filepath]
(go
(let [encrypted-path (first (<! (<encrypt-fnames rsapi [filepath])))]
(let [encrypted-path (first (<! (<encrypt-fnames rsapi graph-uuid [filepath])))]
(<! (.<request this "get_file_version_list" {:GraphUUID graph-uuid :File encrypted-path})))))
(<list-remote-graphs [this]
@ -1168,7 +1168,7 @@
encrypted-path->path-map
(zipmap
encrypted-paths
(<! (<decrypt-fnames rsapi encrypted-paths)))
(<! (<decrypt-fnames rsapi graph-uuid encrypted-paths)))
txns
(mapv
(fn [txn] (update txn :path #(get encrypted-path->path-map %)))
@ -1205,7 +1205,7 @@
encrypted-path->path-map
(zipmap
encrypted-paths
(<! (<decrypt-fnames rsapi encrypted-paths)))
(<! (<decrypt-fnames rsapi graph-uuid encrypted-paths)))
txns
(mapv
(fn [txn]
@ -1334,11 +1334,11 @@
[@graphs-txid local-txid remote-txid])))))
(defn- get-local-files-checksum
[base-path relative-paths]
[graph-uuid base-path relative-paths]
(go
(into {}
(map (juxt #(.-path ^FileMetadata %) #(.-etag ^FileMetadata %)))
(<! (<get-local-files-meta rsapi "" base-path relative-paths)))))
(<! (<get-local-files-meta rsapi graph-uuid base-path relative-paths)))))
(declare sync-state--add-current-local->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* (<! (<case-different-local-file-exist? rsapi base-path relative-p))]
(when-some [relative-p*
(<! (<case-different-local-file-exist? graph-uuid rsapi base-path relative-p))]
(let [recent-remote->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 (<! (<local-file-not-exist? rsapi base-path (relative-path filetxn)))
(if (<! (<local-file-not-exist? graph-uuid rsapi base-path (relative-path filetxn)))
;; not exist, ignore
true
(let [r (<! (<delete-local-files rsapi graph-uuid base-path [(relative-path filetxn)]))]
@ -1547,7 +1548,7 @@
(defn- <file-change-event=>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 (<! (get-local-files-checksum (.-dir e) [path])))))
(val (first (<! (get-local-files-checksum graph-uuid (.-dir e) [path])))))
:path path})))
(defn- distinct-file-change-events-xf
@ -1590,6 +1591,7 @@
(map #(partition-all n %))
cat))
(declare sync-state--valid-to-accept-filewatcher-event?)
(defonce local-changes-chan (chan (async/dropping-buffer 1000)))
(defn file-watch-handler
"file-watcher callback"
@ -1597,12 +1599,13 @@
(when-let [current-graph (state/get-current-repo)]
(when (string/ends-with? current-graph dir)
(let [sync-state (state/get-file-sync-state current-graph)]
(when (and sync-state (not (sync-state--stopped? sync-state)))
(when (and sync-state (sync-state--valid-to-accept-filewatcher-event? sync-state))
(when (or (:mtime stat) (= type "unlink"))
(go
(let [path (remove-dir-prefix dir path)
files-meta (and (not= "unlink" type)
(<! (<get-local-files-meta rsapi "" dir [path])))
(<! (<get-local-files-meta
rsapi (:current-syncing-graph-uuid sync-state) dir [path])))
checksum (and (coll? files-meta) (some-> 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))
(<set-env rsapi prod? private-key public-key graph-uuid)))
(<set-env rsapi graph-uuid prod? private-key public-key)))
(defn- <ensure-set-env&keys
[graph-uuid *stopped?]
@ -2037,6 +2040,13 @@
{:pre [(s/valid? ::sync-state sync-state)]}
(= ::stop (:state sync-state)))
(defn sync-state--valid-to-accept-filewatcher-event?
[sync-state]
{:pre [(s/valid? ::sync-state sync-state)]}
(contains? #{::idle ::local->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-file-not-exist? rsapi basepath r-path))
(<! (<local-file-not-exist? graph-uuid rsapi basepath r-path))
("add" "change")
;; 1. local file exists
;; 2. compare with remote file, and changed
(and (not (<! (<local-file-not-exist? rsapi basepath r-path)))
(and (not (<! (<local-file-not-exist? graph-uuid rsapi basepath r-path)))
(<! (<file-changed? graph-uuid r-path basepath)))))))
(defn- <filter-checksum-not-consistent
"filter out FileChangeEvents checksum changed,
compare checksum in FileChangeEvent and checksum calculated now"
[es]
[graph-uuid es]
{:pre [(or (nil? es) (coll? es))
(every? #(instance? FileChangeEvent %) es)]}
(go
@ -2178,7 +2188,8 @@
(if (= "unlink" (.-type ^FileChangeEvent (first es)))
es
(let [base-path (.-dir (first es))
files-meta (<! (<get-local-files-meta rsapi "" base-path (mapv relative-path es)))
files-meta (<! (<get-local-files-meta
rsapi graph-uuid base-path (mapv relative-path es)))
current-checksum-map (when (coll? files-meta) (into {} (mapv (juxt :path :etag) files-meta)))
origin-checksum-map (into {} (mapv (juxt relative-path #(.-checksum ^FileChangeEvent %)) es))
origin-map (into {} (mapv (juxt relative-path identity) es))]
@ -2243,7 +2254,8 @@
(not (ignored? e)) ;not ignored
;; download files will also trigger file-change-events, ignore them
(let [r (not (contains? (:recent-remote->local-files @*sync-state)
(<! (<file-change-event=>recent-remote->local-file-item e))))]
(<! (<file-change-event=>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* (<! (<filter-checksum-not-consistent es))
(let [es* (<! (<filter-checksum-not-consistent graph-uuid es))
_ (when (not= (count es*) (count es))
(println :debug :filter-checksum-changed
(mapv relative-path (set/difference (set es) (set es*)))))
@ -2385,7 +2397,7 @@
(loop [[f & fs] delete-local-files]
(when f
(let [relative-p (relative-path f)]
(when-not (<! (<local-file-not-exist? rsapi base-path relative-p))
(when-not (<! (<local-file-not-exist? graph-uuid rsapi base-path relative-p))
(let [fake-recent-remote->local-file-item {:remote->local-type :delete
:checksum nil
:path relative-p}]

View File

@ -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))

View File

@ -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))

View File

@ -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))))

View File

@ -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]

View File

@ -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<LogseqPortalShapeProps> {
stroke: 'var(--ls-primary-text-color)',
fill: 'var(--ls-secondary-background-color)',
noFill: false,
borderRadius: 8,
strokeWidth: 2,
strokeType: 'line',
opacity: 1,

View File

@ -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<PencilShapeProps> {
constructor(props = {} as Partial<PencilShapeProps>) {
super(props)
@ -34,16 +77,7 @@ export class PencilShape extends TLDrawShape<PencilShapeProps> {
}
@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) => {

View File

@ -540,7 +540,6 @@ button.tl-select-input-trigger {
}
&[data-recently-changed=true] {
transition-delay: 0.5s;
i.tie {
transition-delay: 0.5s;
}

View File

@ -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
}

View File

@ -22,7 +22,8 @@ export const SelectionForeground = observer(function SelectionForeground<S exten
const canResize = shapes.length === 1 ? shapes[0].canResize : [true, true]
const editing = !!app.editingShape
// @ts-expect-error ???
const borderRadius = app.editingShape?.props['borderRadius'] ?? 0
return (
<SVGContainer>
@ -30,8 +31,8 @@ export const SelectionForeground = observer(function SelectionForeground<S exten
className="tl-bounds-fg"
width={Math.max(width, 1)}
height={Math.max(height, 1)}
rx={editing ? 8 : 0}
ry={editing ? 8 : 0}
rx={borderRadius}
ry={borderRadius}
pointerEvents="none"
/>
<EdgeHandle

View File

@ -32,19 +32,29 @@ export function usePreventNavigation(rCanvas: React.RefObject<HTMLDivElement>):
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) {