Added Rules preset (on top of format and style presets)
This commit is contained in:
395
examples/formatting_manager_mockup.html
Normal file
395
examples/formatting_manager_mockup.html
Normal file
@@ -0,0 +1,395 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" data-theme="light">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>DataGridFormattingManager — Visual Mockup</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/daisyui@5/daisyui.css" rel="stylesheet">
|
||||
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
|
||||
<style>
|
||||
body { font-family: ui-sans-serif, system-ui, sans-serif; }
|
||||
|
||||
/* ---- Manager layout ---- */
|
||||
.manager-root {
|
||||
display: grid;
|
||||
grid-template-columns: 280px 1fr;
|
||||
grid-template-rows: auto 1fr;
|
||||
height: 520px;
|
||||
border: 1px solid oklch(var(--b3));
|
||||
border-radius: 0.75rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* ---- Menu bar (Menu control pattern) ---- */
|
||||
.mf-menu {
|
||||
grid-column: 1 / -1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
padding: 0.35rem 0.6rem;
|
||||
background: oklch(var(--b2));
|
||||
border-bottom: 1px solid oklch(var(--b3));
|
||||
}
|
||||
|
||||
/* Icon button — mirrors mk.icon() output */
|
||||
.mf-icon-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
border-radius: 0.4rem;
|
||||
cursor: pointer;
|
||||
color: oklch(var(--bc) / 0.7);
|
||||
transition: background 0.1s, color 0.1s;
|
||||
position: relative;
|
||||
}
|
||||
.mf-icon-btn:hover { background: oklch(var(--b3)); color: oklch(var(--bc)); }
|
||||
.mf-icon-btn.danger:hover { background: color-mix(in oklab, oklch(var(--er)) 15%, transparent); color: oklch(var(--er)); }
|
||||
|
||||
/* Tooltip — mirrors mk.icon(tooltip=...) */
|
||||
.mf-icon-btn::after {
|
||||
content: attr(data-tip);
|
||||
position: absolute;
|
||||
top: calc(100% + 6px);
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background: oklch(var(--n));
|
||||
color: oklch(var(--nc));
|
||||
font-size: 0.7rem;
|
||||
white-space: nowrap;
|
||||
padding: 0.2rem 0.5rem;
|
||||
border-radius: 0.3rem;
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
transition: opacity 0.15s;
|
||||
z-index: 10;
|
||||
}
|
||||
.mf-icon-btn:hover::after { opacity: 1; }
|
||||
|
||||
.menu-separator { width: 1px; height: 1.2rem; background: oklch(var(--b3)); margin: 0 0.15rem; }
|
||||
|
||||
/* ---- Preset list ---- */
|
||||
.preset-list {
|
||||
overflow-y: auto;
|
||||
border-right: 1px solid oklch(var(--b3));
|
||||
background: oklch(var(--b1));
|
||||
}
|
||||
|
||||
.preset-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.15rem;
|
||||
padding: 0.55rem 0.75rem;
|
||||
cursor: pointer;
|
||||
border-bottom: 1px solid oklch(var(--b2));
|
||||
transition: background 0.1s;
|
||||
}
|
||||
.preset-item:hover { background: oklch(var(--b2)); }
|
||||
.preset-item.active {
|
||||
background: color-mix(in oklab, oklch(var(--p)) 10%, transparent);
|
||||
border-left: 3px solid oklch(var(--p));
|
||||
padding-left: calc(0.75rem - 3px);
|
||||
}
|
||||
|
||||
.preset-name { font-size: 0.875rem; font-weight: 600; }
|
||||
.preset-desc { font-size: 0.7rem; color: oklch(var(--bc) / 0.5); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.preset-badges { display: flex; gap: 0.25rem; margin-top: 0.2rem; }
|
||||
|
||||
/* ---- Editor panel ---- */
|
||||
.editor-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
background: oklch(var(--b1));
|
||||
}
|
||||
|
||||
.editor-meta {
|
||||
padding: 0.6rem 1rem 0.5rem;
|
||||
border-bottom: 1px solid oklch(var(--b3));
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.dsl-area {
|
||||
flex: 1;
|
||||
padding: 0.75rem 1rem;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.35rem;
|
||||
}
|
||||
|
||||
.dsl-label {
|
||||
font-size: 0.7rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: oklch(var(--bc) / 0.4);
|
||||
}
|
||||
|
||||
.dsl-editor {
|
||||
flex: 1;
|
||||
font-family: ui-monospace, 'Cascadia Code', 'Courier New', monospace;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
resize: none;
|
||||
padding: 0.75rem;
|
||||
border: 1px solid oklch(var(--b3));
|
||||
border-radius: 0.5rem;
|
||||
background: oklch(var(--b2));
|
||||
color: oklch(var(--bc));
|
||||
outline: none;
|
||||
transition: border-color 0.15s;
|
||||
}
|
||||
.dsl-editor:focus { border-color: oklch(var(--p)); }
|
||||
|
||||
/* ---- Code block (Python def) ---- */
|
||||
.token-keyword { color: oklch(var(--p)); font-weight: 700; }
|
||||
.token-builtin { color: oklch(var(--s)); font-weight: 600; }
|
||||
.token-string { color: oklch(var(--su)); }
|
||||
.token-number { color: oklch(var(--a)); }
|
||||
.token-comment { color: oklch(var(--bc) / 0.4); font-style: italic; }
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-base-200 p-6">
|
||||
|
||||
<!-- Page header -->
|
||||
<div class="mb-4">
|
||||
<h1 class="text-xl font-bold">DataGridFormattingManager</h1>
|
||||
<p class="text-sm text-base-content/50 mt-0.5">
|
||||
Named rule presets combining formatters, styles and conditions.
|
||||
Reference them with <code class="bg-base-300 px-1 rounded text-xs">format("preset_name")</code>
|
||||
or <code class="bg-base-300 px-1 rounded text-xs">style("preset_name")</code>.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Manager control -->
|
||||
<div class="manager-root shadow-md">
|
||||
|
||||
<!-- Menu bar — Menu(conf=MenuConf(fixed_items=["New","Save","Rename","Delete"]), save_state=False) -->
|
||||
<div class="mf-menu">
|
||||
<!-- fixed_items rendered as mk.icon(command, tooltip=command.description) -->
|
||||
<button class="mf-icon-btn" data-tip="New preset" onclick="newPreset()">
|
||||
<!-- Fluent: Add -->
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
|
||||
</button>
|
||||
<button class="mf-icon-btn" data-tip="Save preset" onclick="savePreset()">
|
||||
<!-- Fluent: Save -->
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/><polyline points="17 21 17 13 7 13 7 21"/><polyline points="7 3 7 8 15 8"/></svg>
|
||||
</button>
|
||||
<button class="mf-icon-btn" data-tip="Rename preset" onclick="renamePreset()">
|
||||
<!-- Fluent: Rename -->
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
|
||||
</button>
|
||||
<div class="menu-separator"></div>
|
||||
<button class="mf-icon-btn danger" data-tip="Delete preset" onclick="deletePreset()">
|
||||
<!-- Fluent: Delete -->
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/><path d="M10 11v6"/><path d="M14 11v6"/><path d="M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Left: preset list -->
|
||||
<div class="preset-list" id="preset-list"></div>
|
||||
|
||||
<!-- Right: editor -->
|
||||
<div class="editor-panel">
|
||||
<div class="editor-meta">
|
||||
<div class="flex-1">
|
||||
<div class="text-sm font-semibold" id="editor-title">—</div>
|
||||
<div class="text-xs text-base-content/45 mt-0.5" id="editor-desc">Select a preset to edit</div>
|
||||
</div>
|
||||
<div class="flex gap-1" id="editor-badges"></div>
|
||||
</div>
|
||||
|
||||
<div class="dsl-area">
|
||||
<div class="dsl-label">Rules — DSL</div>
|
||||
<textarea class="dsl-editor" id="dsl-editor" spellcheck="false"
|
||||
placeholder="No preset selected."></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- DEFAULT_RULE_PRESETS example -->
|
||||
<div class="mt-6 rounded-xl border border-base-300 bg-base-100 overflow-hidden shadow">
|
||||
<div class="px-4 py-2 bg-base-200 border-b border-base-300 text-xs font-bold uppercase tracking-wide text-base-content/50">
|
||||
DEFAULT_RULE_PRESETS — core/formatting/presets.py
|
||||
</div>
|
||||
<pre class="p-4 text-xs font-mono overflow-x-auto leading-relaxed text-base-content"><span class="token-comment"># name + description + list of complete FormatRule descriptors.
|
||||
# Appears in format() suggestions if at least one rule has a formatter.
|
||||
# Appears in style() suggestions if at least one rule has a style.</span>
|
||||
|
||||
DEFAULT_RULE_PRESETS = {
|
||||
|
||||
<span class="token-string">"accounting"</span>: {
|
||||
<span class="token-string">"description"</span>: <span class="token-string">"Negatives in parentheses (red), positives plain"</span>,
|
||||
<span class="token-string">"rules"</span>: [
|
||||
{
|
||||
<span class="token-string">"condition"</span>: {<span class="token-string">"operator"</span>: <span class="token-string">"<"</span>, <span class="token-string">"value"</span>: <span class="token-number">0</span>},
|
||||
<span class="token-string">"formatter"</span>: {<span class="token-string">"type"</span>: <span class="token-string">"number"</span>, <span class="token-string">"precision"</span>: <span class="token-number">0</span>,
|
||||
<span class="token-string">"prefix"</span>: <span class="token-string">"("</span>, <span class="token-string">"suffix"</span>: <span class="token-string">")"</span>,
|
||||
<span class="token-string">"absolute"</span>: <span class="token-keyword">True</span>, <span class="token-string">"thousands_sep"</span>: <span class="token-string">" "</span>},
|
||||
<span class="token-string">"style"</span>: {<span class="token-string">"preset"</span>: <span class="token-string">"error"</span>},
|
||||
},
|
||||
{
|
||||
<span class="token-string">"condition"</span>: {<span class="token-string">"operator"</span>: <span class="token-string">">"</span>, <span class="token-string">"value"</span>: <span class="token-number">0</span>},
|
||||
<span class="token-string">"formatter"</span>: {<span class="token-string">"type"</span>: <span class="token-string">"number"</span>, <span class="token-string">"precision"</span>: <span class="token-number">0</span>,
|
||||
<span class="token-string">"thousands_sep"</span>: <span class="token-string">" "</span>},
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
<span class="token-string">"traffic_light"</span>: {
|
||||
<span class="token-string">"description"</span>: <span class="token-string">"Red / yellow / green style based on sign"</span>,
|
||||
<span class="token-string">"rules"</span>: [
|
||||
{<span class="token-string">"condition"</span>: {<span class="token-string">"operator"</span>: <span class="token-string">"<"</span>, <span class="token-string">"value"</span>: <span class="token-number">0</span>}, <span class="token-string">"style"</span>: {<span class="token-string">"preset"</span>: <span class="token-string">"error"</span>}},
|
||||
{<span class="token-string">"condition"</span>: {<span class="token-string">"operator"</span>: <span class="token-string">"=="</span>, <span class="token-string">"value"</span>: <span class="token-number">0</span>}, <span class="token-string">"style"</span>: {<span class="token-string">"preset"</span>: <span class="token-string">"warning"</span>}},
|
||||
{<span class="token-string">"condition"</span>: {<span class="token-string">"operator"</span>: <span class="token-string">">"</span>, <span class="token-string">"value"</span>: <span class="token-number">0</span>}, <span class="token-string">"style"</span>: {<span class="token-string">"preset"</span>: <span class="token-string">"success"</span>}},
|
||||
],
|
||||
},
|
||||
|
||||
<span class="token-string">"budget_variance"</span>: {
|
||||
<span class="token-string">"description"</span>: <span class="token-string">"% variance: negative=error, over 10%=warning, else plain"</span>,
|
||||
<span class="token-string">"rules"</span>: [
|
||||
{
|
||||
<span class="token-string">"condition"</span>: {<span class="token-string">"operator"</span>: <span class="token-string">"<"</span>, <span class="token-string">"value"</span>: <span class="token-number">0</span>},
|
||||
<span class="token-string">"formatter"</span>: {<span class="token-string">"type"</span>: <span class="token-string">"number"</span>, <span class="token-string">"precision"</span>: <span class="token-number">1</span>, <span class="token-string">"suffix"</span>: <span class="token-string">"%"</span>, <span class="token-string">"multiplier"</span>: <span class="token-number">100</span>},
|
||||
<span class="token-string">"style"</span>: {<span class="token-string">"preset"</span>: <span class="token-string">"error"</span>},
|
||||
},
|
||||
{
|
||||
<span class="token-string">"condition"</span>: {<span class="token-string">"operator"</span>: <span class="token-string">">"</span>, <span class="token-string">"value"</span>: <span class="token-number">0.1</span>},
|
||||
<span class="token-string">"formatter"</span>: {<span class="token-string">"type"</span>: <span class="token-string">"number"</span>, <span class="token-string">"precision"</span>: <span class="token-number">1</span>, <span class="token-string">"suffix"</span>: <span class="token-string">"%"</span>, <span class="token-string">"multiplier"</span>: <span class="token-number">100</span>},
|
||||
<span class="token-string">"style"</span>: {<span class="token-string">"preset"</span>: <span class="token-string">"warning"</span>},
|
||||
},
|
||||
{
|
||||
<span class="token-string">"formatter"</span>: {<span class="token-string">"type"</span>: <span class="token-string">"number"</span>, <span class="token-string">"precision"</span>: <span class="token-number">1</span>, <span class="token-string">"suffix"</span>: <span class="token-string">"%"</span>, <span class="token-string">"multiplier"</span>: <span class="token-number">100</span>},
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
}</pre>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const presets = [
|
||||
{
|
||||
id: "accounting", name: "accounting",
|
||||
description: "Negatives in parentheses (red), positives plain",
|
||||
hasFormatter: true, hasStyle: true,
|
||||
dsl:
|
||||
`format.number(precision=0, prefix="(", suffix=")", absolute=True, thousands_sep=" ") style("error") if value < 0
|
||||
format.number(precision=0, thousands_sep=" ") if value > 0`,
|
||||
},
|
||||
{
|
||||
id: "traffic_light", name: "traffic_light",
|
||||
description: "Red / yellow / green style based on sign",
|
||||
hasFormatter: false, hasStyle: true,
|
||||
dsl:
|
||||
`style("error") if value < 0
|
||||
style("warning") if value == 0
|
||||
style("success") if value > 0`,
|
||||
},
|
||||
{
|
||||
id: "budget_variance", name: "budget_variance",
|
||||
description: "% variance: negative=error, over 10%=warning, else plain",
|
||||
hasFormatter: true, hasStyle: true,
|
||||
dsl:
|
||||
`format.number(precision=1, suffix="%", multiplier=100) style("error") if value < 0
|
||||
format.number(precision=1, suffix="%", multiplier=100) style("warning") if value > 0.1
|
||||
format.number(precision=1, suffix="%", multiplier=100)`,
|
||||
},
|
||||
];
|
||||
|
||||
let activeId = null;
|
||||
|
||||
function renderList() {
|
||||
const list = document.getElementById("preset-list");
|
||||
list.innerHTML = "";
|
||||
presets.forEach(p => {
|
||||
const item = document.createElement("div");
|
||||
item.className = "preset-item" + (p.id === activeId ? " active" : "");
|
||||
item.onclick = () => selectPreset(p.id);
|
||||
const badges = [];
|
||||
if (p.hasFormatter) badges.push(`<span class="badge badge-xs badge-secondary">Format</span>`);
|
||||
if (p.hasStyle) badges.push(`<span class="badge badge-xs badge-primary">Style</span>`);
|
||||
item.innerHTML = `
|
||||
<div class="preset-name">${p.name}</div>
|
||||
<div class="preset-desc">${p.description}</div>
|
||||
<div class="preset-badges">${badges.join("")}</div>
|
||||
`;
|
||||
list.appendChild(item);
|
||||
});
|
||||
}
|
||||
|
||||
function selectPreset(id) {
|
||||
activeId = id;
|
||||
const p = presets.find(x => x.id === id);
|
||||
document.getElementById("editor-title").textContent = p.name;
|
||||
document.getElementById("editor-desc").textContent = p.description;
|
||||
document.getElementById("dsl-editor").value = p.dsl;
|
||||
const badges = [];
|
||||
if (p.hasFormatter) badges.push(`<span class="badge badge-secondary">format()</span>`);
|
||||
if (p.hasStyle) badges.push(`<span class="badge badge-primary">style()</span>`);
|
||||
document.getElementById("editor-badges").innerHTML = badges.join("");
|
||||
renderList();
|
||||
}
|
||||
|
||||
function savePreset() {
|
||||
if (!activeId) return;
|
||||
const p = presets.find(x => x.id === activeId);
|
||||
p.dsl = document.getElementById("dsl-editor").value;
|
||||
p.hasFormatter = p.dsl.includes("format");
|
||||
p.hasStyle = p.dsl.includes("style");
|
||||
renderList();
|
||||
showToast("Preset saved.");
|
||||
}
|
||||
|
||||
function newPreset() {
|
||||
const name = prompt("Preset name:");
|
||||
if (!name?.trim()) return;
|
||||
const id = name.trim().toLowerCase().replace(/\s+/g, "_");
|
||||
if (presets.find(x => x.id === id)) { alert("Name already exists."); return; }
|
||||
presets.push({ id, name: id, description: "New rule preset", hasFormatter: false, hasStyle: false, dsl: "" });
|
||||
renderList();
|
||||
selectPreset(id);
|
||||
}
|
||||
|
||||
function renamePreset() {
|
||||
if (!activeId) return;
|
||||
const p = presets.find(x => x.id === activeId);
|
||||
const name = prompt("New name:", p.name);
|
||||
if (!name?.trim()) return;
|
||||
p.name = name.trim();
|
||||
p.id = p.name.toLowerCase().replace(/\s+/g, "_");
|
||||
activeId = p.id;
|
||||
renderList();
|
||||
document.getElementById("editor-title").textContent = p.name;
|
||||
}
|
||||
|
||||
function deletePreset() {
|
||||
if (!activeId) return;
|
||||
if (!confirm(`Delete "${activeId}"?`)) return;
|
||||
presets.splice(presets.findIndex(x => x.id === activeId), 1);
|
||||
activeId = null;
|
||||
document.getElementById("editor-title").textContent = "—";
|
||||
document.getElementById("editor-desc").textContent = "Select a preset to edit";
|
||||
document.getElementById("dsl-editor").value = "";
|
||||
document.getElementById("editor-badges").innerHTML = "";
|
||||
renderList();
|
||||
}
|
||||
|
||||
function showToast(msg) {
|
||||
const t = document.createElement("div");
|
||||
t.className = "toast toast-end toast-bottom z-50";
|
||||
t.innerHTML = `<div class="alert alert-success text-sm py-2 px-4">${msg}</div>`;
|
||||
document.body.appendChild(t);
|
||||
setTimeout(() => t.remove(), 2000);
|
||||
}
|
||||
|
||||
renderList();
|
||||
selectPreset("accounting");
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user