Last active
September 15, 2025 06:28
-
-
Save mdroidian/88dc865156bef4359a860e9f962203b1 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import React, { useEffect, useState, useCallback } from "react"; | |
| import type { OnloadArgs } from "roamjs-components/types"; | |
| import { HTMLTable, Button, MenuItem, Spinner } from "@blueprintjs/core"; | |
| import { Select } from "@blueprintjs/select"; | |
| import { | |
| getSupabaseContext, | |
| getLoggedInClient, | |
| type SupabaseContext, | |
| } from "~/utils/supabaseContext"; | |
| import type { Tables } from "@repo/database/dbTypes"; | |
| import { | |
| getNodes, | |
| getNodeSchemas, | |
| type NodeSignature, | |
| type PConcept, | |
| } from "@repo/database/lib/queries"; | |
| import { DGSupabaseClient } from "@repo/database/lib/client"; | |
| type AdminPanelProps = { | |
| onloadArgs: OnloadArgs; | |
| }; | |
| const AdminPanel = ({ onloadArgs }: AdminPanelProps) => { | |
| const [context, setContext] = useState<SupabaseContext | null>(null); | |
| const [supabase, setSupabase] = useState<DGSupabaseClient | null>(null); | |
| const [schemas, setSchemas] = useState<NodeSignature[]>([]); | |
| const [showingSchema, setShowingSchema] = useState<NodeSignature | null>(null); | |
| const [nodes, setNodes] = useState<PConcept[]>([]); | |
| const [loading, setLoading] = useState(true); | |
| const [loadingNodes, setLoadingNodes] = useState(false); | |
| const [error, setError] = useState<string | null>(null); | |
| // Initial load: context, client, schemas, and initial nodes | |
| useEffect(() => { | |
| let cancelled = false; | |
| (async () => { | |
| try { | |
| setLoading(true); | |
| const ctx = await getSupabaseContext(); | |
| if (cancelled) return; | |
| setContext(ctx); | |
| if (!ctx) { | |
| setError("No Supabase context available."); | |
| return; | |
| } | |
| const client = await getLoggedInClient(); | |
| if (cancelled) return; | |
| if (!client) { | |
| setError("Not logged in to Supabase."); | |
| return; | |
| } | |
| setSupabase(client); | |
| const fetchedSchemas = await getNodeSchemas(client, ctx.spaceId); | |
| if (cancelled) return; | |
| setSchemas(fetchedSchemas); | |
| // Pick first schema by default (if any) | |
| const initialSchema = fetchedSchemas[0] ?? null; | |
| setShowingSchema(initialSchema); | |
| if (initialSchema) { | |
| const initialNodes = await getNodes({ | |
| supabase: client, | |
| spaceId: ctx.spaceId, | |
| schemaLocalIds: initialSchema.sourceLocalId, | |
| }); | |
| if (cancelled) return; | |
| setNodes(initialNodes); | |
| } else { | |
| // Fallback: load all nodes if no schemas returned | |
| const allNodes = await getNodes({ supabase: client, spaceId: ctx.spaceId }); | |
| if (cancelled) return; | |
| setNodes(allNodes); | |
| } | |
| } catch (e) { | |
| console.error("AdminPanel init failed", e); | |
| setError("Failed to initialize Admin Panel."); | |
| } finally { | |
| if (!cancelled) setLoading(false); | |
| } | |
| })(); | |
| return () => { | |
| cancelled = true; | |
| }; | |
| }, []); | |
| const handleSelectSchema = useCallback( | |
| async (choice: NodeSignature) => { | |
| setShowingSchema(choice); | |
| if (!supabase || !context) return; | |
| try { | |
| setLoadingNodes(true); | |
| const next = await getNodes({ | |
| supabase, | |
| spaceId: context.spaceId, | |
| schemaLocalIds: choice.sourceLocalId, | |
| }); | |
| setNodes(next); | |
| } catch (e) { | |
| console.error("Failed to load nodes for schema", e); | |
| setError("Failed to load nodes for selected schema."); | |
| } finally { | |
| setLoadingNodes(false); | |
| } | |
| }, | |
| [supabase, context], | |
| ); | |
| if (loading) { | |
| return ( | |
| <div className="p-3"> | |
| <Spinner /> | |
| <span style={{ marginLeft: 8 }}>Loading admin data…</span> | |
| </div> | |
| ); | |
| } | |
| if (error) { | |
| return <p style={{ color: "red" }}>{error}</p>; | |
| } | |
| return ( | |
| <div> | |
| <p> | |
| Context:{" "} | |
| <code> | |
| {JSON.stringify( | |
| context ? { ...context, spacePassword: "****" } : null, | |
| )} | |
| </code> | |
| </p> | |
| {schemas.length > 0 ? ( | |
| <div> | |
| <div style={{ marginBottom: 12 }}> | |
| <Select<NodeSignature> | |
| items={schemas} | |
| onItemSelect={handleSelectSchema} | |
| itemRenderer={(node, { handleClick, modifiers }) => ( | |
| <MenuItem | |
| active={modifiers.active} | |
| key={node.sourceLocalId} | |
| onClick={handleClick} | |
| text={node.name} | |
| /> | |
| )} | |
| filterable={false} | |
| > | |
| <Button | |
| text={ | |
| showingSchema ? `display: ${showingSchema.name}` : "Select schema" | |
| } | |
| /> | |
| </Select> | |
| </div> | |
| <div> | |
| {loadingNodes ? ( | |
| <div className="p-2"> | |
| <Spinner /> | |
| <span style={{ marginLeft: 8 }}>Loading nodes…</span> | |
| </div> | |
| ) : ( | |
| <HTMLTable striped interactive> | |
| <thead> | |
| <tr> | |
| <th>Name</th> | |
| <th>Created</th> | |
| <th>Last Modified</th> | |
| <th>Concept</th> | |
| <th>Content</th> | |
| <th>Document</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| {nodes.map((node: PConcept) => ( | |
| <tr key={node.id}> | |
| <td>{node.name}</td> | |
| <td>{node.created}</td> | |
| <td>{node.last_modified}</td> | |
| <td> | |
| <pre>{JSON.stringify({ ...node, Content: null }, null, 2)}</pre> | |
| </td> | |
| <td> | |
| <pre> | |
| {JSON.stringify( | |
| { ...node.Content, Document: null }, | |
| null, | |
| 2, | |
| )} | |
| </pre> | |
| <span | |
| data-link-title={node.Content?.text} | |
| data-link-uid={node.Content?.source_local_id} | |
| > | |
| <span className="rm-page-ref__brackets">[[</span> | |
| <span | |
| className="rm-page-ref rm-page-ref--link" | |
| onClick={async (event) => { | |
| if (event.shiftKey) { | |
| if (node.Content?.source_local_id) { | |
| await window.roamAlphaAPI.ui.rightSidebar.addWindow( | |
| { | |
| window: { | |
| // @ts-expect-error TODO: fix this | |
| "block-uid": node.Content.source_local_id, | |
| type: "outline", | |
| }, | |
| }, | |
| ); | |
| } | |
| } else if (node.Content?.Document?.source_local_id) { | |
| window.roamAlphaAPI.ui.mainWindow.openPage({ | |
| page: { uid: node.Content.Document.source_local_id }, | |
| }); | |
| } | |
| }} | |
| > | |
| {node.Content?.text} | |
| </span> | |
| <span className="rm-page-ref__brackets">]]</span> | |
| </span> | |
| </td> | |
| <td> | |
| <pre>{JSON.stringify(node.Content?.Document, null, 2)}</pre> | |
| </td> | |
| </tr> | |
| ))} | |
| </tbody> | |
| </HTMLTable> | |
| )} | |
| </div> | |
| </div> | |
| ) : ( | |
| <p>No node schemas found</p> | |
| )} | |
| </div> | |
| ); | |
| } | |
| export default AdminPanel; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment