sanity-plugin-graph-view 3.0.0-v3-studio.7 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,366 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { useUserColorManager, useClient, definePlugin } from "sanity";
3
+ import { useRouter, route } from "sanity/router";
4
+ import BezierEasing from "bezier-easing";
5
+ import deepEqual from "deep-equal";
6
+ import { rgba } from "polished";
7
+ import { useEffect, useState, useCallback } from "react";
8
+ import { ForceGraph2D } from "react-force-graph";
9
+ import { v4 } from "uuid";
10
+ import { black, gray, white, hues, COLOR_HUES } from "@sanity/color";
11
+ import { useTheme } from "@sanity/ui";
12
+ import styled from "styled-components";
13
+ const GraphRoot = styled.div`
14
+ font-family: ${({ theme }) => theme.fonts.text.family};
15
+ position: absolute;
16
+ top: 0;
17
+ left: 0;
18
+ width: 100%;
19
+ height: 100%;
20
+ background: ${black.hex};
21
+ `, GraphWrapper = styled.div`
22
+ position: relative;
23
+ width: 100%;
24
+ height: 100%;
25
+ `, HoverNode = styled.div`
26
+ font-family: ${({ theme }) => theme.fonts.text.family};
27
+ display: none;
28
+ position: absolute;
29
+ bottom: ${({ theme }) => theme.space[0]}px;
30
+ left: 50%;
31
+ transform: translate3d(-50%, 0, 0);
32
+ background: var(--component-bg);
33
+ border-radius: ${({ theme }) => theme.radius[2]}px;
34
+ padding: ${({ theme }) => theme.space[2]}px;
35
+ z-index: 1000;
36
+
37
+ &:empty {
38
+ display: none;
39
+ }
40
+ `, Legend = styled.div`
41
+ color: #ccc;
42
+ position: absolute;
43
+ top: ${({ theme }) => theme.space[4]}px;
44
+ left: ${({ theme }) => theme.space[4]}px;
45
+
46
+ & > div {
47
+ margin: 5px 0;
48
+ }
49
+ `, LegendRow = styled.div`
50
+ display: flex;
51
+ `, LegendBadge = styled.div`
52
+ width: 1.25em;
53
+ height: 1.25em;
54
+ background: currentColor;
55
+ border-radius: 50%;
56
+ margin-right: ${({ theme }) => theme.space[2]}px;
57
+ `;
58
+ function useListen(query, params, options, onUpdate, dependencies, client) {
59
+ useEffect(() => {
60
+ const subscription = client.listen(query, params, options).subscribe((update) => {
61
+ onUpdate(update);
62
+ });
63
+ return () => {
64
+ subscription.unsubscribe();
65
+ };
66
+ }, [...dependencies, client]);
67
+ }
68
+ function useFetchDocuments(query, onFetch, dependencies, client) {
69
+ useEffect(() => {
70
+ client.fetch(query).then((result) => {
71
+ onFetch(result);
72
+ });
73
+ }, [...dependencies, client]);
74
+ }
75
+ function sizeOf(value) {
76
+ return value === null ? 0 : typeof value == "object" ? Object.entries(value).reduce((total, [k, v]) => total + sizeOf(k) + sizeOf(v), 0) : Array.isArray(value) ? Object.entries(value).reduce((total, v) => total + sizeOf(v), 0) : typeof value == "string" ? value.length : 1;
77
+ }
78
+ function loadImage(url, w, h) {
79
+ return new Promise((resolve) => {
80
+ const img = new Image(w, h);
81
+ img.onload = () => {
82
+ resolve(img);
83
+ }, img.onerror = (event) => {
84
+ console.log("Image error", event), resolve(null);
85
+ }, img.src = url;
86
+ });
87
+ }
88
+ function sortBy(array, f) {
89
+ return array.sort((a, b) => {
90
+ const va = f(a), vb = f(b);
91
+ return va < vb ? -1 : va > vb ? 1 : 0;
92
+ });
93
+ }
94
+ function truncate(s, limit) {
95
+ return s.length > limit ? `${s.substring(0, limit)}\u2026` : s;
96
+ }
97
+ const DEFAULT_QUERY = `
98
+ *[
99
+ !(_id in path("_.*")) &&
100
+ !(_type match "system.*") &&
101
+ !(_type match "sanity.*")
102
+ ]
103
+ `, fadeEasing = BezierEasing(0, 0.9, 1, 1), softEasing = BezierEasing(0.25, 0.1, 0, 1), idleTimeout = 1e4, imageSize = 40;
104
+ function getTopDocTypes(counts) {
105
+ return sortBy(Object.keys(counts), (docType) => counts[docType] || 0).reverse().slice(0, 10);
106
+ }
107
+ function formatDocType(docType) {
108
+ return (docType.substring(0, 1).toUpperCase() + docType.substring(1)).replace(/\./g, " ").replace(/[A-Z]/g, " $&").trim();
109
+ }
110
+ function getDocTypeCounts(docs) {
111
+ const types = {};
112
+ for (const doc of docs)
113
+ types[doc._type] = (types[doc._type] || 0) + 1;
114
+ return types;
115
+ }
116
+ function labelFor(doc) {
117
+ return `${doc.title || doc.name || doc._id}`.trim();
118
+ }
119
+ function valueFor(doc, maxSize) {
120
+ return 5 + 100 * (sizeOf(doc) / maxSize);
121
+ }
122
+ function findRefs(obj, dest = []) {
123
+ if (obj !== null) {
124
+ if (typeof obj == "object")
125
+ for (const [k, v] of Object.entries(obj))
126
+ k === "_ref" && typeof v == "string" && v.length > 0 && dest.push(stripDraftId(v)), findRefs(v, dest);
127
+ else if (Array.isArray(obj))
128
+ for (const v of obj)
129
+ findRefs(v, dest);
130
+ }
131
+ return dest;
132
+ }
133
+ function stripDraftId(id) {
134
+ return id.replace(/^drafts\./, "");
135
+ }
136
+ function deduplicateDrafts(docs) {
137
+ const deduped = {};
138
+ for (const doc of docs)
139
+ doc._id.startsWith("drafts.") || (deduped[doc._id] = doc);
140
+ for (const doc of docs)
141
+ if (doc._id.startsWith("drafts.")) {
142
+ const id = stripDraftId(doc._id);
143
+ deduped[id] = Object.assign(doc, { _id: id });
144
+ }
145
+ return Object.values(deduped);
146
+ }
147
+ class Users {
148
+ _users = [];
149
+ async getById(id, client) {
150
+ let user = this._users.find((u) => u._id === id);
151
+ return user || (user = await client.users.getById(id), this._users.push(user), user.image = await loadImage(
152
+ user.imageUrl || "https://raw.githubusercontent.com/sanity-io/sanity-plugin-graph-view/main/assets/head-silhouette.jpg",
153
+ imageSize,
154
+ imageSize
155
+ )), user;
156
+ }
157
+ }
158
+ class Session {
159
+ id = null;
160
+ user = null;
161
+ doc = null;
162
+ lastActive = 0;
163
+ startTime = 0;
164
+ angle = 0;
165
+ }
166
+ class GraphData {
167
+ sessions = [];
168
+ data;
169
+ constructor(docs = []) {
170
+ const docsById = {};
171
+ for (const doc of docs)
172
+ docsById[doc._id] = doc;
173
+ this.data = {
174
+ nodes: docs.map((d) => Object.assign({ id: d._id, type: "document", doc: d })),
175
+ links: docs.flatMap((doc) => findRefs(doc).map((ref) => ({ source: doc._id, target: ref }))).filter((link) => docsById[link.source] && docsById[link.target])
176
+ };
177
+ }
178
+ setSession(user, docNode) {
179
+ let session = this.sessions.find((s) => s.user.id === user.id && s.doc?._id === docNode.doc._id);
180
+ session || (session = new Session(), session.id = v4(), session.user = user, session.startTime = Date.now(), session.doc = docNode.doc, session.angle = Math.random() * 2 * Math.PI, this.sessions.push(session)), session.lastActive = Date.now();
181
+ }
182
+ reapSessions() {
183
+ for (let i = 0; i < this.sessions.length; i++) {
184
+ const session = this.sessions[i];
185
+ Date.now() - session.lastActive > idleTimeout && (this.sessions = [...this.sessions.slice(0, i), ...this.sessions.slice(i + 1)], i--);
186
+ }
187
+ }
188
+ clone() {
189
+ const copy = new GraphData();
190
+ return Object.assign(copy, this), copy.data = {
191
+ nodes: [...this.data.nodes],
192
+ links: [...this.data.links]
193
+ }, copy;
194
+ }
195
+ }
196
+ const users = new Users();
197
+ function GraphView(props) {
198
+ const query = props.query || DEFAULT_QUERY, apiVersion = props.apiVersion ?? "2022-09-01", userColorManager = useUserColorManager(), [maxSize, setMaxSize] = useState(0), [hoverNode, setHoverNode] = useState(null), [documents, setDocuments] = useState([]), [docTypes, setDocTypes] = useState({}), [graph, setGraph] = useState(() => new GraphData()), router = useRouter(), client = useClient({ apiVersion }), fetchCallback = useCallback((_docs) => {
199
+ const docs = deduplicateDrafts(_docs);
200
+ setMaxSize(Math.max(...docs.map(sizeOf))), setDocuments(docs), setDocTypes(getDocTypeCounts(docs)), setGraph(new GraphData(docs));
201
+ }, []), listenCallback = useCallback(
202
+ async (update) => {
203
+ const doc = update.result;
204
+ if (doc) {
205
+ doc._id = stripDraftId(doc._id);
206
+ const docsById = {};
207
+ for (const d of documents)
208
+ docsById[d._id] = d;
209
+ let oldDoc;
210
+ const docs = [...documents], idx = documents.findIndex((d) => d._id === doc._id);
211
+ idx >= 0 ? (oldDoc = docs[idx], docs[idx] = doc) : docs.push(doc), setDocuments(docs), setDocTypes(getDocTypeCounts(docs)), setMaxSize(Math.max(...docs.map(sizeOf)));
212
+ const newGraph = graph.clone(), oldRefs = findRefs(oldDoc || {}).filter(
213
+ (id) => id === doc._id || docsById[id] !== null
214
+ ), newRefs = findRefs(doc).filter((id) => id === doc._id || docsById[id] !== null);
215
+ let graphChanged = !deepEqual(oldRefs, newRefs);
216
+ graphChanged && (newGraph.data.links = newGraph.data.links.filter((l) => l.source.id !== doc._id).concat(newRefs.map((ref) => ({ source: doc._id, target: ref }))));
217
+ let docNode;
218
+ const nodeIdx = graph.data.nodes.findIndex((n) => n.doc && n.doc._id === doc._id);
219
+ nodeIdx >= 0 ? (docNode = graph.data.nodes[nodeIdx], docNode.doc = doc) : (docNode = { id: doc._id, type: "document", doc }, newGraph.data.nodes.push(docNode), graphChanged = !0), graphChanged && setGraph(newGraph);
220
+ const user = await users.getById(update.identity, client);
221
+ graph.setSession(user, docNode);
222
+ } else if (update.transition === "disappear") {
223
+ const docId = stripDraftId(update.documentId), docs = documents.filter((d) => d._id !== docId);
224
+ setDocuments(docs), setDocTypes(getDocTypeCounts(docs)), setMaxSize(Math.max(...docs.map(sizeOf)));
225
+ const newGraph = graph.clone();
226
+ newGraph.data.links = newGraph.data.links.filter(
227
+ (l) => l.source.id !== docId && l.target.id !== docId
228
+ ), newGraph.data.nodes = newGraph.data.nodes.filter((n) => n.id !== docId), setGraph(newGraph);
229
+ }
230
+ },
231
+ [documents, graph, client]
232
+ );
233
+ useFetchDocuments(query, fetchCallback, [], client), useListen(query, {}, {}, listenCallback, [documents, graph], client), useEffect(() => {
234
+ const interval = setInterval(() => graph.reapSessions(), 1e3);
235
+ return () => clearInterval(interval);
236
+ }, [graph]);
237
+ const theme = useTheme().sanity;
238
+ return /* @__PURE__ */ jsx(GraphWrapper, { theme, children: /* @__PURE__ */ jsxs(GraphRoot, { theme, children: [
239
+ /* @__PURE__ */ jsx(Legend, { theme, children: getTopDocTypes(docTypes).map((docType) => /* @__PURE__ */ jsxs(
240
+ LegendRow,
241
+ {
242
+ className: "legend__row",
243
+ style: { color: getDocTypeColor(docType).fill },
244
+ children: [
245
+ /* @__PURE__ */ jsx(LegendBadge, { theme }),
246
+ /* @__PURE__ */ jsx("div", { children: formatDocType(docType) })
247
+ ]
248
+ },
249
+ docType
250
+ )) }),
251
+ hoverNode && /* @__PURE__ */ jsx(HoverNode, { theme, children: labelFor(hoverNode.doc) }),
252
+ /* @__PURE__ */ jsx(
253
+ ForceGraph2D,
254
+ {
255
+ graphData: graph.data,
256
+ nodeAutoColorBy: "group",
257
+ enableNodeDrag: !1,
258
+ onNodeHover: (node) => setHoverNode(node),
259
+ onNodeClick: (node) => {
260
+ router.navigateIntent("edit", { id: node.doc._id, documentType: node.doc._type });
261
+ },
262
+ linkColor: () => rgba(gray[500].hex, 0.25),
263
+ nodeLabel: () => "",
264
+ nodeRelSize: 1,
265
+ nodeVal: (node) => valueFor(node.doc, maxSize),
266
+ onRenderFramePost: (ctx, globalScale) => {
267
+ for (const session of graph.sessions) {
268
+ const node = graph.data.nodes.find(
269
+ (n) => n.doc && n.doc._id === session?.doc?._id
270
+ );
271
+ if (node) {
272
+ const idleFactorRange = idleTimeout, angle = session.angle, radius = Math.sqrt(valueFor(node.doc, maxSize)), image = session.user.image, userColor = userColorManager.get(session.user.displayName).tints[400].hex, distance = radius * globalScale + 40, imgW = image ? image.width : 0, imgH = image ? image.height : 0, x = node.x + Math.sin(angle) * distance / globalScale, y = node.y + Math.cos(angle) * distance / globalScale;
273
+ ctx.save();
274
+ try {
275
+ if (ctx.globalAlpha = fadeEasing(
276
+ 1 - Math.min(idleFactorRange, Date.now() - session.lastActive) / idleFactorRange
277
+ ), ctx.font = `bold ${Math.round(12 / globalScale)}px sans-serif`, ctx.beginPath(), ctx.strokeStyle = rgba(white.hex, 1), ctx.lineWidth = 2 / globalScale, ctx.moveTo(
278
+ node.x + Math.sin(angle) * (distance - imgW / 2) / globalScale,
279
+ node.y + Math.cos(angle) * (distance - imgH / 2) / globalScale
280
+ ), ctx.lineTo(node.x + Math.sin(angle) * radius, node.y + Math.cos(angle) * radius), ctx.stroke(), ctx.beginPath(), ctx.strokeStyle = rgba(white.hex, 1), ctx.lineWidth = 2 / globalScale, ctx.arc(node.x, node.y, radius, 0, 2 * Math.PI, !1), ctx.stroke(), image) {
281
+ ctx.save();
282
+ try {
283
+ const f = softEasing(
284
+ Math.max(0, (700 - (Date.now() - session.startTime)) / 700)
285
+ );
286
+ f > 0 && (ctx.beginPath(), ctx.fillStyle = rgba(userColor, f), ctx.arc(x, y, (imgW / 2 + 10) / globalScale, 0, 2 * Math.PI, !1), ctx.fill()), ctx.beginPath(), ctx.fillStyle = rgba(white.hex, 1), ctx.arc(x, y, imgW / globalScale / 2, 0, 2 * Math.PI, !1), ctx.clip(), ctx.drawImage(
287
+ image,
288
+ x - imgW / globalScale / 2,
289
+ y - imgH / globalScale / 2,
290
+ imgW / globalScale,
291
+ imgH / globalScale
292
+ ), ctx.strokeStyle = black.hex, ctx.lineWidth = 6 / globalScale, ctx.stroke(), ctx.strokeStyle = userColor, ctx.lineWidth = 4 / globalScale, ctx.stroke();
293
+ } finally {
294
+ ctx.restore();
295
+ }
296
+ }
297
+ ctx.beginPath(), ctx.strokeStyle = rgba(black.hex, 1), ctx.lineWidth = 0.5 / globalScale, ctx.arc(x, y, imgW / globalScale / 2, 0, 2 * Math.PI, !1), ctx.stroke();
298
+ const above = angle >= Math.PI / 2 && angle < Math.PI * 1.5, textY = above ? y - (imgH / 2 + 5) / globalScale : y + (imgH / 2 + 5) / globalScale;
299
+ ctx.fillStyle = rgba(white.hex, 1), ctx.textAlign = "center", ctx.textBaseline = above ? "bottom" : "top", ctx.fillText(session.user.displayName, x, textY);
300
+ } finally {
301
+ ctx.restore();
302
+ }
303
+ }
304
+ }
305
+ },
306
+ nodeCanvasObject: (node, ctx, globalScale) => {
307
+ switch (node.type) {
308
+ case "document": {
309
+ const nodeColor = getDocTypeColor(node.doc._type), radius = Math.sqrt(valueFor(node.doc, maxSize)), fontSize = Math.min(100, 10 / globalScale);
310
+ if (ctx.beginPath(), ctx.fillStyle = hoverNode !== null && node.doc._id === hoverNode.doc._id ? rgba(gray[500].hex, 0.8) : nodeColor.fill, ctx.strokeStyle = nodeColor.border, ctx.lineWidth = 0.5, ctx.arc(node.x, node.y, radius, 0, 2 * Math.PI, !1), ctx.stroke(), ctx.fill(), radius * globalScale > 10) {
311
+ ctx.font = `${fontSize}px sans-serif`;
312
+ const w = radius * 2 + 30 / globalScale;
313
+ for (let len = 50; len >= 5; len /= 1.2) {
314
+ const label = truncate(labelFor(node.doc), Math.round(len));
315
+ if (ctx.measureText(label).width < w) {
316
+ ctx.textAlign = "center", ctx.textBaseline = "top", ctx.strokeStyle = rgba(black.hex, 0.5), ctx.lineWidth = 2 / globalScale, ctx.strokeText(label, node.x, node.y + radius + 5 / globalScale), ctx.fillText(label, node.x, node.y + radius + 5 / globalScale);
317
+ break;
318
+ }
319
+ }
320
+ }
321
+ }
322
+ }
323
+ },
324
+ linkCanvasObject: (link, ctx, globalScale) => {
325
+ ctx.beginPath(), ctx.strokeStyle = rgba(gray[500].hex, 0.125), ctx.lineWidth = 2 / globalScale, ctx.moveTo(link.source.x, link.source.y), ctx.lineTo(link.target.x, link.target.y), ctx.stroke();
326
+ }
327
+ }
328
+ )
329
+ ] }) });
330
+ }
331
+ const colorCache = {};
332
+ let typeColorNum = 0;
333
+ function getDocTypeColor(docType) {
334
+ if (colorCache[docType])
335
+ return colorCache[docType];
336
+ const hue = COLOR_HUES[typeColorNum % COLOR_HUES.length];
337
+ return typeColorNum += 1, colorCache[docType] = {
338
+ fill: hues[hue][400].hex,
339
+ border: rgba(black.hex, 0.5)
340
+ }, colorCache[docType];
341
+ }
342
+ const GraphViewIcon = () => /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "1em", height: "1em", viewBox: "0 0 46.063 46.063", children: /* @__PURE__ */ jsx(
343
+ "path",
344
+ {
345
+ fill: "currentColor",
346
+ d: "M18.022 38.676v2.813h-1.21q-4.864 0-6.525-1.447-1.64-1.445-1.64-5.762v-4.666q0-2.95-1.055-4.083-1.055-1.13-3.828-1.13h-1.19v-2.794h1.19q2.793 0 3.828-1.114 1.055-1.132 1.055-4.043V11.76q0-4.316 1.64-5.742 1.66-1.446 6.524-1.446h1.212v2.793h-1.328q-2.754 0-3.594.86-.84.86-.84 3.613v4.844q0 3.066-.898 4.453-.88 1.387-3.028 1.875 2.168.527 3.047 1.914.88 1.387.88 4.434v4.843q0 2.754.84 3.614.84.86 3.594.86h1.328zM28.04 38.676h1.368q2.735 0 3.555-.84.84-.84.84-3.633V29.36q0-3.047.88-4.434.878-1.387 3.046-1.914-2.17-.488-3.048-1.875-.88-1.387-.88-4.453V11.84q0-2.773-.84-3.613-.82-.86-3.554-.86H28.04V4.574h1.232q4.863 0 6.484 1.446 1.64 1.426 1.64 5.742v4.687q0 2.91 1.055 4.042 1.056 1.114 3.83 1.114h1.21V24.4h-1.21q-2.774 0-3.83 1.13-1.053 1.134-1.053 4.084v4.667q0 4.318-1.64 5.763-1.622 1.446-6.485 1.446h-1.23v-2.814z"
347
+ }
348
+ ) }), contentGraphView = definePlugin((config = {}) => ({
349
+ name: "@sanity/content-graph-view",
350
+ tools: (prev) => [
351
+ ...prev,
352
+ {
353
+ name: "graph-your-content",
354
+ title: "Graph",
355
+ icon: GraphViewIcon,
356
+ component: function() {
357
+ return /* @__PURE__ */ jsx(GraphView, { ...config });
358
+ },
359
+ router: route.create("/:selectedDocumentId")
360
+ }
361
+ ]
362
+ }));
363
+ export {
364
+ contentGraphView
365
+ };
366
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../src/tool/GraphViewStyle.tsx","../src/tool/hooks.ts","../src/tool/utils.ts","../src/tool/GraphView.tsx","../src/tool/GraphViewIcon.tsx","../src/plugin.tsx"],"sourcesContent":["import React, {type PropsWithChildren} from 'react'\nimport styled from 'styled-components'\n\nimport type {Theme} from '@sanity/ui'\n\nimport {black} from '@sanity/color'\n\ntype Style = PropsWithChildren<{theme: SanityTheme}>\ntype SanityTheme = Theme['sanity']\n\nexport const GraphRoot: React.FC<Style> = styled.div`\n font-family: ${({theme}: Style) => theme.fonts.text.family};\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background: ${black.hex};\n`\n\nexport const GraphWrapper: React.FC<Style> = styled.div`\n position: relative;\n width: 100%;\n height: 100%;\n` as React.FC<Style>\n\nexport const HoverNode: React.FC<Style> = styled.div`\n font-family: ${({theme}: Style) => theme.fonts.text.family};\n display: none;\n position: absolute;\n bottom: ${({theme}: Style) => theme.space[0]}px;\n left: 50%;\n transform: translate3d(-50%, 0, 0);\n background: var(--component-bg);\n border-radius: ${({theme}: Style) => theme.radius[2]}px;\n padding: ${({theme}: Style) => theme.space[2]}px;\n z-index: 1000;\n\n &:empty {\n display: none;\n }\n`\n\nexport const Legend: React.FC<Style> = styled.div`\n color: #ccc;\n position: absolute;\n top: ${({theme}: Style) => theme.space[4]}px;\n left: ${({theme}: Style) => theme.space[4]}px;\n\n & > div {\n margin: 5px 0;\n }\n`\n\nexport const LegendRow = styled.div`\n display: flex;\n`\n\nexport const LegendBadge: React.FC<Style> = styled.div`\n width: 1.25em;\n height: 1.25em;\n background: currentColor;\n border-radius: 50%;\n margin-right: ${({theme}: Style) => theme.space[2]}px;\n`\n","import {useEffect} from 'react'\n\nimport type {ListenEvent, ListenOptions, SanityClient} from '@sanity/client'\n\nexport function useListen(\n query: string,\n params: {[key: string]: any},\n options: ListenOptions,\n onUpdate: (event: ListenEvent<any>) => void,\n dependencies: unknown[],\n client: SanityClient,\n): void {\n useEffect(() => {\n const subscription = client.listen(query, params, options).subscribe((update) => {\n onUpdate(update)\n })\n return () => {\n subscription.unsubscribe()\n }\n }, [...dependencies, client])\n}\n\nexport function useFetchDocuments(\n query: string,\n onFetch: (event: ListenEvent<any>) => void,\n dependencies: unknown[],\n client: SanityClient,\n): void {\n useEffect(() => {\n void client.fetch(query).then((result) => {\n onFetch(result)\n })\n }, [...dependencies, client])\n}\n","export function sizeOf(value: any): number {\n if (value === null) {\n return 0\n }\n\n if (typeof value === 'object') {\n return Object.entries(value).reduce((total, [k, v]) => total + sizeOf(k) + sizeOf(v), 0)\n }\n\n if (Array.isArray(value)) {\n return Object.entries(value).reduce((total, v) => total + sizeOf(v), 0)\n }\n\n if (typeof value === 'string') {\n return value.length\n }\n\n return 1\n}\n\nexport function loadImage(url: string, w: number, h: number): Promise<HTMLImageElement | null> {\n return new Promise((resolve) => {\n const img = new Image(w, h)\n img.onload = () => {\n resolve(img)\n }\n img.onerror = (event) => {\n console.log('Image error', event)\n resolve(null)\n }\n img.src = url\n })\n}\n\nexport function sortBy<T>(array: T[], f: (t: T) => number): T[] {\n return array.sort((a, b) => {\n const va = f(a)\n const vb = f(b)\n return va < vb ? -1 : va > vb ? 1 : 0\n })\n}\n\nexport function truncate(s: string, limit: number): string {\n if (s.length > limit) {\n return `${s.substring(0, limit)}…`\n }\n return s\n}\n","import BezierEasing from 'bezier-easing'\nimport deepEqual from 'deep-equal'\nimport {rgba} from 'polished'\nimport React, {useCallback, useEffect, useState} from 'react'\nimport {ForceGraph2D} from 'react-force-graph'\nimport {useClient, useUserColorManager} from 'sanity'\nimport {useRouter} from 'sanity/router'\nimport {v4 as uuidv4} from 'uuid'\n\nimport type {SanityDocument, SanityClient} from '@sanity/client'\n\nimport {black, COLOR_HUES, gray, white, hues} from '@sanity/color'\nimport {useTheme} from '@sanity/ui'\n\nimport {GraphRoot, GraphWrapper, HoverNode, Legend, LegendBadge, LegendRow} from './GraphViewStyle'\nimport {useFetchDocuments, useListen} from './hooks'\nimport {sortBy, loadImage, sizeOf, truncate} from './utils'\n\nconst DEFAULT_QUERY = `\n *[\n !(_id in path(\"_.*\")) &&\n !(_type match \"system.*\") &&\n !(_type match \"sanity.*\")\n ]\n`\n\nconst fadeEasing = BezierEasing(0, 0.9, 1, 1)\nconst softEasing = BezierEasing(0.25, 0.1, 0.0, 1.0)\nconst idleTimeout = 10000\nconst imageSize = 40\n\nfunction getTopDocTypes(counts: Record<string, number>) {\n return sortBy(Object.keys(counts), (docType) => counts[docType] || 0)\n .reverse()\n .slice(0, 10)\n}\n\nfunction formatDocType(docType: string) {\n return (docType.substring(0, 1).toUpperCase() + docType.substring(1))\n .replace(/\\./g, ' ')\n .replace(/[A-Z]/g, ' $&')\n .trim()\n}\n\nfunction getDocTypeCounts(docs: SanityDocument[]) {\n const types: Record<string, number> = {}\n for (const doc of docs) {\n types[doc._type] = (types[doc._type] || 0) + 1\n }\n return types\n}\n\nfunction labelFor(doc: SanityDocument) {\n return `${doc.title || doc.name || doc._id}`.trim()\n}\n\nfunction valueFor(doc: any, maxSize: number) {\n return 5 + 100 * (sizeOf(doc) / maxSize)\n}\n\nfunction findRefs(obj: any, dest: any[] = []) {\n if (obj !== null) {\n if (typeof obj === 'object') {\n for (const [k, v] of Object.entries(obj)) {\n if (k === '_ref' && typeof v === 'string' && v.length > 0) {\n dest.push(stripDraftId(v))\n }\n findRefs(v, dest)\n }\n } else if (Array.isArray(obj)) {\n for (const v of obj) {\n findRefs(v, dest)\n }\n }\n }\n return dest\n}\n\nfunction stripDraftId(id: string) {\n return id.replace(/^drafts\\./, '')\n}\n\nfunction deduplicateDrafts(docs: SanityDocument[]) {\n const deduped: Record<string, SanityDocument> = {}\n for (const doc of docs) {\n if (!doc._id.startsWith('drafts.')) {\n deduped[doc._id] = doc\n }\n }\n for (const doc of docs) {\n if (doc._id.startsWith('drafts.')) {\n const id = stripDraftId(doc._id)\n deduped[id] = Object.assign(doc, {_id: id})\n }\n }\n return Object.values(deduped)\n}\n\nclass Users {\n _users: any[] = []\n\n async getById(id: string, client: SanityClient) {\n let user = this._users.find((u) => u._id === id)\n if (!user) {\n user = await client.users.getById(id)\n this._users.push(user)\n user.image = await loadImage(\n user.imageUrl ||\n 'https://raw.githubusercontent.com/sanity-io/sanity-plugin-graph-view/main/assets/head-silhouette.jpg',\n imageSize,\n imageSize,\n )\n }\n return user\n }\n}\n\nclass Session {\n id: string | null = null\n user: any = null\n doc: SanityDocument | null = null\n lastActive: number = 0\n startTime: number = 0\n angle: number = 0\n}\n\nclass GraphData {\n sessions: Session[] = []\n data: any\n\n constructor(docs: SanityDocument[] = []) {\n const docsById: Record<string, SanityDocument> = {}\n for (const doc of docs) {\n docsById[doc._id] = doc\n }\n\n this.data = {\n nodes: docs.map((d) => Object.assign({id: d._id, type: 'document', doc: d})),\n links: docs\n .flatMap((doc) => findRefs(doc).map((ref) => ({source: doc._id, target: ref})))\n .filter((link) => docsById[link.source] && docsById[link.target]),\n }\n }\n\n setSession(user: any, docNode: any) {\n let session = this.sessions.find((s) => s.user.id === user.id && s.doc?._id === docNode.doc._id)\n if (!session) {\n session = new Session()\n session.id = uuidv4()\n session.user = user\n session.startTime = Date.now()\n session.doc = docNode.doc\n session.angle = Math.random() * 2 * Math.PI\n this.sessions.push(session)\n }\n session.lastActive = Date.now()\n }\n\n reapSessions() {\n for (let i = 0; i < this.sessions.length; i++) {\n const session = this.sessions[i]\n if (Date.now() - session.lastActive > idleTimeout) {\n this.sessions = [...this.sessions.slice(0, i), ...this.sessions.slice(i + 1)]\n i--\n }\n }\n }\n\n clone() {\n const copy = new GraphData()\n Object.assign(copy, this)\n copy.data = {\n nodes: [...this.data.nodes],\n links: [...this.data.links],\n }\n return copy\n }\n}\n\nconst users = new Users()\n\ninterface GraphViewConfig {\n query?: string\n /** default: '2022-09-01' */\n apiVersion?: string\n}\n\nexport function GraphView(props: GraphViewConfig) {\n const query = props.query || DEFAULT_QUERY\n const apiVersion = props.apiVersion ?? '2022-09-01'\n\n const userColorManager = useUserColorManager()\n const [maxSize, setMaxSize] = useState(0)\n const [hoverNode, setHoverNode] = useState<any>(null)\n const [documents, setDocuments] = useState<SanityDocument[]>([])\n const [docTypes, setDocTypes] = useState<Record<string, number>>({})\n const [graph, setGraph] = useState(() => new GraphData())\n const router = useRouter()\n const client = useClient({apiVersion})\n\n const fetchCallback = useCallback((_docs: any) => {\n const docs = deduplicateDrafts(_docs)\n setMaxSize(Math.max(...docs.map(sizeOf)))\n setDocuments(docs)\n setDocTypes(getDocTypeCounts(docs))\n setGraph(new GraphData(docs))\n }, [])\n\n const listenCallback = useCallback(\n async (update: any) => {\n const doc = update.result\n if (doc) {\n doc._id = stripDraftId(doc._id)\n\n const docsById: Record<string, SanityDocument> = {}\n for (const d of documents) {\n docsById[d._id] = d\n }\n\n let oldDoc\n const docs = [...documents]\n const idx = documents.findIndex((d) => d._id === doc._id)\n if (idx >= 0) {\n oldDoc = docs[idx]\n docs[idx] = doc\n } else {\n docs.push(doc)\n }\n setDocuments(docs)\n setDocTypes(getDocTypeCounts(docs))\n setMaxSize(Math.max(...docs.map(sizeOf)))\n\n const newGraph = graph.clone()\n\n const oldRefs = findRefs(oldDoc || {}).filter(\n (id) => id === doc._id || docsById[id] !== null,\n )\n const newRefs = findRefs(doc).filter((id) => id === doc._id || docsById[id] !== null)\n\n let graphChanged = !deepEqual(oldRefs, newRefs)\n if (graphChanged) {\n newGraph.data.links = newGraph.data.links\n .filter((l: any) => l.source.id !== doc._id)\n .concat(newRefs.map((ref) => ({source: doc._id, target: ref})))\n }\n\n let docNode\n const nodeIdx = graph.data.nodes.findIndex((n: any) => n.doc && n.doc._id === doc._id)\n if (nodeIdx >= 0) {\n docNode = graph.data.nodes[nodeIdx]\n docNode.doc = doc\n } else {\n docNode = {id: doc._id, type: 'document', doc: doc}\n newGraph.data.nodes.push(docNode)\n graphChanged = true\n }\n if (graphChanged) {\n setGraph(newGraph)\n }\n\n // @ts-expect-error - TODO: fix this\n const user = await users.getById(update.identity, client)\n graph.setSession(user, docNode)\n } else if (update.transition === 'disappear') {\n const docId = stripDraftId(update.documentId)\n\n const docs = documents.filter((d) => d._id !== docId)\n setDocuments(docs)\n setDocTypes(getDocTypeCounts(docs))\n setMaxSize(Math.max(...docs.map(sizeOf)))\n\n const newGraph = graph.clone()\n newGraph.data.links = newGraph.data.links.filter(\n (l: any) => l.source.id !== docId && l.target.id !== docId,\n )\n newGraph.data.nodes = newGraph.data.nodes.filter((n: any) => n.id !== docId)\n setGraph(newGraph)\n }\n },\n [documents, graph, client],\n )\n // @ts-expect-error - TODO: fix this\n useFetchDocuments(query, fetchCallback, [], client)\n // @ts-expect-error - TODO: fix this\n useListen(query, {}, {}, listenCallback, [documents, graph], client)\n useEffect(() => {\n const interval = setInterval(() => graph.reapSessions(), 1000)\n return () => clearInterval(interval)\n }, [graph])\n\n const theme = useTheme().sanity\n\n return (\n <GraphWrapper theme={theme}>\n <GraphRoot theme={theme}>\n <Legend theme={theme}>\n {getTopDocTypes(docTypes).map((docType) => (\n <LegendRow\n className={'legend__row'}\n key={docType}\n style={{color: getDocTypeColor(docType).fill}}\n >\n <LegendBadge theme={theme} />\n <div>{formatDocType(docType)}</div>\n </LegendRow>\n ))}\n </Legend>\n {hoverNode && <HoverNode theme={theme}>{labelFor(hoverNode.doc)}</HoverNode>}\n\n {/* @ts-expect-error - TODO: fix this */}\n <ForceGraph2D\n graphData={graph.data}\n nodeAutoColorBy=\"group\"\n enableNodeDrag={false}\n onNodeHover={(node: any) => setHoverNode(node)}\n onNodeClick={(node: any) => {\n router.navigateIntent('edit', {id: node.doc._id, documentType: node.doc._type})\n }}\n linkColor={() => rgba(gray[500].hex, 0.25)}\n nodeLabel={() => ''}\n nodeRelSize={1}\n nodeVal={(node: any) => valueFor(node.doc, maxSize)}\n onRenderFramePost={(ctx, globalScale) => {\n for (const session of graph.sessions) {\n const node = graph.data.nodes.find(\n (n: any) => n.doc && n.doc._id === session?.doc?._id,\n )\n if (node) {\n const idleFactorRange = idleTimeout\n const angle = session.angle\n const radius = Math.sqrt(valueFor(node.doc, maxSize))\n const image = session.user.image\n const userColor = userColorManager.get(session.user.displayName).tints[400].hex\n const distance = radius * globalScale + 40\n const imgW = image ? image.width : 0\n const imgH = image ? image.height : 0\n const x = node.x + (Math.sin(angle) * distance) / globalScale\n const y = node.y + (Math.cos(angle) * distance) / globalScale\n\n ctx.save()\n try {\n ctx.globalAlpha = fadeEasing(\n 1 -\n Math.min(idleFactorRange, Date.now() - session.lastActive) / idleFactorRange,\n )\n ctx.font = `bold ${Math.round(12 / globalScale)}px sans-serif`\n\n ctx.beginPath()\n ctx.strokeStyle = rgba(white.hex, 1.0)\n ctx.lineWidth = 2 / globalScale\n ctx.moveTo(\n node.x + (Math.sin(angle) * (distance - imgW / 2)) / globalScale,\n node.y + (Math.cos(angle) * (distance - imgH / 2)) / globalScale,\n )\n ctx.lineTo(node.x + Math.sin(angle) * radius, node.y + Math.cos(angle) * radius)\n ctx.stroke()\n\n ctx.beginPath()\n ctx.strokeStyle = rgba(white.hex, 1.0)\n ctx.lineWidth = 2 / globalScale\n ctx.arc(node.x, node.y, radius, 0, 2 * Math.PI, false)\n ctx.stroke()\n\n if (image) {\n ctx.save()\n try {\n const dur = 700\n const f = softEasing(\n Math.max(0, (dur - (Date.now() - session.startTime)) / dur),\n )\n if (f > 0) {\n ctx.beginPath()\n ctx.fillStyle = rgba(userColor, f)\n ctx.arc(x, y, (imgW / 2 + 10) / globalScale, 0, 2 * Math.PI, false)\n ctx.fill()\n }\n\n ctx.beginPath()\n ctx.fillStyle = rgba(white.hex, 1.0)\n ctx.arc(x, y, imgW / globalScale / 2, 0, 2 * Math.PI, false)\n ctx.clip()\n\n ctx.drawImage(\n image,\n x - imgW / globalScale / 2,\n y - imgH / globalScale / 2,\n imgW / globalScale,\n imgH / globalScale,\n )\n\n ctx.strokeStyle = black.hex\n ctx.lineWidth = 6 / globalScale\n ctx.stroke()\n\n ctx.strokeStyle = userColor\n ctx.lineWidth = 4 / globalScale\n ctx.stroke()\n } finally {\n ctx.restore()\n }\n }\n\n ctx.beginPath()\n ctx.strokeStyle = rgba(black.hex, 1)\n ctx.lineWidth = 0.5 / globalScale\n ctx.arc(x, y, imgW / globalScale / 2, 0, 2 * Math.PI, false)\n ctx.stroke()\n\n const above = angle >= Math.PI / 2 && angle < Math.PI * 1.5\n const textY = above\n ? y - (imgH / 2 + 5) / globalScale\n : y + (imgH / 2 + 5) / globalScale\n ctx.fillStyle = rgba(white.hex, 1.0)\n ctx.textAlign = 'center'\n ctx.textBaseline = above ? 'bottom' : 'top'\n ctx.fillText(session.user.displayName, x, textY)\n } finally {\n ctx.restore()\n }\n }\n }\n }}\n nodeCanvasObject={(node: any, ctx, globalScale) => {\n switch (node.type) {\n case 'document': {\n const nodeColor = getDocTypeColor(node.doc._type)\n const radius = Math.sqrt(valueFor(node.doc, maxSize))\n const fontSize = Math.min(100, 10.0 / globalScale)\n\n ctx.beginPath()\n ctx.fillStyle =\n hoverNode !== null && node.doc._id === hoverNode.doc._id\n ? rgba(gray[500].hex, 0.8)\n : nodeColor.fill\n ctx.strokeStyle = nodeColor.border\n ctx.lineWidth = 0.5\n ctx.arc(node.x, node.y, radius, 0, 2 * Math.PI, false)\n ctx.stroke()\n ctx.fill()\n\n if (radius * globalScale > 10) {\n ctx.font = `${fontSize}px sans-serif`\n const w = radius * 2 + 30 / globalScale\n for (let len = 50; len >= 5; len /= 1.2) {\n const label = truncate(labelFor(node.doc), Math.round(len))\n const textMetrics = ctx.measureText(label)\n if (textMetrics.width < w) {\n // ctx.fillStyle = rgba(color.white.hex, 1.0)\n ctx.textAlign = 'center'\n ctx.textBaseline = 'top'\n\n ctx.strokeStyle = rgba(black.hex, 0.5)\n ctx.lineWidth = 2 / globalScale\n ctx.strokeText(label, node.x, node.y + radius + 5 / globalScale)\n\n ctx.fillText(label, node.x, node.y + radius + 5 / globalScale)\n break\n }\n }\n }\n }\n }\n }}\n linkCanvasObject={(link: any, ctx, globalScale) => {\n ctx.beginPath()\n ctx.strokeStyle = rgba(gray[500].hex, 0.125)\n ctx.lineWidth = 2 / globalScale\n ctx.moveTo(link.source.x, link.source.y)\n ctx.lineTo(link.target.x, link.target.y)\n ctx.stroke()\n }}\n />\n </GraphRoot>\n </GraphWrapper>\n )\n}\n\nconst colorCache: Record<string, {fill: string; border: string}> = {}\nlet typeColorNum = 0\n\nfunction getDocTypeColor(docType: any) {\n if (colorCache[docType]) {\n return colorCache[docType]\n }\n\n const hue = COLOR_HUES[typeColorNum % COLOR_HUES.length]\n\n typeColorNum += 1\n\n colorCache[docType] = {\n fill: hues[hue][400].hex,\n border: rgba(black.hex, 0.5),\n }\n\n return colorCache[docType]\n}\n","import React from 'react'\n\n/**\n * Couple of things to note:\n * - width and height is set to 1em\n * - fill is `currentColor` - this will ensure that the icon looks uniform and\n * that the hover/active state works. You can of course render anything you\n * would like here, but for plugins that are to be used in more than one\n * studio, we suggest these rules are followed\n **/\nexport const GraphViewIcon = () => (\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"1em\" height=\"1em\" viewBox=\"0 0 46.063 46.063\">\n <path\n fill=\"currentColor\"\n d=\"M18.022 38.676v2.813h-1.21q-4.864 0-6.525-1.447-1.64-1.445-1.64-5.762v-4.666q0-2.95-1.055-4.083-1.055-1.13-3.828-1.13h-1.19v-2.794h1.19q2.793 0 3.828-1.114 1.055-1.132 1.055-4.043V11.76q0-4.316 1.64-5.742 1.66-1.446 6.524-1.446h1.212v2.793h-1.328q-2.754 0-3.594.86-.84.86-.84 3.613v4.844q0 3.066-.898 4.453-.88 1.387-3.028 1.875 2.168.527 3.047 1.914.88 1.387.88 4.434v4.843q0 2.754.84 3.614.84.86 3.594.86h1.328zM28.04 38.676h1.368q2.735 0 3.555-.84.84-.84.84-3.633V29.36q0-3.047.88-4.434.878-1.387 3.046-1.914-2.17-.488-3.048-1.875-.88-1.387-.88-4.453V11.84q0-2.773-.84-3.613-.82-.86-3.554-.86H28.04V4.574h1.232q4.863 0 6.484 1.446 1.64 1.426 1.64 5.742v4.687q0 2.91 1.055 4.042 1.056 1.114 3.83 1.114h1.21V24.4h-1.21q-2.774 0-3.83 1.13-1.053 1.134-1.053 4.084v4.667q0 4.318-1.64 5.763-1.622 1.446-6.485 1.446h-1.23v-2.814z\"\n />\n </svg>\n)\n","import React from 'react'\nimport {definePlugin} from 'sanity'\nimport {route} from 'sanity/router'\n\nimport {GraphView} from './tool/GraphView'\nimport {GraphViewIcon} from './tool/GraphViewIcon'\n\nexport interface GraphViewConfig {\n query?: string\n}\n\nexport const contentGraphView = definePlugin<GraphViewConfig>((config: GraphViewConfig = {}) => {\n return {\n name: '@sanity/content-graph-view',\n\n tools: (prev) => {\n return [\n ...prev,\n {\n name: 'graph-your-content',\n title: 'Graph',\n icon: GraphViewIcon,\n component: function component() {\n return <GraphView {...config} />\n },\n router: route.create('/:selectedDocumentId'),\n },\n ]\n },\n }\n})\n"],"names":["uuidv4"],"mappings":"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"}
package/package.json CHANGED
@@ -1,94 +1,66 @@
1
1
  {
2
2
  "name": "sanity-plugin-graph-view",
3
- "version": "3.0.0-v3-studio.7",
3
+ "version": "3.0.0",
4
4
  "description": "",
5
- "homepage": "https://github.com/sanity-io/sanity-plugin-graph-view#readme",
5
+ "homepage": "https://github.com/sanity-io/plugins/tree/main/plugins/sanity-plugin-graph-view#readme",
6
6
  "bugs": {
7
- "url": "https://github.com/sanity-io/sanity-plugin-graph-view/issues"
7
+ "url": "https://github.com/sanity-io/plugins/issues"
8
8
  },
9
9
  "repository": {
10
10
  "type": "git",
11
- "url": "git@github.com:sanity-io/sanity-plugin-graph-view.git"
11
+ "url": "git+ssh://git@github.com/sanity-io/plugins.git",
12
+ "directory": "plugins/sanity-plugin-graph-view"
12
13
  },
13
14
  "license": "MIT",
14
15
  "author": "Sanity.io <hello@sanity.io>",
16
+ "type": "module",
15
17
  "exports": {
16
18
  ".": {
17
- "types": "./lib/src/index.d.ts",
18
19
  "source": "./src/index.ts",
19
- "import": "./lib/index.esm.js",
20
- "require": "./lib/index.js",
21
- "default": "./lib/index.esm.js"
20
+ "import": "./dist/index.js",
21
+ "require": "./dist/index.cjs",
22
+ "default": "./dist/index.js"
22
23
  },
23
24
  "./package.json": "./package.json"
24
25
  },
25
- "main": "./lib/index.js",
26
- "module": "./lib/index.esm.js",
27
- "source": "./src/index.ts",
28
- "types": "./lib/src/index.d.ts",
26
+ "types": "./dist/index.d.ts",
29
27
  "files": [
30
- "src",
31
- "lib",
32
- "v2-incompatible.js",
33
- "sanity.json"
28
+ "dist"
34
29
  ],
35
- "scripts": {
36
- "prebuild": "npm run clean && plugin-kit verify-package --silent && pkg-utils",
37
- "build": "pkg-utils build --strict",
38
- "clean": "rimraf lib",
39
- "compile": "tsc --noEmit",
40
- "format": "prettier src -w",
41
- "link-watch": "plugin-kit link-watch",
42
- "lint": "eslint .",
43
- "lint:fix": "eslint . --fix",
44
- "prepare": "husky install",
45
- "prepublishOnly": "npm run build",
46
- "watch": "pkg-utils watch"
47
- },
48
30
  "dependencies": {
49
31
  "@sanity/client": "^3.3.0",
50
- "@sanity/color": "^2.1.10",
51
- "@sanity/incompatible-plugin": "^1.0.4",
52
- "@sanity/ui": "1.0.0-beta.32",
32
+ "@sanity/color": "^2.1.19",
33
+ "@sanity/ui": "^1.0.0",
53
34
  "bezier-easing": "^2.1.0",
54
35
  "deep-equal": "^2.0.5",
55
36
  "polished": "^4.2.2",
56
- "react-force-graph": "^1.41.12",
37
+ "react-force-graph": "1.41.14",
57
38
  "styled-components": "^5.3.5",
58
39
  "uuid": "^8.3.2"
59
40
  },
60
41
  "devDependencies": {
61
- "@commitlint/cli": "^17.2.0",
62
- "@commitlint/config-conventional": "^17.2.0",
63
- "@sanity/pkg-utils": "^1.17.2",
64
- "@sanity/plugin-kit": "^2.1.6",
65
- "@sanity/semantic-release-preset": "^2.0.2",
42
+ "@sanity/pkg-utils": "^9.1.1",
66
43
  "@types/deep-equal": "^1.0.1",
67
44
  "@types/react": "^18",
45
+ "@types/uuid": "^8",
68
46
  "@types/styled-components": "^5.1.25",
69
- "@typescript-eslint/eslint-plugin": "^5.42.0",
70
- "@typescript-eslint/parser": "^5.42.0",
71
- "eslint": "^8.26.0",
72
- "eslint-config-prettier": "^8.5.0",
73
- "eslint-config-sanity": "^6.0.0",
74
- "eslint-plugin-prettier": "^4.2.1",
75
- "eslint-plugin-react": "^7.31.10",
76
- "eslint-plugin-react-hooks": "^4.6.0",
77
- "husky": "^8.0.1",
78
- "lint-staged": "^13.0.3",
79
- "prettier": "^2.7.1",
80
- "prettier-plugin-packagejson": "^2.3.0",
47
+ "@typescript/native-preview": "7.0.0-dev.20251122.1",
81
48
  "react": "^18",
82
49
  "rimraf": "^3.0.2",
83
- "sanity": "3.0.0-rc.0",
84
- "typescript": "^4.8.4"
50
+ "sanity": "^4.17.0",
51
+ "typescript": "5.9.3",
52
+ "@repo/package.config": "0.0.0",
53
+ "@repo/tsconfig": "0.0.0"
85
54
  },
86
55
  "peerDependencies": {
87
- "react": "^18",
88
- "sanity": "dev-preview || 3.0.0-rc.0"
56
+ "react": "^18.3 || ^19",
57
+ "sanity": "^4"
89
58
  },
90
59
  "engines": {
91
- "node": ">=14"
60
+ "node": ">=20.19 <22 || >=22.12"
92
61
  },
93
- "sanityExchangeUrl": "https://www.sanity.io/plugins/graph-view"
94
- }
62
+ "scripts": {
63
+ "build": "pkg build --strict --check --clean",
64
+ "typecheck": "(cd ../.. && tsgo --project plugins/sanity-plugin-graph-view/tsconfig.json)"
65
+ }
66
+ }
package/lib/index.esm.js DELETED
@@ -1,2 +0,0 @@
1
- var t,e,n,r,o,i;function s(t,e){var n=Object.keys(t);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(t);e&&(r=r.filter((function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable}))),n.push.apply(n,r)}return n}function a(t){for(var e=1;e<arguments.length;e++){var n=null!=arguments[e]?arguments[e]:{};e%2?s(Object(n),!0).forEach((function(e){c(t,e,n[e])})):Object.getOwnPropertyDescriptors?Object.defineProperties(t,Object.getOwnPropertyDescriptors(n)):s(Object(n)).forEach((function(e){Object.defineProperty(t,e,Object.getOwnPropertyDescriptor(n,e))}))}return t}function c(t,e,n){return e in t?Object.defineProperty(t,e,{value:n,enumerable:!0,configurable:!0,writable:!0}):t[e]=n,t}function l(t,e){return e||(e=t.slice(0)),Object.freeze(Object.defineProperties(t,{raw:{value:Object.freeze(e)}}))}import{jsx as d,jsxs as h}from"react/jsx-runtime";import{rgba as u}from"polished";import f from"deep-equal";import{useEffect as p,useState as m,useCallback as y}from"react";import{ForceGraph2D as g}from"react-force-graph";import{v4 as b}from"uuid";import v from"bezier-easing";import{useUserColorManager as x,useClient as _,definePlugin as w}from"sanity";import{useRouter as k,route as O}from"sanity/router";import j from"styled-components";import{black as M,gray as P,white as I,hues as S,COLOR_HUES as q}from"@sanity/color";import{useTheme as A}from"@sanity/ui";function D(t){return null===t?0:"object"==typeof t?Object.entries(t).reduce(((t,e)=>{let[n,r]=e;return t+D(n)+D(r)}),0):Array.isArray(t)?Object.entries(t).reduce(((t,e)=>t+D(e)),0):"string"==typeof t?t.length:1}const T=j.div(t||(t=l(["\n font-family: ",";\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background: ",";\n"])),(t=>{let{theme:e}=t;return e.fonts.text.family}),M.hex),C=j.div(e||(e=l(["\n position: relative;\n width: 100%;\n height: 100%;\n"]))),V=j.div(n||(n=l(["\n font-family: ",";\n display: none;\n position: absolute;\n bottom: ","px;\n left: 50%;\n transform: translate3d(-50%, 0, 0);\n background: var(--component-bg);\n border-radius: ","px;\n padding: ","px;\n z-index: 1000;\n\n &:empty {\n display: none;\n }\n"])),(t=>{let{theme:e}=t;return e.fonts.text.family}),(t=>{let{theme:e}=t;return e.space[0]}),(t=>{let{theme:e}=t;return e.radius[2]}),(t=>{let{theme:e}=t;return e.space[2]})),W=j.div(r||(r=l(["\n color: #ccc;\n position: absolute;\n top: ","px;\n left: ","px;\n\n & > div {\n margin: 5px 0;\n }\n"])),(t=>{let{theme:e}=t;return e.space[4]}),(t=>{let{theme:e}=t;return e.space[4]})),z=j.div(o||(o=l(["\n display: flex;\n"]))),B=j.div(i||(i=l(["\n width: 1.25em;\n height: 1.25em;\n background: currentColor;\n border-radius: 50%;\n margin-right: ","px;\n"])),(t=>{let{theme:e}=t;return e.space[2]})),N=v(0,.9,1,1),E=v(.25,.1,0,1);function H(t){return(t.substring(0,1).toUpperCase()+t.substring(1)).replace(/\./g," ").replace(/[A-Z]/g," $&").trim()}function R(t){const e={};for(const n of t)e[n._type]=(e[n._type]||0)+1;return e}function U(t){return"".concat(t.title||t.name||t._id).trim()}function F(t,e){return 5+D(t)/e*100}function G(t){let e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:[];if(null!==t)if("object"==typeof t)for(const[n,r]of Object.entries(t))"_ref"===n&&"string"==typeof r&&r.length>0&&e.push(L(r)),G(r,e);else if(Array.isArray(t))for(const n of t)G(n,e);return e}function L(t){return t.replace(/^drafts\./,"")}class Z{constructor(){this.id=null,this.user=null,this.doc=null,this.lastActive=0,this.startTime=0,this.angle=0}}class ${constructor(){let t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];this.sessions=[];const e={};for(const n of t)e[n._id]=n;this.data={nodes:t.map((t=>Object.assign({id:t._id,type:"document",doc:t}))),links:t.flatMap((t=>G(t).map((e=>({source:t._id,target:e}))))).filter((t=>e[t.source]&&e[t.target]))}}setSession(t,e){let n=this.sessions.find((n=>{var r;return n.user.id===t.id&&(null==(r=n.doc)?void 0:r._id)===e.doc._id}));n||(n=new Z,n.id=b(),n.user=t,n.startTime=Date.now(),n.doc=e.doc,n.angle=2*Math.random()*Math.PI,this.sessions.push(n)),n.lastActive=Date.now()}reapSessions(){for(let t=0;t<this.sessions.length;t++){const e=this.sessions[t];Date.now()-e.lastActive>1e4&&(this.sessions=[...this.sessions.slice(0,t),...this.sessions.slice(t+1)],t--)}}clone(){const t=new $;return Object.assign(t,this),t.data={nodes:[...this.data.nodes],links:[...this.data.links]},t}}const J=new class{constructor(){this._users=[]}async getById(t,e){let n=this._users.find((e=>e._id===t));var r,o,i;return n||(n=await e.users.getById(t),this._users.push(n),n.image=await(r=n.imageUrl||"https://raw.githubusercontent.com/sanity-io/sanity-plugin-graph-view/main/assets/head-silhouette.jpg",o=40,i=40,new Promise((t=>{const e=new Image(o,i);e.onload=()=>{t(e)},e.onerror=e=>{console.log("Image error",e),t(null)},e.src=r})))),n}};function K(t){var e;const n=t.query||'\n *[\n !(_id in path("_.*")) &&\n !(_type match "system.*") &&\n !(_type match "sanity.*")\n ]\n',r=null!=(e=t.apiVersion)?e:"2022-09-01",o=x(),[i,s]=m(0),[a,c]=m(null),[l,b]=m([]),[v,w]=m({}),[O,j]=m((()=>new $)),S=k(),q=_({apiVersion:r}),Z=y((t=>{const e=function(t){const e={};for(const n of t)/^drafts\./.test(n._id)||(e[n._id]=n);for(const n of t)if(/^drafts\./.test(n._id)){const t=L(n._id);e[t]=Object.assign(n,{_id:t})}return Object.values(e)}(t);s(Math.max(...e.map(D))),b(e),w(R(e)),j(new $(e))}),[]),K=y((async t=>{const e=t.result;if(e){e._id=L(e._id);const n={};for(const t of l)n[t._id]=t;let r;const o=[...l],i=l.findIndex((t=>t._id===e._id));i>=0?(r=o[i],o[i]=e):o.push(e),b(o),w(R(o)),s(Math.max(...o.map(D)));const a=O.clone(),c=G(r||{}).filter((t=>t===e._id||null!==n[t])),d=G(e).filter((t=>t===e._id||null!==n[t]));let h,u=!f(c,d);u&&(a.data.links=a.data.links.filter((t=>t.source.id!==e._id)).concat(d.map((t=>({source:e._id,target:t})))));const p=O.data.nodes.findIndex((t=>t.doc&&t.doc._id===e._id));p>=0?(h=O.data.nodes[p],h.doc=e):(h={id:e._id,type:"document",doc:e},a.data.nodes.push(h),u=!0),u&&j(a);const m=await J.getById(t.identity,q);O.setSession(m,h)}else if("disappear"===t.transition){const e=L(t.documentId),n=l.filter((t=>t._id!==e));b(n),w(R(n)),s(Math.max(...n.map(D)));const r=O.clone();r.data.links=r.data.links.filter((t=>t.source.id!==e&&t.target.id!==e)),r.data.nodes=r.data.nodes.filter((t=>t.id!==e)),j(r)}}),[l,O,q]);!function(t,e,n,r){p((()=>{r.fetch(t).then((t=>{e(t)}))}),[...n,r])}(n,Z,[],q),function(t,e,n,r,o,i){p((()=>{const o=i.listen(t,e,n).subscribe((t=>{r(t)}));return()=>{o.unsubscribe()}}),[...o,i])}(n,{},{},K,[l,O],q),p((()=>{const t=setInterval((()=>O.reapSessions()),1e3);return()=>clearInterval(t)}),[O]);const Q=A().sanity;return d(C,{theme:Q,children:h(T,{theme:Q,children:[d(W,{theme:Q,children:(X=v,(tt=Object.keys(X),et=t=>X[t]||0,tt.sort(((t,e)=>{const n=et(t),r=et(e);return n<r?-1:n>r?1:0}))).reverse().slice(0,10)).map((t=>h(z,{className:"legend__row",style:{color:Y(t).fill},children:[d(B,{theme:Q}),d("div",{children:H(t)})]},t)))}),a&&d(V,{theme:Q,children:U(a.doc)}),d(g,{graphData:O.data,nodeAutoColorBy:"group",enableNodeDrag:!1,onNodeHover:t=>c(t),onNodeClick:t=>{S.navigateIntent("edit",{id:t.doc._id,documentType:t.doc._type})},linkColor:()=>u(P[500].hex,.25),nodeLabel:()=>"",nodeRelSize:1,nodeVal:t=>F(t.doc,i),onRenderFramePost:(t,e)=>{for(const n of O.sessions){const r=O.data.nodes.find((t=>{var e;return t.doc&&t.doc._id===(null==(e=null==n?void 0:n.doc)?void 0:e._id)}));if(r){const s=1e4,a=n.angle,c=Math.sqrt(F(r.doc,i)),l=n.user.image,d=o.get(n.user.displayName).tints[400].hex,h=c*e+40,f=l?l.width:0,p=l?l.height:0,m=r.x+Math.sin(a)*h/e,y=r.y+Math.cos(a)*h/e;t.save();try{if(t.globalAlpha=N(1-Math.min(s,Date.now()-n.lastActive)/s),t.font="bold ".concat(Math.round(12/e),"px sans-serif"),t.beginPath(),t.strokeStyle=u(I.hex,1),t.lineWidth=2/e,t.moveTo(r.x+Math.sin(a)*(h-f/2)/e,r.y+Math.cos(a)*(h-p/2)/e),t.lineTo(r.x+Math.sin(a)*c,r.y+Math.cos(a)*c),t.stroke(),t.beginPath(),t.strokeStyle=u(I.hex,1),t.lineWidth=2/e,t.arc(r.x,r.y,c,0,2*Math.PI,!1),t.stroke(),l){t.save();try{const r=700,o=E(Math.max(0,(r-(Date.now()-n.startTime))/r));o>0&&(t.beginPath(),t.fillStyle=u(d,o),t.arc(m,y,(f/2+10)/e,0,2*Math.PI,!1),t.fill()),t.beginPath(),t.fillStyle=u(I.hex,1),t.arc(m,y,f/e/2,0,2*Math.PI,!1),t.clip(),t.drawImage(l,m-f/e/2,y-p/e/2,f/e,p/e),t.strokeStyle=M.hex,t.lineWidth=6/e,t.stroke(),t.strokeStyle=d,t.lineWidth=4/e,t.stroke()}finally{t.restore()}}t.beginPath(),t.strokeStyle=u(M.hex,1),t.lineWidth=.5/e,t.arc(m,y,f/e/2,0,2*Math.PI,!1),t.stroke();const o=a>=Math.PI/2&&a<1.5*Math.PI,i=o?y-(p/2+5)/e:y+(p/2+5)/e;t.fillStyle=u(I.hex,1),t.textAlign="center",t.textBaseline=o?"bottom":"top",t.fillText(n.user.displayName,m,i)}finally{t.restore()}}}},nodeCanvasObject:(t,e,n)=>{if("document"===t.type){const s=Y(t.doc._type),c=Math.sqrt(F(t.doc,i)),l=Math.min(100,10/n);if(e.beginPath(),e.fillStyle=null!==a&&t.doc._id===a.doc._id?u(P[500].hex,.8):s.fill,e.strokeStyle=s.border,e.lineWidth=.5,e.arc(t.x,t.y,c,0,2*Math.PI,!1),e.stroke(),e.fill(),c*n>10){e.font="".concat(l,"px sans-serif");const i=2*c+30/n;for(let s=50;s>=5;s/=1.2){const a=(r=U(t.doc),o=Math.round(s),r.length>o?"".concat(r.substring(0,o),"…"):r);if(e.measureText(a).width<i){e.textAlign="center",e.textBaseline="top",e.strokeStyle=u(M.hex,.5),e.lineWidth=2/n,e.strokeText(a,t.x,t.y+c+5/n),e.fillText(a,t.x,t.y+c+5/n);break}}}}var r,o},linkCanvasObject:(t,e,n)=>{e.beginPath(),e.strokeStyle=u(P[500].hex,.125),e.lineWidth=2/n,e.moveTo(t.source.x,t.source.y),e.lineTo(t.target.x,t.target.y),e.stroke()}})]})});var X,tt,et}const Q={};let X=0;function Y(t){if(Q[t])return Q[t];const e=q[X%q.length];return X+=1,Q[t]={fill:S[e][400].hex,border:u(M.hex,.5)},Q[t]}const tt=()=>d("svg",{xmlns:"http://www.w3.org/2000/svg",width:"1em",height:"1em",viewBox:"0 0 46.063 46.063",children:d("path",{fill:"currentColor",d:"M18.022 38.676v2.813h-1.21q-4.864 0-6.525-1.447-1.64-1.445-1.64-5.762v-4.666q0-2.95-1.055-4.083-1.055-1.13-3.828-1.13h-1.19v-2.794h1.19q2.793 0 3.828-1.114 1.055-1.132 1.055-4.043V11.76q0-4.316 1.64-5.742 1.66-1.446 6.524-1.446h1.212v2.793h-1.328q-2.754 0-3.594.86-.84.86-.84 3.613v4.844q0 3.066-.898 4.453-.88 1.387-3.028 1.875 2.168.527 3.047 1.914.88 1.387.88 4.434v4.843q0 2.754.84 3.614.84.86 3.594.86h1.328zM28.04 38.676h1.368q2.735 0 3.555-.84.84-.84.84-3.633V29.36q0-3.047.88-4.434.878-1.387 3.046-1.914-2.17-.488-3.048-1.875-.88-1.387-.88-4.453V11.84q0-2.773-.84-3.613-.82-.86-3.554-.86H28.04V4.574h1.232q4.863 0 6.484 1.446 1.64 1.426 1.64 5.742v4.687q0 2.91 1.055 4.042 1.056 1.114 3.83 1.114h1.21V24.4h-1.21q-2.774 0-3.83 1.13-1.053 1.134-1.053 4.084v4.667q0 4.318-1.64 5.763-1.622 1.446-6.485 1.446h-1.23v-2.814z"})}),et=w((function(){let t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return{name:"@sanity/content-graph-view",tools:e=>[...e,{name:"graph-your-content",title:"Graph",icon:tt,component:function(){return d(K,a({},t))},router:O.create("/:selectedDocumentId")}]}}));export{et as contentGraphView};
2
- //# sourceMappingURL=index.esm.js.map