kopia lustrzana https://github.com/Tldraw/Tldraw
examples: clean up Canvas/Store events and make UiEvents have code snippets (#2770)
Fixes https://linear.app/tldraw/issue/TLD-2059 <img width="1220" alt="Screenshot 2024-02-07 at 12 38 09" src="https://github.com/tldraw/tldraw/assets/469604/15dc4298-670d-489b-8bee-810d34a0fbae"> ### Change Type - [x] `internal` — Any other changes that don't affect the published package[^2] ### Release Notes - Examples: add an interactive example that shows code snippets for the SDK.pull/2814/head
rodzic
e2a03abf5c
commit
f16e597761
|
@ -40,12 +40,14 @@
|
||||||
"@vercel/analytics": "^1.1.1",
|
"@vercel/analytics": "^1.1.1",
|
||||||
"classnames": "^2.3.2",
|
"classnames": "^2.3.2",
|
||||||
"lazyrepo": "0.0.0-alpha.27",
|
"lazyrepo": "0.0.0-alpha.27",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-router-dom": "^6.17.0",
|
"react-router-dom": "^6.17.0",
|
||||||
"vite": "^5.0.0"
|
"vite": "^5.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/lodash": "^4.14.188",
|
||||||
"@vitejs/plugin-react": "^4.2.0",
|
"@vitejs/plugin-react": "^4.2.0",
|
||||||
"dotenv": "^16.3.1",
|
"dotenv": "^16.3.1",
|
||||||
"remark": "^15.0.1",
|
"remark": "^15.0.1",
|
||||||
|
|
|
@ -64,6 +64,11 @@ export function ExamplePage({
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="example__sidebar__header-links">
|
||||||
|
<a className="example__sidebar__header-link" href="/develop">
|
||||||
|
Develop
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
<ul className="example__sidebar__categories scroll-light">
|
<ul className="example__sidebar__categories scroll-light">
|
||||||
{categories.map((currentCategory) => (
|
{categories.map((currentCategory) => (
|
||||||
<li key={currentCategory} className="example__sidebar__category">
|
<li key={currentCategory} className="example__sidebar__category">
|
||||||
|
|
|
@ -5,10 +5,23 @@ import { useCallback, useState } from 'react'
|
||||||
// There's a guide at the bottom of this file!
|
// There's a guide at the bottom of this file!
|
||||||
|
|
||||||
export default function CanvasEventsExample() {
|
export default function CanvasEventsExample() {
|
||||||
const [events, setEvents] = useState<string[]>([])
|
const [events, setEvents] = useState<any[]>([])
|
||||||
|
|
||||||
const handleEvent = useCallback((data: TLEventInfo) => {
|
const handleEvent = useCallback((data: TLEventInfo) => {
|
||||||
setEvents((events) => [JSON.stringify(data, null, '\t'), ...events.slice(0, 100)])
|
setEvents((events) => {
|
||||||
|
const newEvents = events.slice(0, 100)
|
||||||
|
if (
|
||||||
|
newEvents[newEvents.length - 1] &&
|
||||||
|
newEvents[newEvents.length - 1].type === 'pointer' &&
|
||||||
|
data.type === 'pointer' &&
|
||||||
|
data.target === 'canvas'
|
||||||
|
) {
|
||||||
|
newEvents[newEvents.length - 1] = data
|
||||||
|
} else {
|
||||||
|
newEvents.unshift(data)
|
||||||
|
}
|
||||||
|
return newEvents
|
||||||
|
})
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -36,9 +49,7 @@ export default function CanvasEventsExample() {
|
||||||
whiteSpace: 'pre-wrap',
|
whiteSpace: 'pre-wrap',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{events.map((t, i) => (
|
<div>{JSON.stringify(events, undefined, 2)}</div>
|
||||||
<div key={i}>{t}</div>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { Editor, TLEventMapHandler, Tldraw } from '@tldraw/tldraw'
|
import { Editor, TLEventMapHandler, Tldraw } from '@tldraw/tldraw'
|
||||||
import '@tldraw/tldraw/tldraw.css'
|
import '@tldraw/tldraw/tldraw.css'
|
||||||
|
import _ from 'lodash'
|
||||||
import { useCallback, useEffect, useState } from 'react'
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
|
|
||||||
// There's a guide at the bottom of this file!
|
// There's a guide at the bottom of this file!
|
||||||
|
@ -17,7 +18,7 @@ export default function StoreEventsExample() {
|
||||||
if (!editor) return
|
if (!editor) return
|
||||||
|
|
||||||
function logChangeEvent(eventName: string) {
|
function logChangeEvent(eventName: string) {
|
||||||
setStoreEvents((events) => [eventName, ...events])
|
setStoreEvents((events) => [...events, eventName])
|
||||||
}
|
}
|
||||||
|
|
||||||
//[1]
|
//[1]
|
||||||
|
@ -25,7 +26,7 @@ export default function StoreEventsExample() {
|
||||||
// Added
|
// Added
|
||||||
for (const record of Object.values(change.changes.added)) {
|
for (const record of Object.values(change.changes.added)) {
|
||||||
if (record.typeName === 'shape') {
|
if (record.typeName === 'shape') {
|
||||||
logChangeEvent(`created shape (${record.type})`)
|
logChangeEvent(`created shape (${record.type})\n`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,13 +38,29 @@ export default function StoreEventsExample() {
|
||||||
from.currentPageId !== to.currentPageId
|
from.currentPageId !== to.currentPageId
|
||||||
) {
|
) {
|
||||||
logChangeEvent(`changed page (${from.currentPageId}, ${to.currentPageId})`)
|
logChangeEvent(`changed page (${from.currentPageId}, ${to.currentPageId})`)
|
||||||
|
} else if (from.id.startsWith('shape') && to.id.startsWith('shape')) {
|
||||||
|
let diff = _.reduce(
|
||||||
|
from,
|
||||||
|
(result: any[], value, key: string) =>
|
||||||
|
_.isEqual(value, (to as any)[key]) ? result : result.concat([key, value]),
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
if (diff?.[0] === 'props') {
|
||||||
|
diff = _.reduce(
|
||||||
|
(from as any).props,
|
||||||
|
(result: any[], value, key) =>
|
||||||
|
_.isEqual(value, (to as any).props[key]) ? result : result.concat([key, value]),
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
logChangeEvent(`updated shape (${JSON.stringify(diff)})\n`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Removed
|
// Removed
|
||||||
for (const record of Object.values(change.changes.removed)) {
|
for (const record of Object.values(change.changes.removed)) {
|
||||||
if (record.typeName === 'shape') {
|
if (record.typeName === 'shape') {
|
||||||
logChangeEvent(`deleted shape (${record.type})`)
|
logChangeEvent(`deleted shape (${record.type})\n`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -76,9 +93,7 @@ export default function StoreEventsExample() {
|
||||||
overflow: 'auto',
|
overflow: 'auto',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{storeEvents.map((t, i) => (
|
<pre>{storeEvents}</pre>
|
||||||
<div key={i}>{t}</div>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,14 +1,19 @@
|
||||||
import { TLUiEventHandler, Tldraw } from '@tldraw/tldraw'
|
import { TLUiEventHandler, Tldraw } from '@tldraw/tldraw'
|
||||||
import '@tldraw/tldraw/tldraw.css'
|
import '@tldraw/tldraw/tldraw.css'
|
||||||
import { useCallback, useState } from 'react'
|
import { Fragment, useCallback, useState } from 'react'
|
||||||
|
import { getCodeSnippet } from './codeSnippets'
|
||||||
|
|
||||||
// There's a guide at the bottom of this file!
|
// There's a guide at the bottom of this file!
|
||||||
|
|
||||||
export default function UiEventsExample() {
|
export default function UiEventsExample() {
|
||||||
const [uiEvents, setUiEvents] = useState<string[]>([])
|
const [uiEvents, setUiEvents] = useState<string[]>([])
|
||||||
|
|
||||||
const handleUiEvent = useCallback<TLUiEventHandler>((name, data) => {
|
const handleUiEvent = useCallback<TLUiEventHandler>((name, data: any) => {
|
||||||
setUiEvents((events) => [`${name} ${JSON.stringify(data)}`, ...events])
|
const codeSnippet = getCodeSnippet(name, data)
|
||||||
|
setUiEvents((events) => [
|
||||||
|
`event: ${name} ${JSON.stringify(data)}${codeSnippet && `\ncode: ${codeSnippet}`}`,
|
||||||
|
...events,
|
||||||
|
])
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -32,7 +37,11 @@ export default function UiEventsExample() {
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{uiEvents.map((t, i) => (
|
{uiEvents.map((t, i) => (
|
||||||
<div key={i}>{t}</div>
|
<Fragment key={i}>
|
||||||
|
<pre style={{ borderBottom: '1px solid #000', marginBottom: 0, paddingBottom: '12px' }}>
|
||||||
|
{t}
|
||||||
|
</pre>
|
||||||
|
</Fragment>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -45,6 +54,9 @@ grouping shapes, zooming etc. Events are included even if they are triggered by
|
||||||
However, interactions with the style panel are not included. For a full list of events and sources,
|
However, interactions with the style panel are not included. For a full list of events and sources,
|
||||||
check out the TLUiEventSource and TLUiEventMap types.
|
check out the TLUiEventSource and TLUiEventMap types.
|
||||||
|
|
||||||
|
It also shows the relevant code snippet for each event. This is useful for debugging and learning
|
||||||
|
the tldraw SDK.
|
||||||
|
|
||||||
We can pass a handler function to the onUiEvent prop of the Tldraw component. This handler function
|
We can pass a handler function to the onUiEvent prop of the Tldraw component. This handler function
|
||||||
will be called with the name of the event and the data associated with the event. We're going to
|
will be called with the name of the event and the data associated with the event. We're going to
|
||||||
display these events in a list on the right side of the screen.
|
display these events in a list on the right side of the screen.
|
||||||
|
|
|
@ -0,0 +1,161 @@
|
||||||
|
const STYLE_EVENT = {
|
||||||
|
'tldraw:color': 'DefaultColorStyle',
|
||||||
|
'tldraw:dash': 'DefaultDashStyle',
|
||||||
|
'tldraw:fill': 'DefaultFillStyle',
|
||||||
|
'tldraw:font': 'DefaultFontStyle',
|
||||||
|
'tldraw:horizontalAlign': 'DefaultHorizontalAlignStyle',
|
||||||
|
'tldraw:size': 'DefaultSizeStyle',
|
||||||
|
'tldraw:verticalAlign': 'DefaultVerticalAlignStyle',
|
||||||
|
'tldraw:geo': 'GeoShapeGeoStyle',
|
||||||
|
}
|
||||||
|
|
||||||
|
const REORDER_EVENT = {
|
||||||
|
toFront: 'bringToFront',
|
||||||
|
forward: 'bringForward',
|
||||||
|
backward: 'sendBackward',
|
||||||
|
toBack: 'sendToBack',
|
||||||
|
}
|
||||||
|
|
||||||
|
const SHAPES_META_EVENT = {
|
||||||
|
'group-shapes': 'groupShapes',
|
||||||
|
'ungroup-shapes': 'ungroupShapes',
|
||||||
|
'delete-shapes': 'deleteShapes',
|
||||||
|
}
|
||||||
|
|
||||||
|
const SHAPES_EVENT = {
|
||||||
|
'distribute-shapes': 'distributeShapes',
|
||||||
|
'align-shapes': 'alignShapes',
|
||||||
|
'stretch-shapes': 'stretchShapes',
|
||||||
|
'flip-shapes': 'flipShapes',
|
||||||
|
}
|
||||||
|
|
||||||
|
const USER_PREFS_EVENT = {
|
||||||
|
'toggle-snap-mode': 'isSnapMode',
|
||||||
|
'toggle-dark-mode': 'isDarkMode',
|
||||||
|
'toggle-reduce-motion': 'animationSpeed',
|
||||||
|
'toggle-edge-scrolling': 'edgeScrollSpeed',
|
||||||
|
}
|
||||||
|
|
||||||
|
const PREFS_EVENT = {
|
||||||
|
'toggle-transparent': 'exportBackground',
|
||||||
|
'toggle-tool-lock': 'isToolLocked',
|
||||||
|
'toggle-focus-mode': 'isFocusMode',
|
||||||
|
'toggle-grid-mode': 'isGridMode',
|
||||||
|
'toggle-debug-mode': 'isDebugMode',
|
||||||
|
}
|
||||||
|
|
||||||
|
const ZOOM_EVENT = {
|
||||||
|
'zoom-in': 'zoomIn',
|
||||||
|
'zoom-out': 'zoomOut',
|
||||||
|
'reset-zoom': 'resetZoom',
|
||||||
|
'zoom-to-fit': 'zoomToFit',
|
||||||
|
'zoom-to-selection': 'zoomToSelection',
|
||||||
|
'zoom-to-content': 'zoomToContent',
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCodeSnippet(name: string, data: any) {
|
||||||
|
let codeSnippet = ''
|
||||||
|
|
||||||
|
if (name === 'set-style') {
|
||||||
|
if (data.id === 'opacity') {
|
||||||
|
codeSnippet = `editor.setOpacityForNextShapes(${data.value});`
|
||||||
|
} else {
|
||||||
|
codeSnippet = `editor.setStyleForNextShapes(${
|
||||||
|
STYLE_EVENT[data.id as keyof typeof STYLE_EVENT] ?? '?'
|
||||||
|
}, '${data.value}');`
|
||||||
|
}
|
||||||
|
} else if (['rotate-ccw', 'rotate-cw'].includes(name)) {
|
||||||
|
codeSnippet = 'editor.rotateShapesBy(editor.getSelectedShapeIds(), <number>)'
|
||||||
|
} else if (name === 'edit-link') {
|
||||||
|
codeSnippet =
|
||||||
|
'editor.updateShapes([{ id: editor.getOnlySelectedShape().id, type: editor.getOnlySelectedShape().type, props: { url: <url> }, }, ])'
|
||||||
|
} else if (name.startsWith('export-as')) {
|
||||||
|
codeSnippet = `exportAs(editor.getSelectedShapeIds(), '${data.format}')`
|
||||||
|
} else if (name.startsWith('copy-as')) {
|
||||||
|
codeSnippet = `copyAs(editor.getSelectedShapeIds(), '${data.format}')`
|
||||||
|
} else if (name === 'select-all-shapes') {
|
||||||
|
codeSnippet = `editor.selectAll()`
|
||||||
|
} else if (name === 'select-none-shapes') {
|
||||||
|
codeSnippet = `editor.selectNone()`
|
||||||
|
} else if (name === 'reorder-shapes') {
|
||||||
|
codeSnippet = `editor.${
|
||||||
|
REORDER_EVENT[data.operation as keyof typeof REORDER_EVENT] ?? '?'
|
||||||
|
}(editor.getSelectedShapeIds())`
|
||||||
|
} else if (['group-shapes', 'ungroup-shapes', 'delete-shapes'].includes(name)) {
|
||||||
|
codeSnippet = `editor.${
|
||||||
|
SHAPES_META_EVENT[name as keyof typeof SHAPES_META_EVENT] ?? '?'
|
||||||
|
}(editor.getSelectedShapeIds())`
|
||||||
|
} else if (name === 'stack-shapes') {
|
||||||
|
codeSnippet = `editor.stackShapes(editor.getSelectedShapeIds(), '${data.operation}', 16)`
|
||||||
|
} else if (name === 'pack-shapes') {
|
||||||
|
codeSnippet = `editor.packShapes(editor.getSelectedShapeIds(), 16)`
|
||||||
|
} else if (name === 'duplicate-shapes') {
|
||||||
|
codeSnippet = `editor.duplicateShapes(editor.getSelectedShapeIds(), {x: <value>, y: <value>})`
|
||||||
|
} else if (name.endsWith('-shapes')) {
|
||||||
|
codeSnippet = `editor.${
|
||||||
|
SHAPES_EVENT[name as keyof typeof SHAPES_EVENT] ?? '?'
|
||||||
|
}(editor.getSelectedShapeIds(), '${data.operation}')`
|
||||||
|
} else if (name === 'select-tool') {
|
||||||
|
if (data.id === 'media') {
|
||||||
|
codeSnippet = 'insertMedia()'
|
||||||
|
} else if (data.id.startsWith('geo-')) {
|
||||||
|
codeSnippet = `\n editor.updateInstanceState({
|
||||||
|
stylesForNextShape: {
|
||||||
|
...editor.getInstanceState().stylesForNextShape,
|
||||||
|
[GeoShapeGeoStyle.id]: '${data.id.replace('geo-', '')}',
|
||||||
|
},
|
||||||
|
}, { ephemeral: true });
|
||||||
|
editor.setCurrentTool('${data.id}')`
|
||||||
|
} else {
|
||||||
|
codeSnippet = `editor.setCurrentTool('${data.id}')`
|
||||||
|
}
|
||||||
|
} else if (name === 'print') {
|
||||||
|
codeSnippet = 'printSelectionOrPages()'
|
||||||
|
} else if (name === 'unlock-all') {
|
||||||
|
codeSnippet = `\n const updates = [] as TLShapePartial[]
|
||||||
|
for (const shape of editor.getCurrentPageShapes()) {
|
||||||
|
if (shape.isLocked) {
|
||||||
|
updates.push({ id: shape.id, type: shape.type, isLocked: false })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (updates.length > 0) {
|
||||||
|
editor.updateShapes(updates)
|
||||||
|
}`
|
||||||
|
} else if (['undo', 'redo'].includes(name)) {
|
||||||
|
codeSnippet = `editor.${name}()`
|
||||||
|
} else if (['cut', 'copy'].includes(name)) {
|
||||||
|
codeSnippet = `\n const { ${name} } = useMenuClipboardEvents();\n ${name}()`
|
||||||
|
} else if (name === 'paste') {
|
||||||
|
codeSnippet = `\n const { paste } = useMenuClipboardEvents();\n navigator.clipboard?.read().then((clipboardItems) => {\n paste(clipboardItems)\n })`
|
||||||
|
} else if (name === 'stop-following') {
|
||||||
|
codeSnippet = `editor.stopFollowingUser()`
|
||||||
|
} else if (name === 'exit-pen-mode') {
|
||||||
|
codeSnippet = `editor.updateInstanceState({ isPenMode: false })`
|
||||||
|
} else if (name === 'remove-frame') {
|
||||||
|
codeSnippet = `removeFrame(editor, editor.getSelectedShapes().map((shape) => shape.id))`
|
||||||
|
} else if (name === 'fit-frame-to-content') {
|
||||||
|
codeSnippet = `fitFrameToContent(editor, editor.getOnlySelectedShape().id)`
|
||||||
|
} else if (name.startsWith('zoom-') || name === 'reset-zoom') {
|
||||||
|
if (name === 'zoom-to-content') {
|
||||||
|
codeSnippet = 'editor.zoomToContent()'
|
||||||
|
} else {
|
||||||
|
codeSnippet = `editor.${ZOOM_EVENT[name as keyof typeof ZOOM_EVENT]}(${
|
||||||
|
name !== 'zoom-to-fit' && name !== 'zoom-to-selection'
|
||||||
|
? 'editor.getViewportScreenCenter(), '
|
||||||
|
: ''
|
||||||
|
}{ duration: 320 })`
|
||||||
|
}
|
||||||
|
} else if (name.startsWith('toggle-')) {
|
||||||
|
if (name === 'toggle-lock') {
|
||||||
|
codeSnippet = `editor.toggleLock(editor.getSelectedShapeIds())`
|
||||||
|
} else {
|
||||||
|
const userPrefName = USER_PREFS_EVENT[name as keyof typeof USER_PREFS_EVENT]
|
||||||
|
const prefName = PREFS_EVENT[name as keyof typeof PREFS_EVENT]
|
||||||
|
codeSnippet = userPrefName
|
||||||
|
? `editor.user.updateUserPreferences({ ${userPrefName}: <value> })`
|
||||||
|
: `editor.updateInstanceState({ ${prefName}: !editor.getInstanceState().${prefName} })`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return codeSnippet
|
||||||
|
}
|
|
@ -213,8 +213,9 @@ li.examples__sidebar__item {
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ----------------- Footer Buttons ----------------- */
|
/* ----------------- Header/Footer Buttons ----------------- */
|
||||||
|
|
||||||
|
.example__sidebar__header-links,
|
||||||
.example__sidebar__footer-links {
|
.example__sidebar__footer-links {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
@ -225,6 +226,7 @@ li.examples__sidebar__item {
|
||||||
border-top: 1px solid #e8e8e8;
|
border-top: 1px solid #e8e8e8;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.example__sidebar__header-link,
|
||||||
.example__sidebar__footer-link {
|
.example__sidebar__footer-link {
|
||||||
padding: 8px 8px;
|
padding: 8px 8px;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
|
@ -241,7 +243,7 @@ li.examples__sidebar__item {
|
||||||
0px 1px 3px rgba(0, 0, 0, 0.04);
|
0px 1px 3px rgba(0, 0, 0, 0.04);
|
||||||
}
|
}
|
||||||
|
|
||||||
.example__sidebar__footer-link > a {
|
a.example__sidebar__header-link {
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1533,6 +1533,11 @@ export interface TLUiEventMap {
|
||||||
id: string;
|
id: string;
|
||||||
};
|
};
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
|
'set-style': {
|
||||||
|
id: string;
|
||||||
|
value: number | string;
|
||||||
|
};
|
||||||
|
// (undocumented)
|
||||||
'stack-shapes': {
|
'stack-shapes': {
|
||||||
operation: 'horizontal' | 'vertical';
|
operation: 'horizontal' | 'vertical';
|
||||||
};
|
};
|
||||||
|
@ -1597,7 +1602,7 @@ export interface TLUiEventMap {
|
||||||
}
|
}
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export type TLUiEventSource = 'actions-menu' | 'context-menu' | 'debug-panel' | 'dialog' | 'export-menu' | 'help-menu' | 'helper-buttons' | 'kbd' | 'menu' | 'navigation-zone' | 'page-menu' | 'people-menu' | 'quick-actions' | 'share-menu' | 'toolbar' | 'unknown' | 'zoom-menu';
|
export type TLUiEventSource = 'actions-menu' | 'context-menu' | 'debug-panel' | 'dialog' | 'export-menu' | 'help-menu' | 'helper-buttons' | 'kbd' | 'menu' | 'navigation-zone' | 'page-menu' | 'people-menu' | 'quick-actions' | 'share-menu' | 'style-panel' | 'toolbar' | 'unknown' | 'zoom-menu';
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export type TLUiHelpMenuSchemaContextType = TLUiMenuSchema;
|
export type TLUiHelpMenuSchemaContextType = TLUiMenuSchema;
|
||||||
|
|
|
@ -17370,6 +17370,33 @@
|
||||||
"endIndex": 2
|
"endIndex": 2
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"kind": "PropertySignature",
|
||||||
|
"canonicalReference": "@tldraw/tldraw!TLUiEventMap#\"set-style\":member",
|
||||||
|
"docComment": "",
|
||||||
|
"excerptTokens": [
|
||||||
|
{
|
||||||
|
"kind": "Content",
|
||||||
|
"text": "'set-style': "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "Content",
|
||||||
|
"text": "{\n id: string;\n value: number | string;\n }"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "Content",
|
||||||
|
"text": ";"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"isReadonly": false,
|
||||||
|
"isOptional": false,
|
||||||
|
"releaseTag": "Public",
|
||||||
|
"name": "\"set-style\"",
|
||||||
|
"propertyTypeTokenRange": {
|
||||||
|
"startIndex": 1,
|
||||||
|
"endIndex": 2
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"kind": "PropertySignature",
|
"kind": "PropertySignature",
|
||||||
"canonicalReference": "@tldraw/tldraw!TLUiEventMap#\"stack-shapes\":member",
|
"canonicalReference": "@tldraw/tldraw!TLUiEventMap#\"stack-shapes\":member",
|
||||||
|
@ -18167,7 +18194,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"kind": "Content",
|
"kind": "Content",
|
||||||
"text": "'actions-menu' | 'context-menu' | 'debug-panel' | 'dialog' | 'export-menu' | 'help-menu' | 'helper-buttons' | 'kbd' | 'menu' | 'navigation-zone' | 'page-menu' | 'people-menu' | 'quick-actions' | 'share-menu' | 'toolbar' | 'unknown' | 'zoom-menu'"
|
"text": "'actions-menu' | 'context-menu' | 'debug-panel' | 'dialog' | 'export-menu' | 'help-menu' | 'helper-buttons' | 'kbd' | 'menu' | 'navigation-zone' | 'page-menu' | 'people-menu' | 'quick-actions' | 'share-menu' | 'style-panel' | 'toolbar' | 'unknown' | 'zoom-menu'"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"kind": "Content",
|
"kind": "Content",
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { track, useEditor } from '@tldraw/editor'
|
||||||
import { useActions } from '../hooks/useActions'
|
import { useActions } from '../hooks/useActions'
|
||||||
import { Button } from './primitives/Button'
|
import { Button } from './primitives/Button'
|
||||||
|
|
||||||
export const StopFollowing = track(function ExitPenMode() {
|
export const StopFollowing = track(function StopFollowing() {
|
||||||
const editor = useEditor()
|
const editor = useEditor()
|
||||||
const actions = useActions()
|
const actions = useActions()
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@ import {
|
||||||
useEditor,
|
useEditor,
|
||||||
} from '@tldraw/editor'
|
} from '@tldraw/editor'
|
||||||
import React, { useCallback } from 'react'
|
import React, { useCallback } from 'react'
|
||||||
|
import { useUiEvents } from '../../hooks/useEventsProvider'
|
||||||
import { useRelevantStyles } from '../../hooks/useRevelantStyles'
|
import { useRelevantStyles } from '../../hooks/useRevelantStyles'
|
||||||
import { useTranslation } from '../../hooks/useTranslation/useTranslation'
|
import { useTranslation } from '../../hooks/useTranslation/useTranslation'
|
||||||
import { Button } from '../primitives/Button'
|
import { Button } from '../primitives/Button'
|
||||||
|
@ -73,6 +74,7 @@ export const StylePanel = function StylePanel({ isMobile }: StylePanelProps) {
|
||||||
|
|
||||||
function useStyleChangeCallback() {
|
function useStyleChangeCallback() {
|
||||||
const editor = useEditor()
|
const editor = useEditor()
|
||||||
|
const trackEvent = useUiEvents()
|
||||||
|
|
||||||
return React.useMemo(() => {
|
return React.useMemo(() => {
|
||||||
return function handleStyleChange<T>(style: StyleProp<T>, value: T, squashing: boolean) {
|
return function handleStyleChange<T>(style: StyleProp<T>, value: T, squashing: boolean) {
|
||||||
|
@ -83,8 +85,10 @@ function useStyleChangeCallback() {
|
||||||
editor.setStyleForNextShapes(style, value, { squashing })
|
editor.setStyleForNextShapes(style, value, { squashing })
|
||||||
editor.updateInstanceState({ isChangingStyle: true })
|
editor.updateInstanceState({ isChangingStyle: true })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
trackEvent('set-style', { source: 'style-panel', id: style.id, value: value as string })
|
||||||
}
|
}
|
||||||
}, [editor])
|
}, [editor, trackEvent])
|
||||||
}
|
}
|
||||||
|
|
||||||
const tldrawSupportedOpacities = [0.1, 0.25, 0.5, 0.75, 1] as const
|
const tldrawSupportedOpacities = [0.1, 0.25, 0.5, 0.75, 1] as const
|
||||||
|
@ -97,6 +101,7 @@ function CommonStylePickerSet({
|
||||||
opacity: SharedStyle<number>
|
opacity: SharedStyle<number>
|
||||||
}) {
|
}) {
|
||||||
const editor = useEditor()
|
const editor = useEditor()
|
||||||
|
const trackEvent = useUiEvents()
|
||||||
const msg = useTranslation()
|
const msg = useTranslation()
|
||||||
|
|
||||||
const handleValueChange = useStyleChangeCallback()
|
const handleValueChange = useStyleChangeCallback()
|
||||||
|
@ -111,8 +116,10 @@ function CommonStylePickerSet({
|
||||||
editor.setOpacityForNextShapes(item, { ephemeral })
|
editor.setOpacityForNextShapes(item, { ephemeral })
|
||||||
editor.updateInstanceState({ isChangingStyle: true })
|
editor.updateInstanceState({ isChangingStyle: true })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
trackEvent('set-style', { source: 'style-panel', id: 'opacity', value })
|
||||||
},
|
},
|
||||||
[editor]
|
[editor, trackEvent]
|
||||||
)
|
)
|
||||||
|
|
||||||
const color = styles.get(DefaultColorStyle)
|
const color = styles.get(DefaultColorStyle)
|
||||||
|
|
|
@ -18,6 +18,7 @@ export type TLUiEventSource =
|
||||||
| 'dialog'
|
| 'dialog'
|
||||||
| 'help-menu'
|
| 'help-menu'
|
||||||
| 'helper-buttons'
|
| 'helper-buttons'
|
||||||
|
| 'style-panel'
|
||||||
| 'unknown'
|
| 'unknown'
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
|
@ -72,6 +73,7 @@ export interface TLUiEventMap {
|
||||||
copy: null
|
copy: null
|
||||||
paste: null
|
paste: null
|
||||||
cut: null
|
cut: null
|
||||||
|
'set-style': { id: string; value: string | number }
|
||||||
'toggle-transparent': null
|
'toggle-transparent': null
|
||||||
'toggle-snap-mode': null
|
'toggle-snap-mode': null
|
||||||
'toggle-tool-lock': null
|
'toggle-tool-lock': null
|
||||||
|
|
|
@ -13565,11 +13565,13 @@ __metadata:
|
||||||
"@radix-ui/react-alert-dialog": "npm:^1.0.5"
|
"@radix-ui/react-alert-dialog": "npm:^1.0.5"
|
||||||
"@tldraw/assets": "workspace:*"
|
"@tldraw/assets": "workspace:*"
|
||||||
"@tldraw/tldraw": "workspace:*"
|
"@tldraw/tldraw": "workspace:*"
|
||||||
|
"@types/lodash": "npm:^4.14.188"
|
||||||
"@vercel/analytics": "npm:^1.1.1"
|
"@vercel/analytics": "npm:^1.1.1"
|
||||||
"@vitejs/plugin-react": "npm:^4.2.0"
|
"@vitejs/plugin-react": "npm:^4.2.0"
|
||||||
classnames: "npm:^2.3.2"
|
classnames: "npm:^2.3.2"
|
||||||
dotenv: "npm:^16.3.1"
|
dotenv: "npm:^16.3.1"
|
||||||
lazyrepo: "npm:0.0.0-alpha.27"
|
lazyrepo: "npm:0.0.0-alpha.27"
|
||||||
|
lodash: "npm:^4.17.21"
|
||||||
react: "npm:^18.2.0"
|
react: "npm:^18.2.0"
|
||||||
react-dom: "npm:^18.2.0"
|
react-dom: "npm:^18.2.0"
|
||||||
react-router-dom: "npm:^6.17.0"
|
react-router-dom: "npm:^6.17.0"
|
||||||
|
|
Ładowanie…
Reference in New Issue