Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/components/Properties/PropertiesPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,11 @@ export function PropertiesPanel() {
<div className="px-3 py-2.5 border-b border-border flex items-center gap-2">
<span className="font-mono text-xs text-accent">▣</span>
<span className="text-xs font-medium text-text">
{selectedIds.length} objects selected
{t.properties.multipleSelectedFmt.replace('{n}', String(selectedIds.length))}
</span>
</div>
<p className="px-3 py-3 text-xs text-muted">
{t.properties.x} / {t.properties.y}: use arrow keys to move
{t.properties.x} / {t.properties.y}: {t.properties.multipleSelectedHint}
</p>
</div>
);
Expand Down
2 changes: 2 additions & 0 deletions src/locales/ar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ const ar = {
x: 'X',
y: 'Y',
comment: 'تعليق',
multipleSelectedFmt: '{n} عناصر مختارة',
multipleSelectedHint: 'استخدم أسهم الاتجاه للتحريك',
},

label: {
Expand Down
2 changes: 2 additions & 0 deletions src/locales/bg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ const bg = {
x: 'X',
y: 'Y',
comment: 'Коментар',
multipleSelectedFmt: 'Избрани обекти: {n}',
multipleSelectedHint: 'със стрелките местиш',
},

label: {
Expand Down
2 changes: 2 additions & 0 deletions src/locales/cs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ const cs = {
x: 'X',
y: 'Y',
comment: 'Komentář',
multipleSelectedFmt: 'Vybráno objektů: {n}',
multipleSelectedHint: 'šipkami posunete',
},

label: {
Expand Down
2 changes: 2 additions & 0 deletions src/locales/da.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ const da = {
x: 'X',
y: 'Y',
comment: 'Kommentar',
multipleSelectedFmt: '{n} objekter valgt',
multipleSelectedHint: 'piletaster flytter',
},

label: {
Expand Down
2 changes: 2 additions & 0 deletions src/locales/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ const de = {
x: 'X',
y: 'Y',
comment: 'Kommentar',
multipleSelectedFmt: '{n} Objekte ausgewählt',
multipleSelectedHint: 'Pfeiltasten zum Verschieben',
},

label: {
Expand Down
2 changes: 2 additions & 0 deletions src/locales/el.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ const el = {
x: 'X',
y: 'Y',
comment: 'Σχόλιο',
multipleSelectedFmt: '{n} αντικείμενα επιλέχθηκαν',
multipleSelectedHint: 'τα βέλη μετακινούν',
},

label: {
Expand Down
2 changes: 2 additions & 0 deletions src/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ const en = {
x: 'X',
y: 'Y',
comment: 'Comment',
multipleSelectedFmt: '{n} objects selected',
multipleSelectedHint: 'use arrow keys to move',
},

label: {
Expand Down
2 changes: 2 additions & 0 deletions src/locales/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ const es = {
x: 'X',
y: 'Y',
comment: 'Comentario',
multipleSelectedFmt: '{n} objetos seleccionados',
multipleSelectedHint: 'flechas para mover',
},

label: {
Expand Down
2 changes: 2 additions & 0 deletions src/locales/et.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ const et = {
x: 'X',
y: 'Y',
comment: 'Kommentaar',
multipleSelectedFmt: '{n} objekti valitud',
multipleSelectedHint: 'nooltega liigutad',
},

label: {
Expand Down
2 changes: 2 additions & 0 deletions src/locales/fa.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ const fa = {
x: 'X',
y: 'Y',
comment: 'توضیح',
multipleSelectedFmt: '{n} مورد انتخاب شده',
multipleSelectedHint: 'با کلیدهای جهت‌دار جابه‌جا کنید',
},

label: {
Expand Down
2 changes: 2 additions & 0 deletions src/locales/fi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ const fi = {
x: 'X',
y: 'Y',
comment: 'Kommentti',
multipleSelectedFmt: '{n} objektia valittu',
multipleSelectedHint: 'nuolinäppäimillä siirrät',
},

label: {
Expand Down
2 changes: 2 additions & 0 deletions src/locales/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ const fr = {
x: 'X',
y: 'Y',
comment: 'Commentaire',
multipleSelectedFmt: '{n} objets sélectionnés',
multipleSelectedHint: 'flèches pour déplacer',
},

label: {
Expand Down
2 changes: 2 additions & 0 deletions src/locales/he.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ const he = {
x: 'X',
y: 'Y',
comment: 'הערה',
multipleSelectedFmt: '{n} פריטים נבחרו',
multipleSelectedHint: 'מקשי החצים מזיזים',
},

label: {
Expand Down
2 changes: 2 additions & 0 deletions src/locales/hr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ const hr = {
x: 'X',
y: 'Y',
comment: 'Komentar',
multipleSelectedFmt: 'Odabrano objekata: {n}',
multipleSelectedHint: 'strelicama pomičeš',
},

label: {
Expand Down
2 changes: 2 additions & 0 deletions src/locales/hu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ const hu = {
x: 'X',
y: 'Y',
comment: 'Megjegyzés',
multipleSelectedFmt: '{n} objektum kijelölve',
multipleSelectedHint: 'nyilakkal mozgasd',
},

label: {
Expand Down
2 changes: 2 additions & 0 deletions src/locales/it.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ const it = {
x: 'X',
y: 'Y',
comment: 'Commento',
multipleSelectedFmt: '{n} oggetti selezionati',
multipleSelectedHint: 'frecce per spostare',
},

label: {
Expand Down
2 changes: 2 additions & 0 deletions src/locales/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ const ja = {
x: 'X',
y: 'Y',
comment: 'コメント',
multipleSelectedFmt: '{n} 個のオブジェクトが選択されました',
multipleSelectedHint: '矢印キーで移動',
},

label: {
Expand Down
2 changes: 2 additions & 0 deletions src/locales/ko.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ const ko = {
x: 'X',
y: 'Y',
comment: '설명',
multipleSelectedFmt: '{n}개 항목 선택됨',
multipleSelectedHint: '화살표 키로 이동',
},

label: {
Expand Down
2 changes: 2 additions & 0 deletions src/locales/lt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ const lt = {
x: 'X',
y: 'Y',
comment: 'Komentaras',
multipleSelectedFmt: 'Pasirinkta objektų: {n}',
multipleSelectedHint: 'rodyklėmis perkeli',
},

label: {
Expand Down
2 changes: 2 additions & 0 deletions src/locales/lv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ const lv = {
x: 'X',
y: 'Y',
comment: 'Komentārs',
multipleSelectedFmt: 'Atlasīti {n} objekti',
multipleSelectedHint: 'ar bultiņām pārvietot',
},

label: {
Expand Down
2 changes: 2 additions & 0 deletions src/locales/nl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ const nl = {
x: 'X',
y: 'Y',
comment: 'Opmerking',
multipleSelectedFmt: '{n} objecten geselecteerd',
multipleSelectedHint: 'pijltoetsen om te verplaatsen',
},

label: {
Expand Down
2 changes: 2 additions & 0 deletions src/locales/no.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ const no = {
x: 'X',
y: 'Y',
comment: 'Kommentar',
multipleSelectedFmt: '{n} objekter valgt',
multipleSelectedHint: 'piltaster flytter',
},

label: {
Expand Down
2 changes: 2 additions & 0 deletions src/locales/pl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ const pl = {
x: 'X',
y: 'Y',
comment: 'Komentarz',
multipleSelectedFmt: 'Wybrano obiektów: {n}',
multipleSelectedHint: 'strzałki przesuwają',
},

label: {
Expand Down
2 changes: 2 additions & 0 deletions src/locales/pt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ const pt = {
x: 'X',
y: 'Y',
comment: 'Comentário',
multipleSelectedFmt: '{n} objetos selecionados',
multipleSelectedHint: 'setas para mover',
},

label: {
Expand Down
2 changes: 2 additions & 0 deletions src/locales/ro.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ const ro = {
x: 'X',
y: 'Y',
comment: 'Comentariu',
multipleSelectedFmt: '{n} obiecte selectate',
multipleSelectedHint: 'săgeți pentru mutare',
},

label: {
Expand Down
2 changes: 2 additions & 0 deletions src/locales/sk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ const sk = {
x: 'X',
y: 'Y',
comment: 'Komentár',
multipleSelectedFmt: 'Vybraných objektov: {n}',
multipleSelectedHint: 'šípkami posuniete',
},

label: {
Expand Down
2 changes: 2 additions & 0 deletions src/locales/sl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ const sl = {
x: 'X',
y: 'Y',
comment: 'Komentar',
multipleSelectedFmt: 'Izbranih objektov: {n}',
multipleSelectedHint: 's puščicami premikaš',
},

label: {
Expand Down
2 changes: 2 additions & 0 deletions src/locales/sr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ const sr = {
x: 'X',
y: 'Y',
comment: 'Коментар',
multipleSelectedFmt: 'Изабрано објеката: {n}',
multipleSelectedHint: 'стрелицама померај',
},

label: {
Expand Down
2 changes: 2 additions & 0 deletions src/locales/sv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ const sv = {
x: 'X',
y: 'Y',
comment: 'Kommentar',
multipleSelectedFmt: '{n} objekt markerade',
multipleSelectedHint: 'pilar för att flytta',
},

label: {
Expand Down
2 changes: 2 additions & 0 deletions src/locales/tr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ const tr = {
x: 'X',
y: 'Y',
comment: 'Yorum',
multipleSelectedFmt: '{n} nesne seçildi',
multipleSelectedHint: 'oklarla taşı',
},

label: {
Expand Down
2 changes: 2 additions & 0 deletions src/locales/zh-hans.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ const zhHans = {
x: 'X',
y: 'Y',
comment: '备注',
multipleSelectedFmt: '已选择 {n} 个对象',
multipleSelectedHint: '方向键移动',
},

label: {
Expand Down
2 changes: 2 additions & 0 deletions src/locales/zh-hant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ const zhHant = {
x: 'X',
y: 'Y',
comment: '備註',
multipleSelectedFmt: '已選擇 {n} 個物件',
multipleSelectedHint: '方向鍵移動',
},

label: {
Expand Down
38 changes: 37 additions & 1 deletion src/store/labelStore.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ function reset() {
selectedIds: [],
clipboard: [],
pasteCount: 0,
duplicateCount: 0,
canvasSettings: {
showGrid: false,
snapEnabled: false,
Expand Down Expand Up @@ -138,6 +137,43 @@ describe('duplicateObject', () => {
});
});

// ── duplicateSelectedObjects ──────────────────────────────────────────────────

describe('duplicateSelectedObjects', () => {
it('staggers consecutive duplicates linearly (+20 from current selection)', () => {
state().addObject('text', { x: 100, y: 100 });
state().selectObject(defined(objs()[0]).id);

state().duplicateSelectedObjects();
state().duplicateSelectedObjects();
state().duplicateSelectedObjects();

expect(objs()).toHaveLength(4);
// Selection follows the new copy each time, so the offsets compound
// linearly: 100, 120, 140, 160 — never quadratic.
expect(objs().map((o) => o.x)).toEqual([100, 120, 140, 160]);
expect(objs().map((o) => o.y)).toEqual([100, 120, 140, 160]);
});

it('selects only the new copies', () => {
state().addObject('text');
state().addObject('text');
state().selectObjects(objs().map((o) => o.id));

state().duplicateSelectedObjects();

expect(state().selectedIds).toHaveLength(2);
expect(state().selectedIds).toEqual([objs()[2]!.id, objs()[3]!.id]);
});

it('is a no-op when nothing is selected', () => {
state().addObject('text');
state().selectObject(null);
state().duplicateSelectedObjects();
expect(objs()).toHaveLength(1);
});
});

// ── copy / paste ──────────────────────────────────────────────────────────────

describe('copy / paste', () => {
Expand Down
Loading