Skip to content

Commit

Permalink
Move process page to SC
Browse files Browse the repository at this point in the history
  • Loading branch information
OhKai committed Dec 10, 2023
1 parent 9e0a4af commit 5e50177
Show file tree
Hide file tree
Showing 16 changed files with 198 additions and 83 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import Content from '@/components/content';
import { Space, Spin } from 'antd';
import { LoadingOutlined } from '@ant-design/icons';

const ProcessSkeleton = () => {
return (
<Content>
<Space
direction="vertical"
size="large"
style={{ display: 'flex', textAlign: 'center', marginTop: '2rem' }}
>
<Spin indicator={<LoadingOutlined style={{ fontSize: 24 }} spin />} />
</Space>
</Content>
);
};

export default ProcessSkeleton;
Original file line number Diff line number Diff line change
@@ -1,11 +1,51 @@
import Auth from '@/components/auth';
import Processes from './_page';
import Auth, { getCurrentUser } from '@/components/auth';
import Wrapper from './wrapper';
import styles from './page.module.scss';
import { FC, useEffect, useState } from 'react';
import { useParams, usePathname, useRouter, useSearchParams } from 'next/navigation';
import Modeler from '@/components/modeler';
import cn from 'classnames';
import { getProcess, getProcessVersionBpmn, getProcesses } from '@/lib/data/legacy/process';
import { toCaslResource } from '@/lib/ability/caslAbility';

type ProcessProps = {
params: { processId: string };
searchParams: { version?: string };
};

const Process = async ({ params: { processId }, searchParams }: ProcessProps) => {
// TODO: check if params is correct after fix release. And maybe don't need
// refresh in processes.tsx anymore?
console.log('processId', processId);
console.log('query', searchParams);
const selectedVersionId = searchParams.version ? searchParams.version : undefined;
const { ability } = await getCurrentUser();
// Only load bpmn if no version selected.
const process = await getProcess(processId, !selectedVersionId);
const processes = await getProcesses(ability);

if (!ability.can('view', toCaslResource('Process', process))) {
throw new Error('Forbidden.');
}

const selectedVersionBpmn = selectedVersionId
? await getProcessVersionBpmn(processId, selectedVersionId)
: process.bpmn;

// Since the user is able to minimize and close the page, everyting is in a
// client component from here.
return (
<Wrapper processName={process.definitionName} versions={process.versions}>
<Modeler className={styles.Modeler} processBpmn={selectedVersionBpmn} />
</Wrapper>
);
};

export default Auth(
{
action: 'view',
resource: 'Process',
fallbackRedirect: '/processes',
},
Processes,
Process,
);
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use client';

import styles from './page.module.scss';
import { FC, useEffect, useState } from 'react';
import { FC, PropsWithChildren, useEffect, useState } from 'react';
import { useParams, usePathname, useRouter, useSearchParams } from 'next/navigation';
import Modeler from '@/components/modeler';
import cn from 'classnames';
Expand All @@ -25,13 +25,14 @@ import VersionCreationButton from '@/components/version-creation-button';
import ProcessCreationButton from '@/components/process-creation-button';
import { AuthCan } from '@/components/auth-can';

type ProcessProps = {
params: { processId: string };
};
type WrapperProps = PropsWithChildren<{
processName: string;
versions: { version: number; name: string; description: string }[];
}>;

const LATEST_VERSION = { version: -1, name: 'Latest Version', description: '' };

const Processes: FC<ProcessProps> = () => {
const Wrapper = ({ children, processName, versions }: WrapperProps) => {
// TODO: check if params is correct after fix release. And maybe don't need
// refresh in processes.tsx anymore?
const { processId } = useParams();
Expand Down Expand Up @@ -66,7 +67,7 @@ const Processes: FC<ProcessProps> = () => {
const minimized = pathname !== `/processes/${processId}`;
const selectedVersionId = parseInt(query.get('version') ?? '-1');
const selectedVersion =
process?.versions.find((version) => version.version === selectedVersionId) ?? LATEST_VERSION;
versions.find((version) => version.version === selectedVersionId) ?? LATEST_VERSION;

useEffect(() => {
// Reset closed state when page is not minimized anymore.
Expand Down Expand Up @@ -106,8 +107,8 @@ const Processes: FC<ProcessProps> = () => {
showSearch
filterOption={filterOption}
value={{
value: process?.definitionId,
label: process?.definitionName,
value: processId,
label: processName,
}}
onSelect={(_, option) => {
router.push(`/processes/${option.value}`);
Expand Down Expand Up @@ -167,7 +168,7 @@ const Processes: FC<ProcessProps> = () => {
</Space>
</>
)}
options={[LATEST_VERSION].concat(process?.versions ?? []).map(({ version, name }) => ({
options={[LATEST_VERSION].concat(versions ?? []).map(({ version, name }) => ({
value: version,
label: name,
}))}
Expand All @@ -183,24 +184,22 @@ const Processes: FC<ProcessProps> = () => {
return (
<Content
title={
!processIsLoading && (
<Breadcrumb
style={{ fontSize: fontSizeHeading1, color: 'black' }}
separator={<span style={{ fontSize: '20px' }}>/</span>}
items={breadcrumItems}
/>
)
<Breadcrumb
style={{ fontSize: fontSizeHeading1, color: 'black' }}
separator={<span style={{ fontSize: '20px' }}>/</span>}
items={breadcrumItems}
/>
}
compact
wrapperClass={cn(styles.Wrapper, { [styles.minimized]: minimized })}
headerClass={cn(styles.HF, { [styles.minimizedHF]: minimized })}
>
<Modeler className={styles.Modeler} minimized={minimized} />
{children}
{minimized ? (
<Overlay processId={processId as string} onClose={() => setClosed(true)} />
) : null}
</Content>
);
};

export default Processes;
export default Wrapper;
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Content from '@/components/content';
import { Skeleton, Space } from 'antd';

const ProcessesSkeleton = async () => {
const ProcessesSkeleton = () => {
return (
<Content title="Processes">
<Space direction="vertical" size="large" style={{ display: 'flex', height: '100%' }}>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { AuthOptions } from 'next-auth';
import { User } from '@/types/next-auth';
import { randomUUID } from 'crypto';

export const nextAuthOptions: AuthOptions = {
secret: process.env.NEXTAUTH_SECRET,
providers: [],
callbacks: {
async jwt({ token, user, trigger }) {
if (trigger === 'signIn') token.csrfToken = randomUUID();
if (user) token.user = user as User;
return token;
},
session(args) {
const { session, token } = args;
if (token.user) session.user = token.user;
session.csrfToken = token.csrfToken;
return session;
},
},
};
22 changes: 1 addition & 21 deletions src/management-system-v2/app/api/auth/[...nextauth]/route.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,7 @@
import { AuthOptions } from 'next-auth';
import NextAuth from 'next-auth/next';
import Auth0Provider from 'next-auth/providers/auth0';
import { User } from '@/types/next-auth';
import { randomUUID } from 'crypto';
import CredentialsProvider from 'next-auth/providers/credentials';

export const nextAuthOptions: AuthOptions = {
secret: process.env.NEXTAUTH_SECRET,
providers: [],
callbacks: {
async jwt({ token, user, trigger }) {
if (trigger === 'signIn') token.csrfToken = randomUUID();
if (user) token.user = user as User;
return token;
},
session(args) {
const { session, token } = args;
if (token.user) session.user = token.user;
session.csrfToken = token.csrfToken;
return session;
},
},
};
import { nextAuthOptions } from './auth-options';

if (process.env.USE_AUTH0) {
nextAuthOptions.providers.push(
Expand Down
8 changes: 5 additions & 3 deletions src/management-system-v2/components/auth.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import 'server-only';

import { ComponentProps, ComponentType, cache } from 'react';
import { getServerSession } from 'next-auth/next';
import { redirect } from 'next/navigation';
import { nextAuthOptions } from '@/app/api/auth/[...nextauth]/route';
import { AuthCan, AuthCanProps } from './auth-can';
import { getAbilityForUser } from '@/lib/authorization/authorization';
import { nextAuthOptions } from '@/app/api/auth/[...nextauth]/auth-options';

// TODO: To enable PPR move the session redirect into this function, so it will
// be called when the session is first accessed and everything above can PPR. For
// permissions, each server component should check its permissions anyway, for
// composability.
export const getCurrentUser = cache(async () => {
const session = await getServerSession(nextAuthOptions);
const ability = await getAbilityForUser(session?.user.id || '');
Expand Down
31 changes: 15 additions & 16 deletions src/management-system-v2/components/modeler.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
'use client';

import React, { FC, useEffect, useRef, useState } from 'react';
import React, { FC, useEffect, useRef, useState, useTransition } from 'react';
import 'bpmn-js/dist/assets/bpmn-js.css';
import 'bpmn-js/dist/assets/diagram-js.css';
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn.css';
import type ModelerType from 'bpmn-js/lib/Modeler';
import type ViewerType from 'bpmn-js/lib/NavigatedViewer';
import { useParams, useSearchParams } from 'next/navigation';
import { useParams, usePathname, useSearchParams } from 'next/navigation';

import ModelerToolbar from './modeler-toolbar';
import XmlEditor from './xml-editor';
Expand All @@ -18,6 +18,7 @@ import { useProcessBpmn } from '@/lib/process-queries';
import VersionToolbar from './version-toolbar';

import { copyProcessImage } from '@/lib/process-export/copy-process-image';
import { updateProcess } from '@/lib/data/processes';

// Conditionally load the BPMN modeler only on the client, because it uses
// "window" reference. It won't be included in the initial bundle, but will be
Expand All @@ -32,25 +33,26 @@ const BPMNViewer =
: null;

type ModelerProps = React.HTMLAttributes<HTMLDivElement> & {
minimized: boolean;
processBpmn: string;
};

const Modeler: FC<ModelerProps> = ({ minimized, ...props }) => {
const Modeler = ({ processBpmn, ...divProps }: ModelerProps) => {
const { processId } = useParams();
const pathname = usePathname();
const [initialized, setInitialized] = useState(false);
const [xmlEditorBpmn, setXmlEditorBpmn] = useState<string | undefined>(undefined);
const query = useSearchParams();
const [isPending, startTransition] = useTransition();

const canvas = useRef<HTMLDivElement>(null);
const modeler = useRef<ModelerType | ViewerType | null>(null);

const { mutateAsync: updateProcessMutation } = usePutAsset('/process/{definitionId}');

const setModeler = useModelerStateStore((state) => state.setModeler);
const setSelectedElementId = useModelerStateStore((state) => state.setSelectedElementId);
const setEditingDisabled = useModelerStateStore((state) => state.setEditingDisabled);

/// Derived State
const minimized = pathname !== `/processes/${processId}`;
const selectedVersionId = query.get('version');

useEffect(() => {
Expand Down Expand Up @@ -88,9 +90,9 @@ const Modeler: FC<ModelerProps> = ({ minimized, ...props }) => {
try {
const { xml } = await modeler.current!.saveXML({ format: true });
/* await updateProcess(processId as string, { bpmn: xml! }); */
await updateProcessMutation({
params: { path: { definitionId: processId as string } },
body: { bpmn: xml },

startTransition(async () => {
await updateProcess(processId as string, xml);
});
} catch (err) {
console.log(err);
Expand Down Expand Up @@ -124,9 +126,7 @@ const Modeler: FC<ModelerProps> = ({ minimized, ...props }) => {
modeler.current?.destroy();
};
// only reset the modeler if we switch between editing being enabled or disabled
}, [setModeler, selectedVersionId, processId, updateProcessMutation]);

const { data: processBpmn } = useProcessBpmn(processId as string, selectedVersionId);
}, [setModeler, selectedVersionId, processId]);

useEffect(() => {
// only import the bpmn once (the effect will be retriggered when initialized is set to false at its end)
Expand Down Expand Up @@ -165,9 +165,8 @@ const Modeler: FC<ModelerProps> = ({ minimized, ...props }) => {
});
// if the bpmn contains unexpected content (text content for an element where the model does not define text) the modeler will remove it automatically => make sure the stored bpmn is the same as the one in the modeler
const { xml: cleanedBpmn } = await modeler.current.saveXML({ format: true });
await updateProcessMutation({
params: { path: { definitionId: processId as string } },
body: { bpmn: cleanedBpmn },
startTransition(async () => {
await updateProcess(processId as string, cleanedBpmn);
});
}
};
Expand All @@ -188,7 +187,7 @@ const Modeler: FC<ModelerProps> = ({ minimized, ...props }) => {
)}
</>
)}
<div className="modeler" style={{ height: '100%' }} {...props} ref={canvas} />
<div className="modeler" style={{ height: '100%' }} {...divProps} ref={canvas} />
</div>
);
};
Expand Down
Loading

0 comments on commit 5e50177

Please sign in to comment.