Compare commits

...

11 Commits

Author SHA1 Message Date
Trisha Lim
ebb17e253f make dev generate app.js instead of jazz-inspector.js 2025-03-14 13:15:23 +07:00
Trisha Lim
1ea69dbcc4 add basic json viewer 2025-03-14 12:54:37 +07:00
Trisha Lim
0fd5ec69d3 add covalue search to nav 2025-03-14 12:49:27 +07:00
Trisha Lim
ed9b863040 bare minimum dark styles 2025-03-14 12:19:54 +07:00
Trisha Lim
19de3bb4de add inspector to chat app 2025-03-14 11:43:40 +07:00
Trisha Lim
7b808802c1 replace indigo with blue 2025-03-14 11:38:07 +07:00
Trisha Lim
41f1cbafc3 remove javascript hover styles 2025-03-14 11:36:09 +07:00
Trisha Lim
1a98d9e16c match colors to jazz brand 2025-03-14 11:35:25 +07:00
Trisha Lim
203584df2d add button label 2025-03-14 11:28:25 +07:00
Trisha Lim
29f71d0e30 rewrite styles to tailwind 2025-03-14 11:27:32 +07:00
Trisha Lim
9883eb2c59 install twind 2025-03-14 11:26:51 +07:00
17 changed files with 293 additions and 560 deletions

View File

@@ -16,6 +16,7 @@
"clsx": "^2.0.0",
"hash-slash": "workspace:*",
"jazz-browser-media-images": "workspace:*",
"jazz-inspector": "workspace:*",
"jazz-react": "workspace:*",
"jazz-tools": "workspace:*",
"lucide-react": "^0.274.0",

View File

@@ -1,6 +1,7 @@
import { apiKey } from "@/apiKey.ts";
import { getRandomUsername, inIframe, onChatLoad } from "@/util.ts";
import { useIframeHashRouter } from "hash-slash";
import { JazzInspector } from "jazz-inspector";
import { JazzProvider, useAccount } from "jazz-react";
import { Group, ID } from "jazz-tools";
import { StrictMode } from "react";
@@ -61,6 +62,7 @@ createRoot(document.getElementById("root")!).render(
defaultProfileName={defaultProfileName}
>
<App />
<JazzInspector />
</JazzProvider>
</StrictMode>
</ThemeProvider>,

View File

@@ -16,6 +16,9 @@
"preview": "vite preview"
},
"dependencies": {
"@twind/core": "^1.1.3",
"@twind/preset-autoprefix": "^1.0.7",
"@twind/preset-tailwind": "^1.1.4",
"cojson": "workspace:*",
"jazz-react-core": "workspace:*",
"jazz-tools": "workspace:*"

View File

@@ -1 +1,4 @@
// Import Twind setup
import "./twind.js";
export { JazzInspector } from "./viewer/new-app.js";

View File

@@ -0,0 +1,54 @@
import { defineConfig } from "@twind/core";
import presetAutoprefix from "@twind/preset-autoprefix";
import presetTailwind from "@twind/preset-tailwind";
const stonePalette = {
50: "oklch(0.988281 0.002 75)",
100: "oklch(0.980563 0.002 75)",
200: "oklch(0.917969 0.002 75)",
300: "oklch(0.853516 0.002 75)",
400: "oklch(0.789063 0.002 75)",
500: "oklch(0.726563 0.002 75)",
600: "oklch(0.613281 0.002 75)",
700: "oklch(0.523438 0.002 75)",
800: "oklch(0.412109 0.002 75)",
900: "oklch(0.302734 0.002 75)",
925: "oklch(0.220000 0.002 75)",
950: "oklch(0.193359 0.002 75)",
};
const stonePaletteWithAlpha = { ...stonePalette };
Object.keys(stonePalette).forEach((key) => {
// @ts-ignore
stonePaletteWithAlpha[key] = stonePaletteWithAlpha[key].replace(
")",
"/ <alpha-value>)",
);
});
export default defineConfig({
presets: [presetAutoprefix(), presetTailwind()],
theme: {
extend: {
colors: {
stone: stonePaletteWithAlpha,
gray: stonePaletteWithAlpha,
blue: {
50: "#f5f7ff",
100: "#ebf0fe",
200: "#d6e0fd",
300: "#b3c7fc",
400: "#8aa6f9",
500: "#5870F1",
600: "#3651E7",
700: "#3313F7",
800: "#2A12BE",
900: "#12046A",
950: "#1e1b4b",
DEFAULT: "#3313F7",
},
},
},
},
});

View File

@@ -0,0 +1,8 @@
import { install, tw } from "@twind/core";
import config from "./twind.config";
// Install Twind globally
install(config);
// Export the tw function for use in components
export { tw };

View File

@@ -11,41 +11,10 @@ export const Breadcrumbs: React.FC<BreadcrumbsProps> = ({
onBreadcrumbClick,
}) => {
return (
<div
style={{
position: "relative",
zIndex: 20,
backgroundColor: "rgba(129, 140, 248, 0.1)", // indigo-400/10 equivalent
backdropFilter: "blur(4px)",
borderRadius: "0.5rem",
display: "inline-flex",
paddingLeft: "0.5rem",
paddingRight: "0.5rem",
paddingTop: "0.25rem",
paddingBottom: "0.25rem",
whiteSpace: "pre",
transition: "all",
alignItems: "center",
gap: "0.25rem",
minHeight: "2.5rem",
}}
>
<div className="relative z-20 bg-blue-400/10 backdrop-blur-sm rounded-lg inline-flex px-2 py-1 whitespace-pre transition-all items-center gap-1 min-h-[2.5rem]">
<button
onClick={() => onBreadcrumbClick(-1)}
style={{
display: "flex",
alignItems: "center",
justifyContent: "center",
padding: "0.25rem",
borderRadius: "0.125rem",
transition: "colors",
}}
onMouseOver={(e) =>
(e.currentTarget.style.backgroundColor = "rgba(99, 102, 241, 0.1)")
}
onMouseOut={(e) =>
(e.currentTarget.style.backgroundColor = "transparent")
}
className="flex items-center justify-center p-1 rounded-sm transition-colors"
aria-label="Go to home"
>
Start
@@ -54,26 +23,16 @@ export const Breadcrumbs: React.FC<BreadcrumbsProps> = ({
return (
<span
key={index}
style={{
display: "inline-block",
paddingLeft: index === 0 ? "0.25rem" : "0",
paddingRight: index === path.length - 1 ? "0.25rem" : "0",
}}
className={`inline-block ${index === 0 ? "pl-1" : "pl-0"} ${
index === path.length - 1 ? "pr-1" : "pr-0"
}`}
>
{index === 0 ? null : (
<span style={{ color: "rgba(99, 102, 241, 0.3)" }}>{" / "}</span>
<span className="text-blue-600/30">{" / "}</span>
)}
<button
onClick={() => onBreadcrumbClick(index)}
style={{
color: "rgb(67, 56, 202)",
}}
onMouseOver={(e) =>
(e.currentTarget.style.textDecoration = "underline")
}
onMouseOut={(e) =>
(e.currentTarget.style.textDecoration = "none")
}
className="text-blue hover:underline dark:text-blue-400"
>
{index === 0 ? page.name || "Root" : page.name}
</button>

View File

@@ -161,7 +161,7 @@ const LabelContentPair = ({
content: React.ReactNode;
}) => {
return (
<div style={{ display: "flex", flexDirection: "column", gap: "0.375rem" }}>
<div className="flex flex-col gap-1.5">
<span>{label}</span>
<span>{content}</span>
</div>
@@ -219,34 +219,12 @@ function RenderCoBinaryStream({
const sizeInKB = (file.totalSize || 0) / 1024;
return (
<div
style={{
marginTop: "2rem",
display: "flex",
flexDirection: "column",
gap: "2rem",
}}
>
<div
style={{
display: "grid",
gridTemplateColumns: "repeat(3, 1fr)",
gap: "0.5rem",
maxWidth: "48rem",
}}
>
<div className="mt-8 flex flex-col gap-8">
<div className="grid grid-cols-3 gap-2 max-w-3xl">
<LabelContentPair
label="Mime Type"
content={
<span
style={{
fontFamily: "monospace",
backgroundColor: "rgb(243 244 246)",
borderRadius: "0.25rem",
padding: "0.25rem 0.5rem",
fontSize: "0.875rem",
}}
>
<span className="font-mono bg-gray-100 rounded px-2 py-1 text-sm dark:bg-stone-900">
{mimeType || "No mime type"}
</span>
}
@@ -275,13 +253,7 @@ function RenderCoBinaryStream({
<LabelContentPair
label="Preview"
content={
<div
style={{
backgroundColor: "rgb(249 250 251)",
padding: "0.75rem",
borderRadius: "0.125rem",
}}
>
<div className="bg-gray-50 dark:bg-gray-925 p-3 rounded">
<RenderBlobImage blob={blob} />
</div>
}
@@ -302,30 +274,10 @@ function RenderCoStream({
const userCoIds = streamPerUser.map((stream) => stream.split("_session")[0]);
return (
<div
style={{
display: "grid",
gridTemplateColumns: "repeat(3, 1fr)",
gap: "0.5rem",
}}
>
<div className="grid grid-cols-3 gap-2">
{userCoIds.map((id, idx) => (
<div
style={{
padding: "0.75rem",
borderRadius: "0.5rem",
overflow: "hidden",
backgroundColor: "white",
border: "1px solid #e5e7eb",
cursor: "pointer",
boxShadow: "0 1px 2px 0 rgb(0 0 0 / 0.05)",
transition: "background-color 0.2s",
}}
onMouseOver={(e) =>
(e.currentTarget.style.backgroundColor =
"rgba(243, 244, 246, 0.05)")
}
onMouseOut={(e) => (e.currentTarget.style.backgroundColor = "white")}
className="p-3 rounded-lg overflow-hidden border border-gray-200 cursor-pointer shadow-sm hover:bg-gray-100/5"
key={id}
>
<AccountOrGroupPreview coId={id as CoID<RawCoValue>} node={node} />

View File

@@ -16,66 +16,26 @@ export function GridView({
const entries = Object.entries(data);
return (
<div
style={{
display: "grid",
gridTemplateColumns: "1fr",
gap: "1rem",
padding: "0.5rem",
}}
>
<div className="grid grid-cols-1 gap-4 p-2">
{entries.map(([key, child], childIndex) => (
<div
key={childIndex}
style={{
padding: "0.75rem",
borderRadius: "0.5rem",
overflow: "hidden",
transition: "background-color 0.2s",
...(isCoId(child)
? {
backgroundColor: "white",
border: "1px solid #e5e7eb",
cursor: "pointer",
boxShadow: "0 1px 2px 0 rgba(0, 0, 0, 0.05)",
":hover": {
backgroundColor: "rgba(243, 244, 246, 0.05)",
},
}
: {
backgroundColor: "rgb(249, 250, 251)",
}),
}}
className={`p-3 rounded-lg overflow-hidden transition-colors ${
isCoId(child)
? " border border-gray-200 cursor-pointer shadow-sm hover:bg-gray-100/5"
: "bg-gray-50 dark:bg-gray-925"
}`}
onClick={() =>
isCoId(child) &&
onNavigate([{ coId: child as CoID<RawCoValue>, name: key }])
}
>
<h3
style={{
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
}}
>
<h3 className="overflow-hidden text-ellipsis whitespace-nowrap">
{isCoId(child) ? (
<span
style={{
fontWeight: 500,
display: "flex",
justifyContent: "space-between",
}}
>
<span className="font-medium flex justify-between">
{key}
<div
style={{
padding: "0.25rem 0.5rem",
fontSize: "0.75rem",
backgroundColor: "rgb(243, 244, 246)",
borderRadius: "0.25rem",
}}
>
<div className="py-1 px-2 text-xs bg-gray-100 rounded dark:bg-gray-900">
<ResolveIcon coId={child as CoID<RawCoValue>} node={node} />
</div>
</span>
@@ -83,7 +43,7 @@ export function GridView({
<span>{key}</span>
)}
</h3>
<div style={{ marginTop: "0.5rem", fontSize: "0.875rem" }}>
<div className="mt-2 text-sm">
{isCoId(child) ? (
<CoMapPreview coId={child as CoID<RawCoValue>} node={node} />
) : (

View File

@@ -26,61 +26,24 @@ export function JazzInspector({ position = "right" }: { position?: Position }) {
if (coValueId) {
setPage(coValueId);
}
setCoValueId("");
};
const positionStyles = {
"bottom right": {
bottom: 0,
right: 0,
},
"bottom left": {
bottom: 0,
left: 0,
},
"top right": {
top: 0,
right: 0,
},
"top left": {
top: 0,
left: 0,
},
right: {
right: 0,
top: "50%",
transform: "translateY(-50%)",
},
left: {
left: 0,
top: "50%",
transform: "translateY(-50%)",
},
const positionClasses = {
"bottom right": "bottom-0 right-0",
"bottom left": "bottom-0 left-0",
"top right": "top-0 right-0",
"top left": "top-0 left-0",
right: "right-0 top-1/2 -translate-y-1/2",
left: "left-0 top-1/2 -translate-y-1/2",
};
if (!open) {
return (
<button
onClick={() => setOpen(true)}
style={{
position: "fixed",
margin: "1rem",
backgroundColor: "white",
border: "1px solid #e5e7eb",
borderRadius: "0.5rem",
padding: "0.5rem",
boxShadow:
"0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)",
...positionStyles[position],
}}
className={`fixed w-10 h-10 inline-block bottom-0 right-0 m-4 bg-white border rounded-md shadow-md p-1.5 ${positionClasses[position]}`}
>
<svg
style={{
width: "1.5rem",
height: "1.5rem",
position: "relative",
left: "-1px",
top: "1px",
}}
className="w-full h-auto relative -left-px text-blue"
xmlns="http://www.w3.org/2000/svg"
width="119"
height="115"
@@ -91,35 +54,39 @@ export function JazzInspector({ position = "right" }: { position?: Position }) {
fillRule="evenodd"
clipRule="evenodd"
d="M118.179 23.8277V0.167999C99.931 7.5527 79.9854 11.6192 59.0897 11.6192C47.1466 11.6192 35.5138 10.2908 24.331 7.7737V30.4076V60.1508C23.2955 59.4385 22.1568 58.8458 20.9405 58.3915C18.1732 57.358 15.128 57.0876 12.1902 57.6145C9.2524 58.1414 6.5539 59.4419 4.4358 61.3516C2.3178 63.2613 0.875401 65.6944 0.291001 68.3433C-0.293399 70.9921 0.00659978 73.7377 1.1528 76.2329C2.2991 78.728 4.2403 80.861 6.7308 82.361C9.2214 83.862 12.1495 84.662 15.1448 84.662C15.6054 84.662 15.8365 84.662 16.0314 84.659C26.5583 84.449 35.042 75.9656 35.2513 65.4386C35.2534 65.3306 35.2544 65.2116 35.2548 65.0486L35.2552 64.7149V64.5521V61.0762V32.1993C43.0533 33.2324 51.0092 33.7656 59.0897 33.7656C59.6696 33.7656 60.2489 33.7629 60.8276 33.7574V89.696C59.792 88.983 58.6533 88.391 57.437 87.936C54.6697 86.903 51.6246 86.632 48.6867 87.159C45.7489 87.686 43.0504 88.987 40.9323 90.896C38.8143 92.806 37.3719 95.239 36.7875 97.888C36.2032 100.537 36.5031 103.283 37.6494 105.778C38.7956 108.273 40.7368 110.405 43.2273 111.906C45.7179 113.406 48.646 114.207 51.6414 114.207C52.1024 114.207 52.3329 114.207 52.5279 114.203C63.0548 113.994 71.5385 105.51 71.7478 94.983C71.7517 94.788 71.7517 94.558 71.7517 94.097V90.621V33.3266C83.962 32.4768 95.837 30.4075 107.255 27.2397V59.9017C106.219 59.1894 105.081 58.5966 103.864 58.1424C101.097 57.1089 98.052 56.8384 95.114 57.3653C92.176 57.8922 89.478 59.1927 87.36 61.1025C85.242 63.0122 83.799 65.4453 83.215 68.0941C82.631 70.743 82.931 73.4886 84.077 75.9837C85.223 78.4789 87.164 80.612 89.655 82.112C92.145 83.612 95.073 84.413 98.069 84.413C98.53 84.413 98.76 84.413 98.955 84.409C109.482 84.2 117.966 75.7164 118.175 65.1895C118.179 64.9945 118.179 64.764 118.179 64.3029V60.8271V23.8277Z"
fill="#3313F7"
fill="currentColor"
/>
</svg>
<span className="sr-only">Open Jazz Inspector</span>
</button>
);
}
return (
<div
style={{
position: "fixed",
bottom: 0,
left: 0,
width: "100%",
backgroundColor: "white",
borderTop: "1px solid #e5e7eb",
padding: "1rem",
}}
>
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
marginBottom: "1rem",
}}
>
<div className="fixed bottom-0 left-0 w-full bg-white border-t border-gray-200 p-4 dark:border-stone-900 dark:bg-stone-925">
<div className="flex items-center gap-4 mb-4">
<Breadcrumbs path={path} onBreadcrumbClick={goToIndex} />
<button onClick={() => setOpen(false)}>Close</button>
<div className="flex-1">
<form onSubmit={handleCoValueIdSubmit}>
{path.length !== 0 && (
<input
className="border p-2 rounded-lg min-w-[21rem] font-mono"
placeholder="co_z1234567890abcdef123456789"
value={coValueId}
onChange={(e) =>
setCoValueId(e.target.value as CoID<RawCoValue>)
}
/>
)}
</form>
</div>
<button
className="ml-auto"
type="button"
onClick={() => setOpen(false)}
>
Close
</button>
</div>
<PageStack
@@ -131,80 +98,32 @@ export function JazzInspector({ position = "right" }: { position?: Position }) {
<form
onSubmit={handleCoValueIdSubmit}
aria-hidden={path.length !== 0}
style={{
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
gap: "0.5rem",
height: "100%",
width: "100%",
marginBottom: "5rem",
transition: "all 150ms",
opacity: path.length > 0 ? 0 : 1,
transform:
path.length > 0 ? "translateY(-0.5rem) scale(0.95)" : "none",
}}
className={`flex flex-col justify-center items-center gap-2 h-full w-full mb-20 transition-all duration-150 ${
path.length > 0
? "opacity-0 translate-y-[-0.5rem] scale-95"
: "opacity-100"
}`}
>
<fieldset
style={{
display: "flex",
flexDirection: "column",
gap: "0.5rem",
fontSize: "0.875rem",
}}
>
<h2
style={{
fontSize: "1.875rem",
fontWeight: 500,
color: "#030712",
textAlign: "center",
marginBottom: "1rem",
}}
>
<fieldset className="flex flex-col gap-2 text-sm">
<h2 className="text-lg font-medium mb-4 text-stone-900 dark:text-white">
Jazz CoValue Inspector
</h2>
<input
style={{
border: "1px solid #e5e7eb",
padding: "1rem",
borderRadius: "0.5rem",
minWidth: "21rem",
fontFamily: "monospace",
}}
className="border border-gray-200 p-4 rounded-lg min-w-[21rem] font-mono"
placeholder="co_z1234567890abcdef123456789"
value={coValueId}
onChange={(e) => setCoValueId(e.target.value as CoID<RawCoValue>)}
/>
<button
type="submit"
style={{
backgroundColor: "rgb(99 102 241)",
color: "white",
padding: "0.5rem 1rem",
borderRadius: "0.375rem",
}}
onMouseOver={(e) =>
(e.currentTarget.style.backgroundColor =
"rgba(99 102 241, 0.8)")
}
onMouseOut={(e) =>
(e.currentTarget.style.backgroundColor = "rgb(99 102 241)")
}
className="bg-blue text-white py-2 px-4 rounded-md hover:bg-blue-800"
>
Inspect
</button>
<hr />
<button
type="button"
style={{
border: "1px solid #e5e7eb",
display: "inline-block",
padding: "0.375rem 0.5rem",
color: "black",
borderRadius: "0.375rem",
}}
className="border border-gray-200 inline-block py-1.5 px-2 rounded-md"
onClick={() => {
setCoValueId(me._raw.id);
setPage(me._raw.id);

View File

@@ -27,28 +27,8 @@ export function PageStack({
const index = path.length - 1;
return (
<div
style={{
position: "relative",
marginTop: "1rem",
height: "40vh",
overflowY: "auto",
}}
>
{children && (
<div
style={{
position: "absolute",
top: 0,
right: 0,
bottom: 0,
left: 0,
paddingBottom: "5rem",
}}
>
{children}
</div>
)}
<div className="relative mt-4 h-[40vh] overflow-y-auto">
{children && <div className="absolute inset-0 pb-20">{children}</div>}
{node && page && (
<Page
coId={page.coId}
@@ -57,15 +37,13 @@ export function PageStack({
onHeaderClick={goBack}
onNavigate={addPages}
isTopLevel={index === path.length - 1}
className="transition-transform transition-opacity duration-300 ease-out"
style={{
transform: `translateZ(${(index - path.length + 1) * 200}px) scale(${
1 - (path.length - index - 1) * 0.05
}) translateY(${-(index - path.length + 1) * -4}%)`,
opacity: 1 - (path.length - index - 1) * 0.05,
zIndex: index,
transitionProperty: "transform, opacity",
transitionDuration: "0.3s",
transitionTimingFunction: "ease-out",
}}
/>
)}

View File

@@ -16,6 +16,7 @@ type PageProps = {
onHeaderClick?: () => void;
isTopLevel?: boolean;
style: React.CSSProperties;
className?: string;
};
export function Page({
@@ -25,6 +26,7 @@ export function Page({
onNavigate,
onHeaderClick,
style,
className = "",
isTopLevel,
}: PageProps) {
const { value, snapshot, type, extendedType } = useResolvedCoValue(
@@ -52,31 +54,15 @@ export function Page({
return (
<div
style={{
position: "absolute",
zIndex: 1,
inset: 0,
backgroundColor: "white",
borderWidth: "1px",
borderColor: "rgba(0, 0, 0, 0.05)",
borderRadius: "0.75rem",
boxShadow:
"0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)",
padding: "1.5rem",
width: "100%",
height: "100%",
backgroundClip: "padding-box",
}}
style={style}
className={
className +
" absolute z-10 inset-0 border rounded-xl shadow-lg p-6 w-full h-full bg-clip-padding"
}
>
{!isTopLevel && (
<div
style={{
position: "absolute",
left: 0,
right: 0,
top: 0,
height: "2.5rem",
}}
className="absolute left-0 right-0 top-0 h-10"
aria-label="Back"
onClick={() => {
onHeaderClick?.();
@@ -84,72 +70,30 @@ export function Page({
aria-hidden="true"
></div>
)}
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
marginBottom: "1rem",
}}
>
<div
style={{ display: "flex", flexDirection: "column", gap: "0.5rem" }}
>
<h2
style={{
fontSize: "1.5rem",
fontWeight: "700",
display: "flex",
alignItems: "flex-start",
flexDirection: "column",
gap: "0.25rem",
}}
>
<div className="flex justify-between items-center mb-4">
<div className="flex flex-col gap-2">
<h2 className="text-2xl font-bold flex flex-col items-start gap-1">
<span>
{name}
{typeof snapshot === "object" && "name" in snapshot ? (
<span style={{ color: "rgb(75, 85, 99)", fontWeight: "500" }}>
<span className="text-gray-600 font-medium">
{" "}
{(snapshot as { name: string }).name}
</span>
) : null}
</span>
</h2>
<div style={{ display: "flex", alignItems: "center", gap: "0.5rem" }}>
<span
style={{
fontSize: "0.75rem",
color: "rgb(55, 65, 81)",
fontWeight: "500",
padding: "0.125rem 0.25rem",
marginLeft: "-0.125rem",
borderRadius: "0.25rem",
backgroundColor: "rgba(55, 65, 81, 0.05)",
display: "inline-block",
fontFamily: "monospace",
}}
>
<div className="flex items-center gap-2">
<span className="text-xs text-gray-700 font-medium py-0.5 px-1 -ml-0.5 rounded bg-gray-700/5 inline-block font-mono">
{type && <TypeIcon type={type} extendedType={extendedType} />}
</span>
<span
style={{
fontSize: "0.75rem",
color: "rgb(55, 65, 81)",
fontWeight: "500",
padding: "0.125rem 0.25rem",
marginLeft: "-0.125rem",
borderRadius: "0.25rem",
backgroundColor: "rgba(55, 65, 81, 0.05)",
display: "inline-block",
fontFamily: "monospace",
}}
>
<span className="text-xs text-gray-700 font-medium py-0.5 px-1 -ml-0.5 rounded bg-gray-700/5 inline-block font-mono">
{coId}
</span>
</div>
</div>
</div>
<div style={{ overflow: "auto", maxHeight: "calc(100% - 4rem)" }}>
<div className="overflow-auto max-h-[calc(100%-4rem)]">
{type === "costream" ? (
<CoStreamView
data={snapshot}
@@ -163,13 +107,7 @@ export function Page({
<TableView data={snapshot} node={node} onNavigate={onNavigate} />
)}
{extendedType !== "account" && extendedType !== "group" && (
<div
style={{
fontSize: "0.75rem",
color: "rgb(107, 114, 128)",
marginTop: "1rem",
}}
>
<div className="text-xs text-gray-500 mt-4">
Owned by{" "}
<AccountOrGroupPreview
coId={value.group.id}

View File

@@ -51,45 +51,23 @@ export function TableView({
return (
<div>
<table
style={{
minWidth: "100%",
borderSpacing: 0,
borderCollapse: "collapse",
}}
>
<thead
style={{
position: "sticky",
top: 0,
borderBottom: "1px solid #e5e7eb",
}}
>
<table className="min-w-full border-spacing-0 border-collapse">
<thead className="sticky top-0 border-b border-gray-200">
<tr>
{["", ...keys].map((key) => (
<th
key={key}
style={{
padding: "0.75rem 1rem",
backgroundColor: "#f9fafb",
textAlign: "left",
fontSize: "0.75rem",
fontWeight: 500,
color: "#6b7280",
borderRadius: "0.25rem",
}}
className="p-3 bg-gray-50 dark:bg-gray-925 text-left text-xs font-medium text-gray-500 rounded"
>
{key}
</th>
))}
</tr>
</thead>
<tbody
style={{ backgroundColor: "white", borderTop: "1px solid #e5e7eb" }}
>
<tbody className=" border-t border-gray-200">
{resolvedRows.slice(0, visibleRowsCount).map((item, index) => (
<tr key={index}>
<td style={{ padding: "0.25rem 0.25rem" }}>
<td className="p-1">
<button
onClick={() =>
onNavigate([
@@ -99,21 +77,7 @@ export function TableView({
},
])
}
style={{
padding: "1rem",
whiteSpace: "nowrap",
fontSize: "0.875rem",
color: "#6b7280",
borderRadius: "0.25rem",
}}
onMouseOver={(e) => {
e.currentTarget.style.backgroundColor = "#f3f4f6";
e.currentTarget.style.color = "#3b82f6";
}}
onMouseOut={(e) => {
e.currentTarget.style.backgroundColor = "transparent";
e.currentTarget.style.color = "#6b7280";
}}
className="p-4 whitespace-nowrap text-sm text-gray-500 rounded hover:bg-gray-100 hover:text-blue-500"
>
<LinkIcon />
</button>
@@ -121,12 +85,7 @@ export function TableView({
{keys.map((key) => (
<td
key={key}
style={{
padding: "1rem",
whiteSpace: "nowrap",
fontSize: "0.875rem",
color: "#6b7280",
}}
className="p-4 whitespace-nowrap text-sm text-gray-500"
>
<ValueRenderer
json={(item.snapshot as JsonObject)[key]}
@@ -153,38 +112,18 @@ export function TableView({
))}
</tbody>
</table>
<div
style={{
padding: "1rem 0",
color: "#6b7280",
display: "flex",
alignItems: "center",
justifyContent: "space-between",
gap: "0.5rem",
}}
>
<div className="py-4 text-gray-500 flex items-center justify-between gap-2">
<span>
Showing {Math.min(visibleRowsCount, coIdArray.length)} of{" "}
{coIdArray.length}
</span>
{hasMore && (
<div style={{ textAlign: "center" }}>
<div className="text-center">
<button
onClick={loadMore}
style={{
padding: "0.5rem 1rem",
backgroundColor: "#3b82f6",
color: "white",
borderRadius: "0.25rem",
}}
onMouseOver={(e) => {
e.currentTarget.style.backgroundColor = "#2563eb";
}}
onMouseOut={(e) => {
e.currentTarget.style.backgroundColor = "#3b82f6";
}}
className="px-4 py-2 bg-blue text-white rounded hover:bg-blue-800"
>
Load More
Load more
</button>
</div>
)}

View File

@@ -25,7 +25,7 @@ export const TypeIcon = ({
const iconKey = extendedType || type;
const icon = iconMap[iconKey as keyof typeof iconMap];
return icon ? <span style={{ fontFamily: "monospace" }}>{icon}</span> : null;
return icon ? <span className="font-mono">{icon}</span> : null;
};
export const ResolveIcon = ({
@@ -38,17 +38,10 @@ export const ResolveIcon = ({
const { type, extendedType, snapshot } = useResolvedCoValue(coId, node);
if (snapshot === "unavailable" && !type) {
return <div style={{ color: "#4B5563", fontWeight: 500 }}>Unavailable</div>;
return <div className="text-gray-600 font-medium">Unavailable</div>;
}
if (!type)
return (
<div
style={{ whiteSpace: "pre", width: "3.5rem", fontFamily: "monospace" }}
>
{" "}
</div>
);
if (!type) return <div className="whitespace-pre w-14 font-mono"> </div>;
return <TypeIcon type={type} extendedType={extendedType} />;
};

View File

@@ -17,32 +17,24 @@ export function ValueRenderer({
compact?: boolean;
onCoIDClick?: (childNode: CoID<RawCoValue>) => void;
}) {
const [isExpanded, setIsExpanded] = useState(false);
if (typeof json === "undefined" || json === undefined) {
return <span style={{ color: "#9CA3AF" }}>undefined</span>;
return <span className="text-gray-400">undefined</span>;
}
if (json === null) {
return <span style={{ color: "#9CA3AF" }}>null</span>;
return <span className="text-gray-400">null</span>;
}
if (typeof json === "string" && json.startsWith("co_")) {
const linkStyle = onCoIDClick
? {
color: "#3B82F6",
cursor: "pointer",
display: "inline-flex",
gap: "0.25rem",
alignItems: "center",
}
: {
display: "inline-flex",
gap: "0.25rem",
alignItems: "center",
};
const linkClasses = onCoIDClick
? "text-blue-500 cursor-pointer inline-flex gap-1 items-center"
: "inline-flex gap-1 items-center";
return (
<span
style={linkStyle}
className={linkClasses}
onClick={() => {
onCoIDClick?.(json as CoID<RawCoValue>);
}}
@@ -55,33 +47,22 @@ export function ValueRenderer({
if (typeof json === "string") {
return (
<span style={{ color: "#064E3B", fontFamily: "monospace" }}>{json}</span>
<span className="text-teal-900 font-mono dark:text-teal-200">{json}</span>
);
}
if (typeof json === "number") {
return <span style={{ color: "#A855F7" }}>{json}</span>;
return <span className="text-purple-500 dark:text-purple-200">{json}</span>;
}
if (typeof json === "boolean") {
const booleanStyle = {
color: json ? "#15803D" : "#B45309",
backgroundColor: json
? "rgba(34, 197, 94, 0.05)"
: "rgba(245, 158, 11, 0.05)",
fontFamily: "monospace",
display: "inline-block",
padding: "0.125rem 0.25rem",
borderRadius: "0.25rem",
};
return <span style={booleanStyle}>{json.toString()}</span>;
}
if (Array.isArray(json)) {
return (
<span title={JSON.stringify(json)}>
Array <span style={{ color: "#6B7280" }}>({json.length})</span>
<span
className={`inline-block py-0.5 px-1 rounded ${
json ? "text-green-700 bg-green-50" : "text-amber-700 bg-amber-50"
} font-mono`}
>
{json.toString()}
</span>
);
}
@@ -90,23 +71,31 @@ export function ValueRenderer({
return (
<span
title={JSON.stringify(json, null, 2)}
style={{
display: "inline-block",
maxWidth: "16rem",
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
}}
className="inline-block max-w-64"
>
{compact ? (
<span>
Object{" "}
<span style={{ color: "#6B7280" }}>
({Object.keys(json).length})
</span>
<span className="text-gray-500">({Object.keys(json).length})</span>
<pre className="mt-1 text-sm whitespace-pre-wrap">
{isExpanded
? JSON.stringify(json, null, 2)
: JSON.stringify(json, null, 2)
.split("\n")
.slice(0, 3)
.join("\n") + (Object.keys(json).length > 2 ? "\n..." : "")}
</pre>
<button
onClick={() => setIsExpanded(!isExpanded)}
className="text-xs text-gray-500 hover:text-gray-700"
>
{isExpanded ? "Show less" : "Show more"}
</button>
</span>
) : (
JSON.stringify(json, null, 2)
<pre className="whitespace-pre-wrap">
{JSON.stringify(json, null, 2)}
</pre>
)}
</span>
);
@@ -131,22 +120,14 @@ export const CoMapPreview = ({
if (!snapshot) {
return (
<div
style={{
borderRadius: "0.25rem",
backgroundColor: "#F3F4F6",
animation: "pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite",
whiteSpace: "pre",
width: "6rem",
}}
>
<div className="rounded bg-gray-100 animate-pulse whitespace-pre w-24">
{" "}
</div>
);
}
if (snapshot === "unavailable" && !value) {
return <div style={{ color: "#6B7280" }}>Unavailable</div>;
return <div className="text-gray-500">Unavailable</div>;
}
if (extendedType === "image" && isBrowserImage(snapshot)) {
@@ -154,16 +135,9 @@ export const CoMapPreview = ({
<div>
<img
src={snapshot.placeholderDataURL}
style={{
width: "2rem",
height: "2rem",
border: "2px solid white",
boxShadow:
"0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)",
margin: "0.5rem 0",
}}
className="w-8 h-8 border-2 border-white shadow my-2"
/>
<span style={{ color: "#6B7280", fontSize: "0.875rem" }}>
<span className="text-gray-500 text-sm">
{snapshot.originalSize[0]} x {snapshot.originalSize[1]}
</span>
</div>
@@ -174,9 +148,7 @@ export const CoMapPreview = ({
return (
<div>
Record{" "}
<span style={{ color: "#6B7280" }}>
({Object.keys(snapshot).length})
</span>
<span className="text-gray-500">({Object.keys(snapshot).length})</span>
</div>
);
}
@@ -185,7 +157,7 @@ export const CoMapPreview = ({
return (
<div>
List{" "}
<span style={{ color: "#6B7280" }}>
<span className="text-gray-500">
({(snapshot as unknown as []).length})
</span>
</div>
@@ -193,19 +165,13 @@ export const CoMapPreview = ({
}
return (
<div style={{ display: "flex", flexDirection: "column", gap: "0.5rem" }}>
<div
style={{
display: "grid",
gridTemplateColumns: "auto 1fr",
gap: "0.5rem",
}}
>
<div className="flex flex-col gap-2">
<div className="grid grid-cols-[auto_1fr] gap-2">
{Object.entries(snapshot)
.slice(0, limit)
.map(([key, value]) => (
<React.Fragment key={key}>
<span style={{ fontWeight: "bold" }}>{key}: </span>
<span className="font-bold">{key}: </span>
<span>
<ValueRenderer json={value} />
</span>
@@ -213,9 +179,7 @@ export const CoMapPreview = ({
))}
</div>
{Object.entries(snapshot).length > limit && (
<div
style={{ textAlign: "left", fontSize: "0.875rem", color: "#6B7280" }}
>
<div className="text-left text-sm text-gray-500">
{Object.entries(snapshot).length - limit} more
</div>
)}
@@ -262,18 +226,13 @@ export function AccountOrGroupPreview({
const displayName = extendedType === "account" ? name || "Account" : "Group";
const displayText = showId ? `${displayName} (${coId})` : displayName;
const props = onClick
? {
onClick: () => onClick(displayName),
style: {
color: "#3B82F6",
cursor: "pointer",
textDecoration: "underline",
},
}
: {
style: { color: "#6B7280" },
};
const className = onClick
? "text-blue cursor-pointer underline dark:text-blue-400"
: "text-gray-500";
return <span {...props}>{displayText}</span>;
return (
<span className={className} onClick={() => onClick?.(displayName)}>
{displayText}
</span>
);
}

View File

@@ -16,7 +16,7 @@ export default defineConfig({
entry: path.resolve(__dirname, "src/app.tsx"),
name: "JazzInspector",
// the proper extensions will be added
fileName: "jazz-inspector",
fileName: "app",
formats: ["es"],
},
},

65
pnpm-lock.yaml generated
View File

@@ -67,6 +67,9 @@ importers:
jazz-browser-media-images:
specifier: workspace:*
version: link:../../packages/jazz-browser-media-images
jazz-inspector:
specifier: workspace:*
version: link:../../packages/jazz-inspector
jazz-react:
specifier: workspace:*
version: link:../../packages/jazz-react
@@ -1712,6 +1715,15 @@ importers:
packages/jazz-inspector:
dependencies:
'@twind/core':
specifier: ^1.1.3
version: 1.1.3(typescript@5.6.3)
'@twind/preset-autoprefix':
specifier: ^1.0.7
version: 1.0.7(@twind/core@1.1.3(typescript@5.6.3))(typescript@5.6.3)
'@twind/preset-tailwind':
specifier: ^1.1.4
version: 1.1.4(@twind/core@1.1.3(typescript@5.6.3))(typescript@5.6.3)
cojson:
specifier: workspace:*
version: link:../cojson
@@ -5238,6 +5250,35 @@ packages:
'@tsconfig/node22@22.0.0':
resolution: {integrity: sha512-twLQ77zevtxobBOD4ToAtVmuYrpeYUh3qh+TEp+08IWhpsrIflVHqQ1F1CiPxQGL7doCdBIOOCF+1Tm833faNg==}
'@twind/core@1.1.3':
resolution: {integrity: sha512-/B/aNFerMb2IeyjSJy3SJxqVxhrT77gBDknLMiZqXIRr4vNJqiuhx7KqUSRzDCwUmyGuogkamz+aOLzN6MeSLw==}
engines: {node: '>=14.15.0'}
peerDependencies:
typescript: ^4.8.4
peerDependenciesMeta:
typescript:
optional: true
'@twind/preset-autoprefix@1.0.7':
resolution: {integrity: sha512-3wmHO0pG/CVxYBNZUV0tWcL7CP0wD5KpyWAQE/KOalWmOVBj+nH6j3v6Y3I3pRuMFaG5DC78qbYbhA1O11uG3w==}
engines: {node: '>=14.15.0'}
peerDependencies:
'@twind/core': ^1.1.0
typescript: ^4.8.4
peerDependenciesMeta:
typescript:
optional: true
'@twind/preset-tailwind@1.1.4':
resolution: {integrity: sha512-zv85wrP/DW4AxgWrLfH7kyGn/KJF3K04FMLVl2AjoxZGYdCaoZDkL8ma3hzaKQ+WGgBFRubuB/Ku2Rtv/wjzVw==}
engines: {node: '>=14.15.0'}
peerDependencies:
'@twind/core': ^1.1.0
typescript: ^4.8.4
peerDependenciesMeta:
typescript:
optional: true
'@types/argparse@1.0.38':
resolution: {integrity: sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA==}
@@ -10833,6 +10874,9 @@ packages:
structured-headers@0.4.1:
resolution: {integrity: sha512-0MP/Cxx5SzeeZ10p/bZI0S6MpgD+yxAhi1BOQ34jgnMXsCq3j1t6tQnZu+KdlL7dvJTLT3g9xN8tl10TqgFMcg==}
style-vendorizer@2.2.3:
resolution: {integrity: sha512-/VDRsWvQAgspVy9eATN3z6itKTuyg+jW1q6UoTCQCFRqPDw8bi3E1hXIKnGw5LvXS2AQPuJ7Af4auTLYeBOLEg==}
styleq@0.1.3:
resolution: {integrity: sha512-3ZUifmCDCQanjeej1f6kyl/BeP/Vae5EYkQ9iJfUm/QwZvlgnZzyflqAsAWYURdtea8Vkvswu2GrC57h3qffcA==}
@@ -15440,6 +15484,25 @@ snapshots:
'@tsconfig/node22@22.0.0': {}
'@twind/core@1.1.3(typescript@5.6.3)':
dependencies:
csstype: 3.1.3
optionalDependencies:
typescript: 5.6.3
'@twind/preset-autoprefix@1.0.7(@twind/core@1.1.3(typescript@5.6.3))(typescript@5.6.3)':
dependencies:
'@twind/core': 1.1.3(typescript@5.6.3)
style-vendorizer: 2.2.3
optionalDependencies:
typescript: 5.6.3
'@twind/preset-tailwind@1.1.4(@twind/core@1.1.3(typescript@5.6.3))(typescript@5.6.3)':
dependencies:
'@twind/core': 1.1.3(typescript@5.6.3)
optionalDependencies:
typescript: 5.6.3
'@types/argparse@1.0.38': {}
'@types/aria-query@5.0.4': {}
@@ -21733,6 +21796,8 @@ snapshots:
structured-headers@0.4.1: {}
style-vendorizer@2.2.3: {}
styleq@0.1.3: {}
stylis@4.2.0: {}