feat(화면): 화면 배치를 계정에 저장 — PC 를 바꿔도 따라오게
사용자 승인(2026-09-07). 앞서 취향을 localStorage 로 옮겨 **탭·재시작** 문제는 풀었고, 이 변경은 그 위에 **다른 PC 에서도 같은 배치**를 얹음. 사용자가 노트북·데스크톱 두 대를 오감. - `db_management/018_user_ui_prefs.sql` — 사용자당 한 줄, `prefs` JSON 한 칸. 칸을 나누면 취향이 늘 때마다 마이그레이션이 또 필요해 한 칸에 담음. - `GET/PUT /api/dashboard/me/ui-prefs` — 배치 값만. **설계값은 안 담음**(문자열만 받음). - 로그인이 확인된 첫 순간에 한 번 받아 로컬 위에 얹고, 취향이 바뀌면 1.5초 모아 올림. 서버가 없거나 못 읽으면 **로컬 값으로 그대로 돔**(계획서 원문). ⚠ 같은 함정을 또 밟을 뻔했음 — 키만 아는 자리가 저장소를 직접 고르면 **올려보내기도 안 걸림**. 그래서 `storageOf` 를 없애고 **읽기·쓰기 창구 하나**(`readByKey`/`writeByKey`)로 모음. 저장소 선택과 서버 올려보내기가 그 한 곳에만 있음. 그물도 그 이름으로 갱신. **DB 는 아직 적용하지 않았음** — 표가 없으면 API 가 실패하고 화면은 로컬 값으로 도는 것이 정상 동작임. 적용 시점은 다른 창들과 맞춘 뒤. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { writeCrossDesignChoice } from "./B06_Section_Cross_Design_Session";
|
||||
import { CURRENT_PROJECT_ID_KEY, ROUTES } from "@config/config_frontend";
|
||||
import { leaveForDashboard } from "../A00_Common/b_missing_data_guard";
|
||||
import { stateKey, storageOf } from "../A00_Common/b_page_state";
|
||||
import { readByKey, stateKey, writeByKey } from "../A00_Common/b_page_state";
|
||||
import { navigateTo } from "../A00_Common/router";
|
||||
import { createButton, createInputField, showToast } from "@ui/ui_template_elements";
|
||||
import type { StructureInstance, StructureType } from "../B05_Profile/B05_Profile_Api_Structures";
|
||||
@@ -529,7 +529,7 @@ export async function renderB06ProfileCross(root: HTMLElement): Promise<void> {
|
||||
const width = crossHalfWidth();
|
||||
if (!key || width === undefined) return;
|
||||
try {
|
||||
storageOf(key).setItem(key, String(width));
|
||||
writeByKey(key, String(width));
|
||||
} catch {
|
||||
/* 무시 */
|
||||
}
|
||||
@@ -708,7 +708,7 @@ export async function renderB06ProfileCross(root: HTMLElement): Promise<void> {
|
||||
// 세션에 보관된 표시 반폭이 있으면 그것이 우선한다(사용자가 마지막으로 지정한 값).
|
||||
const sessionDisplayKey = displaySessionKey();
|
||||
if (sessionDisplayKey) {
|
||||
const sessionDisplay = Number(storageOf(sessionDisplayKey).getItem(sessionDisplayKey));
|
||||
const sessionDisplay = Number(readByKey(sessionDisplayKey));
|
||||
if (Number.isFinite(sessionDisplay) && sessionDisplay > 0)
|
||||
crossHalfWidthField.input.value = sessionDisplay.toFixed(1);
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ import { flushPendingPipes } from "../B05_Profile/B05_Profile_Api_Pipes_Draft";
|
||||
import { flushPendingStructures } from "../B05_Profile/B05_Profile_Api_Structures";
|
||||
import { flushUphillOverrides } from "../B05_Profile/B05_Profile_Api_Fetch";
|
||||
import { buildCrossPatches, type CrossPatchSources } from "./B06_Section_UI_Page_Patches";
|
||||
import { readState, storageOf } from "../A00_Common/b_page_state";
|
||||
import { readByKey, readState, writeByKey } from "../A00_Common/b_page_state";
|
||||
import {
|
||||
applyStructureAreaRows,
|
||||
STRUCTURE_AREA_KEYS,
|
||||
@@ -92,7 +92,7 @@ export function createRockBoundaryStore(options: {
|
||||
const storageKey = sessionKey();
|
||||
if (!storageKey) return;
|
||||
try {
|
||||
storageOf(storageKey).setItem(storageKey, JSON.stringify(Object.fromEntries(offsets)));
|
||||
writeByKey(storageKey, JSON.stringify(Object.fromEntries(offsets)));
|
||||
} catch {
|
||||
/* 세션 저장 실패는 무시(용량 초과 등) — 값은 메모리에 유지된다. */
|
||||
}
|
||||
@@ -105,7 +105,7 @@ export function createRockBoundaryStore(options: {
|
||||
const storageKey = sessionKey();
|
||||
if (!storageKey) return;
|
||||
try {
|
||||
const raw = storageOf(storageKey).getItem(storageKey);
|
||||
const raw = readByKey(storageKey);
|
||||
if (!raw) return;
|
||||
const parsed = JSON.parse(raw) as Record<string, number>;
|
||||
Object.entries(parsed).forEach(([chainage, offset]) => {
|
||||
|
||||
Reference in New Issue
Block a user