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>
This commit is contained in:
parent
941647fd1b
commit
3bc72cb822
20 changed files with 233 additions and 19 deletions
40
apps/examples/src/15-custom-zones/ZonesExample.tsx
Normal file
40
apps/examples/src/15-custom-zones/ZonesExample.tsx
Normal file
|
@ -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>
|
||||
)
|
||||
}
|
|
@ -14,6 +14,7 @@ import UserPresenceExample from './11-user-presence/UserPresenceExample'
|
|||
import UiEventsExample from './12-ui-events/UiEventsExample'
|
||||
import StoreEventsExample from './13-store-events/StoreEventsExample'
|
||||
import PersistenceExample from './14-persistence/PersistenceExample'
|
||||
import ZonesExample from './15-custom-zones/ZonesExample'
|
||||
import ExampleApi from './2-api/APIExample'
|
||||
import CustomConfigExample from './3-custom-config/CustomConfigExample'
|
||||
import CustomUiExample from './4-custom-ui/CustomUiExample'
|
||||
|
@ -90,6 +91,10 @@ export const allExamples: Example[] = [
|
|||
path: '/user-presence',
|
||||
element: <UserPresenceExample />,
|
||||
},
|
||||
{
|
||||
path: '/zones',
|
||||
element: <ZonesExample />,
|
||||
},
|
||||
{
|
||||
path: '/persistence',
|
||||
element: <PersistenceExample />,
|
||||
|
|
|
@ -229,6 +229,7 @@
|
|||
"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.share-project": "Share this project",
|
||||
"share-menu.default-project-name": "Shared Project",
|
||||
"share-menu.copy-link": "Copy share link",
|
||||
"share-menu.readonly-link": "Read-only",
|
||||
"share-menu.create-snapshot-link": "Copy snapshot link",
|
||||
|
@ -277,6 +278,12 @@
|
|||
"shortcuts-dialog.tools": "Tools",
|
||||
"shortcuts-dialog.transform": "Transform",
|
||||
"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.align": "Align",
|
||||
"style-panel.vertical-align": "Vertical align",
|
||||
|
|
|
@ -390,6 +390,8 @@ export class App extends EventEmitter<TLEventMap> {
|
|||
panZoomIntoView(ids: TLShapeId[], opts?: AnimationOptions): this;
|
||||
// (undocumented)
|
||||
popFocusLayer(): this;
|
||||
// @internal (undocumented)
|
||||
get projectName(): string;
|
||||
// @internal
|
||||
get props(): null | TLNullableShapeProps;
|
||||
// (undocumented)
|
||||
|
@ -471,6 +473,8 @@ export class App extends EventEmitter<TLEventMap> {
|
|||
setLocale(locale: string): void;
|
||||
// (undocumented)
|
||||
setPenMode(isPenMode: boolean): this;
|
||||
// @internal (undocumented)
|
||||
setProjectName(name: string): void;
|
||||
setProp(key: TLShapeProp, value: any, ephemeral?: boolean, squashing?: boolean): this;
|
||||
// @internal (undocumented)
|
||||
setReadOnly(isReadOnly: boolean): this;
|
||||
|
@ -512,6 +516,8 @@ export class App extends EventEmitter<TLEventMap> {
|
|||
updateAssets(assets: TLAssetPartial[]): this;
|
||||
// @internal
|
||||
updateCullingBounds(): this;
|
||||
// @internal (undocumented)
|
||||
updateDocumentSettings(settings: Partial<TLDocument>): void;
|
||||
updateInstanceState(partial: Partial<Omit<TLInstance, 'currentPageId' | 'documentId' | 'userId'>>, ephemeral?: boolean, squashing?: boolean): this;
|
||||
updatePage(partial: RequiredKeys<TLPage, 'id'>, squashing?: boolean): this;
|
||||
updateShapes(partials: (null | TLShapePartial | undefined)[], squashing?: boolean): this;
|
||||
|
@ -1829,6 +1835,7 @@ export type TldrawEditorProps = {
|
|||
initialData?: StoreSnapshot<TLRecord>;
|
||||
instanceId?: TLInstanceId;
|
||||
persistenceKey?: string;
|
||||
defaultName?: string;
|
||||
});
|
||||
|
||||
// @public (undocumented)
|
||||
|
|
|
@ -119,6 +119,10 @@ export type TldrawEditorProps = {
|
|||
* The id under which to sync and persist the editor's data.
|
||||
*/
|
||||
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 }) {
|
||||
const { initialData, instanceId = TAB_ID, shapes, persistenceKey } = props
|
||||
const { defaultName, initialData, instanceId = TAB_ID, shapes, persistenceKey } = props
|
||||
|
||||
const syncedStore = useLocalStore({
|
||||
customShapes: shapes,
|
||||
instanceId,
|
||||
initialData,
|
||||
persistenceKey,
|
||||
defaultName,
|
||||
})
|
||||
|
||||
return <TldrawEditorWithLoadingStore {...props} store={syncedStore} />
|
||||
|
|
|
@ -35,6 +35,7 @@ import {
|
|||
TLCursor,
|
||||
TLCursorType,
|
||||
TLDOCUMENT_ID,
|
||||
TLDocument,
|
||||
TLFrameShape,
|
||||
TLGroupShape,
|
||||
TLImageAsset,
|
||||
|
@ -1515,10 +1516,25 @@ export class App extends EventEmitter<TLEventMap> {
|
|||
return this.store.get(TLDOCUMENT_ID)!
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
updateDocumentSettings(settings: Partial<TLDocument>) {
|
||||
this.store.put([{ ...this.documentSettings, ...settings }])
|
||||
}
|
||||
|
||||
get gridSize() {
|
||||
return this.documentSettings.gridSize
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
get projectName() {
|
||||
return this.documentSettings.name
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
setProjectName(name: string) {
|
||||
this.updateDocumentSettings({ name })
|
||||
}
|
||||
|
||||
get isSnapMode() {
|
||||
return this.userDocumentSettings.isSnapMode
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ export type StoreOptions = {
|
|||
customShapes?: Record<string, ShapeInfo>
|
||||
instanceId?: TLInstanceId
|
||||
initialData?: StoreSnapshot<TLRecord>
|
||||
defaultName?: string
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -30,7 +31,12 @@ export type StoreOptions = {
|
|||
*
|
||||
* @public */
|
||||
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({
|
||||
schema: createTLSchema({ customShapes }),
|
||||
|
@ -38,6 +44,7 @@ export function createTLStore(opts = {} as StoreOptions): TLStore {
|
|||
props: {
|
||||
instanceId,
|
||||
documentId: TLDOCUMENT_ID,
|
||||
defaultName,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
|
@ -5,11 +5,10 @@ import { usePrevious } from './usePrevious'
|
|||
/** @public */
|
||||
export function useTLStore(opts: StoreOptions) {
|
||||
const [store, setStore] = useState(() => createTLStore(opts))
|
||||
const previousOpts = usePrevious(opts)
|
||||
const prev = usePrevious(opts)
|
||||
if (
|
||||
previousOpts.customShapes !== opts.customShapes ||
|
||||
previousOpts.initialData !== opts.initialData ||
|
||||
previousOpts.instanceId !== opts.instanceId
|
||||
// shallow equality check
|
||||
(Object.keys(prev) as (keyof StoreOptions)[]).some((key) => prev[key] !== opts[key])
|
||||
) {
|
||||
const newStore = createTLStore(opts)
|
||||
setStore(newStore)
|
||||
|
|
|
@ -780,6 +780,8 @@ export type TLDefaultShape = TLArrowShape | TLBookmarkShape | TLDrawShape | TLEm
|
|||
export interface TLDocument extends BaseRecord<'document', ID<TLDocument>> {
|
||||
// (undocumented)
|
||||
gridSize: number;
|
||||
// (undocumented)
|
||||
name: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
|
@ -1250,6 +1252,7 @@ export type TLStore = Store<TLRecord, TLStoreProps>;
|
|||
export type TLStoreProps = {
|
||||
instanceId: TLInstanceId;
|
||||
documentId: typeof TLDOCUMENT_ID;
|
||||
defaultName: string;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
|
|
|
@ -40,6 +40,7 @@ export type TLStoreSnapshot = StoreSnapshot<TLRecord>
|
|||
export type TLStoreProps = {
|
||||
instanceId: TLInstanceId
|
||||
documentId: typeof TLDOCUMENT_ID
|
||||
defaultName: string
|
||||
}
|
||||
|
||||
/** @public */
|
||||
|
@ -91,7 +92,7 @@ export function createIntegrityChecker(store: TLStore): () => void {
|
|||
const { instanceId: tabId } = store.props
|
||||
// make sure we have exactly one document
|
||||
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()
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ import { structuredClone } from '@tldraw/utils'
|
|||
import fs from 'fs'
|
||||
import { imageAssetMigrations } from './assets/TLImageAsset'
|
||||
import { videoAssetMigrations } from './assets/TLVideoAsset'
|
||||
import { documentTypeMigrations } from './records/TLDocument'
|
||||
import { instanceTypeMigrations, instanceTypeVersions } from './records/TLInstance'
|
||||
import { instancePageStateMigrations } from './records/TLInstancePageState'
|
||||
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', () => {
|
||||
const { up, down } = geoShapeTypeMigrations.migrators[4]
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ import { T } from '@tldraw/tlvalidate'
|
|||
*/
|
||||
export interface TLDocument extends BaseRecord<'document', ID<TLDocument>> {
|
||||
gridSize: number
|
||||
name: string
|
||||
}
|
||||
|
||||
/** @public */
|
||||
|
@ -17,22 +18,46 @@ export const documentTypeValidator: T.Validator<TLDocument> = T.model(
|
|||
typeName: T.literal('document'),
|
||||
id: T.literal('document:document' as ID<TLDocument>),
|
||||
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 */
|
||||
export const DocumentRecordType = createRecordType<TLDocument>('document', {
|
||||
migrations: documentTypeMigrations,
|
||||
validator: documentTypeValidator,
|
||||
scope: 'document',
|
||||
}).withDefaultProperties(
|
||||
(): Omit<TLDocument, 'id' | 'typeName'> => ({
|
||||
gridSize: 10,
|
||||
name: '',
|
||||
})
|
||||
)
|
||||
|
||||
// all document records have the same ID: 'document:document'
|
||||
/** @public */
|
||||
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
|
@ -31,6 +31,7 @@ export type TldrawUiProps = {
|
|||
hideUi?: boolean
|
||||
/** A component to use for the share zone (will be deprecated) */
|
||||
shareZone?: ReactNode
|
||||
topZone?: ReactNode
|
||||
/** Additional items to add to the debug menu (will be deprecated)*/
|
||||
renderDebugMenuItems?: () => React.ReactNode
|
||||
} & TldrawUiContextProviderProps
|
||||
|
@ -40,6 +41,7 @@ export type TldrawUiProps = {
|
|||
*/
|
||||
export const TldrawUi = React.memo(function TldrawUi({
|
||||
shareZone,
|
||||
topZone,
|
||||
renderDebugMenuItems,
|
||||
children,
|
||||
hideUi,
|
||||
|
@ -50,6 +52,7 @@ export const TldrawUi = React.memo(function TldrawUi({
|
|||
<TldrawUiInner
|
||||
hideUi={hideUi}
|
||||
shareZone={shareZone}
|
||||
topZone={topZone}
|
||||
renderDebugMenuItems={renderDebugMenuItems}
|
||||
>
|
||||
{children}
|
||||
|
@ -61,6 +64,7 @@ export const TldrawUi = React.memo(function TldrawUi({
|
|||
type TldrawUiContentProps = {
|
||||
hideUi?: boolean
|
||||
shareZone?: ReactNode
|
||||
topZone?: ReactNode
|
||||
renderDebugMenuItems?: () => React.ReactNode
|
||||
}
|
||||
|
||||
|
@ -84,6 +88,7 @@ const TldrawUiInner = React.memo(function TldrawUiInner({
|
|||
/** @public */
|
||||
export const TldrawUiContent = React.memo(function TldrawUI({
|
||||
shareZone,
|
||||
topZone,
|
||||
renderDebugMenuItems,
|
||||
}: TldrawUiContentProps) {
|
||||
const app = useApp()
|
||||
|
@ -127,12 +132,9 @@ export const TldrawUiContent = React.memo(function TldrawUI({
|
|||
<StopFollowing />
|
||||
</div>
|
||||
</div>
|
||||
<div className="tlui-layout__top__center">{topZone}</div>
|
||||
<div className="tlui-layout__top__right">
|
||||
{shareZone && (
|
||||
<div className="tlui-share-zone" draggable={false}>
|
||||
{shareZone}
|
||||
</div>
|
||||
)}
|
||||
{breakpoint >= 5 && !isReadonlyMode && (
|
||||
<div className="tlui-style-panel__wrapper">
|
||||
<StylePanel />
|
||||
|
|
|
@ -24,7 +24,7 @@ export const MenuZone = track(function MenuZone() {
|
|||
<Menu />
|
||||
<div className="tlui-menu-zone__divider" />
|
||||
<PageMenu />
|
||||
{breakpoint >= 5 && showQuickActions && (
|
||||
{breakpoint >= 6 && showQuickActions && (
|
||||
<>
|
||||
<div className="tlui-menu-zone__divider" />
|
||||
<UndoButton />
|
||||
|
|
|
@ -115,7 +115,7 @@ export const Toolbar = function Toolbar() {
|
|||
'tlui-toolbar__extras__hidden': !showExtraActions,
|
||||
})}
|
||||
>
|
||||
{breakpoint < 5 && (
|
||||
{breakpoint < 6 && (
|
||||
<div className="tlui-toolbar__extras__controls">
|
||||
<UndoButton />
|
||||
<RedoButton />
|
||||
|
|
|
@ -19,6 +19,7 @@ export interface InputProps {
|
|||
onComplete?: (value: string) => void
|
||||
onValueChange?: (value: string) => void
|
||||
onCancel?: (value: string) => void
|
||||
onBlur?: (value: string) => void
|
||||
className?: string
|
||||
/**
|
||||
* 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,
|
||||
onValueChange,
|
||||
onCancel,
|
||||
onBlur,
|
||||
shouldManuallyMaintainScrollPositionWhenFocused = false,
|
||||
children,
|
||||
value,
|
||||
|
@ -106,7 +108,14 @@ export const Input = React.forwardRef<HTMLInputElement, InputProps>(function Inp
|
|||
[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(() => {
|
||||
const visualViewport = window.visualViewport
|
||||
|
|
|
@ -233,6 +233,7 @@ export type TLTranslationKey =
|
|||
| 'share-menu.save-note'
|
||||
| 'share-menu.fork-note'
|
||||
| 'share-menu.share-project'
|
||||
| 'share-menu.default-project-name'
|
||||
| 'share-menu.copy-link'
|
||||
| 'share-menu.readonly-link'
|
||||
| 'share-menu.create-snapshot-link'
|
||||
|
@ -281,6 +282,12 @@ export type TLTranslationKey =
|
|||
| 'shortcuts-dialog.tools'
|
||||
| 'shortcuts-dialog.transform'
|
||||
| '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.align'
|
||||
| 'style-panel.vertical-align'
|
||||
|
|
|
@ -233,6 +233,7 @@ export const DEFAULT_TRANSLATION = {
|
|||
'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.share-project': 'Share this project',
|
||||
'share-menu.default-project-name': 'Shared Project',
|
||||
'share-menu.copy-link': 'Copy share link',
|
||||
'share-menu.readonly-link': 'Read-only',
|
||||
'share-menu.create-snapshot-link': 'Copy snapshot link',
|
||||
|
@ -284,6 +285,12 @@ export const DEFAULT_TRANSLATION = {
|
|||
'shortcuts-dialog.tools': 'Tools',
|
||||
'shortcuts-dialog.transform': 'Transform',
|
||||
'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.align': 'Align',
|
||||
'style-panel.vertical-align': 'Vertical align',
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
grid-column: 1;
|
||||
grid-row: 1;
|
||||
display: flex;
|
||||
min-width: 0px;
|
||||
}
|
||||
|
||||
.tlui-layout__top__left {
|
||||
|
@ -37,6 +38,19 @@
|
|||
justify-content: flex-start;
|
||||
width: 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 {
|
||||
|
@ -46,6 +60,8 @@
|
|||
justify-content: flex-start;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
flex-shrink: 1;
|
||||
min-width: 0px;
|
||||
}
|
||||
|
||||
.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 ------------------- */
|
||||
|
||||
.tlui-navigation-zone {
|
||||
|
|
Loading…
Reference in a new issue