feat(app): own compose field and routing state
This commit is contained in:
+1
-1
@@ -16,7 +16,7 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@design.estate/dees-element": "^2.2.4",
|
"@design.estate/dees-element": "^2.2.4",
|
||||||
"@signature.digital/catalog": "^1.6.1",
|
"@signature.digital/catalog": "^1.7.0",
|
||||||
"@signature.digital/interfaces": "file:../interfaces",
|
"@signature.digital/interfaces": "file:../interfaces",
|
||||||
"@signature.digital/tools": "file:../tools"
|
"@signature.digital/tools": "file:../tools"
|
||||||
},
|
},
|
||||||
|
|||||||
Generated
+5
-5
@@ -12,8 +12,8 @@ importers:
|
|||||||
specifier: ^2.2.4
|
specifier: ^2.2.4
|
||||||
version: 2.2.4
|
version: 2.2.4
|
||||||
'@signature.digital/catalog':
|
'@signature.digital/catalog':
|
||||||
specifier: ^1.6.1
|
specifier: ^1.7.0
|
||||||
version: 1.6.1(@tiptap/pm@2.27.2)
|
version: 1.7.0(@tiptap/pm@2.27.2)
|
||||||
'@signature.digital/interfaces':
|
'@signature.digital/interfaces':
|
||||||
specifier: file:../interfaces
|
specifier: file:../interfaces
|
||||||
version: file:../interfaces
|
version: file:../interfaces
|
||||||
@@ -1035,8 +1035,8 @@ packages:
|
|||||||
'@sec-ant/readable-stream@0.4.1':
|
'@sec-ant/readable-stream@0.4.1':
|
||||||
resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==}
|
resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==}
|
||||||
|
|
||||||
'@signature.digital/catalog@1.6.1':
|
'@signature.digital/catalog@1.7.0':
|
||||||
resolution: {integrity: sha512-WER9SO5gYsnYCpcz5+2gP+8ayZMrbQ76oK1hbuO935Yef2Fv6rtyY63W5bB27uhptly4BAD1A3RHGrh3BZMtjg==}
|
resolution: {integrity: sha512-UDgcW2sx5tangR+8omnISYIy4Nqrej17Za3nBPsRCW7hT6+veB8Bu5ZFssDGQqYld+aPe4o2+H3ZiJgdJJ84nQ==}
|
||||||
|
|
||||||
'@signature.digital/interfaces@1.2.0':
|
'@signature.digital/interfaces@1.2.0':
|
||||||
resolution: {integrity: sha512-Aym4CYEJ5TDXshNI/kiBPPjDIkQ+TRXeu5u1d0aVC7aShwHmLj38mWLXHqFnAnQSvpXDRje4vAZML9NTU9Q7sg==}
|
resolution: {integrity: sha512-Aym4CYEJ5TDXshNI/kiBPPjDIkQ+TRXeu5u1d0aVC7aShwHmLj38mWLXHqFnAnQSvpXDRje4vAZML9NTU9Q7sg==}
|
||||||
@@ -4512,7 +4512,7 @@ snapshots:
|
|||||||
|
|
||||||
'@sec-ant/readable-stream@0.4.1': {}
|
'@sec-ant/readable-stream@0.4.1': {}
|
||||||
|
|
||||||
'@signature.digital/catalog@1.6.1(@tiptap/pm@2.27.2)':
|
'@signature.digital/catalog@1.7.0(@tiptap/pm@2.27.2)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@design.estate/dees-catalog': 3.81.0(@tiptap/pm@2.27.2)
|
'@design.estate/dees-catalog': 3.81.0(@tiptap/pm@2.27.2)
|
||||||
'@design.estate/dees-domtools': 2.5.6
|
'@design.estate/dees-domtools': 2.5.6
|
||||||
|
|||||||
+22
-36
@@ -1,41 +1,10 @@
|
|||||||
import '@signature.digital/catalog';
|
import '@signature.digital/catalog';
|
||||||
import { html, render } from '@design.estate/dees-element';
|
import { html, render } from '@design.estate/dees-element';
|
||||||
|
import { appDocuments, appRecipients, documentIdOrDefault, fieldsForDocument, hashForRoute, isWorkspaceView, routeFromLocation, setAppRecipients, setFieldsForDocument, type IAppDocumentRow, type IAppFieldPlacement, type IAppRecipient, type TWorkspaceView } from './state.js';
|
||||||
type TWorkspaceView = 'inbox' | 'compose' | 'sign' | 'audit' | 'developers' | 'templates' | 'team' | 'settings';
|
|
||||||
|
|
||||||
type TDocumentRow = {
|
|
||||||
id: string;
|
|
||||||
title: string;
|
|
||||||
status: 'awaiting' | 'signed' | 'draft' | 'declined';
|
|
||||||
recipients: Array<{ name: string; initials: string; signed: boolean }>;
|
|
||||||
updated: string;
|
|
||||||
sender: string;
|
|
||||||
pages: number;
|
|
||||||
deadline?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const workspaceViews: TWorkspaceView[] = ['inbox', 'compose', 'sign', 'audit', 'developers', 'templates', 'team', 'settings'];
|
|
||||||
|
|
||||||
const appDocuments: TDocumentRow[] = [
|
|
||||||
{ id: 'app_doc_001', title: 'Platform Services Agreement - App-owned data', status: 'awaiting', recipients: [{ name: 'Sarah Chen', initials: 'SC', signed: true }, { name: 'David Park', initials: 'DP', signed: false }], updated: 'just now', sender: 'App Service', pages: 14, deadline: 'May 5' },
|
|
||||||
{ id: 'app_doc_002', title: 'Security Addendum - Catalog Integration', status: 'draft', recipients: [{ name: 'Philipp K.', initials: 'PK', signed: false }], updated: '12 min ago', sender: 'App Service', pages: 5 },
|
|
||||||
{ id: 'app_doc_003', title: 'Completed Vendor NDA - Signed from App', status: 'signed', recipients: [{ name: 'Lila Brooks', initials: 'LB', signed: true }, { name: 'Philipp K.', initials: 'PK', signed: true }], updated: '1h ago', sender: 'App Service', pages: 3 },
|
|
||||||
];
|
|
||||||
|
|
||||||
const isWorkspaceView = (view: string): view is TWorkspaceView => workspaceViews.includes(view as TWorkspaceView);
|
|
||||||
|
|
||||||
const routeFromLocation = (): { view: TWorkspaceView; documentId: string } => {
|
|
||||||
const [view = '', documentId = ''] = globalThis.location.hash.replace(/^#/, '').split(':');
|
|
||||||
return { view: isWorkspaceView(view) ? view : 'inbox', documentId };
|
|
||||||
};
|
|
||||||
|
|
||||||
const hashForRoute = (view: TWorkspaceView, documentId = ''): string => {
|
|
||||||
return documentId && (view === 'sign' || view === 'audit') ? `#${view}:${documentId}` : `#${view}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
let currentRoute = routeFromLocation();
|
let currentRoute = routeFromLocation();
|
||||||
let currentView = currentRoute.view;
|
let currentView = currentRoute.view;
|
||||||
let activeDocumentId = currentRoute.documentId;
|
let activeDocumentId = currentRoute.documentId ? documentIdOrDefault(currentRoute.documentId) : '';
|
||||||
let appRoot: HTMLElement | null = null;
|
let appRoot: HTMLElement | null = null;
|
||||||
let routeChangeFrame: number | null = null;
|
let routeChangeFrame: number | null = null;
|
||||||
|
|
||||||
@@ -57,11 +26,24 @@ const handleViewChange = (event: Event) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleDocumentOpen = (event: Event) => {
|
const handleDocumentOpen = (event: Event) => {
|
||||||
const document = (event as CustomEvent<{ document?: TDocumentRow }>).detail?.document;
|
const document = (event as CustomEvent<{ document?: IAppDocumentRow }>).detail?.document;
|
||||||
if (!document) return;
|
if (!document) return;
|
||||||
activeDocumentId = document.id;
|
activeDocumentId = document.id;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleFieldsChange = (event: Event) => {
|
||||||
|
if (!activeDocumentId) return;
|
||||||
|
const fields = (event as CustomEvent<{ fields?: IAppFieldPlacement[] }>).detail?.fields;
|
||||||
|
if (!fields) return;
|
||||||
|
setFieldsForDocument(activeDocumentId, fields);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRecipientsChange = (event: Event) => {
|
||||||
|
const recipients = (event as CustomEvent<{ recipients?: IAppRecipient[] }>).detail?.recipients;
|
||||||
|
if (!recipients) return;
|
||||||
|
setAppRecipients(recipients);
|
||||||
|
};
|
||||||
|
|
||||||
const handleWorkspaceViewRequest = (event: Event) => {
|
const handleWorkspaceViewRequest = (event: Event) => {
|
||||||
const view = (event as CustomEvent<{ view?: TWorkspaceView }>).detail?.view;
|
const view = (event as CustomEvent<{ view?: TWorkspaceView }>).detail?.view;
|
||||||
if (!view || !isWorkspaceView(view)) return;
|
if (!view || !isWorkspaceView(view)) return;
|
||||||
@@ -82,13 +64,14 @@ const handleRouteChange = () => {
|
|||||||
routeChangeFrame = null;
|
routeChangeFrame = null;
|
||||||
currentRoute = routeFromLocation();
|
currentRoute = routeFromLocation();
|
||||||
currentView = currentRoute.view;
|
currentView = currentRoute.view;
|
||||||
activeDocumentId = currentRoute.documentId;
|
activeDocumentId = currentRoute.documentId ? documentIdOrDefault(currentRoute.documentId) : '';
|
||||||
renderApp();
|
renderApp();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderApp = () => {
|
const renderApp = () => {
|
||||||
if (!appRoot) createAppRoot();
|
if (!appRoot) createAppRoot();
|
||||||
|
const documentId = activeDocumentId || appDocuments[0].id;
|
||||||
render(
|
render(
|
||||||
html`
|
html`
|
||||||
<style>
|
<style>
|
||||||
@@ -110,7 +93,7 @@ const renderApp = () => {
|
|||||||
height: 100vh;
|
height: 100vh;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<sdig-workspace accent="#3b82f6" density="comfortable" theme="dark" initialView=${currentView} .initialView=${currentView} view=${currentView} .view=${currentView} activeDocumentId=${activeDocumentId} .activeDocumentId=${activeDocumentId} .documents=${appDocuments}></sdig-workspace>
|
<sdig-workspace accent="#3b82f6" density="comfortable" theme="dark" initialView=${currentView} .initialView=${currentView} view=${currentView} .view=${currentView} activeDocumentId=${documentId} .activeDocumentId=${documentId} .documents=${appDocuments} .recipients=${appRecipients} .fields=${fieldsForDocument(documentId)}></sdig-workspace>
|
||||||
`,
|
`,
|
||||||
appRoot!
|
appRoot!
|
||||||
);
|
);
|
||||||
@@ -120,6 +103,9 @@ const run = async () => {
|
|||||||
createAppRoot();
|
createAppRoot();
|
||||||
document.body.addEventListener('view-change', handleViewChange);
|
document.body.addEventListener('view-change', handleViewChange);
|
||||||
document.body.addEventListener('document-open', handleDocumentOpen);
|
document.body.addEventListener('document-open', handleDocumentOpen);
|
||||||
|
document.body.addEventListener('fields-change', handleFieldsChange);
|
||||||
|
document.body.addEventListener('recipients-change', handleRecipientsChange);
|
||||||
|
document.body.addEventListener('routing-change', handleRecipientsChange);
|
||||||
document.body.addEventListener('workspace-view-request', handleWorkspaceViewRequest);
|
document.body.addEventListener('workspace-view-request', handleWorkspaceViewRequest);
|
||||||
globalThis.addEventListener('popstate', handleRouteChange);
|
globalThis.addEventListener('popstate', handleRouteChange);
|
||||||
globalThis.addEventListener('hashchange', handleRouteChange);
|
globalThis.addEventListener('hashchange', handleRouteChange);
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
export type TWorkspaceView = 'inbox' | 'compose' | 'sign' | 'audit' | 'developers' | 'templates' | 'team' | 'settings';
|
||||||
|
|
||||||
|
export type TRecipientRole = 'signer' | 'copy' | 'updates';
|
||||||
|
|
||||||
|
export interface IAppDocumentRow {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
status: 'awaiting' | 'signed' | 'draft' | 'declined';
|
||||||
|
recipients: Array<{ name: string; initials: string; signed: boolean }>;
|
||||||
|
updated: string;
|
||||||
|
sender: string;
|
||||||
|
pages: number;
|
||||||
|
deadline?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAppRecipient {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
email: string;
|
||||||
|
color: string;
|
||||||
|
order: number;
|
||||||
|
role: TRecipientRole;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAppFieldPlacement {
|
||||||
|
id: string;
|
||||||
|
type: 'signature' | 'date' | 'text' | 'initials' | 'check';
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
w: number;
|
||||||
|
h: number;
|
||||||
|
page: number;
|
||||||
|
recipient: number;
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const workspaceViews: TWorkspaceView[] = ['inbox', 'compose', 'sign', 'audit', 'developers', 'templates', 'team', 'settings'];
|
||||||
|
|
||||||
|
export const appDocuments: IAppDocumentRow[] = [
|
||||||
|
{ id: 'app_doc_001', title: 'Platform Services Agreement - App-owned data', status: 'awaiting', recipients: [{ name: 'Sarah Chen', initials: 'SC', signed: true }, { name: 'David Park', initials: 'DP', signed: false }], updated: 'just now', sender: 'App Service', pages: 14, deadline: 'May 5' },
|
||||||
|
{ id: 'app_doc_002', title: 'Security Addendum - Catalog Integration', status: 'draft', recipients: [{ name: 'Philipp K.', initials: 'PK', signed: false }], updated: '12 min ago', sender: 'App Service', pages: 5 },
|
||||||
|
{ id: 'app_doc_003', title: 'Completed Vendor NDA - Signed from App', status: 'signed', recipients: [{ name: 'Lila Brooks', initials: 'LB', signed: true }, { name: 'Philipp K.', initials: 'PK', signed: true }], updated: '1h ago', sender: 'App Service', pages: 3 },
|
||||||
|
];
|
||||||
|
|
||||||
|
export let appRecipients: IAppRecipient[] = [
|
||||||
|
{ id: 0, name: 'Sarah Chen', email: 'sarah@acme.com', color: '#60a5fa', order: 1, role: 'signer' },
|
||||||
|
{ id: 1, name: 'David Park', email: 'd.park@acme.com', color: '#fbbf24', order: 2, role: 'signer' },
|
||||||
|
{ id: 2, name: 'Philipp K.', email: 'philipp@lossless.com', color: '#3b82f6', order: 3, role: 'updates' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const defaultFields: IAppFieldPlacement[] = [
|
||||||
|
{ id: 'app_f1', type: 'signature', x: 60, y: 580, w: 200, h: 50, page: 1, recipient: 0, label: 'Signature' },
|
||||||
|
{ id: 'app_f2', type: 'date', x: 320, y: 580, w: 120, h: 30, page: 1, recipient: 0, label: 'Date' },
|
||||||
|
{ id: 'app_f3', type: 'text', x: 60, y: 460, w: 280, h: 30, page: 1, recipient: 1, label: 'Full legal name' },
|
||||||
|
{ id: 'app_f4', type: 'signature', x: 60, y: 700, w: 200, h: 50, page: 1, recipient: 1, label: 'Counter-signature' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const appFieldsByDocument: Record<string, IAppFieldPlacement[]> = Object.fromEntries(
|
||||||
|
appDocuments.map((document) => [document.id, defaultFields.map((field) => ({ ...field, id: `${document.id}_${field.id}` }))])
|
||||||
|
);
|
||||||
|
|
||||||
|
export const isWorkspaceView = (view: string): view is TWorkspaceView => workspaceViews.includes(view as TWorkspaceView);
|
||||||
|
|
||||||
|
export const routeFromLocation = (): { view: TWorkspaceView; documentId: string } => {
|
||||||
|
const [view = '', documentId = ''] = globalThis.location.hash.replace(/^#/, '').split(':');
|
||||||
|
return { view: isWorkspaceView(view) ? view : 'inbox', documentId };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const hashForRoute = (view: TWorkspaceView, documentId = ''): string => {
|
||||||
|
return documentId && (view === 'compose' || view === 'sign' || view === 'audit') ? `#${view}:${documentId}` : `#${view}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const documentIdOrDefault = (documentId: string): string => {
|
||||||
|
return appDocuments.some((document) => document.id === documentId) ? documentId : appDocuments[0].id;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const fieldsForDocument = (documentId: string): IAppFieldPlacement[] => {
|
||||||
|
return appFieldsByDocument[documentId] || [];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const setFieldsForDocument = (documentId: string, fields: IAppFieldPlacement[]) => {
|
||||||
|
appFieldsByDocument[documentId] = fields;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const setAppRecipients = (recipients: IAppRecipient[]) => {
|
||||||
|
appRecipients = recipients;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user