Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,32 @@ import { DocumentActions, EditDocumentActions } from './SchemaDataCardActions';
import { useAppDispatch } from '../../../../../redux/store';
import { Schema } from '../../../models/CmsModels';
import { asyncEditSchemaDocument } from '../../../store/databaseSlice';
import { enqueueErrorNotification } from '../../../../../hooks/useNotifier';
import { Box, styled } from '@mui/material';
import {
enqueueErrorNotification,
enqueueSuccessNotification,
} from '../../../../../hooks/useNotifier';
import { Box, Card, CardContent, CardProps, IconButton, styled, Tooltip } from '@mui/material';
import { CopyAllOutlined } from '@mui/icons-material';
import JsonEditorComponent from '../../../../../components/common/JsonEditorComponent';
import { copyJsonToClipboard } from './SchemaDataUtils';

const StyledDocActions = styled(DocumentActions)(() => ({
display: 'flex',
alignItems: 'center',
flexWrap: 'wrap',
}));

interface Props {
interface Props extends CardProps {
documents: any;
schema: Schema;
getSchemaDocuments: () => void;
onDelete: () => void;
}

const JSONEditor: FC<Props> = ({ documents, getSchemaDocuments, schema, onDelete }) => {
const JSONEditor: FC<Props> = ({ documents, getSchemaDocuments, schema, onDelete, ...rest }) => {
const [edit, setEdit] = useState<boolean>(false);
const [documentState, setDocumentState] = useState<any>(documents);
const [editorNonce, setEditorNonce] = useState(0);
const dispatch = useAppDispatch();

const onEdit = () => {
Expand All @@ -35,10 +41,8 @@ const JSONEditor: FC<Props> = ({ documents, getSchemaDocuments, schema, onDelete

const handleCancel = () => {
setEdit(false);
setDocumentState({});
setTimeout(() => {
setDocumentState(documents);
}, 1);
setDocumentState(documents);
setEditorNonce((nonce) => nonce + 1);
};

const handleSave = () => {
Expand All @@ -62,36 +66,52 @@ const JSONEditor: FC<Props> = ({ documents, getSchemaDocuments, schema, onDelete
setDocumentState(changedText.jsObject);
};

const handleCopy = () => {
copyJsonToClipboard(documentState)
.then(() => {
dispatch(enqueueSuccessNotification('JSON copied to clipboard'));
})
.catch(() => {
dispatch(enqueueErrorNotification('Failed to copy JSON'));
});
};

return (
<Box
sx={{
'& $actionContainer': {
display: 'none',
},
'&:hover $actionContainer': {
display: 'flex',
},
}}>
<Card variant="outlined" {...rest}>
<Box
sx={{
mb: 2,
height: '20px',
mb: 1,
mt: 1,
mr: 1,
height: 'auto',
minHeight: 32,
display: 'flex',
justifyContent: 'flex-end',
alignItems: 'center',
}}>
<StyledDocActions sx={{ mb: 2 }} onEdit={onEdit} onDelete={onDelete} edit={edit} />
{!edit && (
<Tooltip title="Copy JSON">
<IconButton sx={{ mr: 1 }} size="small" onClick={handleCopy} color="primary">
<CopyAllOutlined sx={{ height: 22, width: 22 }} />
</IconButton>
</Tooltip>
)}
<StyledDocActions onEdit={onEdit} onDelete={onDelete} edit={edit} />
</Box>
<JsonEditorComponent
id={documents._id}
placeholder={documentState}
onChange={handleChange}
viewOnly={!edit}
confirmGood={!edit}
height="fit-content"
width="100%"
/>
<CardContent>
<JsonEditorComponent
key={`${documents._id}-${editorNonce}`}
id={documents._id}
placeholder={documentState}
onChange={handleChange}
viewOnly={!edit}
confirmGood={!edit}
height="fit-content"
width="100%"
/>
</CardContent>
<EditDocumentActions edit={edit} handleCancel={handleCancel} handleSave={handleSave} />
</Box>
</Card>
);
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,10 @@ import Button from '@mui/material/Button';
import { Paginator } from '@conduitplatform/ui-components';
import { BoxProps } from '@mui/material/Box/Box';
import InputAdornment from '@mui/material/InputAdornment';
import { Search, Refresh, AccountTree } from '@mui/icons-material';
import { Search, Refresh, CopyAllOutlined } from '@mui/icons-material';
import useParseQuery from '../../../hooks/useParseQuery';
import { styled, Typography } from '@mui/material';

const ObjText = '{ }';

const ButtonContainer = styled(Box)(({ theme }) => ({
height: theme.spacing(3),
width: theme.spacing(3),
borderRadius: theme.spacing(0.5),
cursor: 'pointer',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
padding: theme.spacing(2),
marginLeft: theme.spacing(1),
}));
import SchemaDataViewToggle from './SchemaDataViewToggle';
import { SchemaDataViewMode } from './schemaDataViewMode';

interface Filters {
page: number;
Expand All @@ -32,27 +19,29 @@ interface Filters {
interface Props extends BoxProps {
onCreateDocument: () => void;
onRefresh: () => void;
onCopyPage: () => void;
filters: Filters;
setFilters: (data: Filters) => void;
search: string;
setSearch: (value: string) => void;
count: number;
objectView: boolean;
setObjectView: (value: boolean) => void;
viewMode: SchemaDataViewMode;
onViewModeChange: (mode: SchemaDataViewMode) => void;
disabled?: boolean;
}

const SchemaDataHeader: FC<Props> = ({
disabled = false,
onCreateDocument,
onRefresh,
onCopyPage,
filters,
setFilters,
search,
setSearch,
count,
objectView,
setObjectView,
viewMode,
onViewModeChange,
...rest
}) => {
const isValidSearch = useParseQuery(search, 0);
Expand Down Expand Up @@ -133,20 +122,16 @@ const SchemaDataHeader: FC<Props> = ({
display: 'flex',
alignItems: 'center',
pb: 2,
gap: 1,
flexWrap: 'wrap',
}}>
<ButtonContainer onClick={() => setObjectView(false)}>
<AccountTree color={objectView ? 'inherit' : 'primary'} />
</ButtonContainer>
<ButtonContainer onClick={() => setObjectView(true)}>
<Typography
sx={
objectView
? { whiteSpace: 'nowrap', fontSize: 14, color: 'primary.main' }
: { whiteSpace: 'nowrap', fontSize: 14 }
}>
{ObjText}
</Typography>
</ButtonContainer>
<SchemaDataViewToggle viewMode={viewMode} onChange={onViewModeChange} disabled={disabled} />
{viewMode === 'json' && count > 0 && (
<Button disabled={disabled} color="primary" size="small" onClick={onCopyPage}>
<CopyAllOutlined />
Copy page
</Button>
)}
{count > 0 && (
<Paginator
handlePageChange={(event, value) => handlePageChange(value)}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import React, { FC } from 'react';
import { Schema } from '../../../models/CmsModels';
import SchemaDataCard from './SchemaDataCard';
import JSONEditor from './JSONEditor';
import { SchemaDataViewMode } from './schemaDataViewMode';

const documentCardSx = {
background: 'rgba(0,0,0,0.2)',
margin: 1,
paddingLeft: 1,
position: 'relative' as const,
};

interface Props {
viewMode: SchemaDataViewMode;
documents: any[];
schema: Schema;
getSchemaDocuments: () => void;
onDelete: (index: number) => void;
}

const SchemaDataList: FC<Props> = ({
viewMode,
documents,
schema,
getSchemaDocuments,
onDelete,
}) => {
switch (viewMode) {
case 'json':
return (
<>
{documents.map((docs, index) => (
<JSONEditor
documents={docs}
getSchemaDocuments={getSchemaDocuments}
schema={schema}
onDelete={() => onDelete(index)}
key={docs?._id ?? index}
sx={documentCardSx}
/>
))}
</>
);
case 'tree':
return (
<>
{documents.map((docs, index) => (
<SchemaDataCard
schema={schema}
documents={docs}
sx={documentCardSx}
onDelete={() => onDelete(index)}
getSchemaDocuments={getSchemaDocuments}
key={`card${index}`}
id={docs?._id}
/>
))}
</>
);
default: {
const exhaustiveCheck: never = viewMode;
throw new Error(`Unhandled view mode: ${exhaustiveCheck}`);
}
}
};

export default SchemaDataList;
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@ export const getExpandableFields = (fields: any) => {
return expandableFields;
};

export const copyJsonToClipboard = (value: unknown): Promise<void> => {
const json = JSON.stringify(value, null, 2);
if (json === undefined) {
return Promise.reject(new Error('Nothing to copy'));
}
return navigator.clipboard.writeText(json);
};

export const getFieldsArray = (schemaFields: any) => {
const fields: any = [];

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import React, { FC } from 'react';
import { ToggleButton, ToggleButtonGroup, Tooltip } from '@mui/material';
import { AccountTree, DataObject } from '@mui/icons-material';
import { SchemaDataViewMode } from './schemaDataViewMode';

interface Props {
viewMode: SchemaDataViewMode;
onChange: (mode: SchemaDataViewMode) => void;
disabled?: boolean;
}

const SchemaDataViewToggle: FC<Props> = ({ viewMode, onChange, disabled = false }) => {
const handleChange = (
_event: React.MouseEvent<HTMLElement>,
value: SchemaDataViewMode | null
) => {
if (value === null) return;
onChange(value);
};

return (
<ToggleButtonGroup
size="small"
value={viewMode}
exclusive
disabled={disabled}
onChange={handleChange}
aria-label="Document view mode">
<ToggleButton value="tree" aria-label="Tree view">
<Tooltip title="Tree view">
<AccountTree fontSize="small" sx={{ mr: 0.5 }} />
</Tooltip>
Tree
</ToggleButton>
<ToggleButton value="json" aria-label="JSON view">
<Tooltip title="JSON view">
<DataObject fontSize="small" sx={{ mr: 0.5 }} />
</Tooltip>
JSON
</ToggleButton>
</ToggleButtonGroup>
);
};

export default SchemaDataViewToggle;
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
export type SchemaDataViewMode = 'tree' | 'json';

const STORAGE_KEY = 'conduit.schemaData.viewMode';

const isSchemaDataViewMode = (value: unknown): value is SchemaDataViewMode => {
return value === 'tree' || value === 'json';
};

export const readSchemaDataViewMode = (): SchemaDataViewMode => {
if (typeof window === 'undefined') return 'tree';
try {
const stored = localStorage.getItem(STORAGE_KEY);
if (isSchemaDataViewMode(stored)) return stored;
} catch {
return 'tree';
}
return 'tree';
};

export const writeSchemaDataViewMode = (mode: SchemaDataViewMode): void => {
if (typeof window === 'undefined') return;
try {
localStorage.setItem(STORAGE_KEY, mode);
} catch {
return;
}
};
Loading
Loading