Snap-to-grid for moving/resizing canvas objects, plus a per-sheet grid size
Sheet.gridSize (4-200px, default 24, configurable in Settings -> Blatt) now doubles as both the visible dot-grid's spacing and the snap increment while dragging to move any canvas object, or free/viewport- resizing a chart/text/button/fixed-viewport table. An "Automatisch" table's resize is left unsnapped on top of this -- it already discretizes to row/col boundaries by construction. Also fixes the visible grid not actually lining up with real snap positions once zoomed: the dot pattern's background-size now scales with zoom like the objects themselves already did, instead of staying a fixed screen-pixel size regardless of zoom level. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -632,8 +632,8 @@ wichtigen Punkte:
|
|||||||
- Textfeld → Name, Text, Schriftgröße, Fett/Kursiv, Ausrichtung, **Textfarbe**,
|
- Textfeld → Name, Text, Schriftgröße, Fett/Kursiv, Ausrichtung, **Textfarbe**,
|
||||||
**Hintergrund**, Größe, Position.
|
**Hintergrund**, Größe, Position.
|
||||||
- **Blatt**: Name, Zahlenformat, **Hintergrundfarbe**, **Schriftfarbe**,
|
- **Blatt**: Name, Zahlenformat, **Hintergrundfarbe**, **Schriftfarbe**,
|
||||||
**Raster ein-/ausblenden** (`↺` setzt eine Farbe auf den Theme-Wert zurück),
|
**Raster ein-/ausblenden**, **Rasterweite** (`↺` setzt eine Farbe auf den
|
||||||
neues Blatt, Blatt löschen.
|
Theme-Wert zurück), neues Blatt, Blatt löschen.
|
||||||
- **Ansicht**: Zoom-Regler (**pro Blatt** gespeichert).
|
- **Ansicht**: Zoom-Regler (**pro Blatt** gespeichert).
|
||||||
- **Dokument**: Erscheinungsbild (Auto/Hell/Dunkel), Zahlenformat
|
- **Dokument**: Erscheinungsbild (Auto/Hell/Dunkel), Zahlenformat
|
||||||
(Ländereinstellung), CSV-Standard-Trennzeichen, „Auf Beispiel zurücksetzen".
|
(Ländereinstellung), CSV-Standard-Trennzeichen, „Auf Beispiel zurücksetzen".
|
||||||
@@ -646,6 +646,17 @@ das **Punkt-Raster** ein- oder ausblenden. Die Farben überschreiben die
|
|||||||
Theme-Variablen `--bg` / `--text` nur für dieses Blatt; ohne Wert gilt das
|
Theme-Variablen `--bg` / `--text` nur für dieses Blatt; ohne Wert gilt das
|
||||||
Theme. Der Canvas-**Zoom** wird ebenfalls pro Blatt gemerkt.
|
Theme. Der Canvas-**Zoom** wird ebenfalls pro Blatt gemerkt.
|
||||||
|
|
||||||
|
Die **Rasterweite** (`Sheet.gridSize`, 4–200 px, Standard 24) ist ebenfalls
|
||||||
|
pro Blatt einstellbar (Einstellungen → Blatt) und dient **doppelt**: sie
|
||||||
|
bestimmt sowohl den Abstand des sichtbaren Punkt-Rasters (skaliert mit dem
|
||||||
|
Zoom, damit die Punkte immer exakt dort liegen, wo ein Objekt tatsächlich
|
||||||
|
einrastet) als auch die **Schrittweite beim Einrasten** (Snapping), wenn ein
|
||||||
|
Tabellen-, Diagramm-, Text- oder Button-Objekt per Maus **verschoben** oder
|
||||||
|
(bei „Feste Größe"/Diagramm/Text/Button) **frei skaliert** wird. Eine
|
||||||
|
„Automatisch"-Tabelle rastet beim Skalieren stattdessen bereits durch ihre
|
||||||
|
Zeilen-/Spaltengrenzen selbst ein und wird nicht zusätzlich pixelweise
|
||||||
|
gerastert.
|
||||||
|
|
||||||
Alle diese Einstellungen sind Teil des Dokuments (`DocumentState` /
|
Alle diese Einstellungen sind Teil des Dokuments (`DocumentState` /
|
||||||
`Sheet`) und werden damit in **localStorage gespeichert und in `.json`
|
`Sheet`) und werden damit in **localStorage gespeichert und in `.json`
|
||||||
exportiert** – Round-Trip verlustfrei, ungültige Werte werden beim Import
|
exportiert** – Round-Trip verlustfrei, ungültige Werte werden beim Import
|
||||||
|
|||||||
@@ -17,11 +17,14 @@
|
|||||||
* - name is editable via double-click, delete via the × button.
|
* - name is editable via double-click, delete via the × button.
|
||||||
*
|
*
|
||||||
* All pointer deltas are divided by `zoom` so dragging tracks the cursor at any
|
* All pointer deltas are divided by `zoom` so dragging tracks the cursor at any
|
||||||
* zoom level.
|
* zoom level. Moving and free/viewport resizing both snap to the object's own
|
||||||
|
* sheet's grid spacing (`Sheet.gridSize`, "Raster" in the Settings panel) --
|
||||||
|
* an "Automatisch" table's resize instead already snaps to row/col
|
||||||
|
* boundaries by construction, so it isn't additionally pixel-snapped.
|
||||||
*/
|
*/
|
||||||
import { computed, nextTick, ref } from 'vue';
|
import { computed, nextTick, ref } from 'vue';
|
||||||
import type { SheetObject, TableObject } from '../store/types';
|
import type { SheetObject, TableObject } from '../store/types';
|
||||||
import { DEFAULT_ROW_H } from '../store/types';
|
import { DEFAULT_ROW_H, DEFAULT_GRID_SIZE } from '../store/types';
|
||||||
import { useDocument } from '../store/document';
|
import { useDocument } from '../store/document';
|
||||||
import TableGrid from './TableGrid.vue';
|
import TableGrid from './TableGrid.vue';
|
||||||
import ChartView from './ChartView.vue';
|
import ChartView from './ChartView.vue';
|
||||||
@@ -47,6 +50,16 @@ function commitRename() {
|
|||||||
renaming.value = false;
|
renaming.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- move/resize snap-to-grid --------------------------------------
|
||||||
|
/** This object's own sheet's grid spacing -- also the snap increment while
|
||||||
|
* dragging to move or resize it (SettingsPanel's "Raster" field). */
|
||||||
|
const gridSize = computed(() => {
|
||||||
|
const sheetId = store.sheetIdOfObject(props.object.id);
|
||||||
|
const sheet = sheetId ? store.sheetById(sheetId) : undefined;
|
||||||
|
return sheet?.gridSize ?? DEFAULT_GRID_SIZE;
|
||||||
|
});
|
||||||
|
const snap = (v: number) => Math.round(v / gridSize.value) * gridSize.value;
|
||||||
|
|
||||||
// ---- drag to move -------------------------------------------------
|
// ---- drag to move -------------------------------------------------
|
||||||
let drag: { px: number; py: number; ox: number; oy: number } | null = null;
|
let drag: { px: number; py: number; ox: number; oy: number } | null = null;
|
||||||
function onMoveDown(e: MouseEvent) {
|
function onMoveDown(e: MouseEvent) {
|
||||||
@@ -61,7 +74,7 @@ function onMoveMove(e: MouseEvent) {
|
|||||||
if (!drag) return;
|
if (!drag) return;
|
||||||
const dx = (e.clientX - drag.px) / props.zoom;
|
const dx = (e.clientX - drag.px) / props.zoom;
|
||||||
const dy = (e.clientY - drag.py) / props.zoom;
|
const dy = (e.clientY - drag.py) / props.zoom;
|
||||||
store.moveObject(props.object.id, drag.ox + dx, drag.oy + dy);
|
store.moveObject(props.object.id, snap(drag.ox + dx), snap(drag.oy + dy));
|
||||||
}
|
}
|
||||||
function onMoveUp() {
|
function onMoveUp() {
|
||||||
drag = null;
|
drag = null;
|
||||||
@@ -174,14 +187,14 @@ function onResizeMove(e: MouseEvent) {
|
|||||||
const dy = (e.clientY - rs.py) / props.zoom;
|
const dy = (e.clientY - rs.py) / props.zoom;
|
||||||
const o = props.object;
|
const o = props.object;
|
||||||
if (resizeKind.value === 'free') {
|
if (resizeKind.value === 'free') {
|
||||||
if (o.kind === 'chart') store.resizeChart(o.id, rs.w + dx, rs.h + dy);
|
if (o.kind === 'chart') store.resizeChart(o.id, snap(rs.w + dx), snap(rs.h + dy));
|
||||||
else if (o.kind === 'text') store.resizeText(o.id, rs.w + dx, rs.h + dy);
|
else if (o.kind === 'text') store.resizeText(o.id, snap(rs.w + dx), snap(rs.h + dy));
|
||||||
else if (o.kind === 'button') store.resizeButton(o.id, rs.w + dx, rs.h + dy);
|
else if (o.kind === 'button') store.resizeButton(o.id, snap(rs.w + dx), snap(rs.h + dy));
|
||||||
} else if (resizeKind.value === 'viewport') {
|
} else if (resizeKind.value === 'viewport') {
|
||||||
// "Feste Größe": pure pixel-viewport resize, independent of row/col
|
// "Feste Größe": pure pixel-viewport resize, independent of row/col
|
||||||
// count -- cheap (setTableView never rebuilds the workbook), so a live
|
// count -- cheap (setTableView never rebuilds the workbook), so a live
|
||||||
// update on every mousemove is fine, same as it always was.
|
// update on every mousemove is fine, same as it always was.
|
||||||
store.setTableView(o.id, { width: rs.w + dx, height: rs.h + dy });
|
store.setTableView(o.id, { width: snap(rs.w + dx), height: snap(rs.h + dy) });
|
||||||
} else {
|
} else {
|
||||||
// "Automatisch": dragging drives row/col *count* -- but that's a full
|
// "Automatisch": dragging drives row/col *count* -- but that's a full
|
||||||
// syncWorkbook() per call, too expensive for every mousemove, so only a
|
// syncWorkbook() per call, too expensive for every mousemove, so only a
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { useDocument } from '../store/document';
|
|||||||
import { useTheme } from '../composables/useTheme';
|
import { useTheme } from '../composables/useTheme';
|
||||||
import { NUMBER_LOCALES, NUM_FORMATS } from '../store/format';
|
import { NUMBER_LOCALES, NUM_FORMATS } from '../store/format';
|
||||||
import type { CellFormat, HAlign } from '../store/types';
|
import type { CellFormat, HAlign } from '../store/types';
|
||||||
|
import { DEFAULT_GRID_SIZE, MIN_GRID_SIZE, MAX_GRID_SIZE } from '../store/types';
|
||||||
|
|
||||||
const store = useDocument();
|
const store = useDocument();
|
||||||
const { mode: themeMode } = useTheme();
|
const { mode: themeMode } = useTheme();
|
||||||
@@ -461,6 +462,17 @@ function resetDoc() {
|
|||||||
/>
|
/>
|
||||||
<span>Raster einblenden</span>
|
<span>Raster einblenden</span>
|
||||||
</label>
|
</label>
|
||||||
|
<label class="f">
|
||||||
|
<span>Rasterweite ({{ sheet.gridSize ?? DEFAULT_GRID_SIZE }} px)</span>
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
:min="MIN_GRID_SIZE"
|
||||||
|
:max="MAX_GRID_SIZE"
|
||||||
|
:value="sheet.gridSize ?? DEFAULT_GRID_SIZE"
|
||||||
|
title="Steuert auch, worauf Objekte beim Verschieben/Skalieren einrasten"
|
||||||
|
@input="store.setSheetGridSize(sheet.id, num($event))"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
|
||||||
<div class="row-btns">
|
<div class="row-btns">
|
||||||
<button @click="store.addSheet()">Neues Blatt</button>
|
<button @click="store.addSheet()">Neues Blatt</button>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
*/
|
*/
|
||||||
import { computed, ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
import { useDocument } from '../store/document';
|
import { useDocument } from '../store/document';
|
||||||
|
import { DEFAULT_GRID_SIZE } from '../store/types';
|
||||||
import CanvasObject from './CanvasObject.vue';
|
import CanvasObject from './CanvasObject.vue';
|
||||||
import TheLogo from './TheLogo.vue';
|
import TheLogo from './TheLogo.vue';
|
||||||
|
|
||||||
@@ -30,6 +31,16 @@ const appearance = computed(() => {
|
|||||||
return s;
|
return s;
|
||||||
});
|
});
|
||||||
const showGrid = computed(() => sheet.value.showGrid !== false);
|
const showGrid = computed(() => sheet.value.showGrid !== false);
|
||||||
|
/** Also the snap increment CanvasObject.vue moves/resizes against -- see
|
||||||
|
* its own doc comment. */
|
||||||
|
const gridSize = computed(() => sheet.value.gridSize ?? DEFAULT_GRID_SIZE);
|
||||||
|
/** Scaled by zoom (unlike the rest of `.canvas-scroll`, which isn't) so the
|
||||||
|
* visible dots always land exactly where an object will actually snap --
|
||||||
|
* otherwise a zoomed-in/out grid would visually lie about the snap. */
|
||||||
|
const gridStyle = computed(() => {
|
||||||
|
const px = gridSize.value * zoom.value;
|
||||||
|
return { backgroundSize: `${px}px ${px}px` };
|
||||||
|
});
|
||||||
|
|
||||||
/** Unscaled plane size: enclose every object with generous margin.
|
/** Unscaled plane size: enclose every object with generous margin.
|
||||||
* A `fixed`-viewport table only ever occupies its own `view.width`/
|
* A `fixed`-viewport table only ever occupies its own `view.width`/
|
||||||
@@ -92,6 +103,7 @@ function selectObj(id: string) {
|
|||||||
<div
|
<div
|
||||||
class="canvas-scroll"
|
class="canvas-scroll"
|
||||||
:class="{ 'no-grid': !showGrid }"
|
:class="{ 'no-grid': !showGrid }"
|
||||||
|
:style="gridStyle"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
@wheel="onWheel"
|
@wheel="onWheel"
|
||||||
@keydown="onKeydown"
|
@keydown="onKeydown"
|
||||||
@@ -151,11 +163,11 @@ function selectObj(id: string) {
|
|||||||
inset: 0;
|
inset: 0;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
background-color: var(--bg);
|
background-color: var(--bg);
|
||||||
/* grid dots use a fixed neutral so they read on any custom backdrop */
|
/* grid dots use a fixed neutral so they read on any custom backdrop --
|
||||||
|
background-size is set inline (gridStyle) from the sheet's gridSize */
|
||||||
background-image:
|
background-image:
|
||||||
linear-gradient(rgba(128, 128, 128, 0.22) 1px, transparent 1px),
|
linear-gradient(rgba(128, 128, 128, 0.22) 1px, transparent 1px),
|
||||||
linear-gradient(90deg, rgba(128, 128, 128, 0.22) 1px, transparent 1px);
|
linear-gradient(90deg, rgba(128, 128, 128, 0.22) 1px, transparent 1px);
|
||||||
background-size: 24px 24px;
|
|
||||||
background-position: 0 0;
|
background-position: 0 0;
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-1
@@ -24,7 +24,7 @@ import type {
|
|||||||
TableView,
|
TableView,
|
||||||
TextObject,
|
TextObject,
|
||||||
} from '../store/types';
|
} from '../store/types';
|
||||||
import { defaultTableView } from '../store/types';
|
import { defaultTableView, MIN_GRID_SIZE, MAX_GRID_SIZE } from '../store/types';
|
||||||
import {
|
import {
|
||||||
DEFAULT_LOCALE,
|
DEFAULT_LOCALE,
|
||||||
isCellNumFmt,
|
isCellNumFmt,
|
||||||
@@ -144,6 +144,11 @@ function zoomOrUndef(x: unknown): number | undefined {
|
|||||||
? Math.min(2, Math.max(0.4, Math.round(x * 20) / 20))
|
? Math.min(2, Math.max(0.4, Math.round(x * 20) / 20))
|
||||||
: undefined;
|
: undefined;
|
||||||
}
|
}
|
||||||
|
function gridSizeOrUndef(x: unknown): number | undefined {
|
||||||
|
return typeof x === 'number' && Number.isFinite(x)
|
||||||
|
? Math.round(Math.min(MAX_GRID_SIZE, Math.max(MIN_GRID_SIZE, x)))
|
||||||
|
: undefined;
|
||||||
|
}
|
||||||
function clampNum(x: unknown, lo: number, hi: number, fallback: number): number {
|
function clampNum(x: unknown, lo: number, hi: number, fallback: number): number {
|
||||||
return typeof x === 'number' && Number.isFinite(x)
|
return typeof x === 'number' && Number.isFinite(x)
|
||||||
? Math.min(hi, Math.max(lo, Math.round(x)))
|
? Math.min(hi, Math.max(lo, Math.round(x)))
|
||||||
@@ -433,6 +438,7 @@ export function validateDocument(x: unknown): DocumentState {
|
|||||||
background: colorOrUndef(s.background),
|
background: colorOrUndef(s.background),
|
||||||
foreground: colorOrUndef(s.foreground),
|
foreground: colorOrUndef(s.foreground),
|
||||||
showGrid: boolOrUndef(s.showGrid),
|
showGrid: boolOrUndef(s.showGrid),
|
||||||
|
gridSize: gridSizeOrUndef(s.gridSize),
|
||||||
zoom: zoomOrUndef(s.zoom),
|
zoom: zoomOrUndef(s.zoom),
|
||||||
})),
|
})),
|
||||||
activeSheetId: doc.activeSheetId,
|
activeSheetId: doc.activeSheetId,
|
||||||
|
|||||||
@@ -297,6 +297,21 @@ describe('per-sheet appearance, zoom & CSV default (saved in the file)', () => {
|
|||||||
void b;
|
void b;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('grid size (snap increment) is per-sheet, clamped and rounded', () => {
|
||||||
|
const s = fresh();
|
||||||
|
const a = s.doc.sheets[0].id;
|
||||||
|
const b = s.doc.sheets[1].id;
|
||||||
|
expect(s.doc.sheets[0].gridSize).toBeUndefined(); // unset = DEFAULT_GRID_SIZE at use-sites
|
||||||
|
s.setSheetGridSize(a, 40.6);
|
||||||
|
expect(s.doc.sheets[0].gridSize).toBe(41); // rounded
|
||||||
|
expect(s.doc.sheets[1].gridSize).toBeUndefined(); // untouched
|
||||||
|
s.setSheetGridSize(a, 1); // below MIN_GRID_SIZE
|
||||||
|
expect(s.doc.sheets[0].gridSize).toBe(4);
|
||||||
|
s.setSheetGridSize(a, 9999); // above MAX_GRID_SIZE
|
||||||
|
expect(s.doc.sheets[0].gridSize).toBe(200);
|
||||||
|
void b;
|
||||||
|
});
|
||||||
|
|
||||||
it('zoom is stored per sheet; the active-sheet zoom follows the tab', () => {
|
it('zoom is stored per sheet; the active-sheet zoom follows the tab', () => {
|
||||||
const s = fresh();
|
const s = fresh();
|
||||||
expect(s.activeZoom.value).toBe(1);
|
expect(s.activeZoom.value).toBe(1);
|
||||||
@@ -310,26 +325,30 @@ describe('per-sheet appearance, zoom & CSV default (saved in the file)', () => {
|
|||||||
expect(s.activeZoom.value).toBe(1.5); // sheet 1 kept its own
|
expect(s.activeZoom.value).toBe(1.5); // sheet 1 kept its own
|
||||||
});
|
});
|
||||||
|
|
||||||
it('appearance + zoom + csvDelimiter survive a JSON round-trip; bad values dropped', () => {
|
it('appearance + zoom + gridSize + csvDelimiter survive a JSON round-trip; bad values dropped', () => {
|
||||||
const s = fresh();
|
const s = fresh();
|
||||||
const a = s.doc.sheets[0].id;
|
const a = s.doc.sheets[0].id;
|
||||||
s.setSheetBackground(a, '#abcdef');
|
s.setSheetBackground(a, '#abcdef');
|
||||||
s.setSheetForeground(a, '#012');
|
s.setSheetForeground(a, '#012');
|
||||||
s.setSheetShowGrid(a, false);
|
s.setSheetShowGrid(a, false);
|
||||||
s.setSheetZoom(a, 1.25);
|
s.setSheetZoom(a, 1.25);
|
||||||
|
s.setSheetGridSize(a, 32);
|
||||||
s.setCsvDelimiter(';');
|
s.setCsvDelimiter(';');
|
||||||
const before = s.snapshot();
|
const before = s.snapshot();
|
||||||
s.replaceDocument(parseDocument(serializeDocument(before)));
|
s.replaceDocument(parseDocument(serializeDocument(before)));
|
||||||
expect(s.snapshot()).toEqual(before);
|
expect(s.snapshot()).toEqual(before);
|
||||||
expect(s.doc.csvDelimiter).toBe(';');
|
expect(s.doc.csvDelimiter).toBe(';');
|
||||||
|
expect(s.doc.sheets[0].gridSize).toBe(32);
|
||||||
|
|
||||||
const doc = JSON.parse(serializeDocument(s.snapshot()));
|
const doc = JSON.parse(serializeDocument(s.snapshot()));
|
||||||
doc.sheets[0].background = 'not-a-color';
|
doc.sheets[0].background = 'not-a-color';
|
||||||
doc.sheets[0].zoom = 99;
|
doc.sheets[0].zoom = 99;
|
||||||
|
doc.sheets[0].gridSize = 'huge';
|
||||||
doc.csvDelimiter = 'x';
|
doc.csvDelimiter = 'x';
|
||||||
s.replaceDocument(parseDocument(JSON.stringify(doc)));
|
s.replaceDocument(parseDocument(JSON.stringify(doc)));
|
||||||
expect(s.doc.sheets[0].background).toBeUndefined();
|
expect(s.doc.sheets[0].background).toBeUndefined();
|
||||||
expect(s.doc.sheets[0].zoom).toBe(2); // clamped, not dropped
|
expect(s.doc.sheets[0].zoom).toBe(2); // clamped, not dropped
|
||||||
|
expect(s.doc.sheets[0].gridSize).toBeUndefined(); // not a number -> dropped
|
||||||
expect(s.doc.csvDelimiter).toBe(',');
|
expect(s.doc.csvDelimiter).toBe(',');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ import type {
|
|||||||
TableView,
|
TableView,
|
||||||
TextObject,
|
TextObject,
|
||||||
} from './types';
|
} from './types';
|
||||||
import { defaultTableView, DEFAULT_ROW_H } from './types';
|
import { defaultTableView, DEFAULT_ROW_H, MIN_GRID_SIZE, MAX_GRID_SIZE } from './types';
|
||||||
import { createSeedDocument } from '../data/seed';
|
import { createSeedDocument } from '../data/seed';
|
||||||
import { validateDocument } from '../io/json';
|
import { validateDocument } from '../io/json';
|
||||||
|
|
||||||
@@ -855,6 +855,12 @@ function createStore() {
|
|||||||
s.showGrid = show;
|
s.showGrid = show;
|
||||||
markDirty();
|
markDirty();
|
||||||
}
|
}
|
||||||
|
function setSheetGridSize(sheetId: string, size: number) {
|
||||||
|
const s = sheetById(sheetId);
|
||||||
|
if (!s) return;
|
||||||
|
s.gridSize = Math.round(Math.min(MAX_GRID_SIZE, Math.max(MIN_GRID_SIZE, size)));
|
||||||
|
markDirty();
|
||||||
|
}
|
||||||
function setSheetZoom(sheetId: string, z: number) {
|
function setSheetZoom(sheetId: string, z: number) {
|
||||||
const s = sheetById(sheetId);
|
const s = sheetById(sheetId);
|
||||||
if (!s) return;
|
if (!s) return;
|
||||||
@@ -2874,6 +2880,7 @@ function createStore() {
|
|||||||
setSheetBackground,
|
setSheetBackground,
|
||||||
setSheetForeground,
|
setSheetForeground,
|
||||||
setSheetShowGrid,
|
setSheetShowGrid,
|
||||||
|
setSheetGridSize,
|
||||||
setSheetZoom,
|
setSheetZoom,
|
||||||
activeZoom,
|
activeZoom,
|
||||||
setZoom,
|
setZoom,
|
||||||
|
|||||||
@@ -161,10 +161,17 @@ export interface Sheet {
|
|||||||
foreground?: string;
|
foreground?: string;
|
||||||
/** show the canvas dot-grid; unset / true = shown */
|
/** show the canvas dot-grid; unset / true = shown */
|
||||||
showGrid?: boolean;
|
showGrid?: boolean;
|
||||||
|
/** grid spacing in px -- also the snap increment for moving/resizing
|
||||||
|
* canvas objects on this sheet; unset = DEFAULT_GRID_SIZE */
|
||||||
|
gridSize?: number;
|
||||||
/** per-sheet canvas zoom (0.4 – 2); unset = 1 */
|
/** per-sheet canvas zoom (0.4 – 2); unset = 1 */
|
||||||
zoom?: number;
|
zoom?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const DEFAULT_GRID_SIZE = 24;
|
||||||
|
export const MIN_GRID_SIZE = 4;
|
||||||
|
export const MAX_GRID_SIZE = 200;
|
||||||
|
|
||||||
export interface SelectionRange {
|
export interface SelectionRange {
|
||||||
anchor: CellCoord;
|
anchor: CellCoord;
|
||||||
focus: CellCoord;
|
focus: CellCoord;
|
||||||
|
|||||||
Reference in New Issue
Block a user