sanity-plugin-graph-view
5.0.135.0.14
dist/GraphView.js−
dist/GraphView.jsDeleted−286
Index: package/dist/GraphView.js
===================================================================
--- package/dist/GraphView.js
+++ package/dist/GraphView.js
@@ -1,286 +0,0 @@
-import { useEffect, useState } from "react";
-import { useClient, useUserColorManager } from "sanity";
-import { useRouter } from "sanity/router";
-import { jsx, jsxs } from "react/jsx-runtime";
-import { COLOR_HUES, black, gray, hues, white } from "@sanity/color";
-import { useTheme } from "@sanity/ui";
-import BezierEasing from "bezier-easing";
-import deepEqual from "deep-equal";
-import { rgba } from "polished";
-import ForceGraph2D from "react-force-graph-2d";
-import { v4 } from "uuid";
-import { styled } from "styled-components";
-const GraphRoot = styled.div.withConfig({
- displayName: "GraphRoot",
- componentId: "sc-kbb958-0"
-})`font-family:${({ theme }) => theme.fonts.text.family};position:absolute;top:0;left:0;width:100%;height:100%;background:${black.hex};`, GraphWrapper = styled.div.withConfig({
- displayName: "GraphWrapper",
- componentId: "sc-kbb958-1"
-})`position:relative;width:100%;height:100%;`, HoverNode = styled.div.withConfig({
- displayName: "HoverNode",
- componentId: "sc-kbb958-2"
-})`font-family:${({ theme }) => theme.fonts.text.family};display:none;position:absolute;bottom:${({ theme }) => theme.space[0]}px;left:50%;transform:translate3d(-50%,0,0);background:var(--component-bg);border-radius:${({ theme }) => theme.radius[2]}px;padding:${({ theme }) => theme.space[2]}px;z-index:1000;&:empty{display:none;}`, Legend = styled.div.withConfig({
- displayName: "Legend",
- componentId: "sc-kbb958-3"
-})`color:#ccc;position:absolute;top:${({ theme }) => theme.space[4]}px;left:${({ theme }) => theme.space[4]}px;& > div{margin:5px 0;}`, LegendRow = styled.div.withConfig({
- displayName: "LegendRow",
- componentId: "sc-kbb958-4"
-})`display:flex;`, LegendBadge = styled.div.withConfig({
- displayName: "LegendBadge",
- componentId: "sc-kbb958-5"
-})`width:1.25em;height:1.25em;background:currentColor;border-radius:50%;margin-right:${({ theme }) => theme.space[2]}px;`;
-function sizeOf(value) {
- 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;
-}
-function loadImage(url, w, h) {
- return new Promise((resolve) => {
- let img = new Image(w, h);
- img.onload = () => {
- resolve(img);
- }, img.onerror = (event) => {
- console.error("Image error", event), resolve(null);
- }, img.src = url;
- });
-}
-function sortBy(array, f) {
- return array.sort((a, b) => {
- let va = f(a), vb = f(b);
- return va < vb ? -1 : +(va > vb);
- });
-}
-function truncate(s, limit) {
- return s.length > limit ? `${s.substring(0, limit)}…` : s;
-}
-const fadeEasing = BezierEasing(0, .9, 1, 1), softEasing = BezierEasing(.25, .1, 0, 1), idleTimeout = 1e4;
-function getTopDocTypes(counts) {
- return sortBy(Object.keys(counts), (docType) => counts[docType] || 0).reverse().slice(0, 10);
-}
-function formatDocType(docType) {
- return (docType.substring(0, 1).toUpperCase() + docType.substring(1)).replace(/\./g, " ").replace(/[A-Z]/g, " $&").trim();
-}
-function getDocTypeCounts(docs) {
- let types = {};
- for (let doc of docs) types[doc._type] = (types[doc._type] || 0) + 1;
- return types;
-}
-function labelFor(doc) {
- return `${doc.title || doc.name || doc._id}`.trim();
-}
-function valueFor(doc, maxSize) {
- return 5 + 100 * (sizeOf(doc) / maxSize);
-}
-function findRefs(obj, dest = []) {
- if (obj !== null) {
- if (typeof obj == "object") for (let [k, v] of Object.entries(obj)) k === "_ref" && typeof v == "string" && v.length > 0 && dest.push(stripDraftId(v)), findRefs(v, dest);
- else if (Array.isArray(obj)) for (let v of obj) findRefs(v, dest);
- }
- return dest;
-}
-function stripDraftId(id) {
- return id.replace(/^drafts\./, "");
-}
-function deduplicateDrafts(docs) {
- let deduped = {};
- for (let doc of docs) doc._id.startsWith("drafts.") || (deduped[doc._id] = doc);
- for (let doc of docs) if (doc._id.startsWith("drafts.")) {
- let id = stripDraftId(doc._id);
- deduped[id] = Object.assign(doc, { _id: id });
- }
- return Object.values(deduped);
-}
-var Users = class {
- _users = [];
- async getById(id, client) {
- let user = this._users.find((u) => u._id === id);
- return user || (user = await client.users.getById(id), this._users.push(user), user.image = await loadImage(user.imageUrl || "https://raw.githubusercontent.com/sanity-io/sanity-plugin-graph-view/main/assets/head-silhouette.jpg", 40, 40)), user;
- }
-}, Session = class {
- id = null;
- user = null;
- doc = null;
- lastActive = 0;
- startTime = 0;
- angle = 0;
-}, GraphData = class GraphData {
- sessions = [];
- data;
- constructor(docs = []) {
- let docsById = {};
- for (let doc of docs) docsById[doc._id] = doc;
- this.data = {
- nodes: docs.map((d) => Object.assign({
- id: d._id,
- type: "document",
- doc: d
- })),
- links: docs.flatMap((doc) => findRefs(doc).map((ref) => ({
- source: doc._id,
- target: ref
- }))).filter((link) => docsById[link.source] && docsById[link.target])
- };
- }
- setSession(user, docNode) {
- let session = this.sessions.find((s) => s.user.id === user.id && s.doc?._id === docNode.doc._id);
- 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();
- }
- reapSessions() {
- for (let i = 0; i < this.sessions.length; i++) {
- let session = this.sessions[i];
- Date.now() - session.lastActive > idleTimeout && (this.sessions = [...this.sessions.slice(0, i), ...this.sessions.slice(i + 1)], i--);
- }
- }
- clone() {
- let copy = new GraphData();
- return Object.assign(copy, this), copy.data = {
- nodes: [...this.data.nodes],
- links: [...this.data.links]
- }, copy;
- }
-};
-const users = new Users();
-function GraphView({ tool: { options: props } }) {
- let query = props.query || "\n *[\n !(_id in path(\"_.*\")) &&\n !(_type match \"system.*\") &&\n !(_type match \"sanity.*\")\n ]\n", 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 });
- useEffect(() => {
- client.fetch(query).then((_docs) => {
- let docs = deduplicateDrafts(_docs);
- setMaxSize(Math.max(...docs.map(sizeOf))), setDocuments(docs), setDocTypes(getDocTypeCounts(docs)), setGraph(new GraphData(docs));
- });
- }, [client, query]), useEffect(() => {
- let subscription = client.listen(query, {}, {}).subscribe(async (update) => {
- if (update.type !== "mutation") return;
- let doc = update.result;
- if (doc) {
- doc._id = stripDraftId(doc._id);
- let docsById = {};
- for (let d of documents) docsById[d._id] = d;
- let oldDoc, docs_0 = [...documents], idx = documents.findIndex((d_0) => d_0._id === doc._id);
- idx >= 0 ? (oldDoc = docs_0[idx], docs_0[idx] = doc) : docs_0.push(doc), setDocuments(docs_0), setDocTypes(getDocTypeCounts(docs_0)), setMaxSize(Math.max(...docs_0.map(sizeOf)));
- let newGraph = graph.clone(), oldRefs = findRefs(oldDoc || {}).filter((id) => id === doc._id || docsById[id] !== null), newRefs = findRefs(doc).filter((id_0) => id_0 === doc._id || docsById[id_0] !== null), graphChanged = !deepEqual(oldRefs, newRefs);
- graphChanged && (newGraph.data.links = newGraph.data.links.filter((l) => l.source.id !== doc._id).concat(newRefs.map((ref) => ({
- source: doc._id,
- target: ref
- }))));
- let docNode, nodeIdx = graph.data.nodes.findIndex((n) => n.doc && n.doc._id === doc._id);
- 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);
- let user = await users.getById(update.identity, client);
- graph.setSession(user, docNode);
- } else if (update.transition === "disappear") {
- let docId = stripDraftId(update.documentId), docs_1 = documents.filter((d_1) => d_1._id !== docId);
- setDocuments(docs_1), setDocTypes(getDocTypeCounts(docs_1)), setMaxSize(Math.max(...docs_1.map(sizeOf)));
- let newGraph_0 = graph.clone();
- newGraph_0.data.links = newGraph_0.data.links.filter((l_0) => l_0.source.id !== docId && l_0.target.id !== docId), newGraph_0.data.nodes = newGraph_0.data.nodes.filter((n_0) => n_0.id !== docId), setGraph(newGraph_0);
- }
- });
- return () => {
- subscription.unsubscribe();
- };
- }, [
- client,
- query,
- documents,
- graph
- ]), useEffect(() => {
- let interval = setInterval(() => graph.reapSessions(), 1e3);
- return () => clearInterval(interval);
- }, [graph]);
- let theme = useTheme().sanity;
- return /* @__PURE__ */ jsx(GraphWrapper, {
- theme,
- children: /* @__PURE__ */ jsxs(GraphRoot, {
- theme,
- children: [
- /* @__PURE__ */ jsx(Legend, {
- theme,
- children: getTopDocTypes(docTypes).map((docType) => /* @__PURE__ */ jsxs(LegendRow, {
- className: "legend__row",
- style: { color: getDocTypeColor(docType).fill },
- children: [/* @__PURE__ */ jsx(LegendBadge, { theme }), /* @__PURE__ */ jsx("div", { children: formatDocType(docType) })]
- }, docType))
- }),
- hoverNode && /* @__PURE__ */ jsx(HoverNode, {
- theme,
- children: labelFor(hoverNode.doc)
- }),
- /* @__PURE__ */ jsx(ForceGraph2D, {
- graphData: graph.data,
- nodeAutoColorBy: "group",
- enableNodeDrag: !1,
- onNodeHover: (node) => setHoverNode(node),
- onNodeClick: (node_0) => {
- router.navigateIntent("edit", {
- id: node_0.doc._id,
- documentType: node_0.doc._type
- });
- },
- linkColor: () => rgba(gray[500].hex, .25),
- nodeLabel: () => "",
- nodeRelSize: 1,
- nodeVal: (node_1) => valueFor(node_1.doc, maxSize),
- onRenderFramePost: (ctx, globalScale) => {
- for (let session of graph.sessions) {
- let node_2 = graph.data.nodes.find((n_1) => n_1.doc && n_1.doc._id === session?.doc?._id);
- if (node_2) {
- let idleFactorRange = idleTimeout, angle = session.angle, radius = Math.sqrt(valueFor(node_2.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_2.x + Math.sin(angle) * distance / globalScale, y = node_2.y + Math.cos(angle) * distance / globalScale;
- ctx.save();
- try {
- if (ctx.globalAlpha = fadeEasing(1 - Math.min(idleFactorRange, Date.now() - session.lastActive) / idleFactorRange), ctx.font = `bold ${Math.round(12 / globalScale)}px sans-serif`, ctx.beginPath(), ctx.strokeStyle = rgba(white.hex, 1), ctx.lineWidth = 2 / globalScale, ctx.moveTo(node_2.x + Math.sin(angle) * (distance - imgW / 2) / globalScale, node_2.y + Math.cos(angle) * (distance - imgH / 2) / globalScale), ctx.lineTo(node_2.x + Math.sin(angle) * radius, node_2.y + Math.cos(angle) * radius), ctx.stroke(), ctx.beginPath(), ctx.strokeStyle = rgba(white.hex, 1), ctx.lineWidth = 2 / globalScale, ctx.arc(node_2.x, node_2.y, radius, 0, 2 * Math.PI, !1), ctx.stroke(), image) {
- ctx.save();
- try {
- let f = softEasing(Math.max(0, (700 - (Date.now() - session.startTime)) / 700));
- 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(image, x - imgW / globalScale / 2, y - imgH / globalScale / 2, imgW / globalScale, imgH / globalScale), ctx.strokeStyle = black.hex, ctx.lineWidth = 6 / globalScale, ctx.stroke(), ctx.strokeStyle = userColor, ctx.lineWidth = 4 / globalScale, ctx.stroke();
- } finally {
- ctx.restore();
- }
- }
- ctx.beginPath(), ctx.strokeStyle = rgba(black.hex, 1), ctx.lineWidth = .5 / globalScale, ctx.arc(x, y, imgW / globalScale / 2, 0, 2 * Math.PI, !1), ctx.stroke();
- let above = angle >= Math.PI / 2 && angle < Math.PI * 1.5, textY = above ? y - (imgH / 2 + 5) / globalScale : y + (imgH / 2 + 5) / globalScale;
- ctx.fillStyle = rgba(white.hex, 1), ctx.textAlign = "center", ctx.textBaseline = above ? "bottom" : "top", ctx.fillText(session.user.displayName, x, textY);
- } finally {
- ctx.restore();
- }
- }
- }
- },
- nodeCanvasObject: (node_3, ctx_0, globalScale_0) => {
- switch (node_3.type) {
- case "document": {
- let nodeColor = getDocTypeColor(node_3.doc._type), radius_0 = Math.sqrt(valueFor(node_3.doc, maxSize)), fontSize = Math.min(100, 10 / globalScale_0);
- if (ctx_0.beginPath(), ctx_0.fillStyle = hoverNode !== null && node_3.doc._id === hoverNode.doc._id ? rgba(gray[500].hex, .8) : nodeColor.fill, ctx_0.strokeStyle = nodeColor.border, ctx_0.lineWidth = .5, ctx_0.arc(node_3.x, node_3.y, radius_0, 0, 2 * Math.PI, !1), ctx_0.stroke(), ctx_0.fill(), radius_0 * globalScale_0 > 10) {
- ctx_0.font = `${fontSize}px sans-serif`;
- let w = radius_0 * 2 + 30 / globalScale_0;
- for (let len = 50; len >= 5; len /= 1.2) {
- let label = truncate(labelFor(node_3.doc), Math.round(len));
- if (ctx_0.measureText(label).width < w) {
- ctx_0.textAlign = "center", ctx_0.textBaseline = "top", ctx_0.strokeStyle = rgba(black.hex, .5), ctx_0.lineWidth = 2 / globalScale_0, ctx_0.strokeText(label, node_3.x, node_3.y + radius_0 + 5 / globalScale_0), ctx_0.fillText(label, node_3.x, node_3.y + radius_0 + 5 / globalScale_0);
- break;
- }
- }
- }
- }
- }
- },
- linkCanvasObject: (link, ctx_1, globalScale_1) => {
- ctx_1.beginPath(), ctx_1.strokeStyle = rgba(gray[500].hex, .125), ctx_1.lineWidth = 2 / globalScale_1, ctx_1.moveTo(link.source.x, link.source.y), ctx_1.lineTo(link.target.x, link.target.y), ctx_1.stroke();
- }
- })
- ]
- })
- });
-}
-const colorCache = {};
-let typeColorNum = 0;
-function getDocTypeColor(docType) {
- if (colorCache[docType]) return colorCache[docType];
- let hue = COLOR_HUES[typeColorNum % COLOR_HUES.length];
- return typeColorNum += 1, colorCache[docType] = {
- fill: hues[hue][400].hex,
- border: rgba(black.hex, .5)
- }, colorCache[docType];
-}
-export { GraphView as default };
-
-//# sourceMappingURL=GraphView.js.map
\ No newline at end of file