Merge remote-tracking branch 'origin/main_desktop_1' into sub_laptop_8
This commit is contained in:
@@ -171,7 +171,7 @@ export function createSpreadsheet(
|
||||
}
|
||||
|
||||
grid.render();
|
||||
select(ctx.selection);
|
||||
select(ctx.selection, false, true);
|
||||
loadLinks();
|
||||
if (opts.conditions && !readOnly)
|
||||
void import("./spreadsheet_condition").then((m) => {
|
||||
@@ -221,7 +221,7 @@ export function createSpreadsheet(
|
||||
if (command.종류 === "칸") grid.invalidate([...again, ...touched(command.시트, command.칸)]);
|
||||
else grid.render();
|
||||
}
|
||||
select(ctx.selection);
|
||||
select(ctx.selection, false, true); // 명령이 바꾼 뒤 — 알약 점 등 문서 종속 부품도 다시(81-2)
|
||||
opts.onChange?.(structuredClone(ctx.book));
|
||||
}
|
||||
|
||||
@@ -235,7 +235,7 @@ export function createSpreadsheet(
|
||||
return out;
|
||||
}
|
||||
|
||||
function select(selection: Selection, reveal = false): void {
|
||||
function select(selection: Selection, reveal = false, dataChanged = false): void {
|
||||
ctx.selection = selection;
|
||||
memory.set(selection.시트, selection);
|
||||
grid.renderSelection();
|
||||
@@ -244,7 +244,7 @@ export function createSpreadsheet(
|
||||
grid.reveal(far.r, far.c);
|
||||
}
|
||||
ed.sync();
|
||||
for (const p of others) p.refresh();
|
||||
for (const p of others) p.refresh(dataChanged);
|
||||
const g = selection.범위[0];
|
||||
opts.onSelect?.({
|
||||
시트: selection.시트,
|
||||
@@ -261,7 +261,7 @@ export function createSpreadsheet(
|
||||
ctx.book.활성 = sheetId;
|
||||
ctx.selection = memory.get(sheetId) ?? selectCell(sheet, 0, 0);
|
||||
grid.render();
|
||||
select(ctx.selection, true);
|
||||
select(ctx.selection, true, true);
|
||||
ed.focus();
|
||||
}
|
||||
|
||||
@@ -275,13 +275,13 @@ export function createSpreadsheet(
|
||||
memory.clear();
|
||||
ctx.selection = selectCell(firstSheet(next), 0, 0);
|
||||
grid.render();
|
||||
select(ctx.selection);
|
||||
select(ctx.selection, false, true);
|
||||
loadLinks();
|
||||
},
|
||||
recalc() {
|
||||
ctx.engine.rebuild(ctx.book);
|
||||
grid.render();
|
||||
for (const p of others) p.refresh();
|
||||
for (const p of others) p.refresh(true);
|
||||
return ctx.engine.snapshot();
|
||||
},
|
||||
getLinkedValues() {
|
||||
|
||||
@@ -358,7 +358,9 @@ export function mountLinks(
|
||||
bar.querySelector(".is-here")?.scrollIntoView?.({ block: "nearest", inline: "nearest" });
|
||||
}
|
||||
|
||||
/** 이은 칸 — 알약 색 옅은 배경 + 왼아래 작은 세모 · 칸이 든 판 층에 얹어 스크롤을 따라감 */
|
||||
/** 이은 칸 — 알약 색 옅은 배경 + 왼아래 작은 세모 · 칸이 든 판 층에 얹어 스크롤을 따라감
|
||||
* 읽기(slot — getBoundingClientRect) 를 칸마다 먼저 다 끝내고 쓰기(append)는 나중에 — 안 그러면
|
||||
* 읽기·쓰기가 번갈아 와서 매 칸마다 강제 레이아웃이 남(81-2②). */
|
||||
function placeDots(): void {
|
||||
for (const d of dots) d.remove();
|
||||
dots = [];
|
||||
@@ -368,11 +370,37 @@ export function mountLinks(
|
||||
// 칸마다 이은 알약(이은 차례) — 색 = 먼저 이은 알약 · 둘 넘으면 수
|
||||
const byCell = new Map<string, CellLink[]>();
|
||||
for (const l of ctx.sheet().연결 ?? []) byCell.set(l.칸, [...(byCell.get(l.칸) ?? []), l]);
|
||||
|
||||
// 1) 읽기 — 칸마다 자리만 먼저 모음(DOM 변경 없음)
|
||||
const cellSlots: Array<{
|
||||
list: CellLink[];
|
||||
layer: HTMLElement;
|
||||
box: ReturnType<typeof slot>["box"];
|
||||
}> = [];
|
||||
for (const [a1, list] of byCell) {
|
||||
const p = parseA1(a1);
|
||||
if (!p) continue;
|
||||
const { layer, box } = slot(ctx, p.r, p.c);
|
||||
if (!box.w || !box.h) continue; // 숨긴 행열
|
||||
cellSlots.push({ list, layer, box });
|
||||
}
|
||||
const rangeSlots: Array<{
|
||||
mark: { 시트: string; 범위: string; 이름: string; 설명?: string };
|
||||
layer: HTMLElement;
|
||||
box: ReturnType<typeof slot>["box"];
|
||||
end: ReturnType<typeof slot>["box"];
|
||||
}> = [];
|
||||
for (const mark of opts?.표시범위 ?? []) {
|
||||
const g = mark.시트 === ctx.sheet().id ? parseRange(mark.범위) : null;
|
||||
if (!g) continue;
|
||||
const { layer, box } = slot(ctx, g.r0, g.c0);
|
||||
const end = slot(ctx, g.r1, g.c1).box;
|
||||
if (!box.w || !box.h) continue;
|
||||
rangeSlots.push({ mark, layer, box, end });
|
||||
}
|
||||
|
||||
// 2) 쓰기 — 모은 자리에 한 번에 그림
|
||||
for (const { list, layer, box } of cellSlots) {
|
||||
const first = list[0];
|
||||
const tint = el("div", { className: "ss-link-cell", attrs: { "data-key": first.키 } });
|
||||
tint.style.cssText = `left:${box.x}px;top:${box.y}px;width:${box.w}px;height:${box.h}px`;
|
||||
@@ -392,12 +420,7 @@ export function mountLinks(
|
||||
dots.push(...marks);
|
||||
}
|
||||
// 보낸 범위(도면 표 등) — 채움 없는 점선 테두리 + 왼위 이름표(알약 색 채움과 안 겹침)
|
||||
for (const mark of opts?.표시범위 ?? []) {
|
||||
const g = mark.시트 === ctx.sheet().id ? parseRange(mark.범위) : null;
|
||||
if (!g) continue;
|
||||
const { layer, box } = slot(ctx, g.r0, g.c0);
|
||||
const end = slot(ctx, g.r1, g.c1).box;
|
||||
if (!box.w || !box.h) continue;
|
||||
for (const { mark, layer, box, end } of rangeSlots) {
|
||||
const frame = el("div", {
|
||||
className: "ss-link-range",
|
||||
attrs: { "data-range": mark.범위 },
|
||||
@@ -542,12 +565,17 @@ export function mountLinks(
|
||||
ctx.grid.root.addEventListener("drop", onDropEvent);
|
||||
ctx.grid.root.addEventListener("mousemove", onHover);
|
||||
|
||||
let dotsReady = false;
|
||||
return {
|
||||
root: opts ? bar : null,
|
||||
apply: applyLinked,
|
||||
refresh() {
|
||||
// dataChanged 거짓(고름만 바뀜) 이면 점은 건너뜀 — 점은 선택에 안 매임(81-2②)
|
||||
refresh(dataChanged = false) {
|
||||
renderBar();
|
||||
placeDots();
|
||||
if (dataChanged || !dotsReady) {
|
||||
placeDots();
|
||||
dotsReady = true;
|
||||
}
|
||||
},
|
||||
destroy() {
|
||||
ctx.grid.root.removeEventListener("aislo-grid-zoom", onZoom);
|
||||
|
||||
@@ -112,7 +112,7 @@ export function attachMenu(ctx: SpreadsheetContext): PartHandle {
|
||||
}
|
||||
|
||||
function rowMenuItems(rows: number[]): MapContextMenuItem[] {
|
||||
const at = Math.min(...rows);
|
||||
const at = rows[0]; // rows 는 늘 오름차순 연속(81-2③) — 전체 고름이면 104만 개라 Math.min(...rows) 는 스택 넘침
|
||||
const 수 = rows.length;
|
||||
return [
|
||||
[st("MenuCut"), () => cutCopy("cut")],
|
||||
@@ -152,7 +152,7 @@ export function attachMenu(ctx: SpreadsheetContext): PartHandle {
|
||||
}
|
||||
|
||||
function colMenuItems(cols: number[]): MapContextMenuItem[] {
|
||||
const at = Math.min(...cols);
|
||||
const at = cols[0]; // cols 도 늘 오름차순 연속 — 같은 까닭(81-2③)
|
||||
const 수 = cols.length;
|
||||
return [
|
||||
[st("MenuCut"), () => cutCopy("cut")],
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
import { el } from "@ui/ui_template_elements";
|
||||
import { createMapContextMenu, type MapContextMenuItem } from "@ui/ui_template_context_menu";
|
||||
import { isFrac, fracToDouble } from "./spreadsheet_functions_math";
|
||||
import { computeExtent } from "./spreadsheet_grid_cells";
|
||||
import type { CellRange, Scalar } from "./spreadsheet_types";
|
||||
import type { PartHandle, SpreadsheetContext } from "./spreadsheet_view_types";
|
||||
|
||||
@@ -31,13 +32,16 @@ export interface SelectionStats {
|
||||
/** 고른 범위(여럿) 안 칸 값을 모아 통계를 냄 — 엔진 값 기준(식 칸도 풀린 값). */
|
||||
export function computeSelectionStats(ctx: SpreadsheetContext): SelectionStats {
|
||||
const sheet = ctx.selection.시트;
|
||||
const extent = computeExtent(ctx.sheet()); // 쓴 범위까지만 셈(81-2 — 열 머리 전체 고름이면 104만 칸을 다 돎)
|
||||
const nums: number[] = [];
|
||||
let 개수 = 0;
|
||||
const seen = new Set<number>();
|
||||
|
||||
const visit = (range: CellRange): void => {
|
||||
for (let r = range.r0; r <= range.r1; r++) {
|
||||
for (let c = range.c0; c <= range.c1; c++) {
|
||||
const r1 = Math.min(range.r1, extent.rows - 1);
|
||||
const c1 = Math.min(range.c1, extent.cols - 1);
|
||||
for (let r = range.r0; r <= r1; r++) {
|
||||
for (let c = range.c0; c <= c1; c++) {
|
||||
const key = r * 1_000_000 + c;
|
||||
if (seen.has(key)) continue;
|
||||
seen.add(key);
|
||||
|
||||
@@ -119,6 +119,7 @@ export interface SpreadsheetContext {
|
||||
/** E 부품 — D 가 `mountToolbar(ctx)` 등으로 붙이고 고름 · 문서가 바뀌면 `refresh()`. */
|
||||
export interface PartHandle {
|
||||
root: HTMLElement | null;
|
||||
refresh(): void;
|
||||
/** `dataChanged` — 문서가 실제로 바뀐 다시 그림(참) vs 고름만 바뀐 다시 그림(거짓 · 81-2 알약 점 건너뜀용) */
|
||||
refresh(dataChanged?: boolean): void;
|
||||
destroy(): void;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
/* =============================================================================
|
||||
* test_stage4_menu_perf.ts — 우클릭 속도(81-2③) 재현 시험. 돌리기: npx tsx <이 파일>.
|
||||
* `spreadsheet_menu.ts` rowMenuItems/colMenuItems 의 `at` 계산 — 옛 Math.min(...rows) 는
|
||||
* 전체 고름(열 머리 104만 개) 배열을 스프레드해 콜스택을 넘침(RangeError) · 고친 `rows[0]`
|
||||
* (rows 는 늘 오름차순 연속이라 첫 자리가 최소)는 크기와 무관하게 안전·상수 시간.
|
||||
* `computeSelectionStats` 쪽(①)은 createMapContextMenu 가 끌어오는 css 임포트 때문에
|
||||
* 이 node 시험 환경에서 직접 불러오지 못함 — ORCA 화면 검증으로 수치 확인(81-2 보고 참고).
|
||||
* ========================================================================== */
|
||||
|
||||
import { MAX_COLS, MAX_ROWS } from "../../../A00_Common/spreadsheet/spreadsheet_types.ts";
|
||||
import { assertEqual } from "./test_stage2_helpers.ts";
|
||||
|
||||
const bigRows = Array.from({ length: MAX_ROWS }, (_, i) => i); // 열 머리 전체 고름 재현
|
||||
|
||||
let oldThrew = false;
|
||||
try {
|
||||
Math.min(...bigRows); // 옛 코드 — 이 크기에선 RangeError(콜스택 넘침)
|
||||
} catch {
|
||||
oldThrew = true;
|
||||
}
|
||||
if (!oldThrew) {
|
||||
console.error(
|
||||
"FAIL 옛 Math.min(...rows) 재현 실패 — 이 Node 가 다른 콜스택 한도를 씀(시험 전제 깨짐)",
|
||||
);
|
||||
process.exitCode = 1;
|
||||
} else {
|
||||
console.log("ok 옛 Math.min(...rows) 는 104만 개에서 콜스택을 넘침(고치기 전 재현)");
|
||||
}
|
||||
|
||||
const t0 = performance.now();
|
||||
const at = bigRows[0]; // 고친 코드(spreadsheet_menu.ts rowMenuItems)
|
||||
const ms = performance.now() - t0;
|
||||
assertEqual(at, 0, "rows[0] = 최소값(오름차순 연속)");
|
||||
if (ms >= 5) {
|
||||
console.error(`FAIL rows[0] 은 5ms 안(상수 시간) — 실측 ${ms.toFixed(2)}ms`);
|
||||
process.exitCode = 1;
|
||||
} else {
|
||||
console.log(`ok rows[0] ${ms.toFixed(3)}ms(크기 무관 상수 시간)`);
|
||||
}
|
||||
|
||||
// 열 머리(MAX_COLS) 도 같은 모양
|
||||
const bigCols = Array.from({ length: MAX_COLS }, (_, i) => i);
|
||||
assertEqual(bigCols[0], 0, "cols[0] = 최소값");
|
||||
|
||||
if (process.exitCode) process.exit(process.exitCode);
|
||||
console.log("spreadsheet_menu 우클릭 속도(81-2③) 시험 끝");
|
||||
Reference in New Issue
Block a user