From 4d900fb7fd154d70c1c0ddcfc20dfd2b76689b58 Mon Sep 17 00:00:00 2001 From: Enrico <39310565+Leone25@users.noreply.github.com> Date: Mon, 4 Jul 2022 16:17:47 +0200 Subject: [PATCH] Implemented better page numbering (#779) * Implemented better page numbering * Added spanish and french translation * Add tests, fix regex * Improve page naming logic Co-authored-by: Steve Ruiz --- .../editor/src/utils/defaultDocument.ts | 1 + packages/core/src/utils/utils.ts | 18 --- .../components/TopPanel/PageMenu/PageMenu.tsx | 4 +- packages/tldraw/src/hooks/useTranslation.ts | 13 +- packages/tldraw/src/state/TldrawApp.ts | 9 +- .../commands/createPage/createPage.spec.ts | 133 +++++++++++++++--- .../state/commands/createPage/createPage.ts | 5 +- .../commands/shared/getIncrementedName.ts | 18 +++ packages/tldraw/src/translations/es.json | 1 + packages/tldraw/src/translations/fr.json | 1 + packages/tldraw/src/translations/it.json | 1 + packages/tldraw/src/translations/main.json | 1 + .../tldraw/src/translations/translations.ts | 48 ++++--- 13 files changed, 184 insertions(+), 69 deletions(-) create mode 100644 packages/tldraw/src/state/commands/shared/getIncrementedName.ts diff --git a/apps/vscode/editor/src/utils/defaultDocument.ts b/apps/vscode/editor/src/utils/defaultDocument.ts index 96027167b..97108f6e0 100644 --- a/apps/vscode/editor/src/utils/defaultDocument.ts +++ b/apps/vscode/editor/src/utils/defaultDocument.ts @@ -13,6 +13,7 @@ export const defaultDocument: TDDocument = { bindings: {}, }, }, + assets: {}, pageStates: { page: { id: 'page', diff --git a/packages/core/src/utils/utils.ts b/packages/core/src/utils/utils.ts index 1b4d03b4b..dd55f788e 100644 --- a/packages/core/src/utils/utils.ts +++ b/packages/core/src/utils/utils.ts @@ -1485,24 +1485,6 @@ left past the initial left edge) then swap points on that axis. static metaKey(e: KeyboardEvent | React.KeyboardEvent): boolean { return Utils.isDarwin() ? e.metaKey : e.ctrlKey } - - /** - * Get an incremented name (e.g. New page (2)) from a name (e.g. New page), based on an array of existing names. - * - * @param name The name to increment. - * @param others The array of existing names. - */ - static getIncrementedName(name: string, others: string[]) { - let result = name - - while (others.includes(result)) { - result = /\s\((\d+)\)$/.exec(result)?.[1] - ? result.replace(/\d+(?=\)$)/, (m) => (+m + 1).toString()) - : `${result} (1)` - } - - return result - } } export default Utils diff --git a/packages/tldraw/src/components/TopPanel/PageMenu/PageMenu.tsx b/packages/tldraw/src/components/TopPanel/PageMenu/PageMenu.tsx index de5194854..872bfddcb 100644 --- a/packages/tldraw/src/components/TopPanel/PageMenu/PageMenu.tsx +++ b/packages/tldraw/src/components/TopPanel/PageMenu/PageMenu.tsx @@ -66,7 +66,9 @@ function PageMenuContent({ onClose }: { onClose: () => void }) { const currentPageId = app.useStore(currentPageIdSelector) const handleCreatePage = React.useCallback(() => { - app.createPage(undefined, intl.formatMessage({ id: 'new.page' })) + const pageName = + intl.formatMessage({ id: 'page' }) + ' ' + (Object.keys(app.document.pages).length + 1) + app.createPage(undefined, pageName) }, [app]) const handleChangePage = React.useCallback( diff --git a/packages/tldraw/src/hooks/useTranslation.ts b/packages/tldraw/src/hooks/useTranslation.ts index 0dfc1277b..7d27d33dd 100644 --- a/packages/tldraw/src/hooks/useTranslation.ts +++ b/packages/tldraw/src/hooks/useTranslation.ts @@ -1,19 +1,10 @@ import * as React from 'react' -import { TRANSLATIONS, TDLanguage } from '../translations/translations' +import { getTranslation, TDLanguage } from '../translations/translations' export function useTranslation(code?: TDLanguage) { return React.useMemo(() => { const locale = code ?? navigator.language.split(/[-_]/)[0] - const translation = TRANSLATIONS.find((t) => t.code === locale) - - const defaultTranslation = TRANSLATIONS.find((t) => t.code === 'en')! - - const messages = { - ...defaultTranslation.messages, - ...translation?.messages, - } - - return { locale, messages } + return getTranslation(locale) }, [code]) } diff --git a/packages/tldraw/src/state/TldrawApp.ts b/packages/tldraw/src/state/TldrawApp.ts index e59fbf458..29cf9987c 100644 --- a/packages/tldraw/src/state/TldrawApp.ts +++ b/packages/tldraw/src/state/TldrawApp.ts @@ -83,6 +83,7 @@ import { StateManager } from './StateManager' import { clearPrevSize } from './shapes/shared/getTextSize' import { getClipboard, setClipboard } from './IdbClipboard' import { deepCopy } from './StateManager/copy' +import { getTranslation } from '~translations' const uuid = Utils.uniqueId() @@ -1226,9 +1227,15 @@ export class TldrawApp extends StateManager { this.pasteInfo.offset = [0, 0] this.currentTool = this.tools.select + const doc = TldrawApp.defaultDocument + + // Set the default page name to the localized version of "Page" + const translation = getTranslation(this.settings.language) + doc.pages['page'].name = translation.messages['page'] + ' 1' ?? 'Page 1' + this.resetHistory() .clearSelectHistory() - .loadDocument(migrate(TldrawApp.defaultDocument, TldrawApp.version)) + .loadDocument(migrate(doc, TldrawApp.version)) .persist({}) return this diff --git a/packages/tldraw/src/state/commands/createPage/createPage.spec.ts b/packages/tldraw/src/state/commands/createPage/createPage.spec.ts index 79ef7b30e..c5d8127c1 100644 --- a/packages/tldraw/src/state/commands/createPage/createPage.spec.ts +++ b/packages/tldraw/src/state/commands/createPage/createPage.spec.ts @@ -1,8 +1,17 @@ import { mockDocument, TldrawTestApp } from '~test' -describe('Create page command', () => { - const app = new TldrawTestApp() +let app: TldrawTestApp +beforeEach(() => { + app = new TldrawTestApp() +}) + +function createPageWithName(app: TldrawTestApp) { + const pageName = 'Page' + ' ' + (Object.keys(app.document.pages).length + 1) + app.createPage(undefined, pageName) +} + +describe('Create page command', () => { it('does, undoes and redoes command', () => { app.loadDocument(mockDocument) @@ -34,30 +43,116 @@ describe('Create page command', () => { it('increments page names', () => { app.loadDocument(mockDocument) - app.createPage() + createPageWithName(app) - expect(app.page.name).toBe('New page') + expect(app.page.name).toBe('Page 2') - app.createPage() + createPageWithName(app) - expect(app.page.name).toBe('New page (1)') - - app.createPage() - - expect(app.page.name).toBe('New page (2)') - - app.renamePage(app.page.id, 'New page!') - - app.createPage() - - expect(app.page.name).toBe('New page (2)') + expect(app.page.name).toBe('Page 3') app.deletePage(app.page.id) - expect(app.page.name).toBe('New page!') + createPageWithName(app) - app.createPage(undefined, 'New page!') + expect(app.page.name).toBe('Page 3') - expect(app.page.name).toBe('New page! (1)') + createPageWithName(app) + + expect(app.page.name).toBe('Page 4') + + app.renamePage(app.page.id, 'Page!') + + createPageWithName(app) + + expect(app.page.name).toBe('Page 5') + + app.renamePage(app.page.id, 'Page 6') + + createPageWithName(app) + + expect(app.page.name).toBe('Page 7') + }) +}) + +describe('when the page name exists', () => { + it('when others is empty', () => { + app.loadDocument(mockDocument) + app.createPage(undefined, 'Apple') + expect(app.page.name).toBe('Apple') + }) + + it('when others has no match', () => { + app.createPage(undefined, 'Orange') + app.createPage(undefined, 'Apple') + expect(app.page.name).toBe('Apple') + }) + + it('when others has one match', () => { + app.createPage(undefined, 'Orange') + app.createPage(undefined, 'Apple') + app.createPage(undefined, 'Apple') + expect(app.page.name).toBe('Apple 1') + }) + + it('when others has two matches', () => { + app.createPage(undefined, 'Orange') + app.createPage(undefined, 'Apple') + app.createPage(undefined, 'Apple 1') + app.createPage(undefined, 'Apple') + expect(app.page.name).toBe('Apple 2') + }) + + it('when others has a near match', () => { + app.createPage(undefined, 'Orange') + app.createPage(undefined, 'Apple') + app.createPage(undefined, 'Apple ()') + app.createPage(undefined, 'Apples') + app.createPage(undefined, 'Apple') + expect(app.page.name).toBe('Apple 1') + }) + + it('when others has a near match', () => { + app.createPage(undefined, 'Orange') + app.createPage(undefined, 'Apple') + app.createPage(undefined, 'Apple 1!') + app.createPage(undefined, 'Apple') + expect(app.page.name).toBe('Apple 1') + }) + + it('when others has a near match', () => { + app.createPage(undefined, 'Orange') + app.createPage(undefined, 'Apple') + app.createPage(undefined, 'Apple 1!') + app.createPage(undefined, 'Apple 1!') + expect(app.page.name).toBe('Apple 1! 1') + }) + + it('when others has a near match', () => { + app.createPage(undefined, 'Orange') + app.createPage(undefined, 'Apple') + app.createPage(undefined, 'Apple 1') + app.createPage(undefined, 'Apple 2') + app.createPage(undefined, 'Apple 3') + app.createPage(undefined, 'Apple 5') + app.createPage(undefined, 'Apple') + expect(app.page.name).toBe('Apple 4') + }) + + it('when others has a near match', () => { + app.createPage(undefined, 'Orange') + app.createPage(undefined, 'Apple') + app.createPage(undefined, 'Apple 1') + app.createPage(undefined, 'Apple 2') + app.createPage(undefined, 'Apple 3') + app.createPage(undefined, 'Apple 4') + app.createPage(undefined, 'Apple 5') + app.createPage(undefined, 'Apple 6') + app.createPage(undefined, 'Apple 7') + app.createPage(undefined, 'Apple 8') + app.createPage(undefined, 'Apple 9') + app.createPage(undefined, 'Apple 10') + app.createPage(undefined, 'Apple') + expect(app.page.name).toBe('Apple 11') }) }) diff --git a/packages/tldraw/src/state/commands/createPage/createPage.ts b/packages/tldraw/src/state/commands/createPage/createPage.ts index db4a168fd..d49169c8f 100644 --- a/packages/tldraw/src/state/commands/createPage/createPage.ts +++ b/packages/tldraw/src/state/commands/createPage/createPage.ts @@ -1,12 +1,13 @@ import type { TldrawCommand, TDPage } from '~types' import { Utils, TLPageState } from '@tldraw/core' import type { TldrawApp } from '~state' +import { getIncrementedName } from '../shared/getIncrementedName' export function createPage( app: TldrawApp, center: number[], pageId = Utils.uniqueId(), - pageName = 'New page' + pageName = 'Page' ): TldrawCommand { const { currentPageId } = app @@ -20,7 +21,7 @@ export function createPage( const page: TDPage = { id: pageId, - name: Utils.getIncrementedName( + name: getIncrementedName( pageName, pages.map((p) => p.name ?? '') ), diff --git a/packages/tldraw/src/state/commands/shared/getIncrementedName.ts b/packages/tldraw/src/state/commands/shared/getIncrementedName.ts new file mode 100644 index 000000000..d583da5d5 --- /dev/null +++ b/packages/tldraw/src/state/commands/shared/getIncrementedName.ts @@ -0,0 +1,18 @@ +/** + * Get an incremented name (e.g. Page 2) from a name (e.g. Page 1), based on an array of existing names. + * + * @param name The name to increment. + * @param others The array of existing names. + */ +export function getIncrementedName(name: string, others: string[]) { + let result = name + const set = new Set(others) + + while (set.has(result)) { + result = /^.*(\d+)$/.exec(result)?.[1] + ? result.replace(/(\d+)(?=\D?)$/, (m) => (+m + 1).toString()) + : `${result} 1` + } + + return result +} diff --git a/packages/tldraw/src/translations/es.json b/packages/tldraw/src/translations/es.json index ad614cfbd..99a56ef1b 100644 --- a/packages/tldraw/src/translations/es.json +++ b/packages/tldraw/src/translations/es.json @@ -51,6 +51,7 @@ "create.page": "Crear página", "new.page": "Nueva página", "page.name": "Nombre de página", + "page": "Página", "duplicate": "Duplicar", "cancel": "Cancelar", "copy.invite.link": "Copiar invitación", diff --git a/packages/tldraw/src/translations/fr.json b/packages/tldraw/src/translations/fr.json index 3c0cb9175..9cb9c19a2 100644 --- a/packages/tldraw/src/translations/fr.json +++ b/packages/tldraw/src/translations/fr.json @@ -51,6 +51,7 @@ "create.page": "Créer une Page", "new.page": "Nouvelle Page", "page.name": "Nom de la Page", + "page": "Page", "duplicate": "Dupliquer", "cancel": "Annuler", "copy.invite.link": "Copier le Lien d'Invitation", diff --git a/packages/tldraw/src/translations/it.json b/packages/tldraw/src/translations/it.json index 2a01589a2..2921678a6 100644 --- a/packages/tldraw/src/translations/it.json +++ b/packages/tldraw/src/translations/it.json @@ -51,6 +51,7 @@ "create.page": "Crea nuova pagina", "new.page": "Nuova pagina", "page.name": "Nome pagina", + "page": "Pagina", "duplicate": "Duplica", "cancel": "Chiudi", "copy.invite.link": "Copia link invito", diff --git a/packages/tldraw/src/translations/main.json b/packages/tldraw/src/translations/main.json index 23cb0a9cb..90b204514 100644 --- a/packages/tldraw/src/translations/main.json +++ b/packages/tldraw/src/translations/main.json @@ -51,6 +51,7 @@ "create.page": "Create Page", "new.page": "New Page", "page.name": "Page Name", + "page": "Page", "duplicate": "Duplicate", "cancel": "Cancel", "copy.invite.link": "Copy Invite Link", diff --git a/packages/tldraw/src/translations/translations.ts b/packages/tldraw/src/translations/translations.ts index 56230fe6c..617a6df2e 100644 --- a/packages/tldraw/src/translations/translations.ts +++ b/packages/tldraw/src/translations/translations.ts @@ -22,23 +22,23 @@ import zh_cn from './zh-cn.json' // translation instead. export const TRANSLATIONS: TDTranslations = [ - { code: 'ar', label: 'عربي', messages: ar }, - { code: 'da', label: 'Danish', messages: da }, - { code: 'de', label: 'Deutsch', messages: de }, - { code: 'en', label: 'English', messages: en }, - { code: 'es', label: 'Español', messages: es }, - { code: 'fa', label: 'فارسی', messages: fa }, - { code: 'fr', label: 'Français', messages: fr }, - { code: 'it', label: 'Italiano', messages: it }, - { code: 'ja', label: '日本語', messages: ja }, - { code: 'ko-kr', label: '한국어', messages: ko_kr }, - { code: 'ne', label: 'नेपाली', messages: ne }, - { code: 'no', label: 'Norwegian', messages: no }, - { code: 'pl', label: 'Polski', messages: pl }, - { code: 'pt-br', label: 'Português - Brasil', messages: pt_br }, - { code: 'ru', label: 'Russian', messages: ru }, - { code: 'tr', label: 'Türkçe', messages: tr }, - { code: 'zh-cn', label: 'Chinese - Simplified', messages: zh_cn }, + { code: 'ar', locale: 'ar', label: 'عربي', messages: ar }, + { code: 'en', locale: 'en', label: 'English', messages: en }, + { code: 'es', locale: 'es', label: 'Español', messages: es }, + { code: 'fr', locale: 'fr', label: 'Français', messages: fr }, + { code: 'fa', locale:'fa', label: 'فارسی', messages: fa }, + { code: 'it', locale: 'it', label: 'Italiano', messages: it }, + { code: 'ja', locale: 'ja', label: '日本語', messages: ja }, + { code: 'ko-kr', locale: 'ko-kr', label: '한국어', messages: ko_kr }, + { code: 'ne', locale: 'ne', label: 'नेपाली', messages: ne }, + { code: 'no', locale: 'no', label: 'Norwegian', messages: no }, + { code: 'pl', locale: 'pl', label: 'Polski', messages: pl }, + { code: 'pt-br', locale: 'pt-br', label: 'Português - Brasil', messages: pt_br }, + { code: 'tr', locale: 'tr', label: 'Türkçe', messages: tr }, + { code: 'zh-cn', locale: 'zh-ch', label: 'Chinese - Simplified', messages: zh_cn }, + { code: 'da', locale: 'da', label: 'Danish', messages: da }, + { code: 'de', locale: 'de', label: 'Deutsch', messages: de}, + { code: 'ru', locale: 'ru', label: 'Russian', messages: ru }, ] /* ----------------- (do not change) ---------------- */ @@ -48,9 +48,23 @@ TRANSLATIONS.sort((a, b) => (a.code < b.code ? -1 : 1)) export type TDTranslation = { readonly code: string readonly label: string + readonly locale: string readonly messages: Partial } export type TDTranslations = TDTranslation[] export type TDLanguage = TDTranslations[number]['code'] + +export function getTranslation(code: TDLanguage): TDTranslation { + const translation = TRANSLATIONS.find((t) => t.code === code) + + const defaultTranslation = TRANSLATIONS.find((t) => t.code === 'en')! + + const messages = { + ...defaultTranslation.messages, + ...translation?.messages, + } + + return { code, messages, locale: code, label: translation?.label ?? code } +} \ No newline at end of file