340 lines
11 KiB
TypeScript
340 lines
11 KiB
TypeScript
/**
|
|
* SharePoint node config — connection selector, paths, search.
|
|
* All nodes use SharepointBrowseTree with the selected connection (fetchBrowse + onLoadChildren).
|
|
* Folder-style nodes (list, upload target, copy destination): folders only, folder selection.
|
|
* File-style nodes (read, download, find path, copy source): file selection; folders expand only.
|
|
*/
|
|
|
|
import React, { useEffect, useState, useCallback } from 'react';
|
|
import type { NodeConfigRendererProps } from './types';
|
|
import { fetchConnections, fetchBrowse, type UserConnection, type BrowseEntry } from '../../../../api/automation2Api';
|
|
import { SharepointBrowseTree } from '../../../FolderTree/SharepointBrowseTree';
|
|
|
|
const browseDetailsStyle: React.CSSProperties = {
|
|
marginTop: 12,
|
|
border: '1px solid var(--border-color, #e0e0e0)',
|
|
borderRadius: 6,
|
|
background: 'var(--bg-secondary, #f8f9fa)',
|
|
overflow: 'hidden',
|
|
};
|
|
|
|
const browseSummaryStyle: React.CSSProperties = {
|
|
padding: '0.5rem 0.75rem',
|
|
cursor: 'pointer',
|
|
fontWeight: 500,
|
|
fontSize: '0.875rem',
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
gap: '0.5rem',
|
|
userSelect: 'none',
|
|
};
|
|
|
|
const browseBodyStyle: React.CSSProperties = {
|
|
padding: '0.5rem 0.75rem',
|
|
borderTop: '1px solid var(--border-color, #e0e0e0)',
|
|
maxHeight: 280,
|
|
overflowY: 'auto',
|
|
};
|
|
|
|
function browsePanelTitle(nodeType: string): string {
|
|
switch (nodeType) {
|
|
case 'sharepoint.uploadFile':
|
|
return 'Zielordner durchsuchen';
|
|
case 'sharepoint.listFiles':
|
|
return 'Ordner durchsuchen';
|
|
case 'sharepoint.readFile':
|
|
return 'Datei auswählen';
|
|
case 'sharepoint.downloadFile':
|
|
return 'Datei auswählen';
|
|
case 'sharepoint.findFile':
|
|
return 'Pfad aus Bibliothek wählen';
|
|
default:
|
|
return 'SharePoint durchsuchen';
|
|
}
|
|
}
|
|
|
|
/** Folder / location pickers — tree shows folders only; selecting sets folder path. */
|
|
function isFolderPickerNode(nodeType: string): boolean {
|
|
return nodeType === 'sharepoint.uploadFile' || nodeType === 'sharepoint.listFiles';
|
|
}
|
|
|
|
export const SharePointNodeConfig: React.FC<NodeConfigRendererProps> = ({
|
|
params,
|
|
updateParam,
|
|
instanceId,
|
|
request,
|
|
nodeType = 'sharepoint.findFile',
|
|
}) => {
|
|
const [connections, setConnections] = useState<UserConnection[]>([]);
|
|
const [browseExpanded, setBrowseExpanded] = useState(false);
|
|
const [findFileBrowseExpanded, setFindFileBrowseExpanded] = useState(false);
|
|
const [copySourceExpanded, setCopySourceExpanded] = useState(false);
|
|
const [copyDestExpanded, setCopyDestExpanded] = useState(false);
|
|
const [connectionsLoading, setConnectionsLoading] = useState(false);
|
|
|
|
const connectionId = (params.connectionId as string) ?? '';
|
|
const path =
|
|
(params.path as string) ?? (params.filePath as string) ?? '';
|
|
|
|
useEffect(() => {
|
|
if (instanceId && request) {
|
|
setConnectionsLoading(true);
|
|
fetchConnections(request, instanceId)
|
|
.then(setConnections)
|
|
.catch(() => setConnections([]))
|
|
.finally(() => setConnectionsLoading(false));
|
|
}
|
|
}, [instanceId, request]);
|
|
|
|
const loadChildren = useCallback(
|
|
async (pathToLoad: string): Promise<BrowseEntry[]> => {
|
|
if (!instanceId || !request || !connectionId) return [];
|
|
const r = await fetchBrowse(request, instanceId, connectionId, 'sharepoint', pathToLoad);
|
|
return r?.items ?? [];
|
|
},
|
|
[instanceId, request, connectionId]
|
|
);
|
|
|
|
const selectPath = useCallback(
|
|
(p: string) => {
|
|
updateParam('path', p);
|
|
setBrowseExpanded(false);
|
|
},
|
|
[updateParam]
|
|
);
|
|
|
|
const selectSearchQueryFromFile = useCallback(
|
|
(p: string) => {
|
|
updateParam('searchQuery', p);
|
|
setFindFileBrowseExpanded(false);
|
|
},
|
|
[updateParam]
|
|
);
|
|
|
|
const selectSourcePath = useCallback(
|
|
(p: string) => {
|
|
updateParam('sourcePath', p);
|
|
setCopySourceExpanded(false);
|
|
},
|
|
[updateParam]
|
|
);
|
|
|
|
const selectDestPath = useCallback(
|
|
(p: string) => {
|
|
updateParam('destPath', p);
|
|
setCopyDestExpanded(false);
|
|
},
|
|
[updateParam]
|
|
);
|
|
|
|
const needsSearch = nodeType === 'sharepoint.findFile';
|
|
const needsSiteId = false;
|
|
|
|
const showPathFieldsForList =
|
|
nodeType === 'sharepoint.listFiles';
|
|
const showPathFieldsForFileUploadDownload =
|
|
nodeType === 'sharepoint.readFile' ||
|
|
nodeType === 'sharepoint.uploadFile' ||
|
|
nodeType === 'sharepoint.downloadFile';
|
|
|
|
/** Path + browse (same tree wiring) for these types — not copyFile (copy uses its own trees). */
|
|
const showStandardPathBrowse =
|
|
connectionId &&
|
|
(showPathFieldsForList || showPathFieldsForFileUploadDownload);
|
|
|
|
const showFindFileBrowse = connectionId && needsSearch;
|
|
|
|
return (
|
|
<>
|
|
<div>
|
|
<label>Connection</label>
|
|
<select
|
|
value={connectionId}
|
|
onChange={(e) => updateParam('connectionId', e.target.value)}
|
|
disabled={connectionsLoading}
|
|
>
|
|
<option value="">{connectionsLoading ? 'Loading...' : 'Select connection'}</option>
|
|
{connections.map((c) => (
|
|
<option key={c.id} value={c.id}>
|
|
{c.externalUsername ?? c.id}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
|
|
{needsSearch && (
|
|
<div>
|
|
<label>Search query / path</label>
|
|
<input
|
|
value={(params.searchQuery as string) ?? ''}
|
|
onChange={(e) => updateParam('searchQuery', e.target.value)}
|
|
placeholder="/sites/SiteName/Shared Documents or search term"
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
{showPathFieldsForList && (
|
|
<div>
|
|
<label>Folder path</label>
|
|
<input
|
|
value={path}
|
|
onChange={(e) => updateParam('path', e.target.value)}
|
|
placeholder="/ or /sites/SiteName/Shared Documents/Folder"
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
{showPathFieldsForFileUploadDownload && (
|
|
<div>
|
|
<label>
|
|
{nodeType === 'sharepoint.uploadFile'
|
|
? 'Target folder path'
|
|
: nodeType === 'sharepoint.downloadFile'
|
|
? 'File path'
|
|
: 'Path'}
|
|
</label>
|
|
<input
|
|
value={(params.path as string) ?? (params.filePath as string) ?? ''}
|
|
onChange={(e) => updateParam('path', e.target.value)}
|
|
placeholder={
|
|
nodeType === 'sharepoint.downloadFile'
|
|
? '/sites/SiteName/Shared Documents/file.pdf'
|
|
: nodeType === 'sharepoint.uploadFile'
|
|
? '/sites/.../Shared Documents/TargetFolder/'
|
|
: 'File path'
|
|
}
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
{needsSiteId && (
|
|
<div>
|
|
<label>Site ID</label>
|
|
<input
|
|
value={(params.siteId as string) ?? ''}
|
|
onChange={(e) => updateParam('siteId', e.target.value)}
|
|
placeholder="SharePoint site ID"
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
{nodeType === 'sharepoint.copyFile' && (
|
|
<>
|
|
<div>
|
|
<label>Source file</label>
|
|
<input
|
|
value={(params.sourcePath as string) ?? ''}
|
|
onChange={(e) => updateParam('sourcePath', e.target.value)}
|
|
placeholder="/sites/.../folder/file.pdf"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label>Destination folder</label>
|
|
<input
|
|
value={(params.destPath as string) ?? ''}
|
|
onChange={(e) => updateParam('destPath', e.target.value)}
|
|
placeholder="/sites/.../target-folder/"
|
|
/>
|
|
</div>
|
|
{connectionId && (
|
|
<>
|
|
<details
|
|
open={copySourceExpanded}
|
|
onToggle={(e) => setCopySourceExpanded((e.target as HTMLDetailsElement).open)}
|
|
style={browseDetailsStyle}
|
|
>
|
|
<summary style={{ ...browseSummaryStyle, padding: '0.5rem 0.75rem' }}>
|
|
<span style={{ opacity: copySourceExpanded ? 0.7 : 1 }}>📂</span>
|
|
Quelldatei durchsuchen
|
|
</summary>
|
|
<div style={browseBodyStyle}>
|
|
<SharepointBrowseTree
|
|
rootPath="/"
|
|
onLoadChildren={loadChildren}
|
|
foldersOnly={false}
|
|
onSelectFile={selectSourcePath}
|
|
selectedPath={(params.sourcePath as string) || null}
|
|
/>
|
|
</div>
|
|
</details>
|
|
<details
|
|
open={copyDestExpanded}
|
|
onToggle={(e) => setCopyDestExpanded((e.target as HTMLDetailsElement).open)}
|
|
style={{ ...browseDetailsStyle, marginTop: 8 }}
|
|
>
|
|
<summary style={{ ...browseSummaryStyle, padding: '0.5rem 0.75rem' }}>
|
|
<span style={{ opacity: copyDestExpanded ? 0.7 : 1 }}>📂</span>
|
|
Zielordner durchsuchen
|
|
</summary>
|
|
<div style={browseBodyStyle}>
|
|
<SharepointBrowseTree
|
|
rootPath="/"
|
|
onLoadChildren={loadChildren}
|
|
foldersOnly
|
|
onSelectFile={() => {}}
|
|
onSelectFolder={selectDestPath}
|
|
selectedPath={(params.destPath as string) || null}
|
|
/>
|
|
</div>
|
|
</details>
|
|
</>
|
|
)}
|
|
</>
|
|
)}
|
|
|
|
{showStandardPathBrowse && (
|
|
<details
|
|
open={browseExpanded}
|
|
onToggle={(e) => setBrowseExpanded((e.target as HTMLDetailsElement).open)}
|
|
style={browseDetailsStyle}
|
|
>
|
|
<summary style={browseSummaryStyle}>
|
|
<span style={{ opacity: browseExpanded ? 0.7 : 1 }}>📂</span>
|
|
{browsePanelTitle(nodeType)}
|
|
</summary>
|
|
<div style={browseBodyStyle}>
|
|
{isFolderPickerNode(nodeType) && (
|
|
<SharepointBrowseTree
|
|
rootPath="/"
|
|
onLoadChildren={loadChildren}
|
|
foldersOnly
|
|
onSelectFile={() => {}}
|
|
onSelectFolder={selectPath}
|
|
selectedPath={path || null}
|
|
/>
|
|
)}
|
|
{(nodeType === 'sharepoint.readFile' || nodeType === 'sharepoint.downloadFile') && (
|
|
<SharepointBrowseTree
|
|
rootPath="/"
|
|
onLoadChildren={loadChildren}
|
|
onSelectFile={selectPath}
|
|
selectedPath={path || null}
|
|
/>
|
|
)}
|
|
</div>
|
|
</details>
|
|
)}
|
|
|
|
{showFindFileBrowse && (
|
|
<details
|
|
open={findFileBrowseExpanded}
|
|
onToggle={(e) => setFindFileBrowseExpanded((e.target as HTMLDetailsElement).open)}
|
|
style={browseDetailsStyle}
|
|
>
|
|
<summary style={browseSummaryStyle}>
|
|
<span style={{ opacity: findFileBrowseExpanded ? 0.7 : 1 }}>📂</span>
|
|
{browsePanelTitle('sharepoint.findFile')}
|
|
</summary>
|
|
<div style={browseBodyStyle}>
|
|
<SharepointBrowseTree
|
|
rootPath="/"
|
|
onLoadChildren={loadChildren}
|
|
onSelectFile={selectSearchQueryFromFile}
|
|
selectedPath={(params.searchQuery as string) || null}
|
|
/>
|
|
</div>
|
|
</details>
|
|
)}
|
|
</>
|
|
);
|
|
};
|