Skip to content
Open
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
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -683,10 +683,14 @@ This is the list of events that can be subscribed to with this update. You can s

## 🧑‍💻 Development

### Playground
The local playground lives in `playground/` and is served via Vite. It is excluded from the published package (only `dist/` is shipped).

### Setting up the development environment
```sh
npm install # Install dependencies
npm run dev # Start development server
npm run dev # Start playground server
npm run playground # Start playground server (alias)
npm run build # Build the library
npm run lint:fix # Fix code formatting
```
Expand Down Expand Up @@ -728,4 +732,4 @@ The file `src/utils/zod-deep-strict-partial.js` contains code originally license

## Fira Code
This project incorporates the [Fira Code](https://github.com/tonsky/FiraCode) font to enhance code readability.
Fira Code is distributed under the [SIL Open Font License, Version 1.1](https://scripts.sil.org/OFL), and a copy of the license is provided in [OFL-1.1.txt](./src/assets/fonts/OFL-1.1.txt).
Fira Code is distributed under the [SIL Open Font License, Version 1.1](https://scripts.sil.org/OFL), and a copy of the license is provided in [OFL-1.1.txt](./src/assets/fonts/OFL-1.1.txt).
8 changes: 6 additions & 2 deletions README_KR.md
Original file line number Diff line number Diff line change
Expand Up @@ -692,11 +692,15 @@ undoRedoManager.redo();

## 🧑‍💻 개발

### Playground
로컬 playground는 `playground/`에 있으며 Vite로 실행됩니다. 배포 패키지에는 포함되지 않습니다 (`dist/`만 배포).

### 개발 환경 세팅

```sh
npm install # 의존성 설치
npm run dev # 개발 서버 시작
npm run dev # playground 서버 시작
npm run playground # playground 서버 시작 (alias)
npm run build # 라이브러리 빌드
npm run lint:fix # 코드 포맷팅 수정
```
Expand Down Expand Up @@ -740,4 +744,4 @@ npm run lint:fix # 코드 포맷팅 수정

## Fira Code
이 프로젝트는 캔버스 상에서 문자 가독성을 높이기 위해 [Fira Code](https://github.com/tonsky/FiraCode) 폰트를 사용합니다.
Fira Code는 [SIL Open Font License, Version 1.1](https://scripts.sil.org/OFL) 하에 배포되며, 라이선스 사본은 [OFL-1.1.txt](./src/assets/fonts/OFL-1.1.txt)에 제공됩니다.
Fira Code는 [SIL Open Font License, Version 1.1](https://scripts.sil.org/OFL) 하에 배포되며, 라이선스 사본은 [OFL-1.1.txt](./src/assets/fonts/OFL-1.1.txt)에 제공됩니다.
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,12 @@
"umd": "dist/index.umd.js",
"types": "dist/types/src/patchmap.d.ts",
"scripts": {
"dev": "vite --config playground/vite.config.js",
"build": "tsc && rollup -c",
"format": "biome format",
"lint": "biome check --staged",
"lint:fix": "biome check --staged --write",
"playground": "vite --config playground/vite.config.js",
"test:unit": "vitest --project unit",
"test:browser": "vitest --browser=chromium",
"test:headless": "vitest --browser.headless",
Expand Down Expand Up @@ -72,6 +74,7 @@
"rollup-plugin-copy": "^3.5.0",
"standard-version": "^9.5.0",
"typescript": "^5.9.3",
"vite": "^5.4.10",
"vitest": "^4.0.8",
"lint-staged": "^15.2.10"
},
Expand All @@ -80,4 +83,4 @@
"biome check --write --no-errors-on-unmatched"
]
}
}
}
179 changes: 179 additions & 0 deletions playground/data-editor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
import { createAddPopover } from './data-editor/add-popover.js';
import {
colorPresets,
colorPresetValues,
componentTypes,
} from './data-editor/constants.js';
import { createInspector } from './data-editor/inspector.js';
import { createTree } from './data-editor/tree.js';
import {
buildChangesFromPath,
coerceValue,
formatError,
formatPxPercent,
resolveNodeSchema,
setNodeValue,
validateNode,
} from './data-editor/utils.js';

export const createDataEditor = ({ patchmap, elements, setLastAction }) => {
const state = {
currentData: [],
nodeIndex: new Map(),
treeItemById: new Map(),
selectedNodeId: null,
};

let tree;
let inspector;
let addPopover;

const setEditorValue = (data) => {
elements.editor.value = JSON.stringify(data, null, 2);
};

const parseEditorValue = () => {
try {
return JSON.parse(elements.editor.value);
} catch (error) {
setEditorError(`JSON parse error: ${error.message}`);
return null;
}
};

const setEditorError = (message) => {
elements.editorError.textContent = message;
};

const clearEditorError = () => {
elements.editorError.textContent = '';
};

const setDataMode = (mode) => {
const isJson = mode === 'json';
elements.dataJsonView.hidden = !isJson;
elements.dataFormView.hidden = isJson;
elements.dataModeJson.classList.toggle('is-active', isJson);
elements.dataModeForm.classList.toggle('is-active', !isJson);
elements.dataModeJson.setAttribute('aria-selected', String(isJson));
elements.dataModeForm.setAttribute('aria-selected', String(!isJson));
if (isJson) {
addPopover.closeAddPopover();
}
if (!isJson) {
tree.renderTree();
inspector.renderInspector(state.selectedNodeId);
}
};

const setCurrentData = (data, { updateEditor = true } = {}) => {
state.currentData = data;
if (updateEditor) {
setEditorValue(data);
}
tree.renderTree();
inspector.renderInspector(state.selectedNodeId);
};

const updateSelection = (target, fallbackId = null) => {
const id = target?.id ?? fallbackId ?? null;
state.selectedNodeId = id;
elements.selectedId.textContent = id ?? 'None';
if (patchmap.transformer) {
patchmap.transformer.elements = target ? [target] : [];
}
tree.highlightTree(id);
inspector.renderInspector(id);
addPopover.updateAddParentOptions();
};

const getSelectedNodeId = () => state.selectedNodeId;

const applyEditorData = () => {
const data = parseEditorValue();
if (!data) return;
if (!Array.isArray(data)) {
setEditorError('Root must be an array of elements.');
return;
}

try {
patchmap.draw(data);
patchmap.fit();
setCurrentData(data, { updateEditor: false });
updateSelection(null);
clearEditorError();
setLastAction('Applied editor data');
} catch (error) {
setEditorError(formatError(error));
}
};

const prettifyEditor = () => {
const data = parseEditorValue();
if (!data) return;
setEditorValue(data);
clearEditorError();
setLastAction('Prettified editor');
};

const selectNodeById = (id) => {
if (!id) return;
const target = patchmap.selector(`$..[?(@.id=="${id}")]`)[0];
updateSelection(target, id);
};

tree = createTree({
elements,
state,
updateAddParentOptions: () => addPopover?.updateAddParentOptions?.(),
});

inspector = createInspector({
patchmap,
elements,
state,
componentTypes,
colorPresets,
colorPresetValues,
resolveNodeSchema,
validateNode,
formatPxPercent,
coerceValue,
setNodeValue,
buildChangesFromPath,
setEditorValue,
setEditorError,
clearEditorError,
setLastAction,
});

addPopover = createAddPopover({
patchmap,
elements,
state,
validateNode,
setCurrentData,
selectNodeById,
updateSelection,
setEditorError,
clearEditorError,
setLastAction,
});

return {
setDataMode,
setCurrentData,
updateSelection,
getSelectedNodeId,
applyEditorData,
prettifyEditor,
selectNodeById,
openAddPopover: (...args) => addPopover.openAddPopover(...args),
closeAddPopover: () => addPopover.closeAddPopover(),
updateAddTypeOptions: () => addPopover.updateAddTypeOptions(),
handleAddElement: () => addPopover.handleAddElement(),
deleteNodeById: (...args) => addPopover.deleteNodeById(...args),
clearEditorError,
};
};
Loading