Skip to content

Commit

Permalink
introduce xlsx paste and transformation process
Browse files Browse the repository at this point in the history
  • Loading branch information
lebalz committed Dec 1, 2024
1 parent 4a208af commit 9cc4dc5
Show file tree
Hide file tree
Showing 10 changed files with 387 additions and 24 deletions.
16 changes: 7 additions & 9 deletions docs/Komponentengalerie/cms-text.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import BrowserWindow from '@tdev-components/BrowserWindow';
import { observer } from 'mobx-react-lite';
import { useFirstMainDocument } from '@tdev-hooks/useFirstMainDocument';
import { CmsTextMeta } from '@tdev-models/documents/CmsText';
import FromXlsxClipboard from '@tdev-components/shared/FromXlsxClipboard'

# CMS Text
export const DocumentCreator = observer(() => {
Expand All @@ -25,8 +24,6 @@ export const DocumentCreator = observer(() => {

<DocumentCreator />

<FromXlsxClipboard matchUsers onDone={(table) => console.log(JSON.stringify(table, null, 2))}/>

::::info[Erstellung von Documents]
Im Gegensatz zu vielen anderen Komponenten erstellen die Komponenten rund um CMS-Text bewusst **nicht** automatisch ein Document, wenn keins vorhanden ist.

Expand Down Expand Up @@ -90,15 +87,15 @@ CMS Text kann auch nützlich sein, um bspw. Prüfungsinhalte erst zur gegebenen
```md
<BrowserWindow>
Diskutieren Sie das nachfolgende Zitat:
> <CmsText id="21535ea1-47d9-4521-a7fa-392f06d08f0a" showPermissionsPanel/>
> <CmsText id="21535ea1-47d9-4521-a7fa-392f06d08f0a" showActions/>

</BrowserWindow>
```

<BrowserWindow>
Diskutieren Sie das nachfolgende Zitat:

> <CmsText id="21535ea1-47d9-4521-a7fa-392f06d08f0a" showPermissionsPanel/>
> <CmsText id="21535ea1-47d9-4521-a7fa-392f06d08f0a" showActions/>
</BrowserWindow>

Expand All @@ -120,7 +117,7 @@ import CmsCode from '@tdev-components/documents/CmsText/Code';
codeBlockProps={{
language: 'python'
}}
showPermissionsPanel
showActions
/>
`codeBlockProps` entspricht den Props der Komponente `@theme/CodeBlock`.
<CmsCode
Expand All @@ -130,7 +127,7 @@ import CmsCode from '@tdev-components/documents/CmsText/Code';
showLineNumbers: true,
title: 'Hello World'
}}
showPermissionsPanel
showActions
/>
```

Expand All @@ -146,7 +143,7 @@ import CmsCode from '@tdev-components/documents/CmsText/Code';
codeBlockProps={{
language: 'python'
}}
showPermissionsPanel
showActions
/>
`codeBlockProps` entspricht den Props der Komponente `@theme/CodeBlock`.
<CmsCode
Expand All @@ -156,7 +153,7 @@ import CmsCode from '@tdev-components/documents/CmsText/Code';
showLineNumbers: true,
title: 'Hello World'
}}
showPermissionsPanel
showActions
/>
</BrowserWindow>

Expand Down Expand Up @@ -304,3 +301,4 @@ Bei mehreren Einträgen zeigt die `<WithCmsText>`-Klammer ihren Inhalt nur dann
</DefinitionList>
</WithCmsText>
</BrowserWindow>

22 changes: 22 additions & 0 deletions src/components/documents/CmsText/CmsActions/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { observer } from 'mobx-react-lite';
import React from 'react';
import PermissionsPanel from '@tdev-components/PermissionsPanel';
import CmsXlsxImporter from '../CmsXlsxImporter';
import styles from './styles.module.scss';
import clsx from 'clsx';

interface Props {
rootId: string;
className?: string;
}

const CmsActions = observer((props: Props) => {
return (
<div className={clsx(styles.actions, props.className)}>
<CmsXlsxImporter toAssign={[{ id: props.rootId }]} />
<PermissionsPanel documentRootId={props.rootId} />
</div>
);
});

export default CmsActions;
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.actions {
display: flex;
gap: 0.25em;
}
259 changes: 259 additions & 0 deletions src/components/documents/CmsText/CmsXlsxImporter/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
import React from 'react';
import clsx from 'clsx';
import styles from './styles.module.scss';
import { observer } from 'mobx-react-lite';
import Popup from 'reactjs-popup';
import Button from '@tdev-components/shared/Button';
import FromXlsxClipboard from '@tdev-components/shared/FromXlsxClipboard';
import { mdiClose, mdiFileExcelOutline } from '@mdi/js';
import { useStore } from '@tdev-hooks/useStore';
import Table from '@tdev-components/shared/Table';
import { translate } from '@docusaurus/Translate';
import { Access } from '@tdev-api/document';
import AccessSelector from '@tdev-components/PermissionsPanel/AccessSelector';
import CodeBlock from '@theme/CodeBlock';
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

interface Props {
className?: string;
toAssign: { id: string; name?: string }[];
}

const COLORS = [
...['primary', 'success', 'warning', 'danger', 'info', 'secondary'].map(
(c) => `var(--ifm-color-${c}-lightest)`
)
] as const;

interface AssignedColumn {
id: string;
idx: number;
name?: string;
}

const getPreview = (table: string[][], selectedColumn: number, cmsFor: string, sharedPermission: Access) => {
const preview = table
.flatMap((row) => {
if (row[selectedColumn].trim().length === 0) {
return null;
}
return {
cmsData: {
for: cmsFor,
text: row[selectedColumn]
},
userPermission: {
access: sharedPermission,
userId: row[0]
}
};
})
.filter((x) => !!x);
return JSON.stringify(preview, null, 2);
};

const createCmsTexts = (table: string[][], assigned: AssignedColumn[], sharedPermission: Access) => {
return Promise.resolve();
};

const CmsXlsxImporter = observer((props: Props) => {
const { toAssign } = props;
const ref = React.useRef(null);
const userStore = useStore('userStore');
const [table, setTable] = React.useState<string[][]>([]);
const [sharedPermission, setSharedPermission] = React.useState<Access>(Access.RO_User);
const [assigned, setAssigned] = React.useState<AssignedColumn[]>([]);
const [isOpen, setIsOpen] = React.useState(false);
const reset = () => {
setTable([]);
setAssigned([]);
};
const closeTooltip = () => {
(ref.current as any)?.close();
};
const isSingleAssignment = toAssign.length === 1;
if (!userStore.current?.isAdmin || userStore.isUserSwitched) {
return null;
}
return (
<Popup
trigger={
<span className={clsx(styles.importer, props.className)}>
<Button
onClick={(e) => e.preventDefault()}
icon={mdiFileExcelOutline}
color={'green'}
noOutline={isOpen}
/>
</span>
}
on="click"
modal
overlayStyle={{ background: 'rgba(0,0,0,0.5)' }}
onOpen={() => {
setIsOpen(true);
}}
closeOnEscape={false}
onClose={() => {
setIsOpen(false);
reset();
}}
ref={ref}
>
<div className={clsx(styles.wrapper, 'card')}>
<div className={clsx('card__header', styles.header)}>
<h3>CMS Texte erstellen</h3>
</div>
<div className={clsx('card__body', styles.cardBody)}>
{table.length === 0 && (
<FromXlsxClipboard
matchUsers
onDone={(table) => {
const newTable = table?.filter(
(row) => row.length > 0 && row[0].trim().length > 0
);
/**
* table includes header
*/
if (!newTable || newTable.length <= 1) {
return closeTooltip();
}
setTable(newTable);
}}
importLabel="Weiter"
cancelIcon={mdiClose}
includeHeader
/>
)}
{table.length > 0 && (
<>
<div className={clsx('badge', 'badge--primary')}>
{table.length} Zeile{table.length > 1 ? 'n' : ''}
</div>
{toAssign.length > 1 && (
<div>
{toAssign.map((assign, i) => {
return (
<Button
onClick={() => {
setAssigned([
...assigned.filter((a) => a.idx >= 0),
{ id: assign.id, name: assign.name, idx: -1 }
]);
}}
text={assign.name || assign.id}
active={assigned.find((a) => a.id === assign.id)?.idx === -1}
color={
COLORS[
assigned.findIndex((a) => a.id === assign.id) %
COLORS.length
]
}
/>
);
})}
</div>
)}
<Table
cells={table}
withHeader
highlightedColumns={assigned.map((a, idx) => ({
index: a.idx,
color: COLORS[idx % COLORS.length]
}))}
onSelectColumn={(idx) => {
if (isSingleAssignment) {
if (assigned.find((a) => a.idx === idx)) {
setAssigned([]);
} else {
setAssigned([{ id: toAssign[0].id, idx }]);
}
} else {
const assignTo = assigned.find((a) => a.idx === -1);
if (!assignTo) {
return;
}
setAssigned([
...assigned.filter((a) => a.idx !== -1),
{ id: assignTo.id, idx: idx, name: assignTo.name }
]);
}
}}
/>
<AccessSelector
accessTypes={[Access.RO_User, Access.RW_User, Access.None_User]}
access={sharedPermission}
onChange={(access) => setSharedPermission(access)}
/>
{assigned.length > 0 && (
<>
{assigned.length > 1 && (
<Tabs>
{assigned.map((assign, i) => (
<TabItem value={assign.id} label={assign.name || assign.id}>
<CodeBlock
language="json"
showLineNumbers
title="Vorschau"
>
{getPreview(
table.slice(1),
assign.idx,
assign.name || assign.id,
sharedPermission
)}
</CodeBlock>
</TabItem>
))}
</Tabs>
)}
{assigned.length === 1 && (
<CodeBlock
language="json"
showLineNumbers
title="Vorschau"
className={clsx(styles.previewCode)}
>
{getPreview(
table.slice(1),
assigned[0].idx,
assigned[0].name || assigned[0].id,
sharedPermission
)}
</CodeBlock>
)}
</>
)}
</>
)}
</div>
{table.length > 0 && (
<div className="card__footer">
<div className={clsx('button-group', 'button-group--block')}>
<Button
text={'Abbrechen'}
onClick={() => {
closeTooltip();
}}
color="black"
/>
<Button
text={'CMS Texte erstellen'}
onClick={() => {
createCmsTexts(table, assigned, sharedPermission).then(() => {
closeTooltip();
});
}}
color="primary"
disabled={assigned.length === 0}
/>
</div>
</div>
)}
</div>
</Popup>
);
});

export default CmsXlsxImporter;
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.wrapper {
.cardBody {
.previewCode {
pre {
max-height: 20em;
}
}
}
}
Loading

0 comments on commit 9cc4dc5

Please sign in to comment.