@sanity/hierarchical-document-list 0.1.0-next.1
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/LICENSE +21 -0
- package/README.md +210 -0
- package/lib/TreeDeskStructure.d.ts +7 -0
- package/lib/TreeDeskStructure.js +43 -0
- package/lib/TreeInputComponent.d.ts +19 -0
- package/lib/TreeInputComponent.js +10 -0
- package/lib/components/DocumentInNode.d.ts +11 -0
- package/lib/components/DocumentInNode.js +46 -0
- package/lib/components/DocumentPreviewStatus.d.ts +7 -0
- package/lib/components/DocumentPreviewStatus.js +16 -0
- package/lib/components/NodeActions.d.ts +10 -0
- package/lib/components/NodeActions.js +23 -0
- package/lib/components/NodeContentRenderer.d.ts +8 -0
- package/lib/components/NodeContentRenderer.js +79 -0
- package/lib/components/PlaceholderDropzone.d.ts +9 -0
- package/lib/components/PlaceholderDropzone.js +17 -0
- package/lib/components/TreeEditor.d.ts +12 -0
- package/lib/components/TreeEditor.js +41 -0
- package/lib/components/TreeEditorErrorBoundary.d.ts +17 -0
- package/lib/components/TreeEditorErrorBoundary.js +40 -0
- package/lib/components/TreeNodeRenderer.d.ts +3 -0
- package/lib/components/TreeNodeRenderer.js +22 -0
- package/lib/components/TreeNodeRendererScaffold.d.ts +4 -0
- package/lib/components/TreeNodeRendererScaffold.js +164 -0
- package/lib/createDeskHierarchy.d.ts +10 -0
- package/lib/createDeskHierarchy.js +52 -0
- package/lib/createHierarchicalField.d.ts +8 -0
- package/lib/createHierarchicalField.js +30 -0
- package/lib/hiearchy.tree.d.ts +23 -0
- package/lib/hiearchy.tree.js +28 -0
- package/lib/index.d.ts +3 -0
- package/lib/index.js +3 -0
- package/lib/utils/flatDataToTree.d.ts +6 -0
- package/lib/utils/flatDataToTree.js +14 -0
- package/lib/utils/getAdjescentNodes.d.ts +12 -0
- package/lib/utils/getAdjescentNodes.js +15 -0
- package/lib/utils/getCommonTreeProps.d.ts +7 -0
- package/lib/utils/getCommonTreeProps.js +15 -0
- package/lib/utils/getTreeHeight.d.ts +3 -0
- package/lib/utils/getTreeHeight.js +7 -0
- package/lib/utils/gradientPatchAdapter.d.ts +4 -0
- package/lib/utils/gradientPatchAdapter.js +34 -0
- package/lib/utils/idUtils.d.ts +2 -0
- package/lib/utils/idUtils.js +6 -0
- package/lib/utils/moveItemInArray.d.ts +5 -0
- package/lib/utils/moveItemInArray.js +13 -0
- package/lib/utils/treeData.d.ts +18 -0
- package/lib/utils/treeData.js +77 -0
- package/lib/utils/treePatches.d.ts +13 -0
- package/lib/utils/treePatches.js +133 -0
- package/lib/utils/useAllItems.d.ts +7 -0
- package/lib/utils/useAllItems.js +92 -0
- package/lib/utils/useLocalTree.d.ts +17 -0
- package/lib/utils/useLocalTree.js +27 -0
- package/lib/utils/useTreeOperations.d.ts +9 -0
- package/lib/utils/useTreeOperations.js +16 -0
- package/lib/utils/useTreeOperationsProvider.d.ts +15 -0
- package/lib/utils/useTreeOperationsProvider.js +52 -0
- package/package.json +54 -0
- package/sanity.json +12 -0
- package/screenshot-1.jpg +0 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useToast } from '@sanity/ui';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
const ErrorToast = ({ error }) => {
|
|
5
|
+
const { push } = useToast();
|
|
6
|
+
React.useEffect(() => {
|
|
7
|
+
if (error?.title) {
|
|
8
|
+
push({
|
|
9
|
+
title: error.title,
|
|
10
|
+
description: error.description,
|
|
11
|
+
closable: true,
|
|
12
|
+
status: 'error',
|
|
13
|
+
id: 'hierarchical-error'
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
}, [error]);
|
|
17
|
+
return null;
|
|
18
|
+
};
|
|
19
|
+
class TreeEditorErrorBoundary extends React.Component {
|
|
20
|
+
constructor(props) {
|
|
21
|
+
super(props);
|
|
22
|
+
this.state = { error: undefined };
|
|
23
|
+
}
|
|
24
|
+
static getDerivedStateFromError(error) {
|
|
25
|
+
if (!error) {
|
|
26
|
+
return {
|
|
27
|
+
error: undefined
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
error: {
|
|
32
|
+
title: 'Something went wrong'
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
render() {
|
|
37
|
+
return (_jsxs(React.Fragment, { children: [_jsx(ErrorToast, { error: this.state.error }, void 0), this.props.children] }, void 0));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
export default TreeEditorErrorBoundary;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import TreeNodeRendererScaffold from './TreeNodeRendererScaffold';
|
|
4
|
+
/**
|
|
5
|
+
* To prevent expand buttons from overflowing on the left, we add a minimum left padding to all entries
|
|
6
|
+
*/
|
|
7
|
+
const BASE_LEFT_PADDING = 10;
|
|
8
|
+
const NESTING_PADDING = 14;
|
|
9
|
+
const TreeNodeRenderer = (props) => {
|
|
10
|
+
const { children, lowerSiblingCounts, connectDropTarget, isOver, draggedNode, canDrop } = props;
|
|
11
|
+
// Construct the scaffold representing the structure of the tree
|
|
12
|
+
const scaffoldBlockCount = lowerSiblingCounts.length;
|
|
13
|
+
return connectDropTarget(_jsxs("div", { style: props.style, children: [_jsx("div", { style: {
|
|
14
|
+
// prettier-ignore
|
|
15
|
+
paddingLeft: `${BASE_LEFT_PADDING + (NESTING_PADDING * scaffoldBlockCount)}px`
|
|
16
|
+
}, children: React.Children.map(children, (child) => React.cloneElement(child, {
|
|
17
|
+
isOver,
|
|
18
|
+
canDrop,
|
|
19
|
+
draggedNode
|
|
20
|
+
})) }, void 0), _jsx(TreeNodeRendererScaffold, { ...props }, void 0)] }, void 0));
|
|
21
|
+
};
|
|
22
|
+
export default TreeNodeRenderer;
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { blue } from '@sanity/color';
|
|
3
|
+
import { createGlobalStyle } from 'styled-components';
|
|
4
|
+
// Adapted from react-sortable-tree/src/tree-node.js
|
|
5
|
+
const ScaffoldStyles = createGlobalStyle `
|
|
6
|
+
.rst__lineBlock,
|
|
7
|
+
.rst__absoluteLineBlock {
|
|
8
|
+
height: 100%;
|
|
9
|
+
position: relative;
|
|
10
|
+
display: inline-block;
|
|
11
|
+
--stroke-width: 3px;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.rst__absoluteLineBlock {
|
|
15
|
+
position: absolute;
|
|
16
|
+
top: 0;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/* Highlight line for pointing to dragged row destination
|
|
20
|
+
========================================================================== */
|
|
21
|
+
/**
|
|
22
|
+
* +--+--+
|
|
23
|
+
* | | |
|
|
24
|
+
* | | |
|
|
25
|
+
* | | |
|
|
26
|
+
* +--+--+
|
|
27
|
+
*/
|
|
28
|
+
.rst__highlightLineVertical {
|
|
29
|
+
z-index: 3;
|
|
30
|
+
}
|
|
31
|
+
.rst__highlightLineVertical::before {
|
|
32
|
+
position: absolute;
|
|
33
|
+
content: '';
|
|
34
|
+
background-color: ${blue[400].hex};
|
|
35
|
+
width: calc(var(--stroke-width) * 2);
|
|
36
|
+
margin-left: calc(var(--stroke-width) * -1);
|
|
37
|
+
left: 50%;
|
|
38
|
+
top: 0;
|
|
39
|
+
height: 100%;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
@keyframes arrow-pulse {
|
|
43
|
+
0% {
|
|
44
|
+
transform: translate(0, 0);
|
|
45
|
+
opacity: 0;
|
|
46
|
+
}
|
|
47
|
+
30% {
|
|
48
|
+
transform: translate(0, 300%);
|
|
49
|
+
opacity: 1;
|
|
50
|
+
}
|
|
51
|
+
70% {
|
|
52
|
+
transform: translate(0, 700%);
|
|
53
|
+
opacity: 1;
|
|
54
|
+
}
|
|
55
|
+
100% {
|
|
56
|
+
transform: translate(0, 1000%);
|
|
57
|
+
opacity: 0;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
.rst__highlightLineVertical::after {
|
|
61
|
+
content: '';
|
|
62
|
+
position: absolute;
|
|
63
|
+
height: 0;
|
|
64
|
+
margin-left: calc(var(--stroke-width) * -1);
|
|
65
|
+
left: 50%;
|
|
66
|
+
top: 0;
|
|
67
|
+
border-left: var(--stroke-width) solid transparent;
|
|
68
|
+
border-right: var(--stroke-width) solid transparent;
|
|
69
|
+
border-top: var(--stroke-width) solid white;
|
|
70
|
+
animation: arrow-pulse 1s infinite linear both;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* +-----+
|
|
75
|
+
* | |
|
|
76
|
+
* | +--+
|
|
77
|
+
* | | |
|
|
78
|
+
* +--+--+
|
|
79
|
+
*/
|
|
80
|
+
.rst__highlightTopLeftCorner::before {
|
|
81
|
+
z-index: 3;
|
|
82
|
+
content: '';
|
|
83
|
+
position: absolute;
|
|
84
|
+
border-top: solid calc(var(--stroke-width) * 2) ${blue[400].hex};
|
|
85
|
+
border-left: solid calc(var(--stroke-width) * 2) ${blue[400].hex};
|
|
86
|
+
box-sizing: border-box;
|
|
87
|
+
height: calc(50% + var(--stroke-width));
|
|
88
|
+
top: 50%;
|
|
89
|
+
margin-top: calc(var(--stroke-width) * -1);
|
|
90
|
+
right: 0;
|
|
91
|
+
width: calc(50% + var(--stroke-width));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* +--+--+
|
|
96
|
+
* | | |
|
|
97
|
+
* | | |
|
|
98
|
+
* | +->|
|
|
99
|
+
* +-----+
|
|
100
|
+
*/
|
|
101
|
+
.rst__highlightBottomLeftCorner {
|
|
102
|
+
z-index: 3;
|
|
103
|
+
}
|
|
104
|
+
.rst__highlightBottomLeftCorner::before {
|
|
105
|
+
content: '';
|
|
106
|
+
position: absolute;
|
|
107
|
+
border-bottom: solid calc(var(--stroke-width) * 2) ${blue[400].hex};
|
|
108
|
+
border-left: solid calc(var(--stroke-width) * 2) ${blue[400].hex};
|
|
109
|
+
box-sizing: border-box;
|
|
110
|
+
height: calc(100% + var(--stroke-width));
|
|
111
|
+
top: 0;
|
|
112
|
+
right: calc(var(--stroke-width) * 3);
|
|
113
|
+
width: calc(50% - calc(var(--stroke-width) * 2));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.rst__highlightBottomLeftCorner::after {
|
|
117
|
+
content: '';
|
|
118
|
+
position: absolute;
|
|
119
|
+
height: 0;
|
|
120
|
+
right: 0;
|
|
121
|
+
top: 100%;
|
|
122
|
+
margin-top: calc(var(--stroke-width) * -3);
|
|
123
|
+
border-top: calc(var(--stroke-width) * 3) solid transparent;
|
|
124
|
+
border-bottom: calc(var(--stroke-width) * 3) solid transparent;
|
|
125
|
+
border-left: calc(var(--stroke-width) * 3) solid ${blue[400].hex};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.rst__unclickable {
|
|
129
|
+
pointer-events: none;
|
|
130
|
+
margin-top: -calc(var(--stroke-width) * 3);
|
|
131
|
+
}
|
|
132
|
+
`;
|
|
133
|
+
const TreeNodeRendererScaffold = (props) => {
|
|
134
|
+
const { lowerSiblingCounts, scaffoldBlockPxWidth, listIndex, swapDepth, swapFrom, swapLength, treeIndex } = props;
|
|
135
|
+
// Construct the scaffold representing the structure of the tree
|
|
136
|
+
const scaffold = lowerSiblingCounts.map((lowerSiblingCount, i) => {
|
|
137
|
+
if (lowerSiblingCount < 0 || treeIndex === listIndex || i !== swapDepth) {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
// This row has been shifted, and is at the depth of
|
|
141
|
+
// the line pointing to the new destination
|
|
142
|
+
let highlightLineClass = '';
|
|
143
|
+
if (listIndex === (swapFrom || 0) + (swapLength || 0) - 1) {
|
|
144
|
+
// This block is on the bottom (target) line
|
|
145
|
+
// This block points at the target block (where the row will go when released)
|
|
146
|
+
highlightLineClass = 'rst__highlightBottomLeftCorner';
|
|
147
|
+
}
|
|
148
|
+
else if (treeIndex === swapFrom) {
|
|
149
|
+
// This block is on the top (source) line
|
|
150
|
+
highlightLineClass = 'rst__highlightTopLeftCorner';
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
// This block is between the bottom and top
|
|
154
|
+
highlightLineClass = 'rst__highlightLineVertical';
|
|
155
|
+
}
|
|
156
|
+
const style = {
|
|
157
|
+
width: scaffoldBlockPxWidth,
|
|
158
|
+
left: scaffoldBlockPxWidth * i
|
|
159
|
+
};
|
|
160
|
+
return (_jsx("div", { style: style, className: `rst__unclickable rst__absoluteLineBlock ${highlightLineClass || ''}`, tabIndex: -1 }, i));
|
|
161
|
+
});
|
|
162
|
+
return (_jsxs(_Fragment, { children: [scaffold, _jsx(ScaffoldStyles, {}, void 0)] }, void 0));
|
|
163
|
+
};
|
|
164
|
+
export default TreeNodeRendererScaffold;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { TreeDeskStructureProps } from './types';
|
|
2
|
+
interface TreeProps extends TreeDeskStructureProps {
|
|
3
|
+
/**
|
|
4
|
+
* Visible title above the tree.
|
|
5
|
+
* Also used as the label in the desk list item.
|
|
6
|
+
*/
|
|
7
|
+
title: string;
|
|
8
|
+
}
|
|
9
|
+
export default function createDeskHierarchy(props: TreeProps): import("@sanity/structure/dist/dts/ListItem").ListItemBuilder;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import S from '@sanity/desk-tool/structure-builder';
|
|
3
|
+
import { AddIcon } from '@sanity/icons';
|
|
4
|
+
import TreeDeskStructure from './TreeDeskStructure';
|
|
5
|
+
const deskTreeValidator = (props) => {
|
|
6
|
+
const { documentId, referenceTo } = props;
|
|
7
|
+
if (typeof documentId !== 'string' && !documentId) {
|
|
8
|
+
throw new Error('[hierarchical input] Please add a documentId to your tree');
|
|
9
|
+
}
|
|
10
|
+
if (!Array.isArray(referenceTo)) {
|
|
11
|
+
throw new Error(`[hierarchical input] Missing valid 'referenceTo' in createDeskHierarchy (documentId "${documentId}")`);
|
|
12
|
+
}
|
|
13
|
+
return (deskProps) => _jsx(TreeDeskStructure, { ...deskProps, options: props }, void 0);
|
|
14
|
+
};
|
|
15
|
+
export default function createDeskHierarchy(props) {
|
|
16
|
+
const { documentId, referenceTo, referenceOptions } = props;
|
|
17
|
+
let mainList = (referenceTo?.length === 1
|
|
18
|
+
? S.documentTypeList(referenceTo[0]).schemaType(referenceTo[0])
|
|
19
|
+
: S.documentList())
|
|
20
|
+
.id(documentId)
|
|
21
|
+
.menuItems((referenceTo || []).map((schemaType) => S.menuItem()
|
|
22
|
+
.intent({
|
|
23
|
+
type: 'create',
|
|
24
|
+
params: { type: schemaType, template: schemaType }
|
|
25
|
+
})
|
|
26
|
+
// @TODO: get the title for each schema type
|
|
27
|
+
.title(schemaType)
|
|
28
|
+
.icon(AddIcon)))
|
|
29
|
+
.canHandleIntent((intent, context) => {
|
|
30
|
+
if (intent === 'edit' && context.id === props.documentId) {
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
if (intent === 'create' && referenceTo.includes(context.type)) {
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
return false;
|
|
37
|
+
});
|
|
38
|
+
if (referenceOptions?.filter) {
|
|
39
|
+
mainList = mainList.filter(referenceOptions.filter);
|
|
40
|
+
}
|
|
41
|
+
if (referenceOptions?.filterParams) {
|
|
42
|
+
mainList = mainList.params(referenceOptions.filterParams);
|
|
43
|
+
}
|
|
44
|
+
return S.listItem()
|
|
45
|
+
.id(documentId)
|
|
46
|
+
.title(props.title || documentId)
|
|
47
|
+
.child(Object.assign(mainList.serialize(), {
|
|
48
|
+
type: 'component',
|
|
49
|
+
component: deskTreeValidator(props),
|
|
50
|
+
options: props
|
|
51
|
+
}, props.title ? { title: props.title } : {}));
|
|
52
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ArraySchemaType } from '@sanity/types';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { TreeFieldSchema } from './types';
|
|
4
|
+
export default function createHierarchicalField({ name, title, options, ...rest }: TreeFieldSchema): Omit<ArraySchemaType, 'type' | 'jsonType' | 'of'> & {
|
|
5
|
+
type: string;
|
|
6
|
+
inputComponent: React.FC<any>;
|
|
7
|
+
of: any[];
|
|
8
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import TreeInputComponent from './TreeInputComponent';
|
|
2
|
+
export default function createHierarchicalField({ name, title, options, ...rest }) {
|
|
3
|
+
if (!Array.isArray(options?.referenceTo)) {
|
|
4
|
+
throw new Error(`[hierarchical input] Missing valid options.referenceTo in createHierarchicalField (field of name "${name}")`);
|
|
5
|
+
}
|
|
6
|
+
return {
|
|
7
|
+
...rest,
|
|
8
|
+
options,
|
|
9
|
+
name,
|
|
10
|
+
title,
|
|
11
|
+
type: 'array',
|
|
12
|
+
of: [
|
|
13
|
+
{
|
|
14
|
+
type: 'object',
|
|
15
|
+
fields: [
|
|
16
|
+
{ name: 'parent', type: 'string' },
|
|
17
|
+
{ name: 'nodeDocType', type: 'string' },
|
|
18
|
+
{
|
|
19
|
+
name: 'node',
|
|
20
|
+
type: 'reference',
|
|
21
|
+
weak: true,
|
|
22
|
+
to: options.referenceTo.map((type) => ({ type })),
|
|
23
|
+
options: options.referenceOptions
|
|
24
|
+
}
|
|
25
|
+
]
|
|
26
|
+
}
|
|
27
|
+
],
|
|
28
|
+
inputComponent: TreeInputComponent
|
|
29
|
+
};
|
|
30
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
declare const _default: {
|
|
3
|
+
name: string;
|
|
4
|
+
title: string;
|
|
5
|
+
type: string;
|
|
6
|
+
liveEdit: boolean;
|
|
7
|
+
fields: (Omit<import("@sanity/types/dist/dts").ArraySchemaType<unknown>, "type" | "of" | "jsonType"> & {
|
|
8
|
+
type: string;
|
|
9
|
+
inputComponent: import("react").FC<any>;
|
|
10
|
+
of: any[];
|
|
11
|
+
})[];
|
|
12
|
+
preview: {
|
|
13
|
+
select: {
|
|
14
|
+
id: string;
|
|
15
|
+
tree: string;
|
|
16
|
+
};
|
|
17
|
+
prepare({ id, tree }: {
|
|
18
|
+
id: string;
|
|
19
|
+
tree: unknown[];
|
|
20
|
+
}): Record<string, string>;
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
export default _default;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import createHierarchicalField from './createHierarchicalField';
|
|
2
|
+
export default {
|
|
3
|
+
name: 'hierarchy.tree',
|
|
4
|
+
title: 'Hierarchical tree',
|
|
5
|
+
type: 'document',
|
|
6
|
+
liveEdit: true,
|
|
7
|
+
fields: [
|
|
8
|
+
createHierarchicalField({
|
|
9
|
+
name: 'tree',
|
|
10
|
+
title: 'Tree',
|
|
11
|
+
options: {
|
|
12
|
+
referenceTo: ['document']
|
|
13
|
+
}
|
|
14
|
+
})
|
|
15
|
+
],
|
|
16
|
+
preview: {
|
|
17
|
+
select: {
|
|
18
|
+
id: '_id',
|
|
19
|
+
tree: 'tree'
|
|
20
|
+
},
|
|
21
|
+
prepare({ id, tree }) {
|
|
22
|
+
return {
|
|
23
|
+
title: `Hierarchical documents (ID: ${id})`,
|
|
24
|
+
subtitle: `${tree?.length || 0} document(s) in its list.`
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
};
|
package/lib/index.d.ts
ADDED
package/lib/index.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { getTreeFromFlatData } from 'react-sortable-tree';
|
|
2
|
+
export default function flatDataToTree(data) {
|
|
3
|
+
return getTreeFromFlatData({
|
|
4
|
+
flatData: data.map((item) => ({
|
|
5
|
+
...item,
|
|
6
|
+
// if parent: undefined, the tree won't be constructed
|
|
7
|
+
parent: item.parent || null
|
|
8
|
+
})),
|
|
9
|
+
getKey: (item) => item._key,
|
|
10
|
+
getParentKey: (item) => item.parent,
|
|
11
|
+
// without rootKey, the tree won't be constructed
|
|
12
|
+
rootKey: null
|
|
13
|
+
});
|
|
14
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { FlatDataItem, TreeItem } from 'react-sortable-tree';
|
|
2
|
+
/**
|
|
3
|
+
* Gets adjescent non-children nodes of a given treeIndex.
|
|
4
|
+
*/
|
|
5
|
+
export default function getAdjescentNodes({ flatTree, node, treeIndex }: {
|
|
6
|
+
flatTree: FlatDataItem[];
|
|
7
|
+
node: TreeItem;
|
|
8
|
+
treeIndex: number;
|
|
9
|
+
}): {
|
|
10
|
+
leadingNode?: FlatDataItem;
|
|
11
|
+
followingNode?: FlatDataItem;
|
|
12
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gets adjescent non-children nodes of a given treeIndex.
|
|
3
|
+
*/
|
|
4
|
+
export default function getAdjescentNodes({ flatTree, node, treeIndex }) {
|
|
5
|
+
const leadingNode = flatTree
|
|
6
|
+
.slice(0, treeIndex)
|
|
7
|
+
.reverse()
|
|
8
|
+
// Disregard children nodes - these include the current node's key in their `path` array
|
|
9
|
+
.find((item) => !item.path.includes(node._key));
|
|
10
|
+
const followingNode = flatTree.slice(treeIndex + 1).find((item) => !item.path.includes(node._key));
|
|
11
|
+
return {
|
|
12
|
+
leadingNode,
|
|
13
|
+
followingNode
|
|
14
|
+
};
|
|
15
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import NodeContentRenderer from '../components/NodeContentRenderer';
|
|
3
|
+
import PlaceholderDropzone from '../components/PlaceholderDropzone';
|
|
4
|
+
import TreeNodeRenderer from '../components/TreeNodeRenderer';
|
|
5
|
+
import { ROW_HEIGHT } from './getTreeHeight';
|
|
6
|
+
export default function getCommonTreeProps({ placeholder }) {
|
|
7
|
+
return {
|
|
8
|
+
theme: {
|
|
9
|
+
nodeContentRenderer: NodeContentRenderer,
|
|
10
|
+
placeholderRenderer: (props) => _jsx(PlaceholderDropzone, { ...placeholder, ...props }, void 0),
|
|
11
|
+
treeNodeRenderer: TreeNodeRenderer,
|
|
12
|
+
rowHeight: ROW_HEIGHT
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { getVisibleNodeCount } from 'react-sortable-tree';
|
|
2
|
+
export const ROW_HEIGHT = 51;
|
|
3
|
+
export default function getTreeHeight(treeData) {
|
|
4
|
+
const visibleNodeCount = getVisibleNodeCount({ treeData });
|
|
5
|
+
// prettier-ignore
|
|
6
|
+
return `${50 + (ROW_HEIGHT * visibleNodeCount)}px`;
|
|
7
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Adapted from @sanity/form-builder/src/sanity/utils/gradientPatchAdapter.ts
|
|
2
|
+
import assert from 'assert';
|
|
3
|
+
import { arrayToJSONMatchPath } from '@sanity/mutator';
|
|
4
|
+
export function toGradient(patches) {
|
|
5
|
+
return patches.map(toGradientPatch);
|
|
6
|
+
}
|
|
7
|
+
function toGradientPatch(patch) {
|
|
8
|
+
const matchPath = arrayToJSONMatchPath(patch.path || []);
|
|
9
|
+
if (patch.type === 'insert') {
|
|
10
|
+
const { position, items } = patch;
|
|
11
|
+
return {
|
|
12
|
+
insert: {
|
|
13
|
+
[position]: matchPath,
|
|
14
|
+
items: items
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
if (patch.type === 'unset') {
|
|
19
|
+
return {
|
|
20
|
+
unset: [matchPath]
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
assert(patch.type, `Missing patch type in patch ${JSON.stringify(patch)}`);
|
|
24
|
+
if (matchPath) {
|
|
25
|
+
return {
|
|
26
|
+
[patch.type]: {
|
|
27
|
+
[matchPath]: patch.value
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
[patch.type]: patch.value
|
|
33
|
+
};
|
|
34
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export default function moveItemInArray({ array, fromIndex, toIndex }) {
|
|
2
|
+
if (fromIndex === toIndex) {
|
|
3
|
+
return array;
|
|
4
|
+
}
|
|
5
|
+
const newArray = [...array];
|
|
6
|
+
const target = newArray[fromIndex];
|
|
7
|
+
const inc = toIndex < fromIndex ? -1 : 1;
|
|
8
|
+
for (let i = fromIndex; i !== toIndex; i += inc) {
|
|
9
|
+
newArray[i] = newArray[i + inc];
|
|
10
|
+
}
|
|
11
|
+
newArray[toIndex] = target;
|
|
12
|
+
return newArray;
|
|
13
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { SanityDocument } from '@sanity/client';
|
|
2
|
+
import { TreeItem } from 'react-sortable-tree';
|
|
3
|
+
import { AllItems, SanityTreeItem, VisibilityMap } from '../types';
|
|
4
|
+
export declare const dataToEditorTree: ({ tree, allItems, visibilityMap }: {
|
|
5
|
+
tree: SanityTreeItem[];
|
|
6
|
+
allItems: AllItems;
|
|
7
|
+
visibilityMap: VisibilityMap;
|
|
8
|
+
}) => TreeItem[];
|
|
9
|
+
export declare const flatTree: (tree: TreeItem[]) => TreeItem[];
|
|
10
|
+
export interface FetchData {
|
|
11
|
+
mainTree?: SanityTreeItem[];
|
|
12
|
+
allItems?: SanityDocument[];
|
|
13
|
+
}
|
|
14
|
+
export declare const getUnaddedItems: (data: {
|
|
15
|
+
allItems: AllItems;
|
|
16
|
+
tree: SanityTreeItem[];
|
|
17
|
+
}) => SanityTreeItem[];
|
|
18
|
+
export declare function normalizeNodeForStorage(item: TreeItem): SanityTreeItem;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { randomKey } from '@sanity/util/content';
|
|
3
|
+
import DocumentInNode from '../components/DocumentInNode';
|
|
4
|
+
import NodeActions from '../components/NodeActions';
|
|
5
|
+
import flatDataToTree from './flatDataToTree';
|
|
6
|
+
export const dataToEditorTree = ({ tree, allItems, visibilityMap }) => {
|
|
7
|
+
const itemsWithTitle = tree
|
|
8
|
+
.filter((item) => item?.value?.reference?._ref)
|
|
9
|
+
.map((item) => {
|
|
10
|
+
const refId = item.value?.reference?._ref;
|
|
11
|
+
const docPair = refId ? allItems[refId] : undefined;
|
|
12
|
+
const draftDoc = docPair?.draft;
|
|
13
|
+
const publishedDoc = docPair?.published;
|
|
14
|
+
const enhancedItem = {
|
|
15
|
+
...item,
|
|
16
|
+
expanded: visibilityMap[item._key] !== false,
|
|
17
|
+
draftId: draftDoc?._id,
|
|
18
|
+
publishedId: publishedDoc?._id,
|
|
19
|
+
draftUpdatedAt: draftDoc?._updatedAt,
|
|
20
|
+
publishedUpdatedAt: publishedDoc?._updatedAt
|
|
21
|
+
};
|
|
22
|
+
return {
|
|
23
|
+
...enhancedItem,
|
|
24
|
+
title: (nodeProps) => (_jsx(DocumentInNode, { item: enhancedItem, action: _jsx(NodeActions, { nodeProps: nodeProps }, void 0) }, void 0)),
|
|
25
|
+
children: []
|
|
26
|
+
};
|
|
27
|
+
});
|
|
28
|
+
return flatDataToTree(itemsWithTitle);
|
|
29
|
+
};
|
|
30
|
+
const documentPairToNode = (doc) => {
|
|
31
|
+
if (!doc?.published?._id) {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
_key: randomKey(12),
|
|
36
|
+
_type: 'hierarchy.node',
|
|
37
|
+
draftId: doc.draft?._id,
|
|
38
|
+
draftUpdatedAt: doc.draft?._updatedAt,
|
|
39
|
+
publishedId: doc.published._id,
|
|
40
|
+
publishedUpdatedAt: doc.published?._updatedAt,
|
|
41
|
+
value: {
|
|
42
|
+
reference: {
|
|
43
|
+
_ref: doc.published._id,
|
|
44
|
+
_type: 'reference',
|
|
45
|
+
_weak: true
|
|
46
|
+
},
|
|
47
|
+
docType: doc.published._type
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
export const flatTree = (tree) => {
|
|
52
|
+
return tree.reduce((flattened, item) => {
|
|
53
|
+
const { children, ...node } = item;
|
|
54
|
+
return [...flattened, node, ...(Array.isArray(children) ? flatTree(children) : [])];
|
|
55
|
+
}, []);
|
|
56
|
+
};
|
|
57
|
+
export const getUnaddedItems = (data) => {
|
|
58
|
+
if (!data.tree) {
|
|
59
|
+
return Object.entries(data.allItems)
|
|
60
|
+
.map((value) => documentPairToNode(value[1]))
|
|
61
|
+
.filter(Boolean);
|
|
62
|
+
}
|
|
63
|
+
return Object.entries(data.allItems)
|
|
64
|
+
.filter(([publishedId]) => publishedId &&
|
|
65
|
+
// unadded items shouldn't be in the tree
|
|
66
|
+
!data.tree.some((treeItem) => treeItem?.value?.reference?._ref === publishedId))
|
|
67
|
+
.map(([_publishedId, documentPair]) => documentPairToNode(documentPair))
|
|
68
|
+
.filter(Boolean);
|
|
69
|
+
};
|
|
70
|
+
export function normalizeNodeForStorage(item) {
|
|
71
|
+
return {
|
|
72
|
+
_key: item._key,
|
|
73
|
+
_type: item._type || 'hierarchy.node',
|
|
74
|
+
value: item.value,
|
|
75
|
+
parent: item.parent
|
|
76
|
+
};
|
|
77
|
+
}
|