feat(studio): a full page says so instead of swallowing the click

This commit is contained in:
Maurice
2026-10-24 09:19:17 +02:00
parent 87f0333808
commit c4091eaf99
34 changed files with 202 additions and 4 deletions
@@ -195,7 +195,11 @@ function useElementAdders(page: BookPageSetup) {
const els = gridElements(grid, defaultGridBox(page, single))
// The page's own cap, as a duplicate keeps it: frames that would make the book unsaveable are not added.
const on = useStudioStore.getState().doc?.spreads[active]
if (!on || on.elements.length + els.length > MAX_SPREAD_ELEMENTS) return
if (!on) return
if (on.elements.length + els.length > MAX_SPREAD_ELEMENTS) {
useStudioStore.getState().refuseFull()
return
}
commit(d => ({
...d,
spreads: d.spreads.map((sp, i) => (i !== active ? sp : { ...sp, elements: [...sp.elements, ...els] })),
@@ -0,0 +1,24 @@
import { MAX_SPREAD_ELEMENTS } from '@trek/shared';
import { useEffect, useRef } from 'react';
import { useTranslation } from '../../i18n';
import { useStudioStore } from '../../store/studioStore';
import { useToast } from '../shared/Toast';
/**
* What the Studio says about a click it could not carry out. Today one thing:
* the page already holds as many elements as a page can, so a sticker, a frame,
* a paste or a duplicate was not added. Every path that refuses counts it in the
* store, and this says it once per count rather than each path in its own words.
*/
export function StudioNotices() {
const { t } = useTranslation();
const toast = useToast();
const refused = useStudioStore((s) => s.refusedFull);
const seen = useRef(refused);
useEffect(() => {
if (refused === seen.current) return;
seen.current = refused;
toast.warning(t('journey.studio.pageFull', { max: MAX_SPREAD_ELEMENTS }));
}, [refused, toast, t]);
return null;
}
@@ -16,6 +16,7 @@ import { StudioExport } from './StudioExport'
import { StudioAssistantColumn, StudioPageStage } from './StudioAssistantPanel'
import { useStudioAssistant } from './useStudioAssistant'
import { StudioHeader } from './StudioHeader'
import { StudioNotices } from './StudioNotices'
import '../../styles/dashboard.css'
import '../../styles/studio.css'
import './bookFontFaces'
@@ -84,6 +85,7 @@ export default function StudioShell() {
aria-label={s.t('journey.studio.title')}
>
<StudioHeader s={s} onExport={() => setExporting(true)} assistant={assistant} />
<StudioNotices />
{/* The journey gallery's picker, inside Studio's own layer (#2271). */}
{s.providerPick && s.journey && (
<ProviderPicker
@@ -99,7 +99,11 @@ export function StudioTextStyles({
const place = (lockup: BookLockup) => {
const els = placeLockup(lockup, { page, spread, words, gallery: source.photos, newId: bookElementId });
// The page's own cap, as a duplicate keeps it: a set that would make the book unsaveable is not placed.
if (!els.length || !spread || spread.elements.length + els.length > MAX_SPREAD_ELEMENTS) return;
if (!els.length || !spread) return;
if (spread.elements.length + els.length > MAX_SPREAD_ELEMENTS) {
useStudioStore.getState().refuseFull();
return;
}
commit((d) => ({
...d,
spreads: d.spreads.map((sp, i) => (i === active ? addBookLockup(sp, els, bookElementId('g')) : sp)),
+6 -1
View File
@@ -54,6 +54,7 @@ interface Host {
styleClip: BookCopiedStyle | null;
commit: (fn: (doc: BookDocument) => BookDocument) => void;
apply: (fn: (doc: BookDocument) => BookDocument) => void;
refuseFull: () => void;
}
type Set = (partial: Partial<Host> | ((s: Host) => Partial<Host>)) => void;
@@ -370,7 +371,11 @@ function panelActions(
if (!doc) return;
const chosen = spread.elements.filter((e) => ids.includes(e.id));
// The page's own cap: copies that would make the book unsaveable are not made.
if (!chosen.length || spread.elements.length + chosen.length > MAX_SPREAD_ELEMENTS) return;
if (!chosen.length) return;
if (spread.elements.length + chosen.length > MAX_SPREAD_ELEMENTS) {
get().refuseFull();
return;
}
const copies = chosen.map(
(e) =>
({
+19 -1
View File
@@ -35,6 +35,14 @@ interface StudioState extends StudioLayerActions {
gestureBase: BookDocument | null
/** Elements copied for pasting onto any page of the book (#2316). Kept across page switches. */
clipboard: StudioClipboard | null
/**
* Counts the times something was not added because the page already holds
* MAX_SPREAD_ELEMENTS. The Studio says so once per count (StudioNotices), so
* a full page no longer swallows a click without a word.
*/
refusedFull: number
/** Notes that an addition was refused because the page is full. */
refuseFull: () => void
/**
* The page a chat card points at, outlined in the page rail (journey-studio.md
* 4.2). Not part of the book or its history; opening a book clears it.
@@ -147,6 +155,8 @@ export const useStudioStore = create<StudioState>((set, get) => ({
future: [],
gestureBase: null,
clipboard: null,
refusedFull: 0,
refuseFull: () => set(st => ({ refusedFull: st.refusedFull + 1 })),
hintSpread: null,
readOnly: false,
@@ -228,7 +238,11 @@ export const useStudioStore = create<StudioState>((set, get) => ({
addElement: (spreadIndex, el) => {
// The page's own cap, as paste and duplicate keep it: what would make the book unsaveable is not added.
const target = get().doc?.spreads[spreadIndex]
if (!target || target.elements.length >= MAX_SPREAD_ELEMENTS) return false
if (!target) return false
if (target.elements.length >= MAX_SPREAD_ELEMENTS) {
get().refuseFull()
return false
}
get().commit(doc =>
replaceSpread(doc, spreadIndex, sp => ({ ...sp, elements: [...sp.elements, el] })))
/*
@@ -268,6 +282,10 @@ export const useStudioStore = create<StudioState>((set, get) => ({
const room = Math.max(0, MAX_SPREAD_ELEMENTS - target.elements.length)
const source = doc.spreads.find(sp => sp.id === clipboard.fromSpreadId)
// A copied group arrives as a group of its own, never as part of one already on the page.
if (room === 0) {
get().refuseFull()
return
}
const copies = regroupCopies(pastedElements(clipboard, target, doc.page).slice(0, room), source, bookElementId)
if (!copies.elements.length) return
get().commit(d => replaceSpread(d, spreadIndex, sp => withCopies(sp, copies)))
@@ -0,0 +1,89 @@
import { act, render } from '@testing-library/react';
import { MAX_SPREAD_ELEMENTS, type BookDocument, type BookElement, type BookSpread } from '@trek/shared';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { StudioNotices } from '../../../src/components/Studio/StudioNotices';
import { useStudioStore } from '../../../src/store/studioStore';
/**
* FE-STUDIO-FULL-001 to FE-STUDIO-FULL-004: a page that already holds
* MAX_SPREAD_ELEMENTS takes nothing more, and the Studio says so instead of
* swallowing the click.
*/
const el = (id: string): BookElement =>
({
id,
kind: 'shape',
frame: { x: 0, y: 0, w: 10, h: 10 },
rotation: 0,
opacity: 1,
locked: false,
shape: 'rect',
fill: '#000000',
gradient: 'none',
stroke: null,
strokeWidth: 0,
strokeStyle: 'solid',
radius: 0,
}) as BookElement;
const spread = (id: string, elements: BookElement[]): BookSpread => ({
id,
role: 'inner',
background: null,
elements,
parked: [],
entryId: null,
});
const book = (...spreads: BookSpread[]): BookDocument =>
({
version: 1,
title: 'T',
page: { preset: 'square-210', pageWidth: 210, pageHeight: 210, bleed: 3, safe: 5 },
spreads,
}) as BookDocument;
const full = () => Array.from({ length: MAX_SPREAD_ELEMENTS }, (_, i) => el(`f${i}`));
const store = () => useStudioStore.getState();
beforeEach(() => {
store().load(book(spread('full', full()), spread('room', [el('a')])));
useStudioStore.setState({ refusedFull: 0, clipboard: null });
});
describe('a full page', () => {
it('FE-STUDIO-FULL-001: refuses one more element and counts the refusal', () => {
expect(store().addElement(0, el('extra'))).toBe(false);
expect(store().doc!.spreads[0].elements).toHaveLength(MAX_SPREAD_ELEMENTS);
expect(store().refusedFull).toBe(1);
expect(store().addElement(1, el('fits'))).toBe(true);
expect(store().refusedFull).toBe(1);
});
it('FE-STUDIO-FULL-002: a paste onto it is refused and counted', () => {
useStudioStore.setState({ clipboard: { fromSpreadId: 'room', elements: [el('a')] } });
store().paste(0);
expect(store().doc!.spreads[0].elements).toHaveLength(MAX_SPREAD_ELEMENTS);
expect(store().refusedFull).toBe(1);
});
it('FE-STUDIO-FULL-003: a duplicate on it is refused and counted', () => {
store().duplicate(0, ['f0']);
expect(store().doc!.spreads[0].elements).toHaveLength(MAX_SPREAD_ELEMENTS);
expect(store().refusedFull).toBe(1);
});
it('FE-STUDIO-FULL-004: the Studio says it once per refusal', () => {
const addToast = vi.fn();
window.__addToast = addToast;
render(<StudioNotices />);
expect(addToast).not.toHaveBeenCalled();
act(() => store().refuseFull());
expect(addToast).toHaveBeenCalledTimes(1);
expect(addToast.mock.calls[0][1]).toBe('warning');
act(() => store().refuseFull());
expect(addToast).toHaveBeenCalledTimes(2);
});
});
+2
View File
@@ -493,6 +493,8 @@ const journey: TranslationStrings = {
'journey.studio.copyToPage': 'نسخ للصق في أي صفحة (Ctrl+C)',
'journey.studio.paste': 'لصق (Ctrl+V)',
'journey.studio.pasteEmpty': 'انسخ شيئًا أولًا، ثم الصقه في أي صفحة',
'journey.studio.pageFull':
'هذه الصفحة ممتلئة: تحتوي على {max} عنصرًا، وهو الحد الأقصى للصفحة. احذف شيئًا أولًا أو استخدم صفحة أخرى.',
'journey.studio.myLayouts': 'تخطيطاتي',
'journey.studio.myLayoutsEmpty': 'احتفظ بصفحة رتّبتها، ورتّب صفحات أخرى بالطريقة نفسها. تبقى صورها ونصوصها كما هي.',
'journey.studio.saveLayout': 'حفظ هذه الصفحة كتخطيط',
+2
View File
@@ -510,6 +510,8 @@ const journey: TranslationStrings = {
'journey.studio.copyToPage': 'İstənilən səhifəyə yapışdırmaq üçün kopyala (Ctrl+C)',
'journey.studio.paste': 'Yapışdır (Ctrl+V)',
'journey.studio.pasteEmpty': 'Əvvəlcə nəyisə kopyalayın, sonra onu istənilən səhifəyə yapışdırın',
'journey.studio.pageFull':
'Bu səhifə doludur: onda artıq {max} element var, bir səhifəyə bundan çoxu sığmır. Əvvəlcə nəyisə silin və ya başqa səhifədən istifadə edin.',
'journey.studio.myLayouts': 'Mənim tərtibatlarım',
'journey.studio.myLayoutsEmpty':
'Tərtib etdiyiniz səhifəni saxlayın və digər səhifələri eyni şəkildə düzün. Onların fotoları və mətnləri qalır.',
+2
View File
@@ -466,6 +466,8 @@ const journey: TranslationStrings = {
'journey.studio.copyToPage': 'Copiar, para colar em qualquer página (Ctrl+C)',
'journey.studio.paste': 'Colar (Ctrl+V)',
'journey.studio.pasteEmpty': 'Copie algo primeiro e depois cole em qualquer página',
'journey.studio.pageFull':
'Esta página está cheia: ela já tem {max} elementos, o máximo de uma página. Remova algo primeiro ou use outra página.',
'journey.studio.myLayouts': 'Minhas disposições',
'journey.studio.myLayoutsEmpty':
'Guarde uma página que você organizou e disponha outras páginas do mesmo jeito. As fotos e os textos delas continuam.',
+2
View File
@@ -465,6 +465,8 @@ const journey: TranslationStrings = {
'journey.studio.copyToPage': 'Copia, per enganxar a qualsevol pàgina (Ctrl+C)',
'journey.studio.paste': 'Enganxa (Ctrl+V)',
'journey.studio.pasteEmpty': 'Primer copia alguna cosa i després enganxa-la a qualsevol pàgina',
'journey.studio.pageFull':
'Aquesta pàgina és plena: ja té {max} elements, el màxim que hi cap. Treu-ne algun primer o fes servir una altra pàgina.',
'journey.studio.myLayouts': 'Les meves disposicions',
'journey.studio.myLayoutsEmpty':
'Guarda una pàgina que hagis organitzat i disposa altres pàgines de la mateixa manera. Les seves fotos i textos es mantenen.',
+2
View File
@@ -470,6 +470,8 @@ const journey: TranslationStrings = {
'journey.studio.copyToPage': 'Kopírovat pro vložení na libovolnou stranu (Ctrl+C)',
'journey.studio.paste': 'Vložit (Ctrl+V)',
'journey.studio.pasteEmpty': 'Nejprve něco zkopírujte a pak to vložte na libovolnou stranu',
'journey.studio.pageFull':
'Tato stránka je plná: má už {max} prvků, víc se na stránku nevejde. Nejdřív něco odeberte, nebo použijte jinou stránku.',
'journey.studio.myLayouts': 'Moje rozvržení',
'journey.studio.myLayoutsEmpty':
'Uložte si stranu, kterou jste uspořádali, a rozvrhněte stejně i další strany. Jejich fotky a texty zůstanou.',
+2
View File
@@ -466,6 +466,8 @@ const journey: TranslationStrings = {
'journey.studio.copyToPage': 'Kopieren, um es auf einer beliebigen Seite einzufügen (Ctrl+C)',
'journey.studio.paste': 'Einfügen (Ctrl+V)',
'journey.studio.pasteEmpty': 'Kopiere zuerst etwas und füge es dann auf einer beliebigen Seite ein',
'journey.studio.pageFull':
'Diese Seite ist voll: Sie hat schon {max} Elemente, mehr geht auf einer Seite nicht. Entferne zuerst etwas oder nimm eine andere Seite.',
'journey.studio.myLayouts': 'Meine Anordnungen',
'journey.studio.myLayoutsEmpty':
'Behalte eine Seite, die du angeordnet hast, und ordne andere Seiten genauso an. Ihre Fotos und Texte bleiben erhalten.',
+2
View File
@@ -463,6 +463,8 @@ const journey: TranslationStrings = {
'journey.studio.copyToPage': 'Copy, to paste on any page (Ctrl+C)',
'journey.studio.paste': 'Paste (Ctrl+V)',
'journey.studio.pasteEmpty': 'Copy something first, then paste it on any page',
'journey.studio.pageFull':
'This page is full: it holds {max} elements, the most a page can. Remove something first, or use another page.',
'journey.studio.myLayouts': 'My layouts',
'journey.studio.myLayoutsEmpty':
'Keep a page you arranged and lay other pages out the same way. Their photos and words stay.',
+2
View File
@@ -466,6 +466,8 @@ const journey: TranslationStrings = {
'journey.studio.copyToPage': 'Copiar, para pegar en cualquier página (Ctrl+C)',
'journey.studio.paste': 'Pegar (Ctrl+V)',
'journey.studio.pasteEmpty': 'Copia algo primero y luego pégalo en cualquier página',
'journey.studio.pageFull':
'Esta página está llena: ya tiene {max} elementos, el máximo que cabe. Quita algo primero o usa otra página.',
'journey.studio.myLayouts': 'Mis disposiciones',
'journey.studio.myLayoutsEmpty':
'Guarda una página que hayas organizado y dispón otras páginas de la misma forma. Sus fotos y textos se mantienen.',
+2
View File
@@ -464,6 +464,8 @@ const journey: TranslationStrings = {
'journey.studio.copyToPage': 'Kopeeri, et kleepida mis tahes lehele (Ctrl+C)',
'journey.studio.paste': 'Kleebi (Ctrl+V)',
'journey.studio.pasteEmpty': 'Kopeeri esmalt midagi, siis kleebi see mis tahes lehele',
'journey.studio.pageFull':
'See leht on täis: sellel on juba {max} elementi, rohkem ühele lehele ei mahu. Eemalda esmalt midagi või kasuta teist lehte.',
'journey.studio.myLayouts': 'Minu paigutused',
'journey.studio.myLayoutsEmpty':
'Salvesta enda kujundatud leht ja paiguta teised lehed samamoodi. Nende fotod ja tekstid jäävad alles.',
+2
View File
@@ -464,6 +464,8 @@ const journey: TranslationStrings = {
'journey.studio.copyToPage': 'Copier, pour coller sur n’importe quelle page (Ctrl+C)',
'journey.studio.paste': 'Coller (Ctrl+V)',
'journey.studio.pasteEmpty': 'Copiez d’abord quelque chose, puis collez-le sur n’importe quelle page',
'journey.studio.pageFull':
"Cette page est pleine : elle contient déjà {max} éléments, le maximum pour une page. Retirez d'abord un élément ou utilisez une autre page.",
'journey.studio.myLayouts': 'Mes dispositions',
'journey.studio.myLayoutsEmpty':
'Conservez une page que vous avez agencée et disposez d’autres pages de la même façon. Leurs photos et textes restent.',
+2
View File
@@ -469,6 +469,8 @@ const journey: TranslationStrings = {
'journey.studio.copyToPage': 'Αντιγραφή, για επικόλληση σε οποιαδήποτε σελίδα (Ctrl+C)',
'journey.studio.paste': 'Επικόλληση (Ctrl+V)',
'journey.studio.pasteEmpty': 'Αντιγράψτε πρώτα κάτι και μετά επικολλήστε το σε οποιαδήποτε σελίδα',
'journey.studio.pageFull':
'Αυτή η σελίδα είναι γεμάτη: έχει ήδη {max} στοιχεία, το μέγιστο για μια σελίδα. Αφαιρέστε πρώτα κάτι ή χρησιμοποιήστε άλλη σελίδα.',
'journey.studio.myLayouts': 'Οι διατάξεις μου',
'journey.studio.myLayoutsEmpty':
'Κρατήστε μια σελίδα που έχετε διαμορφώσει και στήστε άλλες σελίδες με τον ίδιο τρόπο. Οι φωτογραφίες και τα κείμενά τους παραμένουν.',
+2
View File
@@ -463,6 +463,8 @@ const journey: TranslationStrings = {
'journey.studio.copyToPage': 'Másolás, bármelyik oldalra beilleszthető (Ctrl+C)',
'journey.studio.paste': 'Beillesztés (Ctrl+V)',
'journey.studio.pasteEmpty': 'Előbb másoljon ki valamit, aztán illessze be bármelyik oldalra',
'journey.studio.pageFull':
'Ez az oldal megtelt: már {max} elem van rajta, ennél több nem fér egy oldalra. Előbb távolíts el valamit, vagy használj másik oldalt.',
'journey.studio.myLayouts': 'Saját elrendezéseim',
'journey.studio.myLayoutsEmpty':
'Mentsen el egy elrendezett oldalt, és rendezzen el más oldalakat is ugyanígy. Azok fotói és szövegei megmaradnak.',
+2
View File
@@ -455,6 +455,8 @@ const journey: TranslationStrings = {
'journey.studio.copyToPage': 'Salin, untuk ditempel di halaman mana pun (Ctrl+C)',
'journey.studio.paste': 'Tempel (Ctrl+V)',
'journey.studio.pasteEmpty': 'Salin sesuatu dulu, lalu tempel di halaman mana pun',
'journey.studio.pageFull':
'Halaman ini penuh: sudah berisi {max} elemen, batas maksimal satu halaman. Hapus sesuatu dulu atau gunakan halaman lain.',
'journey.studio.myLayouts': 'Tata letakku',
'journey.studio.myLayoutsEmpty':
'Simpan halaman yang sudah kamu susun dan tata halaman lain dengan cara yang sama. Foto dan teksnya tetap.',
+2
View File
@@ -465,6 +465,8 @@ const journey: TranslationStrings = {
'journey.studio.copyToPage': 'Copia, per incollare su qualsiasi pagina (Ctrl+C)',
'journey.studio.paste': 'Incolla (Ctrl+V)',
'journey.studio.pasteEmpty': 'Copia prima qualcosa, poi incollalo su qualsiasi pagina',
'journey.studio.pageFull':
"Questa pagina è piena: contiene già {max} elementi, il massimo per una pagina. Rimuovi prima qualcosa o usa un'altra pagina.",
'journey.studio.myLayouts': 'Le mie disposizioni',
'journey.studio.myLayoutsEmpty':
'Conserva una pagina che hai sistemato e disponi le altre pagine allo stesso modo. Le loro foto e i testi restano.',
+2
View File
@@ -453,6 +453,8 @@ const journey: TranslationStrings = {
'journey.studio.copyToPage': 'コピーして任意のページに貼り付け (Ctrl+C)',
'journey.studio.paste': '貼り付け (Ctrl+V)',
'journey.studio.pasteEmpty': 'まず何かをコピーしてから、任意のページに貼り付けてください',
'journey.studio.pageFull':
'このページはいっぱいです。すでに{max}個の要素があり、1ページの上限です。先に何かを削除するか、別のページを使ってください。',
'journey.studio.myLayouts': 'マイレイアウト',
'journey.studio.myLayoutsEmpty':
'配置したページを保存して、他のページも同じ配置にできます。写真と文章はそのまま残ります。',
+2
View File
@@ -450,6 +450,8 @@ const journey: TranslationStrings = {
'journey.studio.copyToPage': '복사해서 아무 페이지에나 붙여넣기 (Ctrl+C)',
'journey.studio.paste': '붙여넣기 (Ctrl+V)',
'journey.studio.pasteEmpty': '먼저 무언가를 복사한 뒤 아무 페이지에나 붙여넣으세요',
'journey.studio.pageFull':
'이 페이지는 가득 찼습니다. 이미 요소가 {max}개 있어 한 페이지의 최대치입니다. 먼저 무언가를 지우거나 다른 페이지를 사용하세요.',
'journey.studio.myLayouts': '내 레이아웃',
'journey.studio.myLayoutsEmpty':
'배치한 페이지를 저장해 두고 다른 페이지도 같은 방식으로 배치하세요. 사진과 글은 그대로 유지됩니다.',
+2
View File
@@ -466,6 +466,8 @@ const journey: TranslationStrings = {
'journey.studio.copyToPage': 'Kopiëren, om op elke pagina te plakken (Ctrl+C)',
'journey.studio.paste': 'Plakken (Ctrl+V)',
'journey.studio.pasteEmpty': 'Kopieer eerst iets en plak het dan op een willekeurige pagina',
'journey.studio.pageFull':
'Deze pagina is vol: er staan al {max} elementen op, het maximum voor een pagina. Verwijder eerst iets of gebruik een andere pagina.',
'journey.studio.myLayouts': 'Mijn indelingen',
'journey.studio.myLayoutsEmpty':
'Bewaar een pagina die je hebt ingedeeld en deel andere pagina’s op dezelfde manier in. Hun foto’s en teksten blijven staan.',
+2
View File
@@ -482,6 +482,8 @@ const journey: TranslationStrings = {
'journey.studio.copyToPage': 'Kopiuj, aby wkleić na dowolnej stronie (Ctrl+C)',
'journey.studio.paste': 'Wklej (Ctrl+V)',
'journey.studio.pasteEmpty': 'Najpierw coś skopiuj, a potem wklej to na dowolnej stronie',
'journey.studio.pageFull':
'Ta strona jest pełna: ma już {max} elementów, tyle, ile mieści się na stronie. Najpierw coś usuń albo użyj innej strony.',
'journey.studio.myLayouts': 'Moje układy',
'journey.studio.myLayoutsEmpty':
'Zachowaj ułożoną stronę i rozmieść inne strony w ten sam sposób. Ich zdjęcia i teksty pozostaną.',
+2
View File
@@ -481,6 +481,8 @@ const journey: TranslationStrings = {
'journey.studio.copyToPage': 'Копировать, чтобы вставить на любую страницу (Ctrl+C)',
'journey.studio.paste': 'Вставить (Ctrl+V)',
'journey.studio.pasteEmpty': 'Сначала скопируйте что-нибудь, затем вставьте на любую страницу',
'journey.studio.pageFull':
'Эта страница заполнена: на ней уже {max} элементов, больше на страницу не помещается. Сначала удалите что-нибудь или используйте другую страницу.',
'journey.studio.myLayouts': 'Мои макеты',
'journey.studio.myLayoutsEmpty':
'Сохраните оформленную страницу и расположите другие страницы так же. Их фото и тексты останутся.',
+2
View File
@@ -474,6 +474,8 @@ const journey: TranslationStrings = {
'journey.studio.copyToPage': 'Kopírovať na vloženie na ľubovoľnú stranu (Ctrl+C)',
'journey.studio.paste': 'Vložiť (Ctrl+V)',
'journey.studio.pasteEmpty': 'Najprv niečo skopírujte a potom to vložte na ľubovoľnú stranu',
'journey.studio.pageFull':
'Táto stránka je plná: má už {max} prvkov, viac sa na stránku nezmestí. Najprv niečo odstráňte alebo použite inú stránku.',
'journey.studio.myLayouts': 'Moje rozloženia',
'journey.studio.myLayoutsEmpty':
'Uložte si stranu, ktorú ste usporiadali, a rozložte rovnako aj ďalšie strany. Ich fotky a texty zostanú.',
+2
View File
@@ -463,6 +463,8 @@ const journey: TranslationStrings = {
'journey.studio.copyToPage': 'Kopiera för att klistra in på valfri sida (Ctrl+C)',
'journey.studio.paste': 'Klistra in (Ctrl+V)',
'journey.studio.pasteEmpty': 'Kopiera något först och klistra sedan in det på valfri sida',
'journey.studio.pageFull':
'Den här sidan är full: den har redan {max} element, det mesta en sida rymmer. Ta bort något först eller använd en annan sida.',
'journey.studio.myLayouts': 'Mina layouter',
'journey.studio.myLayoutsEmpty':
'Spara en sida du har ordnat och lägg upp andra sidor på samma sätt. Deras foton och texter finns kvar.',
+2
View File
@@ -449,6 +449,8 @@ const journey: TranslationStrings = {
'journey.studio.copyToPage': 'คัดลอกเพื่อวางในหน้าใดก็ได้ (Ctrl+C)',
'journey.studio.paste': 'วาง (Ctrl+V)',
'journey.studio.pasteEmpty': 'คัดลอกบางอย่างก่อน แล้ววางในหน้าใดก็ได้',
'journey.studio.pageFull':
'หน้านี้เต็มแล้ว มีองค์ประกอบ {max} ชิ้น ซึ่งเป็นจำนวนสูงสุดต่อหน้า โปรดลบบางอย่างก่อนหรือใช้หน้าอื่น',
'journey.studio.myLayouts': 'เค้าโครงของฉัน',
'journey.studio.myLayoutsEmpty':
'เก็บหน้าที่คุณจัดไว้ แล้วจัดหน้าอื่นในแบบเดียวกัน รูปภาพและข้อความของหน้าเหล่านั้นยังคงอยู่',
+2
View File
@@ -464,6 +464,8 @@ const journey: TranslationStrings = {
'journey.studio.copyToPage': 'Kopyala, herhangi bir sayfaya yapıştırmak için (Ctrl+C)',
'journey.studio.paste': 'Yapıştır (Ctrl+V)',
'journey.studio.pasteEmpty': 'Önce bir şey kopyala, sonra herhangi bir sayfaya yapıştır',
'journey.studio.pageFull':
'Bu sayfa dolu: zaten {max} öğe var, bir sayfaya sığabilecek en fazla sayı bu. Önce bir şey kaldırın ya da başka bir sayfa kullanın.',
'journey.studio.myLayouts': 'Yerleşimlerim',
'journey.studio.myLayoutsEmpty':
'Düzenlediğin bir sayfayı sakla ve diğer sayfaları da aynı şekilde yerleştir. Fotoğrafları ve metinleri olduğu gibi kalır.',
+2
View File
@@ -485,6 +485,8 @@ const journey: TranslationStrings = {
'journey.studio.copyToPage': 'Копіювати, щоб вставити на будь-яку сторінку (Ctrl+C)',
'journey.studio.paste': 'Вставити (Ctrl+V)',
'journey.studio.pasteEmpty': 'Спершу щось скопіюйте, потім вставте на будь-яку сторінку',
'journey.studio.pageFull':
'Ця сторінка заповнена: на ній уже {max} елементів, більше на сторінку не вміщується. Спершу видаліть щось або скористайтеся іншою сторінкою.',
'journey.studio.myLayouts': 'Мої макети',
'journey.studio.myLayoutsEmpty':
'Збережіть сторінку, яку ви впорядкували, і розкладайте інші сторінки так само. Їхні фото й тексти залишаться.',
+2
View File
@@ -455,6 +455,8 @@ const journey: TranslationStrings = {
'journey.studio.copyToPage': 'Sao chép để dán vào trang bất kỳ (Ctrl+C)',
'journey.studio.paste': 'Dán (Ctrl+V)',
'journey.studio.pasteEmpty': 'Hãy sao chép thứ gì đó trước, rồi dán vào trang bất kỳ',
'journey.studio.pageFull':
'Trang này đã đầy: đã có {max} thành phần, mức tối đa cho một trang. Hãy xóa bớt trước hoặc dùng trang khác.',
'journey.studio.myLayouts': 'Bố cục của tôi',
'journey.studio.myLayoutsEmpty':
'Lưu một trang bạn đã sắp xếp và dàn các trang khác theo cùng cách. Ảnh và chữ của chúng được giữ nguyên.',
+1
View File
@@ -447,6 +447,7 @@ const journey: TranslationStrings = {
'journey.studio.copyToPage': '複製,可貼到任何頁面 (Ctrl+C)',
'journey.studio.paste': '貼上 (Ctrl+V)',
'journey.studio.pasteEmpty': '請先複製內容,再貼到任何頁面',
'journey.studio.pageFull': '此頁面已滿:已有 {max} 個元素,達到單頁上限。請先刪除一些內容,或使用其他頁面。',
'journey.studio.myLayouts': '我的版面',
'journey.studio.myLayoutsEmpty': '保存你排好的頁面,讓其他頁面套用相同的版面。它們的照片與文字會保留。',
'journey.studio.saveLayout': '將此頁面儲存為版面',
+1
View File
@@ -447,6 +447,7 @@ const journey: TranslationStrings = {
'journey.studio.copyToPage': '复制,可粘贴到任意页面 (Ctrl+C)',
'journey.studio.paste': '粘贴 (Ctrl+V)',
'journey.studio.pasteEmpty': '请先复制内容,然后粘贴到任意页面',
'journey.studio.pageFull': '此页面已满:已有 {max} 个元素,达到单页上限。请先删除一些内容,或使用其他页面。',
'journey.studio.myLayouts': '我的版式',
'journey.studio.myLayoutsEmpty': '保存你排好的页面,让其他页面采用相同的排版。它们的照片和文字保持不变。',
'journey.studio.saveLayout': '将此页面保存为版式',