Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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