Merge branch 'dev'
This commit is contained in:
commit
8fcbeab93c
|
|
@ -11,11 +11,25 @@ import { resolve } from "node:path";
|
|||
import { renderShell } from "../../server/shell";
|
||||
import { getModuleInfoList } from "../../shared/module";
|
||||
import type { RSpaceModule } from "../../shared/module";
|
||||
import { loadCommunity, getDocumentData } from "../../server/community-store";
|
||||
|
||||
const DIST_DIR = resolve(import.meta.dir, "../../dist");
|
||||
|
||||
const routes = new Hono();
|
||||
|
||||
// GET /api/meta — space metadata (owner, members) for space-settings fallback
|
||||
routes.get("/api/meta", async (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
try {
|
||||
await loadCommunity(space);
|
||||
const doc = getDocumentData(space);
|
||||
if (!doc) return c.json({ meta: {} });
|
||||
return c.json({ meta: { ownerDID: doc.meta?.ownerDID, members: doc.members } });
|
||||
} catch {
|
||||
return c.json({ meta: {} });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Extract body content and scripts from the full canvas.html page.
|
||||
* Strips the shell chrome (header, tab-bar, welcome overlay) that renderShell provides,
|
||||
|
|
|
|||
|
|
@ -60,6 +60,10 @@ export class RStackNotificationBell extends HTMLElement {
|
|||
this.#open = false;
|
||||
this.#render();
|
||||
this.#fetchCount();
|
||||
// Restart poll timer if it was stopped by a 401
|
||||
if (!this.#pollTimer) {
|
||||
this.#pollTimer = setInterval(() => this.#fetchCount(), POLL_INTERVAL);
|
||||
}
|
||||
};
|
||||
|
||||
#onWsNotification = (e: CustomEvent) => {
|
||||
|
|
@ -96,6 +100,11 @@ export class RStackNotificationBell extends HTMLElement {
|
|||
const data = await res.json();
|
||||
this.#unreadCount = data.unreadCount || 0;
|
||||
this.#render();
|
||||
} else if (res.status === 401) {
|
||||
// Token rejected — stop polling until auth changes
|
||||
this.#unreadCount = 0;
|
||||
if (this.#pollTimer) { clearInterval(this.#pollTimer); this.#pollTimer = null; }
|
||||
this.#render();
|
||||
}
|
||||
} catch {
|
||||
// Silently fail
|
||||
|
|
|
|||
|
|
@ -91,8 +91,12 @@ export class RStackSpaceSettings extends HTMLElement {
|
|||
}
|
||||
} else {
|
||||
// Fallback: fetch from API
|
||||
// On subdomain routing (jeff.rspace.online), omit space prefix to avoid double-prefix
|
||||
const host = window.location.hostname;
|
||||
const isSubdomain = host.split(".").length >= 3 && !host.startsWith("www.");
|
||||
const metaUrl = isSubdomain ? "/rspace/api/meta" : `/${this._space}/rspace/api/meta`;
|
||||
try {
|
||||
const res = await fetch(`/${this._space}/rspace/api/meta`, {
|
||||
const res = await fetch(metaUrl, {
|
||||
headers: token ? { "Authorization": `Bearer ${token}` } : {},
|
||||
});
|
||||
if (res.ok) {
|
||||
|
|
|
|||
|
|
@ -110,6 +110,8 @@ export class RStackTabBar extends HTMLElement {
|
|||
#wiringSourceFeedId = "";
|
||||
#wiringSourceKind: FlowKind | null = null;
|
||||
#escHandler: ((e: KeyboardEvent) => void) | null = null;
|
||||
// Cleanup for document-level listeners (prevent leak on re-render)
|
||||
#docCleanup: (() => void) | null = null;
|
||||
// Recent apps: moduleId → last-used timestamp
|
||||
#recentApps: Map<string, number> = new Map();
|
||||
|
||||
|
|
@ -789,6 +791,9 @@ export class RStackTabBar extends HTMLElement {
|
|||
// ── Events ──
|
||||
|
||||
#attachEvents() {
|
||||
// Clean up previous document-level listeners to prevent leak
|
||||
if (this.#docCleanup) { this.#docCleanup(); this.#docCleanup = null; }
|
||||
|
||||
// Tab clicks
|
||||
this.#shadow.querySelectorAll<HTMLElement>(".tab").forEach(tab => {
|
||||
tab.addEventListener("click", (e) => {
|
||||
|
|
@ -945,20 +950,6 @@ export class RStackTabBar extends HTMLElement {
|
|||
this.#flowDragSource = layerId;
|
||||
plane.classList.add("flow-drag-source");
|
||||
});
|
||||
|
||||
plane.addEventListener("mouseenter", () => {
|
||||
if (this.#flowDragSource && this.#flowDragSource !== layerId) {
|
||||
this.#flowDragTarget = layerId;
|
||||
plane.classList.add("flow-drag-target");
|
||||
}
|
||||
});
|
||||
|
||||
plane.addEventListener("mouseleave", () => {
|
||||
if (this.#flowDragTarget === layerId) {
|
||||
this.#flowDragTarget = null;
|
||||
plane.classList.remove("flow-drag-target");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 3D scene: orbit controls (drag on empty space to rotate)
|
||||
|
|
@ -974,6 +965,9 @@ export class RStackTabBar extends HTMLElement {
|
|||
sceneContainer.style.cursor = "grabbing";
|
||||
});
|
||||
|
||||
// Collect all layer planes + their rects for drag target detection
|
||||
const layerPlanes = [...this.#shadow.querySelectorAll<HTMLElement>(".layer-plane")];
|
||||
|
||||
const onMouseMove = (e: MouseEvent) => {
|
||||
if (this.#orbitDragging) {
|
||||
const dx = e.clientX - this.#orbitLastX;
|
||||
|
|
@ -985,6 +979,29 @@ export class RStackTabBar extends HTMLElement {
|
|||
const scene = this.#shadow.getElementById("stack-scene");
|
||||
if (scene) scene.style.transform = `rotateX(${this.#sceneRotX}deg) rotateZ(${this.#sceneRotZ}deg)`;
|
||||
}
|
||||
|
||||
// Drag-to-connect: track target via bounding rects (robust for 3D)
|
||||
if (this.#flowDragSource) {
|
||||
let newTarget: string | null = null;
|
||||
for (const p of layerPlanes) {
|
||||
const r = p.getBoundingClientRect();
|
||||
if (e.clientX >= r.left && e.clientX <= r.right &&
|
||||
e.clientY >= r.top && e.clientY <= r.bottom &&
|
||||
p.dataset.layerId !== this.#flowDragSource) {
|
||||
newTarget = p.dataset.layerId!;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (newTarget !== this.#flowDragTarget) {
|
||||
this.#shadow.querySelectorAll(".flow-drag-target")
|
||||
.forEach(el => el.classList.remove("flow-drag-target"));
|
||||
this.#flowDragTarget = newTarget;
|
||||
if (newTarget) {
|
||||
this.#shadow.querySelector(`.layer-plane[data-layer-id="${newTarget}"]`)
|
||||
?.classList.add("flow-drag-target");
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onMouseUp = () => {
|
||||
|
|
@ -1003,9 +1020,13 @@ export class RStackTabBar extends HTMLElement {
|
|||
});
|
||||
};
|
||||
|
||||
// Attach to document for drag continuity
|
||||
// Attach to document for drag continuity (cleaned up on re-render)
|
||||
document.addEventListener("mousemove", onMouseMove);
|
||||
document.addEventListener("mouseup", onMouseUp);
|
||||
this.#docCleanup = () => {
|
||||
document.removeEventListener("mousemove", onMouseMove);
|
||||
document.removeEventListener("mouseup", onMouseUp);
|
||||
};
|
||||
|
||||
// Scroll to zoom
|
||||
sceneContainer.addEventListener("wheel", (e) => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue