Add support for project names (#1340)

This PR adds some things that we need for the Project Name feature on
tldraw.com.
It should be reviewed alongside
https://github.com/tldraw/tldraw-lite/pull/1814


## Name Property
This PR adds a `name` property to `TLDocument`. We use this to store a
project's name.

<img width="454" alt="Screenshot 2023-05-09 at 15 47 26"
src="https://github.com/tldraw/tldraw/assets/15892272/f3be438e-aa0f-4dec-8f51-8dfd9f9d0ced">

## Top Zone
This PR adds a `topZone` area of the UI that we can add stuff to,
similar to how `shareZone` works.
It also adds an example to show where the `topZone` and `shareZone` are:

<img width="1511" alt="Screenshot 2023-05-12 at 10 57 40"
src="https://github.com/tldraw/tldraw/assets/15892272/f5e1cd33-017e-4aaf-bfee-4d85119e2974">

## Breakpoints
This PR change's the UI's breakpoints a little bit.
It moves the action bar to the bottom a little bit earlier.
(This gives us more space at the top for the project name).

![2023-05-12 at 11 08 26 - Fuchsia
Bison](https://github.com/tldraw/tldraw/assets/15892272/34563cea-b1d1-47be-ac5e-5650ee0ba02d)

![2023-05-12 at 13 45 04 - Tan
Mole](https://github.com/tldraw/tldraw/assets/15892272/ab190bd3-51d4-4a8b-88de-c72ab14bcba6)

## Input Blur
This PR adds an `onBlur` parameter to `Input`.
This was needed because 'clicking off' the input wasn't firing
`onComplete` or `onCancel`.

<img width="620" alt="Screenshot 2023-05-09 at 16 12 58"
src="https://github.com/tldraw/tldraw/assets/15892272/3b28da74-0a74-4063-8053-e59e47027caf">

## Create Project Name
This PR adds an internal `createProjectName` property to
`TldrawEditorConfig`.
Similar to `derivePresenceState`, you can pass a custom function to it.
It lets you control what gets used as the default project name. We use
it to set different names in our local projects compared to shared
projects.

In the future, when we add more advanced project features, we could
handle this better within the UI.

<img width="454" alt="Screenshot 2023-05-09 at 15 47 26"
src="https://github.com/tldraw/tldraw/assets/15892272/da9a4699-ac32-40d9-a97c-6c682acfac41">

### Test Plan

1. Gradually reduce the width of the browser window.
2. Check that the actions menu jumps to the bottom before the style
panel moves to the bottom.

---

1. In the examples app, open the `/zones` example.
2. Check that there's a 'top zone' at the top.

- [ ] Unit Tests
- [ ] Webdriver tests

### Release Note

- [dev] Added a `topZone` area where you can put stuff.
- [dev] Added a `name` property to `TLDocument` - and `app` methods for
it.
- [dev] Added an internal `createProjectName` config property for
controlling the default project name.
- [dev] Added an `onBlur` parameter to `Input`.
- Moved the actions bar to the bottom on medium-sized screens.

---------

Co-authored-by: Steve Ruiz <steveruizok@gmail.com>
pull/1498/head
Lu Wilson 2023-06-01 19:46:26 +01:00 zatwierdzone przez GitHub
rodzic 941647fd1b
commit 3bc72cb822
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
20 zmienionych plików z 233 dodań i 19 usunięć

Wyświetl plik

@ -0,0 +1,40 @@
import { Tldraw } from '@tldraw/tldraw'
import '@tldraw/tldraw/editor.css'
import '@tldraw/tldraw/ui.css'
export default function Example() {
return (
<div className="tldraw__editor">
<Tldraw shareZone={<CustomShareZone />} topZone={<CustomTopZone />} />
</div>
)
}
function CustomShareZone() {
return (
<div
style={{
backgroundColor: 'var(--palette-light-blue)',
width: '100%',
textAlign: 'center',
minWidth: '80px',
}}
>
<p>Share Zone</p>
</div>
)
}
function CustomTopZone() {
return (
<div
style={{
width: '100%',
backgroundColor: 'var(--palette-light-green)',
textAlign: 'center',
}}
>
<p>Top Zone</p>
</div>
)
}

Wyświetl plik

@ -14,6 +14,7 @@ import UserPresenceExample from './11-user-presence/UserPresenceExample'
import UiEventsExample from './12-ui-events/UiEventsExample' import UiEventsExample from './12-ui-events/UiEventsExample'
import StoreEventsExample from './13-store-events/StoreEventsExample' import StoreEventsExample from './13-store-events/StoreEventsExample'
import PersistenceExample from './14-persistence/PersistenceExample' import PersistenceExample from './14-persistence/PersistenceExample'
import ZonesExample from './15-custom-zones/ZonesExample'
import ExampleApi from './2-api/APIExample' import ExampleApi from './2-api/APIExample'
import CustomConfigExample from './3-custom-config/CustomConfigExample' import CustomConfigExample from './3-custom-config/CustomConfigExample'
import CustomUiExample from './4-custom-ui/CustomUiExample' import CustomUiExample from './4-custom-ui/CustomUiExample'
@ -90,6 +91,10 @@ export const allExamples: Example[] = [
path: '/user-presence', path: '/user-presence',
element: <UserPresenceExample />, element: <UserPresenceExample />,
}, },
{
path: '/zones',
element: <ZonesExample />,
},
{ {
path: '/persistence', path: '/persistence',
element: <PersistenceExample />, element: <PersistenceExample />,

Wyświetl plik

@ -229,6 +229,7 @@
"share-menu.save-note": "Download this project to your computer as a .tldr file.", "share-menu.save-note": "Download this project to your computer as a .tldr file.",
"share-menu.fork-note": "Create a new shared project based on this snapshot.", "share-menu.fork-note": "Create a new shared project based on this snapshot.",
"share-menu.share-project": "Share this project", "share-menu.share-project": "Share this project",
"share-menu.default-project-name": "Shared Project",
"share-menu.copy-link": "Copy share link", "share-menu.copy-link": "Copy share link",
"share-menu.readonly-link": "Read-only", "share-menu.readonly-link": "Read-only",
"share-menu.create-snapshot-link": "Copy snapshot link", "share-menu.create-snapshot-link": "Copy snapshot link",
@ -277,6 +278,12 @@
"shortcuts-dialog.tools": "Tools", "shortcuts-dialog.tools": "Tools",
"shortcuts-dialog.transform": "Transform", "shortcuts-dialog.transform": "Transform",
"shortcuts-dialog.view": "View", "shortcuts-dialog.view": "View",
"home-project-dialog.title": "Home project",
"home-project-dialog.description": "This is your local home project. It's just for you!",
"rename-project-dialog.title": "Rename project",
"rename-project-dialog.cancel": "Cancel",
"rename-project-dialog.rename": "Rename",
"home-project-dialog.ok": "Ok",
"style-panel.title": "Styles", "style-panel.title": "Styles",
"style-panel.align": "Align", "style-panel.align": "Align",
"style-panel.vertical-align": "Vertical align", "style-panel.vertical-align": "Vertical align",

Wyświetl plik

@ -390,6 +390,8 @@ export class App extends EventEmitter<TLEventMap> {
panZoomIntoView(ids: TLShapeId[], opts?: AnimationOptions): this; panZoomIntoView(ids: TLShapeId[], opts?: AnimationOptions): this;
// (undocumented) // (undocumented)
popFocusLayer(): this; popFocusLayer(): this;
// @internal (undocumented)
get projectName(): string;
// @internal // @internal
get props(): null | TLNullableShapeProps; get props(): null | TLNullableShapeProps;
// (undocumented) // (undocumented)
@ -471,6 +473,8 @@ export class App extends EventEmitter<TLEventMap> {
setLocale(locale: string): void; setLocale(locale: string): void;
// (undocumented) // (undocumented)
setPenMode(isPenMode: boolean): this; setPenMode(isPenMode: boolean): this;
// @internal (undocumented)
setProjectName(name: string): void;
setProp(key: TLShapeProp, value: any, ephemeral?: boolean, squashing?: boolean): this; setProp(key: TLShapeProp, value: any, ephemeral?: boolean, squashing?: boolean): this;
// @internal (undocumented) // @internal (undocumented)
setReadOnly(isReadOnly: boolean): this; setReadOnly(isReadOnly: boolean): this;
@ -512,6 +516,8 @@ export class App extends EventEmitter<TLEventMap> {
updateAssets(assets: TLAssetPartial[]): this; updateAssets(assets: TLAssetPartial[]): this;
// @internal // @internal
updateCullingBounds(): this; updateCullingBounds(): this;
// @internal (undocumented)
updateDocumentSettings(settings: Partial<TLDocument>): void;
updateInstanceState(partial: Partial<Omit<TLInstance, 'currentPageId' | 'documentId' | 'userId'>>, ephemeral?: boolean, squashing?: boolean): this; updateInstanceState(partial: Partial<Omit<TLInstance, 'currentPageId' | 'documentId' | 'userId'>>, ephemeral?: boolean, squashing?: boolean): this;
updatePage(partial: RequiredKeys<TLPage, 'id'>, squashing?: boolean): this; updatePage(partial: RequiredKeys<TLPage, 'id'>, squashing?: boolean): this;
updateShapes(partials: (null | TLShapePartial | undefined)[], squashing?: boolean): this; updateShapes(partials: (null | TLShapePartial | undefined)[], squashing?: boolean): this;
@ -1829,6 +1835,7 @@ export type TldrawEditorProps = {
initialData?: StoreSnapshot<TLRecord>; initialData?: StoreSnapshot<TLRecord>;
instanceId?: TLInstanceId; instanceId?: TLInstanceId;
persistenceKey?: string; persistenceKey?: string;
defaultName?: string;
}); });
// @public (undocumented) // @public (undocumented)

Wyświetl plik

@ -119,6 +119,10 @@ export type TldrawEditorProps = {
* The id under which to sync and persist the editor's data. * The id under which to sync and persist the editor's data.
*/ */
persistenceKey?: string persistenceKey?: string
/**
* The initial document name to use for the new store.
*/
defaultName?: string
} }
) )
@ -169,13 +173,14 @@ export const TldrawEditor = memo(function TldrawEditor(props: TldrawEditorProps)
}) })
function TldrawEditorWithOwnStore(props: TldrawEditorProps & { store: undefined }) { function TldrawEditorWithOwnStore(props: TldrawEditorProps & { store: undefined }) {
const { initialData, instanceId = TAB_ID, shapes, persistenceKey } = props const { defaultName, initialData, instanceId = TAB_ID, shapes, persistenceKey } = props
const syncedStore = useLocalStore({ const syncedStore = useLocalStore({
customShapes: shapes, customShapes: shapes,
instanceId, instanceId,
initialData, initialData,
persistenceKey, persistenceKey,
defaultName,
}) })
return <TldrawEditorWithLoadingStore {...props} store={syncedStore} /> return <TldrawEditorWithLoadingStore {...props} store={syncedStore} />

Wyświetl plik

@ -35,6 +35,7 @@ import {
TLCursor, TLCursor,
TLCursorType, TLCursorType,
TLDOCUMENT_ID, TLDOCUMENT_ID,
TLDocument,
TLFrameShape, TLFrameShape,
TLGroupShape, TLGroupShape,
TLImageAsset, TLImageAsset,
@ -1515,10 +1516,25 @@ export class App extends EventEmitter<TLEventMap> {
return this.store.get(TLDOCUMENT_ID)! return this.store.get(TLDOCUMENT_ID)!
} }
/** @internal */
updateDocumentSettings(settings: Partial<TLDocument>) {
this.store.put([{ ...this.documentSettings, ...settings }])
}
get gridSize() { get gridSize() {
return this.documentSettings.gridSize return this.documentSettings.gridSize
} }
/** @internal */
get projectName() {
return this.documentSettings.name
}
/** @internal */
setProjectName(name: string) {
this.updateDocumentSettings({ name })
}
get isSnapMode() { get isSnapMode() {
return this.userDocumentSettings.isSnapMode return this.userDocumentSettings.isSnapMode
} }

Wyświetl plik

@ -21,6 +21,7 @@ export type StoreOptions = {
customShapes?: Record<string, ShapeInfo> customShapes?: Record<string, ShapeInfo>
instanceId?: TLInstanceId instanceId?: TLInstanceId
initialData?: StoreSnapshot<TLRecord> initialData?: StoreSnapshot<TLRecord>
defaultName?: string
} }
/** /**
@ -30,7 +31,12 @@ export type StoreOptions = {
* *
* @public */ * @public */
export function createTLStore(opts = {} as StoreOptions): TLStore { export function createTLStore(opts = {} as StoreOptions): TLStore {
const { customShapes = {}, instanceId = InstanceRecordType.createId(), initialData } = opts const {
customShapes = {},
instanceId = InstanceRecordType.createId(),
initialData,
defaultName = '',
} = opts
return new Store({ return new Store({
schema: createTLSchema({ customShapes }), schema: createTLSchema({ customShapes }),
@ -38,6 +44,7 @@ export function createTLStore(opts = {} as StoreOptions): TLStore {
props: { props: {
instanceId, instanceId,
documentId: TLDOCUMENT_ID, documentId: TLDOCUMENT_ID,
defaultName,
}, },
}) })
} }

Wyświetl plik

@ -5,11 +5,10 @@ import { usePrevious } from './usePrevious'
/** @public */ /** @public */
export function useTLStore(opts: StoreOptions) { export function useTLStore(opts: StoreOptions) {
const [store, setStore] = useState(() => createTLStore(opts)) const [store, setStore] = useState(() => createTLStore(opts))
const previousOpts = usePrevious(opts) const prev = usePrevious(opts)
if ( if (
previousOpts.customShapes !== opts.customShapes || // shallow equality check
previousOpts.initialData !== opts.initialData || (Object.keys(prev) as (keyof StoreOptions)[]).some((key) => prev[key] !== opts[key])
previousOpts.instanceId !== opts.instanceId
) { ) {
const newStore = createTLStore(opts) const newStore = createTLStore(opts)
setStore(newStore) setStore(newStore)

Wyświetl plik

@ -780,6 +780,8 @@ export type TLDefaultShape = TLArrowShape | TLBookmarkShape | TLDrawShape | TLEm
export interface TLDocument extends BaseRecord<'document', ID<TLDocument>> { export interface TLDocument extends BaseRecord<'document', ID<TLDocument>> {
// (undocumented) // (undocumented)
gridSize: number; gridSize: number;
// (undocumented)
name: string;
} }
// @public (undocumented) // @public (undocumented)
@ -1250,6 +1252,7 @@ export type TLStore = Store<TLRecord, TLStoreProps>;
export type TLStoreProps = { export type TLStoreProps = {
instanceId: TLInstanceId; instanceId: TLInstanceId;
documentId: typeof TLDOCUMENT_ID; documentId: typeof TLDOCUMENT_ID;
defaultName: string;
}; };
// @public (undocumented) // @public (undocumented)

Wyświetl plik

@ -40,6 +40,7 @@ export type TLStoreSnapshot = StoreSnapshot<TLRecord>
export type TLStoreProps = { export type TLStoreProps = {
instanceId: TLInstanceId instanceId: TLInstanceId
documentId: typeof TLDOCUMENT_ID documentId: typeof TLDOCUMENT_ID
defaultName: string
} }
/** @public */ /** @public */
@ -91,7 +92,7 @@ export function createIntegrityChecker(store: TLStore): () => void {
const { instanceId: tabId } = store.props const { instanceId: tabId } = store.props
// make sure we have exactly one document // make sure we have exactly one document
if (!store.has(TLDOCUMENT_ID)) { if (!store.has(TLDOCUMENT_ID)) {
store.put([DocumentRecordType.create({ id: TLDOCUMENT_ID })]) store.put([DocumentRecordType.create({ id: TLDOCUMENT_ID, name: store.props.defaultName })])
return ensureStoreIsUsable() return ensureStoreIsUsable()
} }

Wyświetl plik

@ -3,6 +3,7 @@ import { structuredClone } from '@tldraw/utils'
import fs from 'fs' import fs from 'fs'
import { imageAssetMigrations } from './assets/TLImageAsset' import { imageAssetMigrations } from './assets/TLImageAsset'
import { videoAssetMigrations } from './assets/TLVideoAsset' import { videoAssetMigrations } from './assets/TLVideoAsset'
import { documentTypeMigrations } from './records/TLDocument'
import { instanceTypeMigrations, instanceTypeVersions } from './records/TLInstance' import { instanceTypeMigrations, instanceTypeVersions } from './records/TLInstance'
import { instancePageStateMigrations } from './records/TLInstancePageState' import { instancePageStateMigrations } from './records/TLInstancePageState'
import { instancePresenceTypeMigrations } from './records/TLInstancePresence' import { instancePresenceTypeMigrations } from './records/TLInstancePresence'
@ -644,6 +645,18 @@ describe('Adding instance_presence to the schema', () => {
}) })
}) })
describe('Adding name to document', () => {
const { up, down } = documentTypeMigrations.migrators[1]
test('up works as expected', () => {
expect(up({})).toEqual({ name: '' })
})
test('down works as expected', () => {
expect(down({ name: '' })).toEqual({})
})
})
describe('Adding check-box to geo shape', () => { describe('Adding check-box to geo shape', () => {
const { up, down } = geoShapeTypeMigrations.migrators[4] const { up, down } = geoShapeTypeMigrations.migrators[4]

Wyświetl plik

@ -8,6 +8,7 @@ import { T } from '@tldraw/tlvalidate'
*/ */
export interface TLDocument extends BaseRecord<'document', ID<TLDocument>> { export interface TLDocument extends BaseRecord<'document', ID<TLDocument>> {
gridSize: number gridSize: number
name: string
} }
/** @public */ /** @public */
@ -17,22 +18,46 @@ export const documentTypeValidator: T.Validator<TLDocument> = T.model(
typeName: T.literal('document'), typeName: T.literal('document'),
id: T.literal('document:document' as ID<TLDocument>), id: T.literal('document:document' as ID<TLDocument>),
gridSize: T.number, gridSize: T.number,
name: T.string,
}) })
) )
// --- MIGRATIONS ---
// STEP 1: Add a new version number here, give it a meaningful name.
// It should be 1 higher than the current version
const Versions = {
AddName: 1,
} as const
/** @public */
export const documentTypeMigrations = defineMigrations({
// STEP 2: Update the current version to point to your latest version
currentVersion: Versions.AddName,
// STEP 3: Add an up+down migration for the new version here
migrators: {
[Versions.AddName]: {
up: (document: TLDocument) => {
return { ...document, name: '' }
},
down: ({ name: _, ...document }: TLDocument) => {
return document
},
},
},
})
/** @public */ /** @public */
export const DocumentRecordType = createRecordType<TLDocument>('document', { export const DocumentRecordType = createRecordType<TLDocument>('document', {
migrations: documentTypeMigrations,
validator: documentTypeValidator, validator: documentTypeValidator,
scope: 'document', scope: 'document',
}).withDefaultProperties( }).withDefaultProperties(
(): Omit<TLDocument, 'id' | 'typeName'> => ({ (): Omit<TLDocument, 'id' | 'typeName'> => ({
gridSize: 10, gridSize: 10,
name: '',
}) })
) )
// all document records have the same ID: 'document:document' // all document records have the same ID: 'document:document'
/** @public */ /** @public */
export const TLDOCUMENT_ID: ID<TLDocument> = DocumentRecordType.createCustomId('document') export const TLDOCUMENT_ID: ID<TLDocument> = DocumentRecordType.createCustomId('document')
/** @public */
export const documentTypeMigrations = defineMigrations({})

