-
Notifications
You must be signed in to change notification settings - Fork 263
Expand file tree
/
Copy pathWorkspaceMosaic.jsx
More file actions
163 lines (140 loc) · 4.82 KB
/
WorkspaceMosaic.jsx
File metadata and controls
163 lines (140 loc) · 4.82 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
import { useContext, useEffect, useRef } from 'react';
import PropTypes from 'prop-types';
import { styled } from '@mui/material/styles';
import GlobalStyles from '@mui/material/GlobalStyles';
import { DndContext } from 'react-dnd';
import {
Mosaic, MosaicWindow, getLeaves, createBalancedTreeFromLeaves,
} from 'react-mosaic-component';
import difference from 'lodash/difference';
import isEqual from 'lodash/isEqual';
import classNames from 'classnames';
import MosaicRenderPreview from '../containers/MosaicRenderPreview';
import Window from '../containers/Window';
import MosaicLayout from '../lib/MosaicLayout';
import globalReactMosaicStyles from '../styles/react-mosaic-component';
const StyledMosaic = styled(Mosaic)({
'& .mosaic-preview': {
boxShadow: 'none',
},
'& .mosaic-tile': {
boxShadow: '0 1px 3px 0 rgba(0, 0, 0, .2), 0 1px 1px 0 rgba(0, 0, 0, .2), 0 2px 1px -1px rgba(0, 0, 0, .2)',
},
'& .mosaic-window': {
boxShadow: 'none',
},
'& .mosaic-window-toolbar': {
display: 'none !important',
},
});
/** */
const RenderPreview = ({ windowId }) => (
<div className="mosaic-preview" aria-hidden>
<MosaicRenderPreview windowId={windowId} />
</div>
);
RenderPreview.propTypes = {
windowId: PropTypes.string.isRequired,
};
/** */
const ZeroStateView = () => (<div />);
/**
* Used to regenerate a new layout when windows are added or removed
*/
const determineWorkspaceLayout = (currentLayout, windowIds, currentWindowPaths = {}) => {
const leaveKeys = getLeaves(currentLayout);
const mosaicLayout = new MosaicLayout(currentLayout);
// Add new windows to layout
const addedWindows = difference(windowIds, leaveKeys);
if (addedWindows.length > 0) mosaicLayout.addWindows(addedWindows);
const removedWindows = difference(leaveKeys, windowIds);
// if we have paths for the removed windows, we can gracefully remove them and
// preserve the existing layout for the remaining windows
if (removedWindows.every(e => currentWindowPaths[e])) {
mosaicLayout.removeWindows(removedWindows, currentWindowPaths);
} else {
// Windows were removed (perhaps in a different Workspace). We don't have a
// way to reconfigure.. so we have to random generate
return createBalancedTreeFromLeaves(windowIds);
}
return mosaicLayout.layout;
};
/**
* Represents a work area that contains any number of windows
* @memberof Workspace
* @private
*/
export function WorkspaceMosaic({
layout = undefined, updateWorkspaceMosaicLayout, windowIds = [], workspaceId,
}) {
const toolbarControls = [];
const additionalControls = [];
const windowPaths = useRef({});
const dndContext = useContext(DndContext);
useEffect(() => {
const leaveKeys = getLeaves(layout);
// Handle some trivial layout cases:
// 1. No layout
// 2. No windows
// 3. Not enough windows to create a layout
if (!layout || windowIds.length === 0 || leaveKeys.length < 2) {
updateWorkspaceMosaicLayout(createBalancedTreeFromLeaves(windowIds));
return undefined;
}
// nothing was added or removed, and all the windows are accounted for in the layout
if (windowIds.every(e => leaveKeys.includes(e)) && leaveKeys.every(e => windowIds.includes(e))) {
return undefined;
}
const newLayout = determineWorkspaceLayout(layout, windowIds, windowPaths.current);
if (!isEqual(newLayout, layout)) updateWorkspaceMosaicLayout(newLayout);
return undefined;
}, [layout, windowIds, windowPaths, updateWorkspaceMosaicLayout]);
/**
* Render a tile (Window) in the Mosaic.
*/
const tileRenderer = (id, path) => {
if (!windowIds.includes(id)) return null;
windowPaths.current[id] = path;
return (
<MosaicWindow
toolbarControls={toolbarControls}
additionalControls={additionalControls}
path={path}
windowId={id}
renderPreview={RenderPreview}
>
<Window
key={`${id}-${workspaceId}`}
windowId={id}
/>
</MosaicWindow>
);
};
/**
* Update the redux store when the Mosaic is changed.
*/
const mosaicChange = (newLayout) => {
updateWorkspaceMosaicLayout(newLayout);
};
return (
<>
<GlobalStyles styles={{ ...globalReactMosaicStyles }} />
<StyledMosaic
dragAndDropManager={dndContext.dragDropManager}
renderTile={tileRenderer}
initialValue={layout || createBalancedTreeFromLeaves(windowIds)}
onChange={mosaicChange}
className={classNames('mirador-mosaic')}
zeroStateView={<ZeroStateView />}
/>
</>
);
}
WorkspaceMosaic.propTypes = {
layout: PropTypes.oneOfType(
[PropTypes.object, PropTypes.string],
), // eslint-disable-line react/forbid-prop-types
updateWorkspaceMosaicLayout: PropTypes.func.isRequired,
windowIds: PropTypes.arrayOf(PropTypes.string),
workspaceId: PropTypes.string.isRequired,
};