Skip to content

Commit 5643e34

Browse files
committed
add term to ontology, fork, and small other adjustments
1 parent 26482ff commit 5643e34

8 files changed

Lines changed: 297 additions & 178 deletions

File tree

src/api/endpoints/apiService.ts

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,26 @@ export const patchTermPredicates = async ({
283283
return { ok: resp.ok };
284284
};
285285

286+
export const createFork = async (
287+
groupname: string,
288+
termId: string,
289+
sourceGroup: string,
290+
termLabel: string
291+
): Promise<{ ok: boolean; status: number }> => {
292+
const sourceIri = `${API_CONFIG.INTERLEX_URL}/${sourceGroup}/${termId}`;
293+
const synonymIri = "http://uri.interlex.org/base/readable/synonym";
294+
const resp = await fetch(`/${groupname}/${termId}`, {
295+
method: 'PATCH',
296+
headers: { 'Content-Type': 'application/json' },
297+
credentials: 'include',
298+
body: JSON.stringify({
299+
add: [[sourceIri, synonymIri, { type: "literal", value: termLabel }]],
300+
del: [],
301+
}),
302+
});
303+
return { ok: resp.ok, status: resp.status };
304+
};
305+
286306
export const addEntityToOntology = async ({
287307
group,
288308
ontologyUri,
@@ -291,7 +311,7 @@ export const addEntityToOntology = async ({
291311
group: string;
292312
ontologyUri: string;
293313
termId: string;
294-
}): Promise<{ success: boolean; error?: string }> => {
314+
}): Promise<{ success: boolean; status?: number; url?: string; body?: string; error?: string }> => {
295315
const specUrl = ontologyUri.replace(API_CONFIG.INTERLEX_URL, API_CONFIG.BASE_URL);
296316
const termIri = `${API_CONFIG.OLYMPIAN_GODS}/${group}/${termId}`;
297317

@@ -300,12 +320,18 @@ export const addEntityToOntology = async ({
300320
method: 'PATCH',
301321
headers: { 'Content-Type': 'application/json' },
302322
credentials: 'include',
303-
body: JSON.stringify({ subjects: [termIri] }),
323+
body: JSON.stringify({ add: [termIri], del: [] }),
304324
redirect: 'manual',
305325
});
306-
return { success: resp.ok || resp.status === 0 };
326+
const ok = resp.ok || resp.status === 0;
327+
if (!ok) {
328+
let body = '';
329+
try { body = await resp.text(); } catch { /* ignore */ }
330+
return { success: false, status: resp.status, url: specUrl, body };
331+
}
332+
return { success: true, status: resp.status, url: specUrl };
307333
} catch (error: any) {
308-
return { success: false, error: error?.message || String(error) };
334+
return { success: false, url: specUrl, error: error?.message || String(error) };
309335
}
310336
};
311337

Lines changed: 149 additions & 135 deletions
Original file line numberDiff line numberDiff line change
@@ -1,135 +1,149 @@
1-
import { useState } from "react";
2-
import PropTypes from "prop-types";
3-
import StatusDialog from "../common/StatusDialog";
4-
import CustomizedDialog from "../common/CustomizedDialog";
5-
import { Box, Button, Grid, Typography } from "@mui/material";
6-
import CustomSingleSelect from "../common/CustomSingleSelect";
7-
import SearchTermsData from "../../static/SearchTermsData.json"
8-
import ArrowForwardIcon from '@mui/icons-material/ArrowForward';
9-
10-
import { vars } from "../../theme/variables";
11-
const { gray800, gray600, gray500, gray700 } = vars;
12-
13-
const HeaderRightSideContent = ({ handleClose, onSaveFork }) => {
14-
return (
15-
<Box display='flex' alignItems='center' gap={1.5}>
16-
<Button sx={{ p: '0.625rem 0.875rem', minWidth: '0.0625rem' }} variant="outlined" onClick={handleClose}>Cancel</Button>
17-
<Button sx={{ p: '0.625rem 0.875rem', minWidth: '0.0625rem' }} variant='contained' onClick={onSaveFork}>
18-
Create a fork
19-
<ArrowForwardIcon />
20-
</Button>
21-
</Box>
22-
)
23-
}
24-
25-
HeaderRightSideContent.propTypes = {
26-
handleClose: PropTypes.func,
27-
onSaveFork: PropTypes.func
28-
}
29-
30-
// eslint-disable-next-line no-unused-vars
31-
const CreateForkDialog = ({ formState, open, handleClose, onInputChange }) => {
32-
const [openStatusDialog, setOpenStatusDialog] = useState(false);
33-
const [newTerm, setNewTerm] = useState('label')
34-
const handleSaveFork = () => {
35-
setOpenStatusDialog(true);
36-
handleClose()
37-
}
38-
const handleTermChange = (index, field, value) => {
39-
// newTerms[index][field] = value;
40-
setNewTerm(value)
41-
};
42-
const updatedColumnsArray = SearchTermsData.termsColumns.map(item => ({
43-
...item,
44-
value: item.id
45-
}));
46-
const handleCloseStatusDialog = () => {
47-
setOpenStatusDialog(false)
48-
}
49-
const handleStatusDialogActionButtonClick = () => {
50-
setOpenStatusDialog(false);
51-
}
52-
return (
53-
<>
54-
<CustomizedDialog
55-
title='Create a fork'
56-
open={open}
57-
handleClose={handleClose}
58-
sx={{ '& .MuiDialogContent-root': { overflowY: "hidden" } }}
59-
HeaderRightSideContent={
60-
<HeaderRightSideContent
61-
handleClose={handleClose}
62-
onSaveFork={handleSaveFork}
63-
/>
64-
}
65-
>
66-
<Box width={705}>
67-
<Typography color={gray800} fontSize='1.125rem' fontWeight={600} mb='2.75rem'>
68-
Add a fork to Central Nervous System
69-
</Typography>
70-
<Grid container spacing='1.75rem' alignItems={'flex-start'}>
71-
<Grid item xs={12} lg={5}>
72-
<Box display="flex" justifyContent="space-between">
73-
<Typography sx={{
74-
fontSize: '1rem',
75-
fontWeight: '500',
76-
color: gray800,
77-
mb: '.75rem'
78-
}}>
79-
Owner
80-
</Typography>
81-
<Typography variant="body1" sx={{ color: gray600 }}>Required</Typography>
82-
</Box>
83-
<CustomSingleSelect
84-
isFormControlFullWidth={true}
85-
options={updatedColumnsArray}
86-
placeholder='Select fork owner'
87-
value={newTerm}
88-
onChange={handleTermChange}
89-
/>
90-
</Grid>
91-
<Grid item xs={12} lg={1}>
92-
<Box textAlign={'center'} mt="2rem">
93-
<Typography variant="body1" fontSize='1.875rem' sx={{ color: gray500 }}>/</Typography>
94-
</Box>
95-
</Grid>
96-
<Grid item xs={12} lg={6}>
97-
<Box>
98-
<Typography color={gray800} fontSize='1rem' fontWeight={500} mb='1.25rem'>
99-
Fork name
100-
</Typography>
101-
</Box>
102-
<Box>
103-
<Typography color={gray700} fontSize='1rem' fontWeight={400}>
104-
Central nervous system
105-
</Typography>
106-
</Box>
107-
</Grid>
108-
</Grid>
109-
<Typography color={gray600} fontSize='1rem' fontWeight={400} mt='2.75rem'>
110-
By default the fork name is the same as the curated. It’s possible to personalise it.
111-
</Typography>
112-
</Box>
113-
</CustomizedDialog>
114-
<StatusDialog
115-
open={openStatusDialog}
116-
handleClose={handleCloseStatusDialog}
117-
title={"Create a fork"}
118-
message={"Fork successfully created"}
119-
subMessage={"Your fork of “Nervous system” has been created. "}
120-
finishButtonTitle={"Go to fork"}
121-
handleActionButtonClick={handleStatusDialogActionButtonClick}
122-
finishButtonEndIcon={<ArrowForwardIcon />}
123-
/>
124-
</>
125-
);
126-
};
127-
128-
CreateForkDialog.propTypes = {
129-
formState: PropTypes.object,
130-
open: PropTypes.bool,
131-
handleClose: PropTypes.func,
132-
onInputChange: PropTypes.func
133-
}
134-
135-
export default CreateForkDialog;
1+
import { useState } from "react";
2+
import PropTypes from "prop-types";
3+
import { useNavigate } from "react-router-dom";
4+
import CustomizedDialog from "../common/CustomizedDialog";
5+
import FeatureNotAvailableDialog from "../common/FeatureNotAvailableDialog";
6+
import { Box, Button, Grid, Typography, CircularProgress, Alert } from "@mui/material";
7+
import ArrowForwardIcon from '@mui/icons-material/ArrowForward';
8+
import { createFork } from "../../api/endpoints/apiService";
9+
10+
import { vars } from "../../theme/variables";
11+
const { gray800, gray600, gray500 } = vars;
12+
13+
const HeaderRightSideContent = ({ handleClose, onSaveFork, isSaving }) => (
14+
<Box display='flex' alignItems='center' gap={1.5}>
15+
<Button sx={{ p: '0.625rem 0.875rem', minWidth: '0.0625rem' }} variant="outlined" onClick={handleClose} disabled={isSaving}>Cancel</Button>
16+
<Button sx={{ p: '0.625rem 0.875rem', minWidth: '0.0625rem' }} variant='contained' onClick={onSaveFork} disabled={isSaving}>
17+
{isSaving ? <CircularProgress size={16} color="inherit" sx={{ mr: 1 }} /> : null}
18+
Create a fork
19+
<ArrowForwardIcon />
20+
</Button>
21+
</Box>
22+
);
23+
24+
HeaderRightSideContent.propTypes = {
25+
handleClose: PropTypes.func,
26+
onSaveFork: PropTypes.func,
27+
isSaving: PropTypes.bool,
28+
};
29+
30+
const CreateForkDialog = ({ open, handleClose, user, searchTerm, termLabel, group }) => {
31+
const navigate = useNavigate();
32+
const [ownerNotSupportedOpen, setOwnerNotSupportedOpen] = useState(false);
33+
const [isSaving, setIsSaving] = useState(false);
34+
const [saveError, setSaveError] = useState(null);
35+
36+
const groupname = user?.groupname || '';
37+
const displayLabel = termLabel || searchTerm || '';
38+
39+
const handleSaveFork = async () => {
40+
if (!groupname || !searchTerm) return;
41+
setIsSaving(true);
42+
setSaveError(null);
43+
try {
44+
const result = await createFork(groupname, searchTerm, group || 'base', displayLabel);
45+
if (result.ok) {
46+
handleClose();
47+
navigate(`/${groupname}/${searchTerm}/overview`);
48+
} else {
49+
setSaveError(`Fork creation failed (status ${result.status}). Please try again.`);
50+
}
51+
} catch (e) {
52+
setSaveError(e?.message || 'An unexpected error occurred.');
53+
} finally {
54+
setIsSaving(false);
55+
}
56+
};
57+
58+
return (
59+
<>
60+
<CustomizedDialog
61+
title='Create a fork'
62+
open={open}
63+
handleClose={handleClose}
64+
sx={{ '& .MuiDialogContent-root': { overflowY: 'hidden' } }}
65+
HeaderRightSideContent={
66+
<HeaderRightSideContent
67+
handleClose={handleClose}
68+
onSaveFork={handleSaveFork}
69+
isSaving={isSaving}
70+
/>
71+
}
72+
>
73+
<Box width={705}>
74+
<Typography color={gray800} fontSize='1.125rem' fontWeight={600} mb='2.75rem'>
75+
Fork &quot;{displayLabel}&quot; under your account
76+
</Typography>
77+
78+
{saveError && (
79+
<Alert severity="error" sx={{ mb: '1.5rem' }}>{saveError}</Alert>
80+
)}
81+
82+
<Grid container spacing='1.75rem' alignItems='flex-start'>
83+
<Grid item xs={12} lg={5}>
84+
<Box display="flex" justifyContent="space-between" mb='.75rem'>
85+
<Typography sx={{ fontSize: '1rem', fontWeight: '500', color: gray800 }}>
86+
Owner
87+
</Typography>
88+
<Typography variant="body1" sx={{ color: gray600 }}>Required</Typography>
89+
</Box>
90+
<Box
91+
onClick={() => setOwnerNotSupportedOpen(true)}
92+
sx={{
93+
cursor: 'pointer',
94+
border: '1px solid',
95+
borderColor: 'grey.300',
96+
borderRadius: '0.5rem',
97+
padding: '0.5rem 0.75rem',
98+
backgroundColor: 'grey.50',
99+
userSelect: 'none',
100+
display: 'flex',
101+
alignItems: 'center',
102+
justifyContent: 'space-between',
103+
}}
104+
>
105+
<Typography sx={{ color: gray800, fontSize: '1rem' }}>{groupname}</Typography>
106+
<Typography sx={{ color: gray500, fontSize: '0.75rem' }}></Typography>
107+
</Box>
108+
</Grid>
109+
<Grid item xs={12} lg={1}>
110+
<Box textAlign='center' mt='2rem'>
111+
<Typography variant="body1" fontSize='1.875rem' sx={{ color: gray500 }}>/</Typography>
112+
</Box>
113+
</Grid>
114+
<Grid item xs={12} lg={6}>
115+
<Typography color={gray800} fontSize='1rem' fontWeight={500} mb='1.25rem'>
116+
Fork name
117+
</Typography>
118+
<Typography color={gray800} fontSize='1rem' fontWeight={400}>
119+
{displayLabel}
120+
</Typography>
121+
</Grid>
122+
</Grid>
123+
124+
<Typography color={gray600} fontSize='1rem' fontWeight={400} mt='2.75rem'>
125+
The fork will be created under your account with the same term identifier.
126+
</Typography>
127+
</Box>
128+
</CustomizedDialog>
129+
130+
<FeatureNotAvailableDialog
131+
open={ownerNotSupportedOpen}
132+
onClose={() => setOwnerNotSupportedOpen(false)}
133+
title='Custom fork owner not supported'
134+
message='Selecting a different fork owner is not yet supported. Forks can only be created under your own account.'
135+
/>
136+
</>
137+
);
138+
};
139+
140+
CreateForkDialog.propTypes = {
141+
open: PropTypes.bool,
142+
handleClose: PropTypes.func,
143+
user: PropTypes.object,
144+
searchTerm: PropTypes.string,
145+
termLabel: PropTypes.string,
146+
group: PropTypes.string,
147+
};
148+
149+
export default CreateForkDialog;

src/components/SingleTermView/OverView/OverView.jsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
getTermVersion,
1212
} from "../../../api/endpoints/apiService";
1313
import termParser from "../../../parsers/termParser";
14+
import { adaptVersionJsonLd } from "../../../parsers/versionAdapter";
1415
import { patchEndpointsIlx } from "../../../api/endpoints/interLexURIStructureAPI";
1516
import {
1617
focusNodeFromJsonLd,
@@ -354,9 +355,10 @@ const OverView = ({ searchTerm, isCodeViewVisible = false, selectedDataFormat, g
354355

355356
(async () => {
356357
try {
357-
const jsonld = await getTermVersion(group, searchTerm, versionHash);
358+
const raw = await getTermVersion(group, searchTerm, versionHash);
358359
if (isStale()) return;
359360

361+
const jsonld = adaptVersionJsonLd(raw);
360362
const first = termParser(jsonld, searchTerm)?.results?.[0] || null;
361363
store.details$.next({ loading: false, data: first, jsonData: jsonld });
362364
const sv = { id: first?.id || searchTerm, label: first?.label || searchTerm };

0 commit comments

Comments
 (0)