template tutoring-admin-dashboard v1

This commit is contained in:
DevVibeCoder
2026-09-14 23:53:29 +05:30 Unverified
commit c4d9755262
70 changed files with 7955 additions and 0 deletions
+257
View File
@@ -0,0 +1,257 @@
// DevVibeCoder Design-mode runtime. Dev only: main.* imports it behind
// `import.meta.env.DEV`, so it never reaches a production bundle.
//
// It never touches the source. It answers the host's handshake, reports which
// stamped element (`data-vc-loc`, see inspector.ts) is under the pointer, was
// clicked, or was dragged where, and keeps the host supplied with element
// rectangles so the host can draw outlines itself. Messages only flow to and
// from the ONE origin that said hello first.
const V = 1;
type Rect = { x: number; y: number; width: number; height: number };
type Node = { id: string; loc: string; tag: string; children: Node[] };
type Position = 'before' | 'after' | 'into';
let parentOrigin: string | null = null;
let active = false;
let raf = 0;
const post = (msg: Record<string, unknown>) => {
if (parentOrigin) window.parent.postMessage({ vc: V, ...msg }, parentOrigin);
};
const plain = (r: DOMRectReadOnly): Rect => ({ x: r.x, y: r.y, width: r.width, height: r.height });
const stamped = (): HTMLElement[] => Array.from(document.querySelectorAll<HTMLElement>('[data-vc-loc]'));
// The same source element renders many times inside a list, so the identity
// the host uses is `loc#n`: the nth stamped element in document order with
// that loc. Recomputed on every report, never stored on the element.
const idsOf = (all: HTMLElement[]): Map<HTMLElement, string> => {
const seen = new Map<string, number>();
const ids = new Map<HTMLElement, string>();
for (const el of all) {
const loc = el.dataset.vcLoc || '';
const n = seen.get(loc) ?? 0;
seen.set(loc, n + 1);
ids.set(el, `${loc}#${n}`);
}
return ids;
};
const idOf = (el: HTMLElement): string => idsOf(stamped()).get(el) || '';
// Stamped children of `el`, skipping unstamped wrappers in between.
const stampedChildren = (el: Element): HTMLElement[] => {
const out: HTMLElement[] = [];
const walk = (n: Element) => {
for (const c of Array.from(n.children)) {
if ((c as HTMLElement).dataset?.vcLoc !== undefined) out.push(c as HTMLElement);
else walk(c);
}
};
walk(el);
return out;
};
const tree = (): Node[] => {
const all = stamped();
const ids = idsOf(all);
const build = (el: HTMLElement): Node => ({
id: ids.get(el) || '',
loc: el.dataset.vcLoc || '',
tag: el.tagName.toLowerCase(),
children: stampedChildren(el).map(build),
});
return all.filter((el) => !el.parentElement?.closest('[data-vc-loc]')).map(build);
};
const sendRects = () => {
cancelAnimationFrame(raf);
raf = requestAnimationFrame(() => {
const rects: Record<string, Rect> = {};
const all = stamped();
const ids = idsOf(all);
for (const el of all) rects[ids.get(el) || ''] = plain(el.getBoundingClientRect());
post({ type: 'rects', rects });
});
};
const sendTree = () => post({ type: 'tree', tree: tree(), route: location.pathname });
const closestStamped = (n: Element | null): HTMLElement | null =>
(n?.closest?.('[data-vc-loc]') as HTMLElement | null) ?? null;
const target = (e: Event): HTMLElement | null => closestStamped(e.target as Element | null);
// --- drag ---------------------------------------------------------------------
// Pointer-driven, no HTML5 DnD: it works on every element, including ones with
// their own click handlers, and never starts the app's own drag behaviour.
let press: { el: HTMLElement; x: number; y: number } | null = null;
let dragging: HTMLElement | null = null;
let over: { el: HTMLElement; position: Position } | null = null;
const isRow = (el: HTMLElement): boolean => {
const p = el.parentElement;
if (!p) return false;
const cs = getComputedStyle(p);
if (cs.display.includes('flex')) return cs.flexDirection.startsWith('row');
if (cs.display.includes('grid')) return true;
return cs.display === 'inline' || getComputedStyle(el).display.startsWith('inline');
};
const dropAt = (x: number, y: number): { el: HTMLElement; position: Position } | null => {
const hit = closestStamped(document.elementFromPoint(x, y));
if (!hit || !dragging || hit === dragging || dragging.contains(hit)) return null;
const r = hit.getBoundingClientRect();
if (!stampedChildren(hit).length && !hit.textContent?.trim()) {
// An empty container: the middle band drops inside it.
const fx = (x - r.x) / r.width;
const fy = (y - r.y) / r.height;
if (fx > 0.25 && fx < 0.75 && fy > 0.25 && fy < 0.75) return { el: hit, position: 'into' };
}
const half = isRow(hit) ? x < r.x + r.width / 2 : y < r.y + r.height / 2;
return { el: hit, position: half ? 'before' : 'after' };
};
const endDrag = (commit: boolean) => {
if (dragging) {
if (commit && over) post({ type: 'drop', id: idOf(dragging), targetId: idOf(over.el), position: over.position });
else post({ type: 'dragover', targetId: null });
}
press = null;
dragging = null;
over = null;
document.documentElement.style.cursor = active ? 'crosshair' : '';
};
const onDown = (e: PointerEvent) => {
if (e.button !== 0) return;
const el = target(e);
press = el ? { el, x: e.clientX, y: e.clientY } : null;
};
const onMove = (e: PointerEvent) => {
if (press && !dragging && Math.hypot(e.clientX - press.x, e.clientY - press.y) > 4) {
dragging = press.el;
document.documentElement.style.cursor = 'grabbing';
post({ type: 'dragstart', id: idOf(dragging) });
}
if (dragging) {
e.preventDefault();
const next = dropAt(e.clientX, e.clientY);
if (next?.el !== over?.el || next?.position !== over?.position) {
over = next;
post({
type: 'dragover',
targetId: over ? idOf(over.el) : null,
position: over?.position ?? null,
rect: over ? plain(over.el.getBoundingClientRect()) : null,
row: over ? isRow(over.el) : false,
});
}
return;
}
const el = target(e);
post({ type: 'hover', id: el ? idOf(el) : null, rect: el ? plain(el.getBoundingClientRect()) : null });
};
const onUp = () => {
if (dragging) endDrag(true);
press = null;
};
const onKey = (e: KeyboardEvent) => {
if (e.key === 'Escape' && dragging) endDrag(false);
};
const onClick = (e: MouseEvent) => {
// Design mode: a click selects, it does not navigate or submit.
e.preventDefault();
e.stopPropagation();
const el = target(e);
if (!el) {
post({ type: 'select', id: null });
return;
}
post({
type: 'select',
id: idOf(el),
loc: el.dataset.vcLoc,
tag: el.tagName.toLowerCase(),
rect: plain(el.getBoundingClientRect()),
});
};
const observer = new MutationObserver(() => {
sendRects();
sendTree();
});
const setActive = (on: boolean) => {
if (on === active) return;
active = on;
const opts = { capture: true } as const;
if (on) {
document.addEventListener('pointerdown', onDown, opts);
document.addEventListener('pointermove', onMove, opts);
document.addEventListener('pointerup', onUp, opts);
document.addEventListener('keydown', onKey, opts);
document.addEventListener('click', onClick, opts);
window.addEventListener('scroll', sendRects, opts);
window.addEventListener('resize', sendRects);
observer.observe(document.body, { childList: true, subtree: true, attributes: true, characterData: true });
document.documentElement.style.cursor = 'crosshair';
sendTree();
sendRects();
} else {
endDrag(false);
document.removeEventListener('pointerdown', onDown, opts);
document.removeEventListener('pointermove', onMove, opts);
document.removeEventListener('pointerup', onUp, opts);
document.removeEventListener('keydown', onKey, opts);
document.removeEventListener('click', onClick, opts);
window.removeEventListener('scroll', sendRects, opts);
window.removeEventListener('resize', sendRects);
observer.disconnect();
document.documentElement.style.cursor = '';
}
};
// Route changes: the host's page picker and the Layers tree follow them.
for (const m of ['pushState', 'replaceState'] as const) {
const orig = history[m];
history[m] = function (this: History, ...args: Parameters<History['pushState']>) {
const r = orig.apply(this, args);
queueMicrotask(sendTree);
return r;
};
}
window.addEventListener('popstate', () => queueMicrotask(sendTree));
window.addEventListener('message', (e: MessageEvent) => {
const d = e.data;
if (!d || d.vc !== V || window.parent === window) return;
if (d.type === 'hello') {
// The first hello pins the host; any other origin is ignored for the
// life of the page.
if (parentOrigin && e.origin !== parentOrigin) return;
parentOrigin = e.origin;
post({ type: 'ready', route: location.pathname });
setActive(d.mode === 'design');
return;
}
if (e.origin !== parentOrigin) return;
if (d.type === 'mode') setActive(d.mode === 'design');
else if (d.type === 'navigate' && typeof d.path === 'string') {
history.pushState(null, '', d.path);
dispatchEvent(new PopStateEvent('popstate'));
} else if (d.type === 'refresh') {
sendTree();
sendRects();
}
});
export {};