Menu updates / fix flip / add export / remove Shape menu (#3115)

This PR:
- adds the export all menu items to the main menu
- removes the export all menu items from the dotcom menus
- removes the shape menu and reverts several changes from
https://github.com/tldraw/tldraw/pull/2782. This was not properly
reviewed (I thought it was a PR about hiding / showing menu items).
- fixes a bug with exporting (exporting JSON was not working when the
user had no selected shapes)
- fixes a bug that would prevent "flip shapes" from appearing in the
menu
- prevents export / copy actions from running if there are no shapes on
the page
- allows export / copy actions to default to all shapes on the page if
no shapes are selected

These changes have not been released in the dotcom yet. There's will be
some thrash in the APIs.

# Menu philosophy

In the menu, the **edit** submenu relates to undo/redo, plus the user's
current selection.

Menu items that relate to specific to certain shapes are hidden when not
available.

Menu items that relate to all shapes are disabled when not available.

<img width="640" alt="image"
src="https://github.com/tldraw/tldraw/assets/23072548/e467e6bb-d958-4a9a-ac19-1dada52dcfa6">

### Change Type

- [x] `major` — Bug fix

### Test

- Select no shapes (arrange / flip should not be visible)
- Select one geo shape (arrange / flip should not be visible)
- Select two geo shapes (arrange / flip should be visible)
- Select one draw shape (arrange / flip should not be visible)

### Release Notes

- Revert some changes in the menu.
This commit is contained in:
Steve Ruiz 2024-03-11 18:31:28 +00:00 committed by GitHub
parent f1b4f807d8
commit 60cc0dcce3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 389 additions and 401 deletions

View file

@ -1,10 +1,4 @@
import { import { TldrawUiMenuGroup, TldrawUiMenuItem, TldrawUiMenuSubmenu, useActions } from 'tldraw'
ExportFileContentSubMenu,
TldrawUiMenuGroup,
TldrawUiMenuItem,
TldrawUiMenuSubmenu,
useActions,
} from 'tldraw'
import { import {
FORK_PROJECT_ACTION, FORK_PROJECT_ACTION,
LEAVE_SHARED_PROJECT_ACTION, LEAVE_SHARED_PROJECT_ACTION,
@ -21,7 +15,6 @@ export function LocalFileMenu() {
<TldrawUiMenuItem {...actions[NEW_PROJECT_ACTION]} /> <TldrawUiMenuItem {...actions[NEW_PROJECT_ACTION]} />
<TldrawUiMenuItem {...actions[OPEN_FILE_ACTION]} /> <TldrawUiMenuItem {...actions[OPEN_FILE_ACTION]} />
<TldrawUiMenuItem {...actions[SAVE_FILE_COPY_ACTION]} /> <TldrawUiMenuItem {...actions[SAVE_FILE_COPY_ACTION]} />
<ExportFileContentSubMenu />
</TldrawUiMenuGroup> </TldrawUiMenuGroup>
<TldrawUiMenuGroup id="share"> <TldrawUiMenuGroup id="share">
<TldrawUiMenuItem {...actions[SHARE_PROJECT_ACTION]} /> <TldrawUiMenuItem {...actions[SHARE_PROJECT_ACTION]} />
@ -37,7 +30,6 @@ export function MultiplayerFileMenu() {
<TldrawUiMenuSubmenu id="file" label="menu.file"> <TldrawUiMenuSubmenu id="file" label="menu.file">
<TldrawUiMenuGroup id="file-actions"> <TldrawUiMenuGroup id="file-actions">
<TldrawUiMenuItem {...actions[SAVE_FILE_COPY_ACTION]} /> <TldrawUiMenuItem {...actions[SAVE_FILE_COPY_ACTION]} />
<ExportFileContentSubMenu />
</TldrawUiMenuGroup> </TldrawUiMenuGroup>
<TldrawUiMenuGroup id="share"> <TldrawUiMenuGroup id="share">
<TldrawUiMenuItem {...actions[FORK_PROJECT_ACTION]} /> <TldrawUiMenuItem {...actions[FORK_PROJECT_ACTION]} />

View file

@ -11,7 +11,6 @@ import {
Editor, Editor,
ExtrasGroup, ExtrasGroup,
PreferencesGroup, PreferencesGroup,
ShapeSubmenu,
TLComponents, TLComponents,
Tldraw, Tldraw,
TldrawUiMenuGroup, TldrawUiMenuGroup,
@ -49,7 +48,6 @@ const components: TLComponents = {
<DefaultMainMenu> <DefaultMainMenu>
<LocalFileMenu /> <LocalFileMenu />
<EditSubmenu /> <EditSubmenu />
<ShapeSubmenu />
<ViewSubmenu /> <ViewSubmenu />
<ExtrasGroup /> <ExtrasGroup />
<PreferencesGroup /> <PreferencesGroup />

View file

@ -12,7 +12,6 @@ import {
ExtrasGroup, ExtrasGroup,
OfflineIndicator, OfflineIndicator,
PreferencesGroup, PreferencesGroup,
ShapeSubmenu,
TLComponents, TLComponents,
Tldraw, Tldraw,
TldrawUiMenuGroup, TldrawUiMenuGroup,
@ -69,7 +68,6 @@ const components: TLComponents = {
<DefaultMainMenu> <DefaultMainMenu>
<MultiplayerFileMenu /> <MultiplayerFileMenu />
<EditSubmenu /> <EditSubmenu />
<ShapeSubmenu />
<ViewSubmenu /> <ViewSubmenu />
<ExtrasGroup /> <ExtrasGroup />
<PreferencesGroup /> <PreferencesGroup />

View file

@ -212,6 +212,13 @@ test.describe('Actions on shapes', () => {
test('Operations on shapes', async () => { test('Operations on shapes', async () => {
await setupPageWithShapes(page) await setupPageWithShapes(page)
// needs shapes on the canvas
await page.keyboard.press('Control+Shift+c')
expect(await page.evaluate(() => __tldraw_ui_event)).toMatchObject({
name: 'copy-as',
data: { format: 'svg', source: 'kbd' },
})
// select-all — Cmd+A // select-all — Cmd+A
await page.keyboard.press('Control+a') await page.keyboard.press('Control+a')
expect(await page.evaluate(() => __tldraw_ui_event)).toMatchObject({ expect(await page.evaluate(() => __tldraw_ui_event)).toMatchObject({
@ -353,14 +360,6 @@ test.describe('Actions on shapes', () => {
// name: 'open-menu', // name: 'open-menu',
// data: { source: 'dialog' }, // data: { source: 'dialog' },
// }) // })
/* --------------------- Export --------------------- */
await page.keyboard.press('Control+Shift+c')
expect(await page.evaluate(() => __tldraw_ui_event)).toMatchObject({
name: 'copy-as',
data: { format: 'svg', source: 'kbd' },
})
}) })
}) })

View file

@ -37,11 +37,11 @@
"action.export-as-svg.short": "SVG", "action.export-as-svg.short": "SVG",
"action.export-as-svg": "Export as SVG", "action.export-as-svg": "Export as SVG",
"action.export-all-as-json.short": "JSON", "action.export-all-as-json.short": "JSON",
"action.export-all-as-json": "Export all as JSON", "action.export-all-as-json": "Export as JSON",
"action.export-all-as-png.short": "PNG", "action.export-all-as-png.short": "PNG",
"action.export-all-as-png": "Export all as PNG", "action.export-all-as-png": "Export as PNG",
"action.export-all-as-svg.short": "SVG", "action.export-all-as-svg.short": "SVG",
"action.export-all-as-svg": "Export all as SVG", "action.export-all-as-svg": "Export as SVG",
"action.fit-frame-to-content": "Fit to content", "action.fit-frame-to-content": "Fit to content",
"action.flip-horizontal": "Flip horizontally", "action.flip-horizontal": "Flip horizontally",
"action.flip-vertical": "Flip vertically", "action.flip-vertical": "Flip vertically",
@ -225,7 +225,6 @@
"menu.title": "Menu", "menu.title": "Menu",
"menu.copy-as": "Copy as", "menu.copy-as": "Copy as",
"menu.edit": "Edit", "menu.edit": "Edit",
"menu.shape": "Shape",
"menu.export-as": "Export as", "menu.export-as": "Export as",
"menu.file": "File", "menu.file": "File",
"menu.language": "Language", "menu.language": "Language",
@ -234,7 +233,7 @@
"context-menu.arrange": "Arrange", "context-menu.arrange": "Arrange",
"context-menu.copy-as": "Copy as", "context-menu.copy-as": "Copy as",
"context-menu.export-as": "Export as", "context-menu.export-as": "Export as",
"context-menu.export-all-as": "Export all as", "context-menu.export-all-as": "Export",
"context-menu.move-to-page": "Move to page", "context-menu.move-to-page": "Move to page",
"context-menu.reorder": "Reorder", "context-menu.reorder": "Reorder",
"page-menu.title": "Pages", "page-menu.title": "Pages",

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -201,12 +201,14 @@ export {
ArrangeMenuSubmenu, ArrangeMenuSubmenu,
ClipboardMenuGroup, ClipboardMenuGroup,
ConversionsMenuGroup, ConversionsMenuGroup,
ConvertToBookmarkMenuItem,
ConvertToEmbedMenuItem,
CopyAsMenuGroup,
CopyMenuItem, CopyMenuItem,
CutMenuItem, CutMenuItem,
DeleteMenuItem, DeleteMenuItem,
DuplicateMenuItem, DuplicateMenuItem,
EditLinkMenuItem, EditLinkMenuItem,
EmbedsGroup,
FitFrameToContentMenuItem, FitFrameToContentMenuItem,
GroupMenuItem, GroupMenuItem,
MoveToPageMenu, MoveToPageMenu,
@ -214,7 +216,7 @@ export {
PrintItem, PrintItem,
RemoveFrameMenuItem, RemoveFrameMenuItem,
ReorderMenuSubmenu, ReorderMenuSubmenu,
SetSelectionGroup, SelectAllMenuItem,
ToggleAutoSizeMenuItem, ToggleAutoSizeMenuItem,
ToggleDarkModeItem, ToggleDarkModeItem,
ToggleDebugModeItem, ToggleDebugModeItem,
@ -243,11 +245,8 @@ export {
EditSubmenu, EditSubmenu,
ExportFileContentSubMenu, ExportFileContentSubMenu,
ExtrasGroup, ExtrasGroup,
LockGroup,
MiscMenuGroup, MiscMenuGroup,
MultiShapeMenuGroup,
PreferencesGroup, PreferencesGroup,
ShapeSubmenu,
UndoRedoGroup, UndoRedoGroup,
ViewSubmenu, ViewSubmenu,
} from './lib/ui/components/MainMenu/DefaultMainMenuContent' } from './lib/ui/components/MainMenu/DefaultMainMenuContent'

View file

@ -3,14 +3,15 @@ import {
ArrangeMenuSubmenu, ArrangeMenuSubmenu,
ClipboardMenuGroup, ClipboardMenuGroup,
ConversionsMenuGroup, ConversionsMenuGroup,
ConvertToBookmarkMenuItem,
ConvertToEmbedMenuItem,
EditLinkMenuItem, EditLinkMenuItem,
EmbedsGroup,
FitFrameToContentMenuItem, FitFrameToContentMenuItem,
GroupMenuItem, GroupMenuItem,
MoveToPageMenu, MoveToPageMenu,
RemoveFrameMenuItem, RemoveFrameMenuItem,
ReorderMenuSubmenu, ReorderMenuSubmenu,
SetSelectionGroup, SelectAllMenuItem,
ToggleAutoSizeMenuItem, ToggleAutoSizeMenuItem,
ToggleLockMenuItem, ToggleLockMenuItem,
UngroupMenuItem, UngroupMenuItem,
@ -31,16 +32,17 @@ export function DefaultContextMenuContent() {
return ( return (
<> <>
<TldrawUiMenuGroup id="selection"> <TldrawUiMenuGroup id="misc">
<ToggleAutoSizeMenuItem />
<EditLinkMenuItem />
<GroupMenuItem /> <GroupMenuItem />
<UngroupMenuItem /> <UngroupMenuItem />
<EditLinkMenuItem />
<ToggleAutoSizeMenuItem />
<RemoveFrameMenuItem /> <RemoveFrameMenuItem />
<FitFrameToContentMenuItem /> <FitFrameToContentMenuItem />
<ConvertToEmbedMenuItem />
<ConvertToBookmarkMenuItem />
<ToggleLockMenuItem /> <ToggleLockMenuItem />
</TldrawUiMenuGroup> </TldrawUiMenuGroup>
<EmbedsGroup />
<TldrawUiMenuGroup id="modify"> <TldrawUiMenuGroup id="modify">
<ArrangeMenuSubmenu /> <ArrangeMenuSubmenu />
<ReorderMenuSubmenu /> <ReorderMenuSubmenu />
@ -48,7 +50,9 @@ export function DefaultContextMenuContent() {
</TldrawUiMenuGroup> </TldrawUiMenuGroup>
<ClipboardMenuGroup /> <ClipboardMenuGroup />
<ConversionsMenuGroup /> <ConversionsMenuGroup />
<SetSelectionGroup /> <TldrawUiMenuGroup id="select-all">
<SelectAllMenuItem />
</TldrawUiMenuGroup>
</> </>
) )
} }

View file

@ -1,20 +1,19 @@
import { track, useEditor } from '@tldraw/editor' import { LANGUAGES, useEditor, useValue } from '@tldraw/editor'
import { useUiEvents } from '../context/events' import { useUiEvents } from '../context/events'
import { useLanguages } from '../hooks/useTranslation/useLanguages'
import { TldrawUiMenuCheckboxItem } from './primitives/menus/TldrawUiMenuCheckboxItem' import { TldrawUiMenuCheckboxItem } from './primitives/menus/TldrawUiMenuCheckboxItem'
import { TldrawUiMenuGroup } from './primitives/menus/TldrawUiMenuGroup' import { TldrawUiMenuGroup } from './primitives/menus/TldrawUiMenuGroup'
import { TldrawUiMenuSubmenu } from './primitives/menus/TldrawUiMenuSubmenu' import { TldrawUiMenuSubmenu } from './primitives/menus/TldrawUiMenuSubmenu'
/** @public */ /** @public */
export const LanguageMenu = track(function LanguageMenu() { export function LanguageMenu() {
const editor = useEditor() const editor = useEditor()
const trackEvent = useUiEvents() const trackEvent = useUiEvents()
const { languages, currentLanguage } = useLanguages() const currentLanguage = useValue('locale', () => editor.user.getLocale(), [editor])
return ( return (
<TldrawUiMenuSubmenu id="help menu language" label="menu.language"> <TldrawUiMenuSubmenu id="help menu language" label="menu.language">
<TldrawUiMenuGroup id="languages"> <TldrawUiMenuGroup id="languages">
{languages.map(({ locale, label }) => ( {LANGUAGES.map(({ locale, label }) => (
<TldrawUiMenuCheckboxItem <TldrawUiMenuCheckboxItem
id={`language-${locale}`} id={`language-${locale}`}
key={locale} key={locale}
@ -30,4 +29,4 @@ export const LanguageMenu = track(function LanguageMenu() {
</TldrawUiMenuGroup> </TldrawUiMenuGroup>
</TldrawUiMenuSubmenu> </TldrawUiMenuSubmenu>
) )
}) }

View file

@ -5,12 +5,13 @@ import { LanguageMenu } from '../LanguageMenu'
import { import {
ClipboardMenuGroup, ClipboardMenuGroup,
ConversionsMenuGroup, ConversionsMenuGroup,
ConvertToBookmarkMenuItem,
ConvertToEmbedMenuItem,
EditLinkMenuItem, EditLinkMenuItem,
EmbedsGroup,
FitFrameToContentMenuItem, FitFrameToContentMenuItem,
GroupMenuItem, GroupMenuItem,
RemoveFrameMenuItem, RemoveFrameMenuItem,
SetSelectionGroup, SelectAllMenuItem,
ToggleAutoSizeMenuItem, ToggleAutoSizeMenuItem,
ToggleDarkModeItem, ToggleDarkModeItem,
ToggleDebugModeItem, ToggleDebugModeItem,
@ -38,8 +39,8 @@ export function DefaultMainMenuContent() {
return ( return (
<> <>
<EditSubmenu /> <EditSubmenu />
<ShapeSubmenu />
<ViewSubmenu /> <ViewSubmenu />
<ExportFileContentSubMenu />
<ExtrasGroup /> <ExtrasGroup />
<PreferencesGroup /> <PreferencesGroup />
</> </>
@ -78,28 +79,12 @@ export function EditSubmenu() {
<TldrawUiMenuSubmenu id="edit" label="menu.edit" disabled={!selectToolActive}> <TldrawUiMenuSubmenu id="edit" label="menu.edit" disabled={!selectToolActive}>
<UndoRedoGroup /> <UndoRedoGroup />
<ClipboardMenuGroup /> <ClipboardMenuGroup />
<SetSelectionGroup />
</TldrawUiMenuSubmenu>
)
}
/** @public */
export function ShapeSubmenu() {
const editor = useEditor()
const selectToolActive = useValue(
'isSelectToolActive',
() => editor.getCurrentToolId() === 'select',
[editor]
)
return (
<TldrawUiMenuSubmenu id="shape" label="menu.shape" disabled={!selectToolActive}>
<ConversionsMenuGroup /> <ConversionsMenuGroup />
<MultiShapeMenuGroup />
<MiscMenuGroup /> <MiscMenuGroup />
<EmbedsGroup />
<LockGroup /> <LockGroup />
<TldrawUiMenuGroup id="select-all">
<SelectAllMenuItem />
</TldrawUiMenuGroup>
</TldrawUiMenuSubmenu> </TldrawUiMenuSubmenu>
) )
} }
@ -108,8 +93,14 @@ export function ShapeSubmenu() {
export function MiscMenuGroup() { export function MiscMenuGroup() {
return ( return (
<TldrawUiMenuGroup id="misc"> <TldrawUiMenuGroup id="misc">
<ToggleAutoSizeMenuItem /> <GroupMenuItem />
<UngroupMenuItem />
<EditLinkMenuItem /> <EditLinkMenuItem />
<ToggleAutoSizeMenuItem />
<RemoveFrameMenuItem />
<FitFrameToContentMenuItem />
<ConvertToEmbedMenuItem />
<ConvertToBookmarkMenuItem />
</TldrawUiMenuGroup> </TldrawUiMenuGroup>
) )
} }
@ -124,18 +115,6 @@ export function LockGroup() {
) )
} }
/** @public */
export function MultiShapeMenuGroup() {
return (
<TldrawUiMenuGroup id="multi-shape">
<GroupMenuItem />
<UngroupMenuItem />
<RemoveFrameMenuItem />
<FitFrameToContentMenuItem />
</TldrawUiMenuGroup>
)
}
/** @public */ /** @public */
export function UndoRedoGroup() { export function UndoRedoGroup() {
const actions = useActions() const actions = useActions()

View file

@ -31,36 +31,40 @@ import { TldrawUiMenuSubmenu } from './primitives/menus/TldrawUiMenuSubmenu'
export function ToggleAutoSizeMenuItem() { export function ToggleAutoSizeMenuItem() {
const actions = useActions() const actions = useActions()
const shouldDisplay = useShowAutoSizeToggle() const shouldDisplay = useShowAutoSizeToggle()
if (!shouldDisplay) return null
return <TldrawUiMenuItem {...actions['toggle-auto-size']} disabled={!shouldDisplay} /> return <TldrawUiMenuItem {...actions['toggle-auto-size']} />
} }
/** @public */ /** @public */
export function EditLinkMenuItem() { export function EditLinkMenuItem() {
const actions = useActions() const actions = useActions()
const shouldDisplay = useHasLinkShapeSelected() const shouldDisplay = useHasLinkShapeSelected()
if (!shouldDisplay) return null
return <TldrawUiMenuItem {...actions['edit-link']} disabled={!shouldDisplay} /> return <TldrawUiMenuItem {...actions['edit-link']} />
} }
/** @public */ /** @public */
export function DuplicateMenuItem() { export function DuplicateMenuItem() {
const actions = useActions() const actions = useActions()
const shouldDisplay = useUnlockedSelectedShapesCount(1) const shouldDisplay = useUnlockedSelectedShapesCount(1)
if (!shouldDisplay) return null
return <TldrawUiMenuItem {...actions['duplicate']} disabled={!shouldDisplay} /> return <TldrawUiMenuItem {...actions['duplicate']} />
} }
/** @public */ /** @public */
export function GroupMenuItem() { export function GroupMenuItem() {
const actions = useActions() const actions = useActions()
const shouldDisplay = useAllowGroup() const shouldDisplay = useAllowGroup()
if (!shouldDisplay) return null
return <TldrawUiMenuItem {...actions['group']} disabled={!shouldDisplay} /> return <TldrawUiMenuItem {...actions['group']} />
} }
/** @public */ /** @public */
export function UngroupMenuItem() { export function UngroupMenuItem() {
const actions = useActions() const actions = useActions()
const shouldDisplay = useAllowUngroup() const shouldDisplay = useAllowUngroup()
if (!shouldDisplay) return null
return <TldrawUiMenuItem {...actions['ungroup']} disabled={!shouldDisplay} /> return <TldrawUiMenuItem {...actions['ungroup']} />
} }
/** @public */ /** @public */
export function RemoveFrameMenuItem() { export function RemoveFrameMenuItem() {
@ -75,8 +79,9 @@ export function RemoveFrameMenuItem() {
}, },
[editor] [editor]
) )
if (!shouldDisplay) return null
return <TldrawUiMenuItem {...actions['remove-frame']} disabled={!shouldDisplay} /> return <TldrawUiMenuItem {...actions['remove-frame']} />
} }
/** @public */ /** @public */
export function FitFrameToContentMenuItem() { export function FitFrameToContentMenuItem() {
@ -94,8 +99,9 @@ export function FitFrameToContentMenuItem() {
}, },
[editor] [editor]
) )
if (!shouldDisplay) return null
return <TldrawUiMenuItem {...actions['fit-frame-to-content']} disabled={!shouldDisplay} /> return <TldrawUiMenuItem {...actions['fit-frame-to-content']} />
} }
/** @public */ /** @public */
export function ToggleLockMenuItem() { export function ToggleLockMenuItem() {
@ -104,8 +110,9 @@ export function ToggleLockMenuItem() {
const shouldDisplay = useValue('selected shapes', () => editor.getSelectedShapes().length > 0, [ const shouldDisplay = useValue('selected shapes', () => editor.getSelectedShapes().length > 0, [
editor, editor,
]) ])
if (!shouldDisplay) return null
return <TldrawUiMenuItem {...actions['toggle-lock']} disabled={!shouldDisplay} /> return <TldrawUiMenuItem {...actions['toggle-lock']} />
} }
/** @public */ /** @public */
export function ToggleTransparentBgMenuItem() { export function ToggleTransparentBgMenuItem() {
@ -172,20 +179,31 @@ export function ZoomToSelectionMenuItem() {
} }
/* -------------------- Clipboard ------------------- */ /* -------------------- Clipboard ------------------- */
/** @public */ /** @public */
export function ClipboardMenuGroup() { export function ClipboardMenuGroup() {
return (
<TldrawUiMenuGroup id="clipboard">
<CutMenuItem />
<CopyMenuItem />
<PasteMenuItem />
<DuplicateMenuItem />
<DeleteMenuItem />
</TldrawUiMenuGroup>
)
}
/** @public */
export function CopyAsMenuGroup() {
const editor = useEditor() const editor = useEditor()
const actions = useActions() const actions = useActions()
const atLeastOneShapeOnPage = useValue( const atLeastOneShapeOnPage = useValue(
'atLeastOneShapeOnPage', 'atLeastOneShapeOnPage',
() => editor.getCurrentPageShapeIds().size > 0, () => editor.getCurrentPageShapeIds().size > 0,
[] [editor]
) )
return ( return (
<TldrawUiMenuGroup id="clipboard">
<CutMenuItem />
<CopyMenuItem />
<TldrawUiMenuSubmenu <TldrawUiMenuSubmenu
id="copy-as" id="copy-as"
label="context-menu.copy-as" label="context-menu.copy-as"
@ -203,12 +221,9 @@ export function ClipboardMenuGroup() {
<ToggleTransparentBgMenuItem /> <ToggleTransparentBgMenuItem />
</TldrawUiMenuGroup> </TldrawUiMenuGroup>
</TldrawUiMenuSubmenu> </TldrawUiMenuSubmenu>
<DuplicateMenuItem />
<PasteMenuItem />
<DeleteMenuItem />
</TldrawUiMenuGroup>
) )
} }
/** @public */ /** @public */
export function CutMenuItem() { export function CutMenuItem() {
const actions = useActions() const actions = useActions()
@ -216,6 +231,7 @@ export function CutMenuItem() {
return <TldrawUiMenuItem {...actions['cut']} disabled={!shouldDisplay} /> return <TldrawUiMenuItem {...actions['cut']} disabled={!shouldDisplay} />
} }
/** @public */ /** @public */
export function CopyMenuItem() { export function CopyMenuItem() {
const actions = useActions() const actions = useActions()
@ -223,6 +239,7 @@ export function CopyMenuItem() {
return <TldrawUiMenuItem {...actions['copy']} disabled={!shouldDisplay} /> return <TldrawUiMenuItem {...actions['copy']} disabled={!shouldDisplay} />
} }
/** @public */ /** @public */
export function PasteMenuItem() { export function PasteMenuItem() {
const actions = useActions() const actions = useActions()
@ -232,19 +249,23 @@ export function PasteMenuItem() {
} }
/* ------------------- Conversions ------------------ */ /* ------------------- Conversions ------------------ */
/** @public */ /** @public */
export function ConversionsMenuGroup() { export function ConversionsMenuGroup() {
const editor = useEditor()
const actions = useActions() const actions = useActions()
const shouldDisplay = useUnlockedSelectedShapesCount(1) const atLeastOneShapeOnPage = useValue(
'atLeastOneShapeOnPage',
() => editor.getCurrentPageShapeIds().size > 0,
[editor]
)
if (!atLeastOneShapeOnPage) return null
return ( return (
<TldrawUiMenuGroup id="conversions"> <TldrawUiMenuGroup id="conversions">
<TldrawUiMenuSubmenu <CopyAsMenuGroup />
id="export-as" <TldrawUiMenuSubmenu id="export-as" label="context-menu.export-as" size="small">
label="context-menu.export-as"
size="small"
disabled={!shouldDisplay}
>
<TldrawUiMenuGroup id="export-as-group"> <TldrawUiMenuGroup id="export-as-group">
<TldrawUiMenuItem {...actions['export-as-svg']} /> <TldrawUiMenuItem {...actions['export-as-svg']} />
<TldrawUiMenuItem {...actions['export-as-png']} /> <TldrawUiMenuItem {...actions['export-as-png']} />
@ -260,7 +281,7 @@ export function ConversionsMenuGroup() {
/* ------------------ Set Selection ----------------- */ /* ------------------ Set Selection ----------------- */
/** @public */ /** @public */
export function SetSelectionGroup() { export function SelectAllMenuItem() {
const actions = useActions() const actions = useActions()
const editor = useEditor() const editor = useEditor()
const atLeastOneShapeOnPage = useValue( const atLeastOneShapeOnPage = useValue(
@ -269,11 +290,7 @@ export function SetSelectionGroup() {
[editor] [editor]
) )
return ( return <TldrawUiMenuItem {...actions['select-all']} disabled={!atLeastOneShapeOnPage} />
<TldrawUiMenuGroup id="set-selection-group">
<TldrawUiMenuItem {...actions['select-all']} disabled={!atLeastOneShapeOnPage} />
</TldrawUiMenuGroup>
)
} }
/* ------------------ Delete Group ------------------ */ /* ------------------ Delete Group ------------------ */
@ -313,7 +330,7 @@ export function ArrangeMenuSubmenu() {
<TldrawUiMenuItem {...actions['stretch-vertical']} /> <TldrawUiMenuItem {...actions['stretch-vertical']} />
</TldrawUiMenuGroup> </TldrawUiMenuGroup>
)} )}
{onlyFlippableShapeSelected && ( {(twoSelected || onlyFlippableShapeSelected) && (
<TldrawUiMenuGroup id="flip"> <TldrawUiMenuGroup id="flip">
<TldrawUiMenuItem {...actions['flip-horizontal']} /> <TldrawUiMenuItem {...actions['flip-horizontal']} />
<TldrawUiMenuItem {...actions['flip-vertical']} /> <TldrawUiMenuItem {...actions['flip-vertical']} />
@ -423,8 +440,9 @@ export function MoveToPageMenu() {
</TldrawUiMenuSubmenu> </TldrawUiMenuSubmenu>
) )
} }
/** @public */ /** @public */
export function EmbedsGroup() { export function ConvertToBookmarkMenuItem() {
const editor = useEditor() const editor = useEditor()
const actions = useActions() const actions = useActions()
@ -442,6 +460,15 @@ export function EmbedsGroup() {
[editor] [editor]
) )
if (!oneEmbedSelected) return null
return <TldrawUiMenuItem {...actions['convert-to-bookmark']} />
}
/** @public */
export function ConvertToEmbedMenuItem() {
const editor = useEditor()
const actions = useActions()
const oneEmbeddableBookmarkSelected = useValue( const oneEmbeddableBookmarkSelected = useValue(
'oneEmbeddableBookmarkSelected', 'oneEmbeddableBookmarkSelected',
() => { () => {
@ -457,17 +484,9 @@ export function EmbedsGroup() {
[editor] [editor]
) )
return ( if (!oneEmbeddableBookmarkSelected) return null
<TldrawUiMenuGroup id="embeds">
{/* XXX this doesn't exist?? */} return <TldrawUiMenuItem {...actions['convert-to-embed']} />
{/* <TldrawUiMenuItem {...actions['edit-embed']} disabled={!oneEmbedSelected} /> */}
<TldrawUiMenuItem {...actions['convert-to-bookmark']} disabled={!oneEmbedSelected} />
<TldrawUiMenuItem
{...actions['convert-to-embed']}
disabled={!oneEmbeddableBookmarkSelected}
/>
</TldrawUiMenuGroup>
)
} }
/* ------------------- Preferences ------------------ */ /* ------------------- Preferences ------------------ */

View file

@ -174,8 +174,11 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
}, },
readonlyOk: true, readonlyOk: true,
onSelect(source) { onSelect(source) {
let ids = editor.getSelectedShapeIds()
if (ids.length === 0) ids = Array.from(editor.getCurrentPageShapeIds().values())
if (ids.length === 0) return
trackEvent('export-as', { format: 'svg', source }) trackEvent('export-as', { format: 'svg', source })
exportAs(editor.getSelectedShapeIds(), 'svg', getExportName(editor, defaultDocumentName)) exportAs(ids, 'svg', getExportName(editor, defaultDocumentName))
}, },
}, },
{ {
@ -187,8 +190,11 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
}, },
readonlyOk: true, readonlyOk: true,
onSelect(source) { onSelect(source) {
let ids = editor.getSelectedShapeIds()
if (ids.length === 0) ids = Array.from(editor.getCurrentPageShapeIds().values())
if (ids.length === 0) return
trackEvent('export-as', { format: 'png', source }) trackEvent('export-as', { format: 'png', source })
exportAs(editor.getSelectedShapeIds(), 'png', getExportName(editor, defaultDocumentName)) exportAs(ids, 'png', getExportName(editor, defaultDocumentName))
}, },
}, },
{ {
@ -200,8 +206,11 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
}, },
readonlyOk: true, readonlyOk: true,
onSelect(source) { onSelect(source) {
let ids = editor.getSelectedShapeIds()
if (ids.length === 0) ids = Array.from(editor.getCurrentPageShapeIds().values())
if (ids.length === 0) return
trackEvent('export-as', { format: 'json', source }) trackEvent('export-as', { format: 'json', source })
exportAs(editor.getSelectedShapeIds(), 'json', getExportName(editor, defaultDocumentName)) exportAs(ids, 'json', getExportName(editor, defaultDocumentName))
}, },
}, },
{ {
@ -213,6 +222,9 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
}, },
readonlyOk: true, readonlyOk: true,
onSelect(source) { onSelect(source) {
let ids = editor.getSelectedShapeIds()
if (ids.length === 0) ids = Array.from(editor.getCurrentPageShapeIds().values())
if (ids.length === 0) return
trackEvent('export-all-as', { format: 'svg', source }) trackEvent('export-all-as', { format: 'svg', source })
exportAs( exportAs(
Array.from(editor.getCurrentPageShapeIds()), Array.from(editor.getCurrentPageShapeIds()),
@ -230,12 +242,10 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
}, },
readonlyOk: true, readonlyOk: true,
onSelect(source) { onSelect(source) {
const ids = Array.from(editor.getCurrentPageShapeIds().values())
if (ids.length === 0) return
trackEvent('export-all-as', { format: 'png', source }) trackEvent('export-all-as', { format: 'png', source })
exportAs( exportAs(ids, 'png', getExportName(editor, defaultDocumentName))
Array.from(editor.getCurrentPageShapeIds()),
'png',
getExportName(editor, defaultDocumentName)
)
}, },
}, },
{ {
@ -247,12 +257,10 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
}, },
readonlyOk: true, readonlyOk: true,
onSelect(source) { onSelect(source) {
const ids = Array.from(editor.getCurrentPageShapeIds().values())
if (ids.length === 0) return
trackEvent('export-all-as', { format: 'json', source }) trackEvent('export-all-as', { format: 'json', source })
exportAs( exportAs(ids, 'json', getExportName(editor, defaultDocumentName))
Array.from(editor.getCurrentPageShapeIds()),
'json',
getExportName(editor, defaultDocumentName)
)
}, },
}, },
{ {
@ -265,8 +273,11 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
kbd: '$!c', kbd: '$!c',
readonlyOk: true, readonlyOk: true,
onSelect(source) { onSelect(source) {
let ids = editor.getSelectedShapeIds()
if (ids.length === 0) ids = Array.from(editor.getCurrentPageShapeIds().values())
if (ids.length === 0) return
trackEvent('copy-as', { format: 'svg', source }) trackEvent('copy-as', { format: 'svg', source })
copyAs(editor.getSelectedShapeIds(), 'svg') copyAs(ids, 'svg')
}, },
}, },
{ {
@ -278,8 +289,11 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
}, },
readonlyOk: true, readonlyOk: true,
onSelect(source) { onSelect(source) {
let ids = editor.getSelectedShapeIds()
if (ids.length === 0) ids = Array.from(editor.getCurrentPageShapeIds().values())
if (ids.length === 0) return
trackEvent('copy-as', { format: 'png', source }) trackEvent('copy-as', { format: 'png', source })
copyAs(editor.getSelectedShapeIds(), 'png') copyAs(ids, 'png')
}, },
}, },
{ {
@ -291,8 +305,11 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
}, },
readonlyOk: true, readonlyOk: true,
onSelect(source) { onSelect(source) {
let ids = editor.getSelectedShapeIds()
if (ids.length === 0) ids = Array.from(editor.getCurrentPageShapeIds().values())
if (ids.length === 0) return
trackEvent('copy-as', { format: 'json', source }) trackEvent('copy-as', { format: 'json', source })
copyAs(editor.getSelectedShapeIds(), 'json') copyAs(ids, 'json')
}, },
}, },
{ {

View file

@ -188,16 +188,13 @@ export function useOnlyFlippableShape() {
return useValue( return useValue(
'onlyFlippableShape', 'onlyFlippableShape',
() => { () => {
const selectedShapes = editor.getSelectedShapes() const shape = editor.getOnlySelectedShape()
return ( return (
selectedShapes.length === 1 && shape &&
selectedShapes.every( (editor.isShapeOfType<TLGroupShape>(shape, 'group') ||
(shape) =>
editor.isShapeOfType<TLGroupShape>(shape, 'group') ||
editor.isShapeOfType<TLArrowShape>(shape, 'arrow') || editor.isShapeOfType<TLArrowShape>(shape, 'arrow') ||
editor.isShapeOfType<TLLineShape>(shape, 'line') || editor.isShapeOfType<TLLineShape>(shape, 'line') ||
editor.isShapeOfType<TLDrawShape>(shape, 'draw') editor.isShapeOfType<TLDrawShape>(shape, 'draw'))
)
) )
}, },
[editor] [editor]

View file

@ -229,7 +229,6 @@ export type TLUiTranslationKey =
| 'menu.title' | 'menu.title'
| 'menu.copy-as' | 'menu.copy-as'
| 'menu.edit' | 'menu.edit'
| 'menu.shape'
| 'menu.export-as' | 'menu.export-as'
| 'menu.file' | 'menu.file'
| 'menu.language' | 'menu.language'

View file

@ -41,11 +41,11 @@ export const DEFAULT_TRANSLATION = {
'action.export-as-svg.short': 'SVG', 'action.export-as-svg.short': 'SVG',
'action.export-as-svg': 'Export as SVG', 'action.export-as-svg': 'Export as SVG',
'action.export-all-as-json.short': 'JSON', 'action.export-all-as-json.short': 'JSON',
'action.export-all-as-json': 'Export all as JSON', 'action.export-all-as-json': 'Export as JSON',
'action.export-all-as-png.short': 'PNG', 'action.export-all-as-png.short': 'PNG',
'action.export-all-as-png': 'Export all as PNG', 'action.export-all-as-png': 'Export as PNG',
'action.export-all-as-svg.short': 'SVG', 'action.export-all-as-svg.short': 'SVG',
'action.export-all-as-svg': 'Export all as SVG', 'action.export-all-as-svg': 'Export as SVG',
'action.fit-frame-to-content': 'Fit to content', 'action.fit-frame-to-content': 'Fit to content',
'action.flip-horizontal': 'Flip horizontally', 'action.flip-horizontal': 'Flip horizontally',
'action.flip-vertical': 'Flip vertically', 'action.flip-vertical': 'Flip vertically',
@ -229,7 +229,6 @@ export const DEFAULT_TRANSLATION = {
'menu.title': 'Menu', 'menu.title': 'Menu',
'menu.copy-as': 'Copy as', 'menu.copy-as': 'Copy as',
'menu.edit': 'Edit', 'menu.edit': 'Edit',
'menu.shape': 'Shape',
'menu.export-as': 'Export as', 'menu.export-as': 'Export as',
'menu.file': 'File', 'menu.file': 'File',
'menu.language': 'Language', 'menu.language': 'Language',
@ -238,7 +237,7 @@ export const DEFAULT_TRANSLATION = {
'context-menu.arrange': 'Arrange', 'context-menu.arrange': 'Arrange',
'context-menu.copy-as': 'Copy as', 'context-menu.copy-as': 'Copy as',
'context-menu.export-as': 'Export as', 'context-menu.export-as': 'Export as',
'context-menu.export-all-as': 'Export all as', 'context-menu.export-all-as': 'Export',
'context-menu.move-to-page': 'Move to page', 'context-menu.move-to-page': 'Move to page',
'context-menu.reorder': 'Reorder', 'context-menu.reorder': 'Reorder',
'page-menu.title': 'Pages', 'page-menu.title': 'Pages',

View file

@ -1,10 +0,0 @@
import { LANGUAGES, TLLanguage, useEditor } from '@tldraw/editor'
/** @internal */
export function useLanguages() {
const editor = useEditor()
return {
languages: LANGUAGES as readonly TLLanguage[],
currentLanguage: editor.user.getLocale(),
}
}