-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathProjectModals.tsx
More file actions
206 lines (188 loc) · 7.41 KB
/
ProjectModals.tsx
File metadata and controls
206 lines (188 loc) · 7.41 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
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
import type { UseWebViewStateHook } from '@papi/core';
import { useCallback, useState } from 'react';
import type { InterlinearProjectSummary } from '../types/interlinear-project-summary';
import { CreateProjectModal } from './CreateProjectModal';
import { ProjectMetadataModal } from './ProjectMetadataModal';
import { SelectInterlinearProjectModal } from './SelectInterlinearProjectModal';
/** Which modal is currently visible. Only one can be open at a time. */
export type ModalState = 'none' | 'select' | 'create' | 'metadata';
/**
* Single mount point for all project-related dialogs. Renders at most one of
* {@link SelectInterlinearProjectModal}, {@link CreateProjectModal}, or {@link ProjectMetadataModal}
* based on the `modal` prop, and manages the shared WebView state for the active project.
*
* @param props - Component props
* @param props.activeProject - The currently active interlinear project, read from WebView state by
* the parent.
* @param props.modal - Which modal is currently open
* @param props.projectId - PAPI project ID passed from the host
* @param props.setModal - Setter for which modal is open
* @param props.useWebViewState - Hook for reading and writing values persisted in the WebView's
* saved state (survives tab restores)
* @returns The currently active modal, or an empty container when no modal is open.
*/
export default function ProjectModals({
activeProject,
modal,
projectId,
setModal,
useWebViewState,
}: Readonly<{
activeProject: InterlinearProjectSummary | undefined;
modal: ModalState;
projectId: string;
setModal: (modal: ModalState) => void;
useWebViewState: UseWebViewStateHook;
}>) {
const [, setActiveProject, resetActiveProject] = useWebViewState<
InterlinearProjectSummary | undefined
>('activeProject', undefined);
/**
* The project currently open in the metadata modal. Set when the user clicks the info icon in the
* select modal or triggers "View Project Info" from the menu.
*/
const [metadataProject, setMetadataProject] = useState<InterlinearProjectSummary | undefined>(
undefined,
);
/**
* Tracks whether the create modal was opened from the select modal ("Create new" button) or from
* a top-menu command. `true` means opened via the select modal, so closing without creating
* restores the select modal; `false` means opened from the menu, so closing dismisses to
* `'none'`.
*/
const [createSourceIsSelect, setCreateSourceIsSelect] = useState(false);
/**
* Tracks whether the metadata modal was opened from the select modal (info icon) or from the
* top-menu "View Project Info" item. `true` means opened via the select modal, so closing the
* metadata modal restores the select modal; `false` means opened from the menu, so closing
* dismisses to `'none'`.
*/
const [metadataSourceIsSelect, setMetadataSourceIsSelect] = useState(false);
const resolvedMetadataProject = metadataProject ?? activeProject;
/**
* Opens the metadata modal for the project whose info icon was clicked in the select modal.
*
* @param project - The project to display in the metadata modal.
*/
const handleViewInfo = useCallback(
(project: InterlinearProjectSummary) => {
setMetadataProject(project);
setMetadataSourceIsSelect(true);
setModal('metadata');
},
[setModal],
);
/**
* Called when the metadata modal saves changes. Updates `activeProject` state when the edited
* project is the currently active one.
*
* @param updated - The updated name, description, and analysisLanguages.
*/
const handleMetadataProjectSaved = useCallback(
(updated: { name?: string; description?: string; analysisLanguages: string[] }) => {
if (activeProject && resolvedMetadataProject?.id === activeProject.id) {
setActiveProject({ ...activeProject, ...updated });
}
},
[activeProject, resolvedMetadataProject, setActiveProject],
);
/**
* Called when the metadata modal deletes the project. Clears `activeProject` if it was the
* deleted project.
*
* @param deletedId - UUID of the project that was deleted.
*/
const handleMetadataProjectDeleted = useCallback(
(deletedId: string) => {
if (activeProject?.id === deletedId) resetActiveProject();
},
[activeProject, resetActiveProject],
);
/**
* Called when the user selects a project in the select modal. Persists it as the active project
* and dismisses the modal.
*
* @param project - The project the user selected.
*/
const handleSelectProject = useCallback(
(project: InterlinearProjectSummary) => {
setActiveProject(project);
setModal('none');
},
[setActiveProject, setModal],
);
/**
* Called when the user clicks "Create new" in the select modal. Switches to the create modal and
* records that the create modal was opened from the select modal.
*/
const handleSelectCreateNew = useCallback(() => {
setCreateSourceIsSelect(true);
setModal('create');
}, [setModal]);
/** Called when the user dismisses the select modal without choosing a project. */
const handleSelectClose = useCallback(() => setModal('none'), [setModal]);
/**
* Called when the user dismisses the create modal without saving. Returns to the select modal
* when it was opened from there; otherwise dismisses to `'none'`.
*/
const handleCreateClose = useCallback(() => {
setModal(createSourceIsSelect ? 'select' : 'none');
setCreateSourceIsSelect(false);
}, [createSourceIsSelect, setModal]);
/**
* Called when the create modal successfully creates a project. Persists it as the active project
* and dismisses the modal.
*
* @param project - The newly created project.
*/
const handleProjectCreated = useCallback(
(project: InterlinearProjectSummary) => {
setActiveProject(project);
setModal('none');
},
[setActiveProject, setModal],
);
/**
* Called when the metadata modal is dismissed. Returns to the select modal when it was opened
* from there; otherwise dismisses to `'none'`.
*/
const handleMetadataClose = useCallback(() => {
setModal(metadataSourceIsSelect ? 'select' : 'none');
setMetadataSourceIsSelect(false);
setMetadataProject(undefined);
}, [metadataSourceIsSelect, setModal]);
return (
<div>
{modal === 'select' && (
<SelectInterlinearProjectModal
sourceProjectId={projectId}
onSelect={handleSelectProject}
onCreateNew={handleSelectCreateNew}
onClose={handleSelectClose}
onViewInfo={handleViewInfo}
/>
)}
{modal === 'create' && (
<CreateProjectModal
projectId={projectId}
onClose={handleCreateClose}
onProjectCreated={handleProjectCreated}
/>
)}
{modal === 'metadata' && resolvedMetadataProject && (
<ProjectMetadataModal
interlinearProjectId={resolvedMetadataProject.id}
name={resolvedMetadataProject.name}
description={resolvedMetadataProject.description}
sourceProjectId={resolvedMetadataProject.sourceProjectId}
targetProjectId={resolvedMetadataProject.targetProjectId}
analysisLanguages={resolvedMetadataProject.analysisLanguages}
createdAt={resolvedMetadataProject.createdAt}
onClose={handleMetadataClose}
onProjectSaved={handleMetadataProjectSaved}
onProjectDeleted={handleMetadataProjectDeleted}
/>
)}
</div>
);
}