Skip to Content

UI API

dmn.ui를 통해 앱의 UI를 확장할 수 있습니다.

UI API는 메인 윈도우에서만 사용 가능합니다. 오버레이에서 호출하면 경고만 표시되고 동작하지 않습니다.

컨텍스트 메뉴

키 메뉴 (addKeyMenuItem)

키를 우클릭할 때 표시되는 메뉴에 항목을 추가합니다.

const menuId = dmn.ui.contextMenu.addKeyMenuItem({ id: "copy-keycode", label: "키 코드 복사", onClick: (context) => { navigator.clipboard.writeText(context.keyCode); }, });

Context 객체:

속성타입설명
keyCodestring키 코드 (예: “KeyD”)
indexnumber키 인덱스
positionKeyPosition키 위치 정보
modestring현재 키 모드

그리드 메뉴 (addGridMenuItem)

그리드 빈 공간을 우클릭할 때 표시되는 메뉴에 항목을 추가합니다.

dmn.ui.contextMenu.addGridMenuItem({ id: "add-timer", label: "타이머 추가", onClick: (context) => { createTimer(context.position); }, });

Context 객체:

속성타입설명
position{ dx, dy }클릭 위치 (그리드 좌표)
modestring현재 키 모드

메뉴 항목 옵션

{ id: "my-menu", // 플러그인 내 고유 ID label: "메뉴 항목", // 표시 텍스트 position: "bottom", // "top" | "bottom" (기본: bottom) // 조건부 표시 visible: (context) => context.mode === "4key", // 조건부 비활성화 disabled: (context) => context.position.count === 0, // 클릭 핸들러 onClick: async (context) => { // ... }, }

메뉴 관리

// 메뉴 업데이트 dmn.ui.contextMenu.updateMenuItem(menuId, { label: "새로운 라벨", disabled: true, }); // 특정 메뉴 제거 dmn.ui.contextMenu.removeMenuItem(menuId); // 이 플러그인의 모든 메뉴 제거 dmn.ui.contextMenu.clearMyMenuItems();

Display Element

displayElement는 저수준(Primitive) API로, 직접적인 사용을 권장하지 않습니다. 그리드와 오버레이에 커스텀 UI 요소를 추가하려면 선언형 방식인 defineElement 사용을 강력히 권장합니다.

그리드와 오버레이에 커스텀 UI 요소를 추가합니다.

기본 사용법

const panel = dmn.ui.displayElement.add({ html: `<div style="background: #000; color: #fff; padding: 16px;"> Hello! </div>`, position: { x: 100, y: 100 }, draggable: true, }); // 제거 panel.remove();

상태 기반 템플릿

const panel = dmn.ui.displayElement.add({ position: { x: 100, y: 100 }, state: { value: 0, history: [] }, template: (state, { html }) => html` <div style="background: #000; color: #fff; padding: 16px;"> <strong>${state.value}</strong> <div style="display: flex; gap: 2px;"> ${state.history.map( (v) => html` <span style="width: 4px; height: ${v}px; background: #86EFAC;" ></span> ` )} </div> </div> `, }); // 상태 업데이트 → 자동 리렌더링 panel.setState({ value: 42, history: [10, 20, 30] });

이벤트 핸들러

dmn.ui.displayElement.add({ html: `<div>클릭하세요</div>`, position: { x: 100, y: 100 }, // 함수 직접 전달 (권장) onClick: async () => { console.log("클릭됨!"); }, onPositionChange: async (pos) => { await dmn.plugin.storage.set("position", pos); }, onDelete: async () => { console.log("삭제됨!"); }, });

인스턴스 메서드

메서드설명
setState(updates)상태 업데이트 (템플릿 리렌더링)
setData(updates)상태 업데이트 (setState 별칭)
getState()현재 상태 조회
setText(selector, text)요소 텍스트 설정
setHTML(selector, html)요소 HTML 설정
setStyle(selector, styles)스타일 적용
addClass(selector, ...classes)클래스 추가
removeClass(selector, ...classes)클래스 제거
toggleClass(selector, className)클래스 토글
query(selector)요소 검색 (Shadow DOM 포함)
update(config)메타데이터 업데이트
remove()요소 제거

앵커

특정 키에 요소를 고정합니다:

dmn.ui.displayElement.add({ html: `<div>→</div>`, position: { x: 0, y: 0 }, anchor: { keyCode: "KeyD", offset: { x: 70, y: 0 }, }, });

Shadow DOM

스타일을 격리하려면 scoped: true를 사용합니다:

dmn.ui.displayElement.add({ html: ` <style> :host { display: block; } .widget { background: red; } </style> <div class="widget">격리된 스타일</div> `, position: { x: 100, y: 100 }, scoped: true, });

클린업

dmn.plugin.registerCleanup(() => { // 모든 Display Element 제거 dmn.ui.displayElement.clearMyElements(); });

Dialog

사용자와 상호작용할 수 있는 모달 다이얼로그입니다.

alert

간단한 알림을 표시합니다.

await dmn.ui.dialog.alert("저장되었습니다!"); await dmn.ui.dialog.alert("작업 완료", { confirmText: "OK" });

confirm

확인/취소 다이얼로그를 표시합니다.

const ok = await dmn.ui.dialog.confirm("진행하시겠습니까?"); if (ok) { // 확인 클릭 } // 위험한 작업 const confirmed = await dmn.ui.dialog.confirm("모든 데이터가 삭제됩니다.", { danger: true, confirmText: "삭제", });

custom

커스텀 HTML 다이얼로그를 표시합니다. Components API와 함께 사용합니다.

const formHtml = ` <div class="flex flex-col gap-[12px]"> ${dmn.ui.components.formRow( "이름", dmn.ui.components.input({ id: "name" }) )} ${dmn.ui.components.formRow( "테마", dmn.ui.components.dropdown({ id: "theme", options: [ { label: "다크", value: "dark" }, { label: "라이트", value: "light" }, ], }) )} </div> `; const confirmed = await dmn.ui.dialog.custom(formHtml, { confirmText: "저장", showCancel: true, }); if (confirmed) { const name = document.getElementById("name").value; const theme = document.getElementById("theme").value; }

Components

Dialog 내부에서 사용할 수 있는 UI 컴포넌트입니다. 모든 컴포넌트는 HTML 문자열을 반환합니다.

button

dmn.ui.components.button("저장", { variant: "primary", // "primary" | "danger" | "secondary" onClick: () => console.log("클릭"), });

checkbox

dmn.ui.components.checkbox({ checked: true, id: "enabled", onChange: (checked) => console.log("상태:", checked), });

input

dmn.ui.components.input({ type: "number", value: 50, min: 0, max: 100, width: 100, id: "volume", onChange: (value) => console.log("값:", value), });
dmn.ui.components.dropdown({ options: [ { label: "다크", value: "dark" }, { label: "라이트", value: "light" }, ], selected: "dark", id: "theme", onChange: (value) => console.log("선택:", value), });

formRow

라벨과 컴포넌트를 수평으로 배치합니다.

dmn.ui.components.formRow("볼륨", volumeInput);

panel

컨텐츠를 감싸는 패널 컨테이너입니다.

panel은 Display Element 전용입니다. Dialog 내부에서는 사용하지 마세요.

const panelHtml = dmn.ui.components.panel(formHtml, { title: "설정", width: 400, }); dmn.ui.displayElement.add({ html: panelHtml, position: { x: 100, y: 100 }, });

Color Picker

색상 선택기를 표시합니다.

dmn.ui.pickColor({ initialColor: "#ff0000", onColorChange: (color) => { console.log("색상 변경 중:", color); }, onColorChangeComplete: (color) => { console.log("색상 선택 완료:", color); }, // 선택적 옵션 position: { x: 100, y: 100 }, // 직접 위치 지정 referenceElement: document.getElementById("my-button"), // 요소 기준 위치 });

옵션

속성타입설명
initialColorstring초기 색상 (HEX)
onColorChange(color) => void색상이 변경될 때마다 호출
onColorChangeComplete(color) => void색상 선택이 완료되었을 때 호출
onClose() => void색상 선택기가 닫힐 때 호출
position{ x, y }표시 위치 (referenceElement가 없을 때 사용)
referenceElementHTMLElement이 요소를 기준으로 위치 설정
idstring고유 ID (중복 방지용)

실전 예제: 설정 패널

// @id settings-panel if (dmn.window.type !== "main") return; let settings = { enabled: true, theme: "dark", volume: 50, }; async function showSettings() { const tempSettings = { ...settings }; const formHtml = ` <div class="flex flex-col gap-[12px]"> ${dmn.ui.components.formRow( "활성화", dmn.ui.components.checkbox({ checked: settings.enabled, onChange: (v) => (tempSettings.enabled = v), }) )} ${dmn.ui.components.formRow( "테마", dmn.ui.components.dropdown({ options: [ { label: "다크", value: "dark" }, { label: "라이트", value: "light" }, ], selected: settings.theme, onChange: (v) => (tempSettings.theme = v), }) )} ${dmn.ui.components.formRow( "볼륨", dmn.ui.components.input({ type: "number", value: settings.volume, min: 0, max: 100, width: 60, onChange: (v) => (tempSettings.volume = parseInt(v)), }) )} </div> `; const confirmed = await dmn.ui.dialog.custom(formHtml, { confirmText: "저장", showCancel: true, }); if (confirmed) { settings = tempSettings; await dmn.plugin.storage.set("settings", settings); await dmn.ui.dialog.alert("저장되었습니다!"); } } // 그리드 메뉴에 설정 추가 dmn.ui.contextMenu.addGridMenuItem({ id: "open-settings", label: "설정", onClick: showSettings, }); // 초기화 (async () => { const saved = await dmn.plugin.storage.get("settings"); if (saved) settings = saved; })(); dmn.plugin.registerCleanup(() => { dmn.ui.contextMenu.clearMyMenuItems(); });