menu: rework File menu / ensure Export menu is present (#2783)
<img width="428" alt="Screenshot 2024-02-16 at 16 46 28" src="https://github.com/tldraw/tldraw/assets/469604/334cd0db-d9d5-4993-8012-c6985173edfb"> - re-orders to be the normative New / Open / Save order — we shouldn't be messing with this conventional ordering - removes the "Don't ask again" from New/Open dialogs because they're non-undoable and not what _anybody_ should ever select. we shouldn't offer users a loaded footgun! :P - makes File menu be part of the default menu — it's presence is glaringly missing for regular development - along with that, make the pieces of that menu available as lego pieces to use - it can't just be `DefaultMainMenuContent`, all or nothing, forcing downstream users to import everything from scratch - finally, adds the Export menu as initially intended by this PR! @steveruizok let's discuss if you have some notes on this and we can talk about the shape of things here. ### Change Type - [x] `patch` — Bug fix ### Release Notes - Composable UI: makes File items be more granularly accessible / usable - Menu: show Export under the File menu.
This commit is contained in:
parent
f19b12c42e
commit
fb852459db
15 changed files with 441 additions and 175 deletions
|
@ -1,4 +1,5 @@
|
||||||
import {
|
import {
|
||||||
|
ExportFileContentSubMenu,
|
||||||
TldrawUiMenuGroup,
|
TldrawUiMenuGroup,
|
||||||
TldrawUiMenuItem,
|
TldrawUiMenuItem,
|
||||||
TldrawUiMenuSubmenu,
|
TldrawUiMenuSubmenu,
|
||||||
|
@ -17,9 +18,10 @@ export function LocalFileMenu() {
|
||||||
return (
|
return (
|
||||||
<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[OPEN_FILE_ACTION]} />
|
|
||||||
<TldrawUiMenuItem {...actions[NEW_PROJECT_ACTION]} />
|
<TldrawUiMenuItem {...actions[NEW_PROJECT_ACTION]} />
|
||||||
|
<TldrawUiMenuItem {...actions[OPEN_FILE_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]} />
|
||||||
|
@ -35,6 +37,7 @@ 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]} />
|
||||||
|
|
|
@ -6,12 +6,16 @@ import {
|
||||||
DefaultKeyboardShortcutsDialog,
|
DefaultKeyboardShortcutsDialog,
|
||||||
DefaultKeyboardShortcutsDialogContent,
|
DefaultKeyboardShortcutsDialogContent,
|
||||||
DefaultMainMenu,
|
DefaultMainMenu,
|
||||||
DefaultMainMenuContent,
|
EditSubmenu,
|
||||||
Editor,
|
Editor,
|
||||||
|
ExtrasGroup,
|
||||||
|
ObjectSubmenu,
|
||||||
|
PreferencesGroup,
|
||||||
TLComponents,
|
TLComponents,
|
||||||
Tldraw,
|
Tldraw,
|
||||||
TldrawUiMenuGroup,
|
TldrawUiMenuGroup,
|
||||||
TldrawUiMenuItem,
|
TldrawUiMenuItem,
|
||||||
|
ViewSubmenu,
|
||||||
useActions,
|
useActions,
|
||||||
} from '@tldraw/tldraw'
|
} from '@tldraw/tldraw'
|
||||||
import { useCallback } from 'react'
|
import { useCallback } from 'react'
|
||||||
|
@ -44,7 +48,11 @@ const components: TLComponents = {
|
||||||
MainMenu: () => (
|
MainMenu: () => (
|
||||||
<DefaultMainMenu>
|
<DefaultMainMenu>
|
||||||
<LocalFileMenu />
|
<LocalFileMenu />
|
||||||
<DefaultMainMenuContent />
|
<EditSubmenu />
|
||||||
|
<ObjectSubmenu />
|
||||||
|
<ViewSubmenu />
|
||||||
|
<ExtrasGroup />
|
||||||
|
<PreferencesGroup />
|
||||||
<Links />
|
<Links />
|
||||||
</DefaultMainMenu>
|
</DefaultMainMenu>
|
||||||
),
|
),
|
||||||
|
|
|
@ -6,13 +6,17 @@ import {
|
||||||
DefaultKeyboardShortcutsDialog,
|
DefaultKeyboardShortcutsDialog,
|
||||||
DefaultKeyboardShortcutsDialogContent,
|
DefaultKeyboardShortcutsDialogContent,
|
||||||
DefaultMainMenu,
|
DefaultMainMenu,
|
||||||
DefaultMainMenuContent,
|
EditSubmenu,
|
||||||
Editor,
|
Editor,
|
||||||
|
ExtrasGroup,
|
||||||
|
ObjectSubmenu,
|
||||||
OfflineIndicator,
|
OfflineIndicator,
|
||||||
|
PreferencesGroup,
|
||||||
TLComponents,
|
TLComponents,
|
||||||
Tldraw,
|
Tldraw,
|
||||||
TldrawUiMenuGroup,
|
TldrawUiMenuGroup,
|
||||||
TldrawUiMenuItem,
|
TldrawUiMenuItem,
|
||||||
|
ViewSubmenu,
|
||||||
atom,
|
atom,
|
||||||
debugFlags,
|
debugFlags,
|
||||||
lns,
|
lns,
|
||||||
|
@ -66,7 +70,11 @@ const components: TLComponents = {
|
||||||
MainMenu: () => (
|
MainMenu: () => (
|
||||||
<DefaultMainMenu>
|
<DefaultMainMenu>
|
||||||
<MultiplayerFileMenu />
|
<MultiplayerFileMenu />
|
||||||
<DefaultMainMenuContent />
|
<EditSubmenu />
|
||||||
|
<ObjectSubmenu />
|
||||||
|
<ViewSubmenu />
|
||||||
|
<ExtrasGroup />
|
||||||
|
<PreferencesGroup />
|
||||||
<Links />
|
<Links />
|
||||||
</DefaultMainMenu>
|
</DefaultMainMenu>
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import {
|
import {
|
||||||
TLUiDialogsContextType,
|
TLUiDialogsContextType,
|
||||||
TldrawUiButton,
|
TldrawUiButton,
|
||||||
TldrawUiButtonCheck,
|
|
||||||
TldrawUiButtonLabel,
|
TldrawUiButtonLabel,
|
||||||
TldrawUiDialogBody,
|
TldrawUiDialogBody,
|
||||||
TldrawUiDialogCloseButton,
|
TldrawUiDialogCloseButton,
|
||||||
|
@ -10,11 +9,8 @@ import {
|
||||||
TldrawUiDialogTitle,
|
TldrawUiDialogTitle,
|
||||||
useTranslation,
|
useTranslation,
|
||||||
} from '@tldraw/tldraw'
|
} from '@tldraw/tldraw'
|
||||||
import { useState } from 'react'
|
|
||||||
import { userPreferences } from './userPreferences'
|
|
||||||
|
|
||||||
export async function shouldClearDocument(addDialog: TLUiDialogsContextType['addDialog']) {
|
export async function shouldClearDocument(addDialog: TLUiDialogsContextType['addDialog']) {
|
||||||
if (userPreferences.showFileClearWarning.get()) {
|
|
||||||
const shouldContinue = await new Promise<boolean>((resolve) => {
|
const shouldContinue = await new Promise<boolean>((resolve) => {
|
||||||
addDialog({
|
addDialog({
|
||||||
component: ({ onClose }) => (
|
component: ({ onClose }) => (
|
||||||
|
@ -37,8 +33,6 @@ export async function shouldClearDocument(addDialog: TLUiDialogsContextType['add
|
||||||
|
|
||||||
return shouldContinue
|
return shouldContinue
|
||||||
}
|
}
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
function ConfirmClearDialog({
|
function ConfirmClearDialog({
|
||||||
onCancel,
|
onCancel,
|
||||||
|
@ -48,7 +42,6 @@ function ConfirmClearDialog({
|
||||||
onContinue: () => void
|
onContinue: () => void
|
||||||
}) {
|
}) {
|
||||||
const msg = useTranslation()
|
const msg = useTranslation()
|
||||||
const [dontShowAgain, setDontShowAgain] = useState(false)
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<TldrawUiDialogHeader>
|
<TldrawUiDialogHeader>
|
||||||
|
@ -59,28 +52,10 @@ function ConfirmClearDialog({
|
||||||
{msg('file-system.confirm-clear.description')}
|
{msg('file-system.confirm-clear.description')}
|
||||||
</TldrawUiDialogBody>
|
</TldrawUiDialogBody>
|
||||||
<TldrawUiDialogFooter className="tlui-dialog__footer__actions">
|
<TldrawUiDialogFooter className="tlui-dialog__footer__actions">
|
||||||
<TldrawUiButton
|
|
||||||
type="normal"
|
|
||||||
onClick={() => setDontShowAgain(!dontShowAgain)}
|
|
||||||
style={{ marginRight: 'auto' }}
|
|
||||||
>
|
|
||||||
<TldrawUiButtonCheck checked={dontShowAgain} />
|
|
||||||
<TldrawUiButtonLabel>
|
|
||||||
{msg('file-system.confirm-clear.dont-show-again')}
|
|
||||||
</TldrawUiButtonLabel>
|
|
||||||
</TldrawUiButton>
|
|
||||||
<TldrawUiButton type="normal" onClick={onCancel}>
|
<TldrawUiButton type="normal" onClick={onCancel}>
|
||||||
<TldrawUiButtonLabel>{msg('file-system.confirm-clear.cancel')}</TldrawUiButtonLabel>
|
<TldrawUiButtonLabel>{msg('file-system.confirm-clear.cancel')}</TldrawUiButtonLabel>
|
||||||
</TldrawUiButton>
|
</TldrawUiButton>
|
||||||
<TldrawUiButton
|
<TldrawUiButton type="primary" onClick={async () => onContinue()}>
|
||||||
type="primary"
|
|
||||||
onClick={async () => {
|
|
||||||
if (dontShowAgain) {
|
|
||||||
userPreferences.showFileClearWarning.set(false)
|
|
||||||
}
|
|
||||||
onContinue()
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<TldrawUiButtonLabel>{msg('file-system.confirm-clear.continue')}</TldrawUiButtonLabel>
|
<TldrawUiButtonLabel>{msg('file-system.confirm-clear.continue')}</TldrawUiButtonLabel>
|
||||||
</TldrawUiButton>
|
</TldrawUiButton>
|
||||||
</TldrawUiDialogFooter>
|
</TldrawUiDialogFooter>
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import {
|
import {
|
||||||
TLUiDialogsContextType,
|
TLUiDialogsContextType,
|
||||||
TldrawUiButton,
|
TldrawUiButton,
|
||||||
TldrawUiButtonCheck,
|
|
||||||
TldrawUiButtonLabel,
|
TldrawUiButtonLabel,
|
||||||
TldrawUiDialogBody,
|
TldrawUiDialogBody,
|
||||||
TldrawUiDialogCloseButton,
|
TldrawUiDialogCloseButton,
|
||||||
|
@ -10,11 +9,9 @@ import {
|
||||||
TldrawUiDialogTitle,
|
TldrawUiDialogTitle,
|
||||||
useTranslation,
|
useTranslation,
|
||||||
} from '@tldraw/tldraw'
|
} from '@tldraw/tldraw'
|
||||||
import { useState } from 'react'
|
|
||||||
import { userPreferences } from './userPreferences'
|
|
||||||
|
|
||||||
|
/** @public */
|
||||||
export async function shouldOverrideDocument(addDialog: TLUiDialogsContextType['addDialog']) {
|
export async function shouldOverrideDocument(addDialog: TLUiDialogsContextType['addDialog']) {
|
||||||
if (userPreferences.showFileOpenWarning.get()) {
|
|
||||||
const shouldContinue = await new Promise<boolean>((resolve) => {
|
const shouldContinue = await new Promise<boolean>((resolve) => {
|
||||||
addDialog({
|
addDialog({
|
||||||
component: ({ onClose }) => (
|
component: ({ onClose }) => (
|
||||||
|
@ -37,8 +34,6 @@ export async function shouldOverrideDocument(addDialog: TLUiDialogsContextType['
|
||||||
|
|
||||||
return shouldContinue
|
return shouldContinue
|
||||||
}
|
}
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
function ConfirmOpenDialog({
|
function ConfirmOpenDialog({
|
||||||
onCancel,
|
onCancel,
|
||||||
|
@ -48,7 +43,6 @@ function ConfirmOpenDialog({
|
||||||
onContinue: () => void
|
onContinue: () => void
|
||||||
}) {
|
}) {
|
||||||
const msg = useTranslation()
|
const msg = useTranslation()
|
||||||
const [dontShowAgain, setDontShowAgain] = useState(false)
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<TldrawUiDialogHeader>
|
<TldrawUiDialogHeader>
|
||||||
|
@ -59,28 +53,10 @@ function ConfirmOpenDialog({
|
||||||
{msg('file-system.confirm-open.description')}
|
{msg('file-system.confirm-open.description')}
|
||||||
</TldrawUiDialogBody>
|
</TldrawUiDialogBody>
|
||||||
<TldrawUiDialogFooter className="tlui-dialog__footer__actions">
|
<TldrawUiDialogFooter className="tlui-dialog__footer__actions">
|
||||||
<TldrawUiButton
|
|
||||||
type="normal"
|
|
||||||
onClick={() => setDontShowAgain(!dontShowAgain)}
|
|
||||||
style={{ marginRight: 'auto' }}
|
|
||||||
>
|
|
||||||
<TldrawUiButtonCheck checked={dontShowAgain} />
|
|
||||||
<TldrawUiButtonLabel>
|
|
||||||
{msg('file-system.confirm-open.dont-show-again')}
|
|
||||||
</TldrawUiButtonLabel>
|
|
||||||
</TldrawUiButton>
|
|
||||||
<TldrawUiButton type="normal" onClick={onCancel}>
|
<TldrawUiButton type="normal" onClick={onCancel}>
|
||||||
<TldrawUiButtonLabel>{msg('file-system.confirm-open.cancel')}</TldrawUiButtonLabel>
|
<TldrawUiButtonLabel>{msg('file-system.confirm-open.cancel')}</TldrawUiButtonLabel>
|
||||||
</TldrawUiButton>
|
</TldrawUiButton>
|
||||||
<TldrawUiButton
|
<TldrawUiButton type="primary" onClick={async () => onContinue()}>
|
||||||
type="primary"
|
|
||||||
onClick={async () => {
|
|
||||||
if (dontShowAgain) {
|
|
||||||
userPreferences.showFileOpenWarning.set(false)
|
|
||||||
}
|
|
||||||
onContinue()
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<TldrawUiButtonLabel>{msg('file-system.confirm-open.open')}</TldrawUiButtonLabel>
|
<TldrawUiButtonLabel>{msg('file-system.confirm-open.open')}</TldrawUiButtonLabel>
|
||||||
</TldrawUiButton>
|
</TldrawUiButton>
|
||||||
</TldrawUiDialogFooter>
|
</TldrawUiDialogFooter>
|
||||||
|
|
|
@ -215,6 +215,7 @@
|
||||||
"menu.title": "Menu",
|
"menu.title": "Menu",
|
||||||
"menu.copy-as": "Copy as",
|
"menu.copy-as": "Copy as",
|
||||||
"menu.edit": "Edit",
|
"menu.edit": "Edit",
|
||||||
|
"menu.object": "Object",
|
||||||
"menu.export-as": "Export as",
|
"menu.export-as": "Export as",
|
||||||
"menu.file": "File",
|
"menu.file": "File",
|
||||||
"menu.language": "Language",
|
"menu.language": "Language",
|
||||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -180,7 +180,15 @@ export {
|
||||||
DefaultMainMenu,
|
DefaultMainMenu,
|
||||||
type TLUiMainMenuProps,
|
type TLUiMainMenuProps,
|
||||||
} from './lib/ui/components/MainMenu/DefaultMainMenu'
|
} from './lib/ui/components/MainMenu/DefaultMainMenu'
|
||||||
export { DefaultMainMenuContent } from './lib/ui/components/MainMenu/DefaultMainMenuContent'
|
export {
|
||||||
|
DefaultMainMenuContent,
|
||||||
|
EditSubmenu,
|
||||||
|
ExportFileContentSubMenu,
|
||||||
|
ExtrasGroup,
|
||||||
|
ObjectSubmenu,
|
||||||
|
PreferencesGroup,
|
||||||
|
ViewSubmenu,
|
||||||
|
} from './lib/ui/components/MainMenu/DefaultMainMenuContent'
|
||||||
|
|
||||||
export {
|
export {
|
||||||
DefaultQuickActions,
|
DefaultQuickActions,
|
||||||
|
|
|
@ -3,8 +3,6 @@ import {
|
||||||
ArrangeMenuSubmenu,
|
ArrangeMenuSubmenu,
|
||||||
ClipboardMenuGroup,
|
ClipboardMenuGroup,
|
||||||
ConversionsMenuGroup,
|
ConversionsMenuGroup,
|
||||||
DeleteGroup,
|
|
||||||
DuplicateMenuItem,
|
|
||||||
EditLinkMenuItem,
|
EditLinkMenuItem,
|
||||||
EmbedsGroup,
|
EmbedsGroup,
|
||||||
FitFrameToContentMenuItem,
|
FitFrameToContentMenuItem,
|
||||||
|
@ -36,7 +34,6 @@ export function DefaultContextMenuContent() {
|
||||||
<TldrawUiMenuGroup id="selection">
|
<TldrawUiMenuGroup id="selection">
|
||||||
<ToggleAutoSizeMenuItem />
|
<ToggleAutoSizeMenuItem />
|
||||||
<EditLinkMenuItem />
|
<EditLinkMenuItem />
|
||||||
<DuplicateMenuItem />
|
|
||||||
<GroupMenuItem />
|
<GroupMenuItem />
|
||||||
<UngroupMenuItem />
|
<UngroupMenuItem />
|
||||||
<RemoveFrameMenuItem />
|
<RemoveFrameMenuItem />
|
||||||
|
@ -52,7 +49,6 @@ export function DefaultContextMenuContent() {
|
||||||
<ClipboardMenuGroup />
|
<ClipboardMenuGroup />
|
||||||
<ConversionsMenuGroup />
|
<ConversionsMenuGroup />
|
||||||
<SetSelectionGroup />
|
<SetSelectionGroup />
|
||||||
<DeleteGroup />
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,6 @@ import { LanguageMenu } from '../LanguageMenu'
|
||||||
import {
|
import {
|
||||||
ClipboardMenuGroup,
|
ClipboardMenuGroup,
|
||||||
ConversionsMenuGroup,
|
ConversionsMenuGroup,
|
||||||
DeleteGroup,
|
|
||||||
DuplicateMenuItem,
|
|
||||||
EditLinkMenuItem,
|
EditLinkMenuItem,
|
||||||
EmbedsGroup,
|
EmbedsGroup,
|
||||||
FitFrameToContentMenuItem,
|
FitFrameToContentMenuItem,
|
||||||
|
@ -23,6 +21,7 @@ import {
|
||||||
ToggleReduceMotionItem,
|
ToggleReduceMotionItem,
|
||||||
ToggleSnapModeItem,
|
ToggleSnapModeItem,
|
||||||
ToggleToolLockItem,
|
ToggleToolLockItem,
|
||||||
|
ToggleTransparentBgMenuItem,
|
||||||
UngroupMenuItem,
|
UngroupMenuItem,
|
||||||
UnlockAllMenuItem,
|
UnlockAllMenuItem,
|
||||||
ZoomTo100MenuItem,
|
ZoomTo100MenuItem,
|
||||||
|
@ -38,6 +37,7 @@ export function DefaultMainMenuContent() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<EditSubmenu />
|
<EditSubmenu />
|
||||||
|
<ObjectSubmenu />
|
||||||
<ViewSubmenu />
|
<ViewSubmenu />
|
||||||
<ExtrasGroup />
|
<ExtrasGroup />
|
||||||
<PreferencesGroup />
|
<PreferencesGroup />
|
||||||
|
@ -45,7 +45,26 @@ export function DefaultMainMenuContent() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function EditSubmenu() {
|
/** @public */
|
||||||
|
export function ExportFileContentSubMenu() {
|
||||||
|
const actions = useActions()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TldrawUiMenuSubmenu id="export-as" label="context-menu.export-as" size="small">
|
||||||
|
<TldrawUiMenuGroup id="export-as-group">
|
||||||
|
<TldrawUiMenuItem {...actions['export-as-svg']} />
|
||||||
|
<TldrawUiMenuItem {...actions['export-as-png']} />
|
||||||
|
<TldrawUiMenuItem {...actions['export-as-json']} />
|
||||||
|
</TldrawUiMenuGroup>
|
||||||
|
<TldrawUiMenuGroup id="export-as-bg">
|
||||||
|
<ToggleTransparentBgMenuItem />
|
||||||
|
</TldrawUiMenuGroup>
|
||||||
|
</TldrawUiMenuSubmenu>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @public */
|
||||||
|
export function EditSubmenu() {
|
||||||
const editor = useEditor()
|
const editor = useEditor()
|
||||||
|
|
||||||
const selectToolActive = useValue(
|
const selectToolActive = useValue(
|
||||||
|
@ -54,38 +73,70 @@ function EditSubmenu() {
|
||||||
[editor]
|
[editor]
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!selectToolActive) return null
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TldrawUiMenuSubmenu id="edit" label="menu.edit">
|
<TldrawUiMenuSubmenu id="edit" label="menu.edit" disabled={!selectToolActive}>
|
||||||
<UndoRedoGroup />
|
<UndoRedoGroup />
|
||||||
<ClipboardMenuGroup />
|
<ClipboardMenuGroup />
|
||||||
<ConversionsMenuGroup />
|
|
||||||
<SetSelectionGroup />
|
<SetSelectionGroup />
|
||||||
<SelectionMenuGroup />
|
|
||||||
<EmbedsGroup />
|
|
||||||
<DeleteGroup />
|
|
||||||
</TldrawUiMenuSubmenu>
|
</TldrawUiMenuSubmenu>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function SelectionMenuGroup() {
|
/** @public */
|
||||||
|
export function ObjectSubmenu() {
|
||||||
|
const editor = useEditor()
|
||||||
|
|
||||||
|
const selectToolActive = useValue(
|
||||||
|
'isSelectToolActive',
|
||||||
|
() => editor.getCurrentToolId() === 'select',
|
||||||
|
[editor]
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TldrawUiMenuGroup id="selection">
|
<TldrawUiMenuSubmenu id="object" label="menu.object" disabled={!selectToolActive}>
|
||||||
|
<ConversionsMenuGroup />
|
||||||
|
<MultiShapeMenuGroup />
|
||||||
|
<MiscMenuGroup />
|
||||||
|
<EmbedsGroup />
|
||||||
|
<LockGroup />
|
||||||
|
</TldrawUiMenuSubmenu>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @public */
|
||||||
|
export function MiscMenuGroup() {
|
||||||
|
return (
|
||||||
|
<TldrawUiMenuGroup id="misc">
|
||||||
<ToggleAutoSizeMenuItem />
|
<ToggleAutoSizeMenuItem />
|
||||||
<EditLinkMenuItem />
|
<EditLinkMenuItem />
|
||||||
<DuplicateMenuItem />
|
</TldrawUiMenuGroup>
|
||||||
<GroupMenuItem />
|
)
|
||||||
<UngroupMenuItem />
|
}
|
||||||
<RemoveFrameMenuItem />
|
|
||||||
<FitFrameToContentMenuItem />
|
/** @public */
|
||||||
|
export function LockGroup() {
|
||||||
|
return (
|
||||||
|
<TldrawUiMenuGroup id="lock">
|
||||||
<ToggleLockMenuItem />
|
<ToggleLockMenuItem />
|
||||||
<UnlockAllMenuItem />
|
<UnlockAllMenuItem />
|
||||||
</TldrawUiMenuGroup>
|
</TldrawUiMenuGroup>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function UndoRedoGroup() {
|
/** @public */
|
||||||
|
export function MultiShapeMenuGroup() {
|
||||||
|
return (
|
||||||
|
<TldrawUiMenuGroup id="multi-shape">
|
||||||
|
<GroupMenuItem />
|
||||||
|
<UngroupMenuItem />
|
||||||
|
<RemoveFrameMenuItem />
|
||||||
|
<FitFrameToContentMenuItem />
|
||||||
|
</TldrawUiMenuGroup>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @public */
|
||||||
|
export function UndoRedoGroup() {
|
||||||
const actions = useActions()
|
const actions = useActions()
|
||||||
const canUndo = useCanUndo()
|
const canUndo = useCanUndo()
|
||||||
const canRedo = useCanRedo()
|
const canRedo = useCanRedo()
|
||||||
|
@ -97,7 +148,8 @@ function UndoRedoGroup() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function ViewSubmenu() {
|
/** @public */
|
||||||
|
export function ViewSubmenu() {
|
||||||
const actions = useActions()
|
const actions = useActions()
|
||||||
return (
|
return (
|
||||||
<TldrawUiMenuSubmenu id="view" label="menu.view">
|
<TldrawUiMenuSubmenu id="view" label="menu.view">
|
||||||
|
@ -112,7 +164,8 @@ function ViewSubmenu() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function ExtrasGroup() {
|
/** @public */
|
||||||
|
export function ExtrasGroup() {
|
||||||
const actions = useActions()
|
const actions = useActions()
|
||||||
return (
|
return (
|
||||||
<TldrawUiMenuGroup id="extras">
|
<TldrawUiMenuGroup id="extras">
|
||||||
|
@ -124,7 +177,8 @@ function ExtrasGroup() {
|
||||||
|
|
||||||
/* ------------------- Preferences ------------------ */
|
/* ------------------- Preferences ------------------ */
|
||||||
|
|
||||||
function PreferencesGroup() {
|
/** @public */
|
||||||
|
export function PreferencesGroup() {
|
||||||
return (
|
return (
|
||||||
<TldrawUiMenuGroup id="preferences">
|
<TldrawUiMenuGroup id="preferences">
|
||||||
<TldrawUiMenuSubmenu id="preferences" label="menu.preferences">
|
<TldrawUiMenuSubmenu id="preferences" label="menu.preferences">
|
||||||
|
|
|
@ -31,36 +31,36 @@ 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']} />
|
return <TldrawUiMenuItem {...actions['toggle-auto-size']} disabled={!shouldDisplay} />
|
||||||
}
|
}
|
||||||
|
|
||||||
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']} />
|
return <TldrawUiMenuItem {...actions['edit-link']} disabled={!shouldDisplay} />
|
||||||
}
|
}
|
||||||
|
|
||||||
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']} />
|
return <TldrawUiMenuItem {...actions['duplicate']} disabled={!shouldDisplay} />
|
||||||
}
|
}
|
||||||
|
|
||||||
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']} />
|
return <TldrawUiMenuItem {...actions['group']} disabled={!shouldDisplay} />
|
||||||
}
|
}
|
||||||
|
|
||||||
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']} />
|
return <TldrawUiMenuItem {...actions['ungroup']} disabled={!shouldDisplay} />
|
||||||
}
|
}
|
||||||
|
|
||||||
export function RemoveFrameMenuItem() {
|
export function RemoveFrameMenuItem() {
|
||||||
|
@ -75,8 +75,8 @@ export function RemoveFrameMenuItem() {
|
||||||
},
|
},
|
||||||
[editor]
|
[editor]
|
||||||
)
|
)
|
||||||
if (!shouldDisplay) return null
|
|
||||||
return <TldrawUiMenuItem {...actions['remove-frame']} />
|
return <TldrawUiMenuItem {...actions['remove-frame']} disabled={!shouldDisplay} />
|
||||||
}
|
}
|
||||||
|
|
||||||
export function FitFrameToContentMenuItem() {
|
export function FitFrameToContentMenuItem() {
|
||||||
|
@ -94,8 +94,8 @@ export function FitFrameToContentMenuItem() {
|
||||||
},
|
},
|
||||||
[editor]
|
[editor]
|
||||||
)
|
)
|
||||||
if (!shouldDisplay) return null
|
|
||||||
return <TldrawUiMenuItem {...actions['fit-frame-to-content']} />
|
return <TldrawUiMenuItem {...actions['fit-frame-to-content']} disabled={!shouldDisplay} />
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ToggleLockMenuItem() {
|
export function ToggleLockMenuItem() {
|
||||||
|
@ -104,8 +104,8 @@ 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']} />
|
return <TldrawUiMenuItem {...actions['toggle-lock']} disabled={!shouldDisplay} />
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ToggleTransparentBgMenuItem() {
|
export function ToggleTransparentBgMenuItem() {
|
||||||
|
@ -125,8 +125,8 @@ export function UnlockAllMenuItem() {
|
||||||
const shouldDisplay = useValue('any shapes', () => editor.getCurrentPageShapeIds().size > 0, [
|
const shouldDisplay = useValue('any shapes', () => editor.getCurrentPageShapeIds().size > 0, [
|
||||||
editor,
|
editor,
|
||||||
])
|
])
|
||||||
if (!shouldDisplay) return null
|
|
||||||
return <TldrawUiMenuItem {...actions['unlock-all']} />
|
return <TldrawUiMenuItem {...actions['unlock-all']} disabled={!shouldDisplay} />
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------------------- Zoom ---------------------- */
|
/* ---------------------- Zoom ---------------------- */
|
||||||
|
@ -174,11 +174,38 @@ export function ZoomToSelectionMenuItem() {
|
||||||
/* -------------------- Clipboard ------------------- */
|
/* -------------------- Clipboard ------------------- */
|
||||||
|
|
||||||
export function ClipboardMenuGroup() {
|
export function ClipboardMenuGroup() {
|
||||||
|
const editor = useEditor()
|
||||||
|
const actions = useActions()
|
||||||
|
const atLeastOneShapeOnPage = useValue(
|
||||||
|
'atLeastOneShapeOnPage',
|
||||||
|
() => editor.getCurrentPageShapeIds().size > 0,
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TldrawUiMenuGroup id="clipboard">
|
<TldrawUiMenuGroup id="clipboard">
|
||||||
<CutMenuItem />
|
<CutMenuItem />
|
||||||
<CopyMenuItem />
|
<CopyMenuItem />
|
||||||
|
<TldrawUiMenuSubmenu
|
||||||
|
id="copy-as"
|
||||||
|
label="context-menu.copy-as"
|
||||||
|
size="small"
|
||||||
|
disabled={!atLeastOneShapeOnPage}
|
||||||
|
>
|
||||||
|
<TldrawUiMenuGroup id="copy-as-group">
|
||||||
|
<TldrawUiMenuItem {...actions['copy-as-svg']} />
|
||||||
|
{Boolean(window.navigator.clipboard?.write) && (
|
||||||
|
<TldrawUiMenuItem {...actions['copy-as-png']} />
|
||||||
|
)}
|
||||||
|
<TldrawUiMenuItem {...actions['copy-as-json']} />
|
||||||
|
</TldrawUiMenuGroup>
|
||||||
|
<TldrawUiMenuGroup id="copy-as-bg">
|
||||||
|
<ToggleTransparentBgMenuItem />
|
||||||
|
</TldrawUiMenuGroup>
|
||||||
|
</TldrawUiMenuSubmenu>
|
||||||
|
<DuplicateMenuItem />
|
||||||
<PasteMenuItem />
|
<PasteMenuItem />
|
||||||
|
<DeleteMenuItem />
|
||||||
</TldrawUiMenuGroup>
|
</TldrawUiMenuGroup>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -186,22 +213,22 @@ export function ClipboardMenuGroup() {
|
||||||
export function CutMenuItem() {
|
export function CutMenuItem() {
|
||||||
const actions = useActions()
|
const actions = useActions()
|
||||||
const shouldDisplay = useUnlockedSelectedShapesCount(1)
|
const shouldDisplay = useUnlockedSelectedShapesCount(1)
|
||||||
if (!shouldDisplay) return null
|
|
||||||
return <TldrawUiMenuItem {...actions['cut']} />
|
return <TldrawUiMenuItem {...actions['cut']} disabled={!shouldDisplay} />
|
||||||
}
|
}
|
||||||
|
|
||||||
export function CopyMenuItem() {
|
export function CopyMenuItem() {
|
||||||
const actions = useActions()
|
const actions = useActions()
|
||||||
const shouldDisplay = useAnySelectedShapesCount(1)
|
const shouldDisplay = useAnySelectedShapesCount(1)
|
||||||
if (!shouldDisplay) return null
|
|
||||||
return <TldrawUiMenuItem {...actions['copy']} />
|
return <TldrawUiMenuItem {...actions['copy']} disabled={!shouldDisplay} />
|
||||||
}
|
}
|
||||||
|
|
||||||
export function PasteMenuItem() {
|
export function PasteMenuItem() {
|
||||||
const actions = useActions()
|
const actions = useActions()
|
||||||
const shouldDisplay = showMenuPaste
|
const shouldDisplay = showMenuPaste
|
||||||
if (!shouldDisplay) return null
|
|
||||||
return <TldrawUiMenuItem {...actions['paste']} />
|
return <TldrawUiMenuItem {...actions['paste']} disabled={!shouldDisplay} />
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------- Conversions ------------------ */
|
/* ------------------- Conversions ------------------ */
|
||||||
|
@ -214,23 +241,15 @@ export function ConversionsMenuGroup() {
|
||||||
() => editor.getCurrentPageShapeIds().size > 0,
|
() => editor.getCurrentPageShapeIds().size > 0,
|
||||||
[]
|
[]
|
||||||
)
|
)
|
||||||
if (!atLeastOneShapeOnPage) return null
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TldrawUiMenuGroup id="conversions">
|
<TldrawUiMenuGroup id="conversions">
|
||||||
<TldrawUiMenuSubmenu id="copy-as" label="context-menu.copy-as" size="small">
|
<TldrawUiMenuSubmenu
|
||||||
<TldrawUiMenuGroup id="copy-as-group">
|
id="export-as"
|
||||||
<TldrawUiMenuItem {...actions['copy-as-svg']} />
|
label="context-menu.export-as"
|
||||||
{Boolean(window.navigator.clipboard?.write) && (
|
size="small"
|
||||||
<TldrawUiMenuItem {...actions['copy-as-png']} />
|
disabled={!atLeastOneShapeOnPage}
|
||||||
)}
|
>
|
||||||
<TldrawUiMenuItem {...actions['copy-as-json']} />
|
|
||||||
</TldrawUiMenuGroup>
|
|
||||||
<TldrawUiMenuGroup id="copy-as-bg">
|
|
||||||
<ToggleTransparentBgMenuItem />
|
|
||||||
</TldrawUiMenuGroup>
|
|
||||||
</TldrawUiMenuSubmenu>
|
|
||||||
<TldrawUiMenuSubmenu id="export-as" label="context-menu.export-as" size="small">
|
|
||||||
<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']} />
|
||||||
|
@ -254,25 +273,21 @@ export function SetSelectionGroup() {
|
||||||
() => editor.getCurrentPageShapeIds().size > 0,
|
() => editor.getCurrentPageShapeIds().size > 0,
|
||||||
[editor]
|
[editor]
|
||||||
)
|
)
|
||||||
if (!atLeastOneShapeOnPage) return null
|
|
||||||
return (
|
return (
|
||||||
<TldrawUiMenuGroup id="set-selection-group">
|
<TldrawUiMenuGroup id="set-selection-group">
|
||||||
<TldrawUiMenuItem {...actions['select-all']} />
|
<TldrawUiMenuItem {...actions['select-all']} disabled={!atLeastOneShapeOnPage} />
|
||||||
</TldrawUiMenuGroup>
|
</TldrawUiMenuGroup>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------ Delete Group ------------------ */
|
/* ------------------ Delete Group ------------------ */
|
||||||
|
|
||||||
export function DeleteGroup() {
|
export function DeleteMenuItem() {
|
||||||
const actions = useActions()
|
const actions = useActions()
|
||||||
const oneSelected = useUnlockedSelectedShapesCount(1)
|
const oneSelected = useUnlockedSelectedShapesCount(1)
|
||||||
if (!oneSelected) return null
|
|
||||||
return (
|
return <TldrawUiMenuItem {...actions['delete']} disabled={!oneSelected} />
|
||||||
<TldrawUiMenuGroup id="delete-group">
|
|
||||||
<TldrawUiMenuItem {...actions['delete']} />
|
|
||||||
</TldrawUiMenuGroup>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --------------------- Modify --------------------- */
|
/* --------------------- Modify --------------------- */
|
||||||
|
@ -449,9 +464,13 @@ export function EmbedsGroup() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TldrawUiMenuGroup id="embeds">
|
<TldrawUiMenuGroup id="embeds">
|
||||||
{oneEmbedSelected && <TldrawUiMenuItem {...actions['edit-embed']} />}
|
{/* XXX this doesn't exist?? */}
|
||||||
{oneEmbedSelected && <TldrawUiMenuItem {...actions['convert-to-bookmark']} />}
|
{/* <TldrawUiMenuItem {...actions['edit-embed']} disabled={!oneEmbedSelected} /> */}
|
||||||
{oneEmbeddableBookmarkSelected && <TldrawUiMenuItem {...actions['convert-to-embed']} />}
|
<TldrawUiMenuItem {...actions['convert-to-bookmark']} disabled={!oneEmbedSelected} />
|
||||||
|
<TldrawUiMenuItem
|
||||||
|
{...actions['convert-to-embed']}
|
||||||
|
disabled={!oneEmbeddableBookmarkSelected}
|
||||||
|
/>
|
||||||
</TldrawUiMenuGroup>
|
</TldrawUiMenuGroup>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -120,7 +120,7 @@ export function TldrawUiDropdownMenuSubTrigger({
|
||||||
disabled,
|
disabled,
|
||||||
}: TLUiDropdownMenuSubTriggerProps) {
|
}: TLUiDropdownMenuSubTriggerProps) {
|
||||||
return (
|
return (
|
||||||
<_DropdownMenu.SubTrigger dir="ltr" asChild>
|
<_DropdownMenu.SubTrigger dir="ltr" asChild disabled={disabled}>
|
||||||
<TldrawUiButton
|
<TldrawUiButton
|
||||||
type="menu"
|
type="menu"
|
||||||
className="tlui-menu__submenu__trigger"
|
className="tlui-menu__submenu__trigger"
|
||||||
|
|
|
@ -219,6 +219,7 @@ export type TLUiTranslationKey =
|
||||||
| 'menu.title'
|
| 'menu.title'
|
||||||
| 'menu.copy-as'
|
| 'menu.copy-as'
|
||||||
| 'menu.edit'
|
| 'menu.edit'
|
||||||
|
| 'menu.object'
|
||||||
| 'menu.export-as'
|
| 'menu.export-as'
|
||||||
| 'menu.file'
|
| 'menu.file'
|
||||||
| 'menu.language'
|
| 'menu.language'
|
||||||
|
|
|
@ -219,6 +219,7 @@ 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.object': 'Object',
|
||||||
'menu.export-as': 'Export as',
|
'menu.export-as': 'Export as',
|
||||||
'menu.file': 'File',
|
'menu.file': 'File',
|
||||||
'menu.language': 'Language',
|
'menu.language': 'Language',
|
||||||
|
|
Loading…
Reference in a new issue