diff --git a/apps/publikator/components/Files/FilesTable.tsx b/apps/publikator/components/Files/FilesTable.tsx new file mode 100644 index 0000000000..189d6d9e8c --- /dev/null +++ b/apps/publikator/components/Files/FilesTable.tsx @@ -0,0 +1,254 @@ +import { useState } from 'react' +import { css } from 'glamor' + +import { + IconLock, + IconPublic, + IconError, + IconReadTime, + IconLink, +} from '@republik/icons' + +import { + A, + Button, + Label, + IconButton, +} from '@project-r/styleguide' + +import { swissTime } from '../../lib/utils/format' + +import Destroy from './actions/Destroy' +import Publish from './actions/Publish' +import Unpublish from './actions/Unpublish' +import type { FileUsageMap } from './utils/extractUrlsFromContent' + +const timeFormat = swissTime.format('%d. %B %Y, %H:%M Uhr') + +const styles = { + list: css({ + display: 'flex', + flexDirection: 'column', + width: '100%', + }), + row: css({ + display: 'flex', + alignItems: 'flex-start', + width: '100%', + padding: '12px 0', + borderBottom: '1px solid rgba(0, 0, 0, 0.1)', + gap: '24px', + '@media (max-width: 600px)': { + flexWrap: 'wrap', + }, + }), + cellIcon: css({ + flexShrink: 0, + flexGrow: 0, + }), + cellContent: css({ + flex: '1 1 0%', + minWidth: 0, + display: 'flex', + flexDirection: 'column', + gap: '0.25rem', + }), + cellActions: css({ + flexShrink: 0, + flexGrow: 0, + '@media (max-width: 600px)': { + width: '100%', + marginTop: '8px', + }, + }), + fileName: css({ + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + maxWidth: '100%', + }), + usageInfo: css({ + paddingLeft: '24px', + margin: '4px 0', + }), + actions: css({ + display: 'flex', + gap: '0.5rem', + justifyContent: 'flex-end', + flexWrap: 'wrap', + '@media (max-width: 600px)': { + justifyContent: 'flex-start', + }, + }), +} + +type FileStatus = 'Pending' | 'Failure' | 'Private' | 'Public' + +interface RepoFile { + id: string + name: string + url: string + status: FileStatus + createdAt: string + error?: string + author?: { + name: string + } +} + +interface UrlInfo { + url: string + type: string + text: string +} + +interface StatusConfig { + Icon: React.ComponentType<{ size?: number | string }> + disabled: boolean + colorName: string | undefined + crumb: string | undefined + Action: React.ComponentType<{ + file: RepoFile + isInUse: boolean + usages: UrlInfo[] | undefined + }> | undefined +} + +const statusMap: Record = { + Pending: { + Icon: IconReadTime, + disabled: true, + colorName: undefined, + crumb: undefined, + Action: undefined, + }, + Failure: { + Icon: IconError, + disabled: false, + colorName: 'error', + crumb: undefined, + Action: Destroy, + }, + Private: { + Icon: IconLock, + disabled: false, + colorName: undefined, + crumb: 'nicht öffentlich', + Action: Publish, + }, + Public: { + Icon: IconPublic, + disabled: false, + colorName: 'primary', + crumb: 'öffentlich', + Action: Unpublish, + }, +} + +interface UsageInfoProps { + usages: UrlInfo[] | undefined +} + +const UsageInfo: React.FC = ({ usages }) => { + if (!usages || usages.length === 0) return null + + return ( + + ) +} + +interface FileRowProps { + file: RepoFile + usages: UrlInfo[] | undefined +} + +const FileRow: React.FC = ({ file, usages }) => { + const { Icon, disabled, crumb, Action } = + statusMap[file.status] || statusMap.Pending + + const isInUse = usages && usages.length > 0 + + const [copied, setCopied] = useState(false) + + const onClick = async () => { + try { + await navigator.clipboard.writeText(file.url) + setCopied(true) + setTimeout(() => setCopied(false), 2000) + } catch (err) { + console.error('Failed to copy link:', err) + } + } + + return ( +
+
+ +
+
+ {disabled ? ( + + {file.name} + + ) : ( + + {file.name} + + )} + + +
+
+
+ + {Action && } +
+
+
+ ) +} + +interface FilesTableProps { + files: RepoFile[] + fileUsageMap: FileUsageMap +} + +const FilesTable: React.FC = ({ files, fileUsageMap }) => { + return ( +
+ {files.map((file) => ( + + ))} +
+ ) +} + +export default FilesTable +export type { RepoFile, UrlInfo } diff --git a/apps/publikator/components/Files/Info.js b/apps/publikator/components/Files/Info.js deleted file mode 100644 index 10953cb019..0000000000 --- a/apps/publikator/components/Files/Info.js +++ /dev/null @@ -1,32 +0,0 @@ -import { css } from 'glamor' -import { IconWarning } from '@republik/icons' - -import { useColorContext } from '@project-r/styleguide' - -const styles = { - container: css({ - margin: '1rem 0 1rem 0', - padding: '1rem', - }), -} - -const Info = () => { - const [colorScheme] = useColorContext() - - return ( -
- {' '} - Keine sensiblen Dateien hochladen -
- ) -} - -export default Info diff --git a/apps/publikator/components/Files/Info.tsx b/apps/publikator/components/Files/Info.tsx new file mode 100644 index 0000000000..0bb6f71648 --- /dev/null +++ b/apps/publikator/components/Files/Info.tsx @@ -0,0 +1,44 @@ +import { css } from 'glamor' +import { IconWarning } from '@republik/icons' + +import { useColorContext } from '@project-r/styleguide' + +const styles = { + container: css({ + margin: '1rem 0 1rem 0', + padding: '1rem', + lineHeight: '1.5', + fontSize: '0.75rem', + }), +} + +const Info: React.FC = () => { + const [colorScheme] = useColorContext() + + return ( +

+ Dateien werden beim hochladen zunächst auf einem privaten Server bei + Amazon AWS gespeichert. Sie werden erst öffentlich zugänglich, wenn Sie + via den "Veröffentlichen"-Button veröffentlicht werden. . Für + die Vorschau der Dateien wird ein temporärer öffentlicher Link erstellt. + Wenn dieser im Dokument verwendet wird, kann das Dokument nicht publiziert + werden.
+ {' '} + + Es dürfen keine sensiblen Dateien (z.B. Whistleblowing-Daten, + Nutzerdaten, Dokumente mit Passwörtern oder Kontaktdaten) hier + hochgeladen werden. + +

+ ) +} + +export default Info diff --git a/apps/publikator/components/Files/Row.js b/apps/publikator/components/Files/Row.js deleted file mode 100644 index e36fdde319..0000000000 --- a/apps/publikator/components/Files/Row.js +++ /dev/null @@ -1,88 +0,0 @@ -import { css } from 'glamor' - -import { IconLock, IconPublic, IconError, IconReadTime } from '@republik/icons' - -import { IconButton, Label } from '@project-r/styleguide' - -import { swissTime } from '../../lib/utils/format' - -import { Tr, Td } from '../Table' - -import Destroy from './actions/Destroy' -import Publish from './actions/Publish' -import Unpublish from './actions/Unpublish' - -const timeFormat = swissTime.format('%d. %B %Y, %H:%M Uhr') - -const styles = { - label: css({ - marginLeft: '2rem', - }), -} - -const statusMap = { - Pending: { - Icon: IconReadTime, - disabled: true, - colorName: undefined, - crumb: undefined, - Action: undefined, - }, - Failure: { - Icon: IconError, - disabled: false, - colorName: 'error', - crumb: undefined, - Action: Destroy, - }, - Private: { - Icon: IconLock, - disabled: false, - colorName: undefined, - crumb: 'nicht öffentlich', - Action: Publish, - }, - Public: { - Icon: IconPublic, - disabled: false, - colorName: 'primary', - crumb: undefined, - Action: Unpublish, - }, -} - -const File = ({ file }) => { - const { Icon, disabled, colorName, crumb, Action } = - statusMap[file.status] || statusMap.Pending - - return ( - - - -
- -
- - {Action && } - - ) -} - -export default File diff --git a/apps/publikator/components/Files/actions/Destroy.js b/apps/publikator/components/Files/actions/Destroy.js index e46a40f3e7..03bdba613e 100644 --- a/apps/publikator/components/Files/actions/Destroy.js +++ b/apps/publikator/components/Files/actions/Destroy.js @@ -25,7 +25,7 @@ const Destroy = ({ file }) => { } return ( - ) diff --git a/apps/publikator/components/Files/actions/Publish.js b/apps/publikator/components/Files/actions/Publish.js index aa0641b356..5501f4ab19 100644 --- a/apps/publikator/components/Files/actions/Publish.js +++ b/apps/publikator/components/Files/actions/Publish.js @@ -21,7 +21,7 @@ const Publish = ({ file }) => { } return ( - ) diff --git a/apps/publikator/components/Files/actions/Unpublish.js b/apps/publikator/components/Files/actions/Unpublish.js index 66a83075e8..c93745ed13 100644 --- a/apps/publikator/components/Files/actions/Unpublish.js +++ b/apps/publikator/components/Files/actions/Unpublish.js @@ -13,7 +13,7 @@ const MAKE_PRIVATE = gql` ${RepoFile} ` -const Publish = ({ file }) => { +const Unpublish = ({ file, isInUse }) => { const [makePrivate, { loading }] = useMutation(MAKE_PRIVATE) const onClick = () => { @@ -26,11 +26,24 @@ const Publish = ({ file }) => { } } + // Disable the button if the file is being used in the document + const disabled = loading || isInUse + return ( - ) } -export default Publish +export default Unpublish diff --git a/apps/publikator/components/Files/index.js b/apps/publikator/components/Files/index.js deleted file mode 100644 index a43d4b0e9b..0000000000 --- a/apps/publikator/components/Files/index.js +++ /dev/null @@ -1,93 +0,0 @@ -import { gql, useQuery } from '@apollo/client' -import { Container } from '@project-r/styleguide' -import { css } from 'glamor' -import { RepoFile } from '../../lib/graphql/fragments' - -import { getRepoIdFromQuery } from '../../lib/repoIdHelper' -import Nav from '../editor/Nav' -import Frame from '../Frame' - -import Loader from '../Loader' -import { Table, Th, Tr } from '../Table' - -import Info from './Info' -import Row from './Row' -import Upload from './Upload' - -const GET_FILES = gql` - query getFiles($id: ID!) { - repo(id: $id) { - id - files { - ...RepoFile - } - } - } - - ${RepoFile} -` - -const styles = { - container: css({ - overflow: 'scroll', - }), -} - -const FilesPage = ({ router, t }) => { - const repoId = getRepoIdFromQuery(router.query) - const variables = { id: repoId } - - const { - data, - loading, - error: queryError, - } = useQuery(GET_FILES, { variables }) - - const error = - data?.repo === null - ? t('repo/warn/missing', { - repoId, - }) - : queryError - - return ( - - - -