File diff suppressed because one or more lines are too long

Wyświetl plik

@ -31,6 +31,7 @@ export type TldrawUiProps = {
hideUi?: boolean hideUi?: boolean
/** A component to use for the share zone (will be deprecated) */ /** A component to use for the share zone (will be deprecated) */
shareZone?: ReactNode shareZone?: ReactNode
topZone?: ReactNode
/** Additional items to add to the debug menu (will be deprecated)*/ /** Additional items to add to the debug menu (will be deprecated)*/
renderDebugMenuItems?: () => React.ReactNode renderDebugMenuItems?: () => React.ReactNode
} & TldrawUiContextProviderProps } & TldrawUiContextProviderProps
@ -40,6 +41,7 @@ export type TldrawUiProps = {
*/ */
export const TldrawUi = React.memo(function TldrawUi({ export const TldrawUi = React.memo(function TldrawUi({
shareZone, shareZone,
topZone,
renderDebugMenuItems, renderDebugMenuItems,
children, children,
hideUi, hideUi,
@ -50,6 +52,7 @@ export const TldrawUi = React.memo(function TldrawUi({
<TldrawUiInner <TldrawUiInner
hideUi={hideUi} hideUi={hideUi}
shareZone={shareZone} shareZone={shareZone}
topZone={topZone}
renderDebugMenuItems={renderDebugMenuItems} renderDebugMenuItems={renderDebugMenuItems}
> >
{children} {children}
@ -61,6 +64,7 @@ export const TldrawUi = React.memo(function TldrawUi({
type TldrawUiContentProps = { type TldrawUiContentProps = {
hideUi?: boolean hideUi?: boolean
shareZone?: ReactNode shareZone?: ReactNode
topZone?: ReactNode
renderDebugMenuItems?: () => React.ReactNode renderDebugMenuItems?: () => React.ReactNode
} }
@ -84,6 +88,7 @@ const TldrawUiInner = React.memo(function TldrawUiInner({
/** @public */ /** @public */
export const TldrawUiContent = React.memo(function TldrawUI({ export const TldrawUiContent = React.memo(function TldrawUI({
shareZone, shareZone,
topZone,
renderDebugMenuItems, renderDebugMenuItems,
}: TldrawUiContentProps) { }: TldrawUiContentProps) {
const app = useApp() const app = useApp()
@ -127,12 +132,9 @@ export const TldrawUiContent = React.memo(function TldrawUI({
<StopFollowing /> <StopFollowing />
</div> </div>
</div> </div>
<div className="tlui-layout__top__center">{topZone}</div>
<div className="tlui-layout__top__right"> <div className="tlui-layout__top__right">
{shareZone && ( {shareZone}
<div className="tlui-share-zone" draggable={false}>
{shareZone}
</div>
)}
{breakpoint >= 5 && !isReadonlyMode && ( {breakpoint >= 5 && !isReadonlyMode && (
<div className="tlui-style-panel__wrapper"> <div className="tlui-style-panel__wrapper">
<StylePanel /> <StylePanel />

Wyświetl plik

@ -24,7 +24,7 @@ export const MenuZone = track(function MenuZone() {
<Menu /> <Menu />
<div className="tlui-menu-zone__divider" /> <div className="tlui-menu-zone__divider" />
<PageMenu /> <PageMenu />
{breakpoint >= 5 && showQuickActions && ( {breakpoint >= 6 && showQuickActions && (
<> <>
<div className="tlui-menu-zone__divider" /> <div className="tlui-menu-zone__divider" />
<UndoButton /> <UndoButton />

Wyświetl plik

@ -115,7 +115,7 @@ export const Toolbar = function Toolbar() {
'tlui-toolbar__extras__hidden': !showExtraActions, 'tlui-toolbar__extras__hidden': !showExtraActions,
})} })}
> >
{breakpoint < 5 && ( {breakpoint < 6 && (
<div className="tlui-toolbar__extras__controls"> <div className="tlui-toolbar__extras__controls">
<UndoButton /> <UndoButton />
<RedoButton /> <RedoButton />

Wyświetl plik

@ -19,6 +19,7 @@ export interface InputProps {
onComplete?: (value: string) => void onComplete?: (value: string) => void
onValueChange?: (value: string) => void onValueChange?: (value: string) => void
onCancel?: (value: string) => void onCancel?: (value: string) => void
onBlur?: (value: string) => void
className?: string className?: string
/** /**
* Usually on iOS when you focus an input, the browser will adjust the viewport to bring the input * Usually on iOS when you focus an input, the browser will adjust the viewport to bring the input
@ -46,6 +47,7 @@ export const Input = React.forwardRef<HTMLInputElement, InputProps>(function Inp
onComplete, onComplete,
onValueChange, onValueChange,
onCancel, onCancel,
onBlur,
shouldManuallyMaintainScrollPositionWhenFocused = false, shouldManuallyMaintainScrollPositionWhenFocused = false,
children, children,
value, value,
@ -106,7 +108,14 @@ export const Input = React.forwardRef<HTMLInputElement, InputProps>(function Inp
[onComplete, onCancel] [onComplete, onCancel]
) )
const handleBlur = React.useCallback(() => setIsFocused(false), []) const handleBlur = React.useCallback(
(e: React.FocusEvent<HTMLInputElement>) => {
setIsFocused(false)
const value = e.currentTarget.value
onBlur?.(value)
},
[onBlur]
)
React.useEffect(() => { React.useEffect(() => {
const visualViewport = window.visualViewport const visualViewport = window.visualViewport

Wyświetl plik

@ -233,6 +233,7 @@ export type TLTranslationKey =
| 'share-menu.save-note' | 'share-menu.save-note'
| 'share-menu.fork-note' | 'share-menu.fork-note'
| 'share-menu.share-project' | 'share-menu.share-project'
| 'share-menu.default-project-name'
| 'share-menu.copy-link' | 'share-menu.copy-link'
| 'share-menu.readonly-link' | 'share-menu.readonly-link'
| 'share-menu.create-snapshot-link' | 'share-menu.create-snapshot-link'
@ -281,6 +282,12 @@ export type TLTranslationKey =
| 'shortcuts-dialog.tools' | 'shortcuts-dialog.tools'
| 'shortcuts-dialog.transform' | 'shortcuts-dialog.transform'
| 'shortcuts-dialog.view' | 'shortcuts-dialog.view'
| 'home-project-dialog.title'
| 'home-project-dialog.description'
| 'rename-project-dialog.title'
| 'rename-project-dialog.cancel'
| 'rename-project-dialog.rename'
| 'home-project-dialog.ok'
| 'style-panel.title' | 'style-panel.title'
| 'style-panel.align' | 'style-panel.align'
| 'style-panel.vertical-align' | 'style-panel.vertical-align'

Wyświetl plik

@ -233,6 +233,7 @@ export const DEFAULT_TRANSLATION = {
'share-menu.save-note': 'Download this project to your computer as a .tldr file.', 'share-menu.save-note': 'Download this project to your computer as a .tldr file.',
'share-menu.fork-note': 'Create a new shared project based on this snapshot.', 'share-menu.fork-note': 'Create a new shared project based on this snapshot.',
'share-menu.share-project': 'Share this project', 'share-menu.share-project': 'Share this project',
'share-menu.default-project-name': 'Shared Project',
'share-menu.copy-link': 'Copy share link', 'share-menu.copy-link': 'Copy share link',
'share-menu.readonly-link': 'Read-only', 'share-menu.readonly-link': 'Read-only',
'share-menu.create-snapshot-link': 'Copy snapshot link', 'share-menu.create-snapshot-link': 'Copy snapshot link',
@ -284,6 +285,12 @@ export const DEFAULT_TRANSLATION = {
'shortcuts-dialog.tools': 'Tools', 'shortcuts-dialog.tools': 'Tools',
'shortcuts-dialog.transform': 'Transform', 'shortcuts-dialog.transform': 'Transform',
'shortcuts-dialog.view': 'View', 'shortcuts-dialog.view': 'View',
'home-project-dialog.title': 'Home project',
'home-project-dialog.description': "This is your local home project. It's just for you!",
'rename-project-dialog.title': 'Rename project',
'rename-project-dialog.cancel': 'Cancel',
'rename-project-dialog.rename': 'Rename',
'home-project-dialog.ok': 'Ok',
'style-panel.title': 'Styles', 'style-panel.title': 'Styles',
'style-panel.align': 'Align', 'style-panel.align': 'Align',
'style-panel.vertical-align': 'Vertical align', 'style-panel.vertical-align': 'Vertical align',

Wyświetl plik

@ -28,6 +28,7 @@
grid-column: 1; grid-column: 1;
grid-row: 1; grid-row: 1;
display: flex; display: flex;
min-width: 0px;
} }
.tlui-layout__top__left { .tlui-layout__top__left {
@ -37,6 +38,19 @@
justify-content: flex-start; justify-content: flex-start;
width: 100%; width: 100%;
height: 100%; height: 100%;
flex-shrink: 1;
}
.tlui-layout__top__center {
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
width: 100%;
height: 100%;
margin-left: var(--space-2);
flex-grow: 1;
min-width: 0px;
} }
.tlui-layout__top__right { .tlui-layout__top__right {
@ -46,6 +60,8 @@
justify-content: flex-start; justify-content: flex-start;
width: 100%; width: 100%;
height: 100%; height: 100%;
flex-shrink: 1;
min-width: 0px;
} }
.scrollable, .scrollable,
@ -1627,6 +1643,47 @@
} }
} }
/* ------------------ Project Menu ------------------ */
.tlui-project-menu__wrapper {
display: flex;
width: 100%;
align-items: center;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
min-width: 0px;
}
.tlui-project-menu__button {
display: flex;
gap: var(--space-4);
pointer-events: all;
}
.tlui-project-menu__button__name {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
min-width: 0px;
}
.tlui-project-menu__input {
min-width: 0px;
text-align: center;
pointer-events: all;
/* Position slightly to the right so that it doesn't jump around */
/* 40px is the width of the icon */
margin-left: 40px;
}
.tlui-rename-project-dialog__input {
background-color: var(--color-muted-2);
flex-grow: 2;
border-radius: var(--radius-2);
padding: 0px var(--space-4);
}
/* ------------------- Navigation ------------------- */ /* ------------------- Navigation ------------------- */
.tlui-navigation-zone { .tlui-navigation-zone {