[improvement] adds option to keep style menu open (#697)

* Adds open to keep style menu open

* fix keyboard shortcuts, add button to menu
This commit is contained in:
Steve Ruiz 2022-05-20 13:56:16 +01:00 committed by GitHub
parent c3fe36c2e7
commit ba0795c595
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 96 additions and 52 deletions

View file

@ -1,7 +1,7 @@
import * as React from 'react'
import { DMCheckboxItem, DMDivider, DMSubMenu } from '~components/Primitives/DropdownMenu'
import { useTldrawApp } from '~hooks'
import type { TDSnapshot } from '~types'
import { TDSnapshot } from '~types'
const settingsSelector = (s: TDSnapshot) => s.settings
@ -11,39 +11,43 @@ export function PreferencesMenu() {
const settings = app.useStore(settingsSelector)
const toggleDebugMode = React.useCallback(() => {
app.setSetting('isDebugMode', (v) => !v)
app.setSetting('isDebugMode', v => !v)
}, [app])
const toggleDarkMode = React.useCallback(() => {
app.setSetting('isDarkMode', (v) => !v)
app.setSetting('isDarkMode', v => !v)
}, [app])
const toggleFocusMode = React.useCallback(() => {
app.setSetting('isFocusMode', (v) => !v)
app.setSetting('isFocusMode', v => !v)
}, [app])
const toggleRotateHandle = React.useCallback(() => {
app.setSetting('showRotateHandles', (v) => !v)
app.setSetting('showRotateHandles', v => !v)
}, [app])
const toggleGrid = React.useCallback(() => {
app.setSetting('showGrid', (v) => !v)
app.setSetting('showGrid', v => !v)
}, [app])
const toggleBoundShapesHandle = React.useCallback(() => {
app.setSetting('showBindingHandles', (v) => !v)
app.setSetting('showBindingHandles', v => !v)
}, [app])
const toggleisSnapping = React.useCallback(() => {
app.setSetting('isSnapping', (v) => !v)
app.setSetting('isSnapping', v => !v)
}, [app])
const toggleKeepStyleMenuOpen = React.useCallback(() => {
app.setSetting('keepStyleMenuOpen', v => !v)
}, [app])
const toggleCloneControls = React.useCallback(() => {
app.setSetting('showCloneHandles', (v) => !v)
app.setSetting('showCloneHandles', v => !v)
}, [app])
const toggleCadSelectMode = React.useCallback(() => {
app.setSetting('isCadSelectMode', (v) => !v)
app.setSetting('isCadSelectMode', v => !v)
}, [app])
return (
@ -87,6 +91,13 @@ export function PreferencesMenu() {
>
Use CAD Selection
</DMCheckboxItem>
<DMCheckboxItem
checked={settings.keepStyleMenuOpen}
onCheckedChange={toggleKeepStyleMenuOpen}
id="TD-MenuItem-Preferences-Style_menu"
>
Keep Style Menu Open
</DMCheckboxItem>
<DMCheckboxItem
checked={settings.isSnapping}
onCheckedChange={toggleisSnapping}

View file

@ -2,7 +2,12 @@ import * as React from 'react'
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
import { strokes, fills, defaultTextStyle } from '~state/shapes/shared/shape-styles'
import { useTldrawApp } from '~hooks'
import { DMCheckboxItem, DMContent, DMRadioItem } from '~components/Primitives/DropdownMenu'
import {
DMCheckboxItem,
DMContent,
DMDivider,
DMRadioItem,
} from '~components/Primitives/DropdownMenu'
import {
CircleIcon,
DashDashedIcon,
@ -63,6 +68,8 @@ const ALIGN_ICONS = {
const themeSelector = (s: TDSnapshot) => (s.settings.isDarkMode ? 'dark' : 'light')
const keepOpenSelector = (s: TDSnapshot) => s.settings.keepStyleMenuOpen
const optionsSelector = (s: TDSnapshot) => {
const { activeTool, currentPageId: pageId } = s.appState
switch (activeTool) {
@ -104,6 +111,8 @@ export const StyleMenu = React.memo(function ColorMenu() {
const theme = app.useStore(themeSelector)
const keepOpen = app.useStore(keepOpenSelector)
const options = app.useStore(optionsSelector)
const currentStyle = app.useStore(currentStyleSelector)
@ -126,9 +135,9 @@ export const StyleMenu = React.memo(function ColorMenu() {
} else {
const overrides = new Set<string>([])
app.selectedIds
.map((id) => page.shapes[id])
.forEach((shape) => {
STYLE_KEYS.forEach((key) => {
.map(id => page.shapes[id])
.forEach(shape => {
STYLE_KEYS.forEach(key => {
if (overrides.has(key)) return
if (commonStyle[key] === undefined) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
@ -152,6 +161,10 @@ export const StyleMenu = React.memo(function ColorMenu() {
}
}, [currentStyle, selectedIds])
const handleToggleKeepOpen = React.useCallback((checked: boolean) => {
app.setSetting('keepStyleMenuOpen', checked)
}, [])
const handleToggleFilled = React.useCallback((checked: boolean) => {
app.style({ isFilled: checked })
}, [])
@ -180,7 +193,11 @@ export const StyleMenu = React.memo(function ColorMenu() {
)
return (
<DropdownMenu.Root dir="ltr" onOpenChange={handleMenuOpenChange}>
<DropdownMenu.Root
dir="ltr"
onOpenChange={handleMenuOpenChange}
open={keepOpen ? true : undefined}
>
<DropdownMenu.Trigger asChild id="TD-Styles">
<ToolButton variant="text">
Styles
@ -240,7 +257,7 @@ export const StyleMenu = React.memo(function ColorMenu() {
<StyledRow id="TD-Styles-Dash-Container">
Dash
<StyledGroup dir="ltr" value={displayedStyle.dash} onValueChange={handleDashChange}>
{Object.values(DashStyle).map((style) => (
{Object.values(DashStyle).map(style => (
<DMRadioItem
key={style}
isActive={style === displayedStyle.dash}
@ -257,7 +274,7 @@ export const StyleMenu = React.memo(function ColorMenu() {
<StyledRow id="TD-Styles-Size-Container">
Size
<StyledGroup dir="ltr" value={displayedStyle.size} onValueChange={handleSizeChange}>
{Object.values(SizeStyle).map((sizeStyle) => (
{Object.values(SizeStyle).map(sizeStyle => (
<DMRadioItem
key={sizeStyle}
isActive={sizeStyle === displayedStyle.size}
@ -277,7 +294,7 @@ export const StyleMenu = React.memo(function ColorMenu() {
<StyledRow id="TD-Styles-Font-Container">
Font
<StyledGroup dir="ltr" value={displayedStyle.font} onValueChange={handleFontChange}>
{Object.values(FontStyle).map((fontStyle) => (
{Object.values(FontStyle).map(fontStyle => (
<DMRadioItem
key={fontStyle}
isActive={fontStyle === displayedStyle.font}
@ -299,7 +316,7 @@ export const StyleMenu = React.memo(function ColorMenu() {
value={displayedStyle.textAlign}
onValueChange={handleTextAlignChange}
>
{Object.values(AlignStyle).map((style) => (
{Object.values(AlignStyle).map(style => (
<DMRadioItem
key={style}
isActive={style === displayedStyle.textAlign}
@ -316,6 +333,15 @@ export const StyleMenu = React.memo(function ColorMenu() {
)}
</>
)}
<DMDivider />
<DMCheckboxItem
variant="styleMenu"
checked={keepOpen}
onCheckedChange={handleToggleKeepOpen}
id="TD-Styles-Keep-Open"
>
Keep Open
</DMCheckboxItem>
</DMContent>
</DropdownMenu.Root>
)

View file

@ -9,7 +9,7 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
const canHandleEvent = React.useCallback(
(ignoreMenus = false) => {
const elm = ref.current
if (ignoreMenus && app.isMenuOpen) return true
if (ignoreMenus && (app.isMenuOpen || app.settings.keepStyleMenuOpen)) return true
return elm && (document.activeElement === elm || elm.contains(document.activeElement))
},
[ref]
@ -159,7 +159,7 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
useHotkeys(
'ctrl+shift+d,⌘+shift+d',
(e) => {
e => {
if (!canHandleEvent(true)) return
app.toggleDarkMode()
e.preventDefault()
@ -192,12 +192,17 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
// File System
const { onNewProject, onOpenProject, onSaveProject, onSaveProjectAs, onOpenMedia } =
useFileSystemHandlers()
const {
onNewProject,
onOpenProject,
onSaveProject,
onSaveProjectAs,
onOpenMedia,
} = useFileSystemHandlers()
useHotkeys(
'ctrl+n,⌘+n',
(e) => {
e => {
if (!canHandleEvent()) return
onNewProject(e)
@ -207,7 +212,7 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
)
useHotkeys(
'ctrl+s,⌘+s',
(e) => {
e => {
if (!canHandleEvent()) return
onSaveProject(e)
@ -218,7 +223,7 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
useHotkeys(
'ctrl+shift+s,⌘+shift+s',
(e) => {
e => {
if (!canHandleEvent()) return
onSaveProjectAs(e)
@ -228,7 +233,7 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
)
useHotkeys(
'ctrl+o,⌘+o',
(e) => {
e => {
if (!canHandleEvent()) return
onOpenProject(e)
@ -238,7 +243,7 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
)
useHotkeys(
'ctrl+u,⌘+u',
(e) => {
e => {
if (!canHandleEvent()) return
onOpenMedia(e)
},
@ -306,7 +311,7 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
useHotkeys(
'ctrl+=,⌘+=,ctrl+num_subtract,⌘+num_subtract',
(e) => {
e => {
if (!canHandleEvent(true)) return
app.zoomIn()
e.preventDefault()
@ -317,7 +322,7 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
useHotkeys(
'ctrl+-,⌘+-,ctrl+num_add,⌘+num_add',
(e) => {
e => {
if (!canHandleEvent(true)) return
app.zoomOut()
@ -361,7 +366,7 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
useHotkeys(
'ctrl+d,⌘+d',
(e) => {
e => {
if (!canHandleEvent()) return
app.duplicate()
@ -536,7 +541,7 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
useHotkeys(
'⌘+shift+c,ctrl+shift+c',
(e) => {
e => {
if (!canHandleEvent()) return
app.copySvg()
@ -571,7 +576,7 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
useHotkeys(
'⌘+g,ctrl+g',
(e) => {
e => {
if (!canHandleEvent()) return
app.group()
@ -583,7 +588,7 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
useHotkeys(
'⌘+shift+g,ctrl+shift+g',
(e) => {
e => {
if (!canHandleEvent()) return
app.ungroup()
@ -637,7 +642,7 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
useHotkeys(
'ctrl+shift+backspace,⌘+shift+backspace',
(e) => {
e => {
if (!canHandleEvent()) return
if (app.settings.isDebugMode) {
app.resetDocument()
@ -652,7 +657,7 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
useHotkeys(
'alt+command+l,alt+ctrl+l',
(e) => {
e => {
if (!canHandleEvent(true)) return
app.style({ textAlign: AlignStyle.Start })
e.preventDefault()
@ -663,7 +668,7 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
useHotkeys(
'alt+command+t,alt+ctrl+t',
(e) => {
e => {
if (!canHandleEvent(true)) return
app.style({ textAlign: AlignStyle.Middle })
e.preventDefault()
@ -674,7 +679,7 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
useHotkeys(
'alt+command+r,alt+ctrl+r',
(e) => {
e => {
if (!canHandleEvent(true)) return
app.style({ textAlign: AlignStyle.End })
e.preventDefault()

View file

@ -4086,6 +4086,7 @@ export class TldrawApp extends StateManager<TDSnapshot> {
isSnapping: false,
isDebugMode: false,
isReadonlyMode: false,
keepStyleMenuOpen: false,
nudgeDistanceLarge: 16,
nudgeDistanceSmall: 1,
showRotateHandles: true,

View file

@ -11,8 +11,8 @@ export function migrate(document: TDDocument, newVersion: number): TDDocument {
// Remove unused assets when loading a document
const assetIdsInUse = new Set<string>()
Object.values(document.pages).forEach((page) =>
Object.values(page.shapes).forEach((shape) => {
Object.values(document.pages).forEach(page =>
Object.values(page.shapes).forEach(shape => {
const { parentId, children, assetId } = shape
if (assetId) {
@ -26,7 +26,7 @@ export function migrate(document: TDDocument, newVersion: number): TDDocument {
}
if (shape.type === TDShapeType.Group && children) {
children.forEach((childId) => {
children.forEach(childId => {
if (!page.shapes[childId]) {
console.warn('Encountered a parent with a missing child!', shape.id, childId)
children?.splice(children.indexOf(childId), 1)
@ -38,7 +38,7 @@ export function migrate(document: TDDocument, newVersion: number): TDDocument {
})
)
Object.keys(document.assets).forEach((assetId) => {
Object.keys(document.assets).forEach(assetId => {
if (!assetIdsInUse.has(assetId)) {
delete document.assets[assetId]
}
@ -47,22 +47,22 @@ export function migrate(document: TDDocument, newVersion: number): TDDocument {
if (version === newVersion) return document
if (version < 14) {
Object.values(document.pages).forEach((page) => {
Object.values(document.pages).forEach(page => {
Object.values(page.shapes)
.filter((shape) => shape.type === TDShapeType.Text)
.forEach((shape) => (shape as TextShape).style.font === FontStyle.Script)
.filter(shape => shape.type === TDShapeType.Text)
.forEach(shape => (shape as TextShape).style.font === FontStyle.Script)
})
}
// Lowercase styles, move binding meta to binding
if (version <= 13) {
Object.values(document.pages).forEach((page) => {
Object.values(page.bindings).forEach((binding) => {
Object.values(document.pages).forEach(page => {
Object.values(page.bindings).forEach(binding => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Object.assign(binding, (binding as any).meta)
})
Object.values(page.shapes).forEach((shape) => {
Object.values(page.shapes).forEach(shape => {
Object.entries(shape.style).forEach(([id, style]) => {
if (typeof style === 'string') {
// @ts-ignore
@ -95,8 +95,8 @@ export function migrate(document: TDDocument, newVersion: number): TDDocument {
document.assets = {}
}
Object.values(document.pages).forEach((page) => {
Object.values(page.shapes).forEach((shape) => {
Object.values(document.pages).forEach(page => {
Object.values(page.shapes).forEach(shape => {
if (version < 15.2) {
if (shape.type === TDShapeType.Image || shape.type === TDShapeType.Video) {
shape.style.isFilled = true
@ -118,8 +118,8 @@ export function migrate(document: TDDocument, newVersion: number): TDDocument {
})
// Cleanup
Object.values(document.pageStates).forEach((pageState) => {
pageState.selectedIds = pageState.selectedIds.filter((id) => {
Object.values(document.pageStates).forEach(pageState => {
pageState.selectedIds = pageState.selectedIds.filter(id => {
return document.pages[pageState.id].shapes[id] !== undefined
})
pageState.bindingId = undefined

View file

@ -85,6 +85,7 @@ export interface TDSnapshot {
isPenMode: boolean
isReadonlyMode: boolean
isZoomSnap: boolean
keepStyleMenuOpen: boolean
nudgeDistanceSmall: number
nudgeDistanceLarge: number
isFocusMode: boolean