workign on folder location in file create node
This commit is contained in:
parent
1308e6d415
commit
9b0923b9da
7 changed files with 285 additions and 204 deletions
|
|
@ -320,6 +320,7 @@ const _LEGACY_RENDERERS_THAT_HANDLE_BINDINGS = new Set([
|
||||||
'featureInstance',
|
'featureInstance',
|
||||||
'sharepointFolder',
|
'sharepointFolder',
|
||||||
'sharepointFile',
|
'sharepointFile',
|
||||||
|
'userFileFolder',
|
||||||
'clickupList',
|
'clickupList',
|
||||||
'clickupTask',
|
'clickupTask',
|
||||||
'dataRef',
|
'dataRef',
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,115 @@
|
||||||
|
/**
|
||||||
|
* userFileFolder — same folder tree as Meine Dateien (FormGeneratorTree) inside a collapsible panel.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { useMemo, useCallback, useState } from 'react';
|
||||||
|
import { useLanguage } from '../../../../providers/language/LanguageContext';
|
||||||
|
import { FormGeneratorTree } from '../../../FormGenerator/FormGeneratorTree';
|
||||||
|
import { createFolderFileProvider } from '../../../FormGenerator/FormGeneratorTree/providers/FolderFileProvider';
|
||||||
|
import type { TreeNode } from '../../../FormGenerator/FormGeneratorTree';
|
||||||
|
import type { FieldRendererProps } from './index';
|
||||||
|
|
||||||
|
export const UserFileFolderPicker: React.FC<FieldRendererProps> = ({ param, value, onChange, request }) => {
|
||||||
|
const { t } = useLanguage();
|
||||||
|
const [panelOpen, setPanelOpen] = useState(true);
|
||||||
|
|
||||||
|
const provider = useMemo(() => createFolderFileProvider({ includeFiles: false }), []);
|
||||||
|
|
||||||
|
const strVal = typeof value === 'string' ? value : '';
|
||||||
|
const rootSelected = strVal === '';
|
||||||
|
|
||||||
|
const handleNodeClick = useCallback(
|
||||||
|
(node: TreeNode) => {
|
||||||
|
if (node.type === 'folder') {
|
||||||
|
onChange(node.id);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[onChange],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ marginBottom: 8 }}>
|
||||||
|
<label style={{ display: 'block', fontSize: 12, marginBottom: 2 }}>{param.description || param.name}</label>
|
||||||
|
{!request && (
|
||||||
|
<div style={{ fontSize: 11, color: '#888' }}>{t('Ordnerliste nicht verfügbar (keine API-Anbindung).')}</div>
|
||||||
|
)}
|
||||||
|
{request && (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setPanelOpen((o) => !o)}
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
width: '100%',
|
||||||
|
padding: '6px 10px',
|
||||||
|
marginBottom: panelOpen ? 6 : 0,
|
||||||
|
borderRadius: 6,
|
||||||
|
border: '1px solid var(--color-border, #cbd5e1)',
|
||||||
|
background: 'var(--table-header-bg, #f1f5f9)',
|
||||||
|
cursor: 'pointer',
|
||||||
|
fontSize: 12,
|
||||||
|
textAlign: 'left',
|
||||||
|
color: 'var(--color-text, #334155)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
|
||||||
|
{panelOpen ? t('Ordnerbaum ausblenden') : t('Ordnerbaum einblenden')}
|
||||||
|
</span>
|
||||||
|
<span aria-hidden style={{ marginLeft: 8, flexShrink: 0 }}>
|
||||||
|
{panelOpen ? '▾' : '▸'}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{panelOpen && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
border: '1px solid var(--color-border, #e2e8f0)',
|
||||||
|
borderRadius: 8,
|
||||||
|
overflow: 'hidden',
|
||||||
|
background: 'var(--color-bg, #fff)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
onClick={() => onChange('')}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
|
e.preventDefault();
|
||||||
|
onChange('');
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
padding: '8px 12px',
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: 600,
|
||||||
|
cursor: 'pointer',
|
||||||
|
borderBottom: '1px solid var(--color-border, #e2e8f0)',
|
||||||
|
background: rootSelected
|
||||||
|
? 'rgba(37, 99, 235, 0.12)'
|
||||||
|
: 'var(--table-header-bg, #f8fafc)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('Stamm — Meine Dateien')}
|
||||||
|
</div>
|
||||||
|
<FormGeneratorTree
|
||||||
|
provider={provider}
|
||||||
|
ownership="own"
|
||||||
|
title={t('Ordner')}
|
||||||
|
compact
|
||||||
|
allowCreateFolder
|
||||||
|
showFilter={false}
|
||||||
|
emptyMessage={t('Noch keine Ordner')}
|
||||||
|
onNodeClick={handleNodeClick}
|
||||||
|
embedMaxHeight={260}
|
||||||
|
hideRowActionButtons
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -34,6 +34,7 @@ import type { CanvasNode } from '../../editor/FlowCanvas';
|
||||||
import { DataRefRenderer } from './DataRefRenderer';
|
import { DataRefRenderer } from './DataRefRenderer';
|
||||||
import { ContextBuilderRenderer } from './ContextBuilderRenderer';
|
import { ContextBuilderRenderer } from './ContextBuilderRenderer';
|
||||||
import { FeatureInstancePicker } from './FeatureInstancePicker';
|
import { FeatureInstancePicker } from './FeatureInstancePicker';
|
||||||
|
import { UserFileFolderPicker } from './UserFileFolderPicker';
|
||||||
import { TemplateTextareaRenderer } from './TemplateTextareaRenderer';
|
import { TemplateTextareaRenderer } from './TemplateTextareaRenderer';
|
||||||
import { getApiBaseUrl } from '../../../../../config/config';
|
import { getApiBaseUrl } from '../../../../../config/config';
|
||||||
|
|
||||||
|
|
@ -917,6 +918,7 @@ export const FRONTEND_TYPE_RENDERERS: Record<string, FieldRendererComponent> = {
|
||||||
featureInstance: FeatureInstancePicker,
|
featureInstance: FeatureInstancePicker,
|
||||||
sharepointFolder: SharepointPathPicker,
|
sharepointFolder: SharepointPathPicker,
|
||||||
sharepointFile: SharepointPathPicker,
|
sharepointFile: SharepointPathPicker,
|
||||||
|
userFileFolder: UserFileFolderPicker,
|
||||||
clickupList: FolderPicker,
|
clickupList: FolderPicker,
|
||||||
clickupTask: FolderPicker,
|
clickupTask: FolderPicker,
|
||||||
caseList: CaseListEditor,
|
caseList: CaseListEditor,
|
||||||
|
|
|
||||||
|
|
@ -543,6 +543,22 @@
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Embedded workflow / compact pickers — fixed height so flex children (treeWrapper) get a real viewport */
|
||||||
|
.embeddedPicker {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: none !important;
|
||||||
|
min-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
/* height + maxHeight set inline (embedMaxHeight) */
|
||||||
|
}
|
||||||
|
|
||||||
|
.embeddedPicker .treeWrapper {
|
||||||
|
flex: 1 1 0;
|
||||||
|
min-height: 0;
|
||||||
|
max-height: none;
|
||||||
|
}
|
||||||
|
|
||||||
/* Compact mode */
|
/* Compact mode */
|
||||||
.compactMode .sectionHeader {
|
.compactMode .sectionHeader {
|
||||||
padding: 6px 8px;
|
padding: 6px 8px;
|
||||||
|
|
|
||||||
|
|
@ -180,6 +180,7 @@ interface TreeNodeRowProps<T = any> {
|
||||||
onDragOver: (e: React.DragEvent, node: TreeNode<T>) => void;
|
onDragOver: (e: React.DragEvent, node: TreeNode<T>) => void;
|
||||||
onDragLeave: (e: React.DragEvent) => void;
|
onDragLeave: (e: React.DragEvent) => void;
|
||||||
onDrop: (e: React.DragEvent, node: TreeNode<T>) => void;
|
onDrop: (e: React.DragEvent, node: TreeNode<T>) => void;
|
||||||
|
hideRowActionButtons?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const TreeNodeRow = React.memo(function TreeNodeRow<T>({
|
const TreeNodeRow = React.memo(function TreeNodeRow<T>({
|
||||||
|
|
@ -213,6 +214,7 @@ const TreeNodeRow = React.memo(function TreeNodeRow<T>({
|
||||||
onDragOver,
|
onDragOver,
|
||||||
onDragLeave,
|
onDragLeave,
|
||||||
onDrop,
|
onDrop,
|
||||||
|
hideRowActionButtons = false,
|
||||||
}: TreeNodeRowProps<T>) {
|
}: TreeNodeRowProps<T>) {
|
||||||
const { node, depth, hasChildren } = entry;
|
const { node, depth, hasChildren } = entry;
|
||||||
const renameRef = useRef<HTMLInputElement>(null);
|
const renameRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
@ -246,11 +248,12 @@ const TreeNodeRow = React.memo(function TreeNodeRow<T>({
|
||||||
const _handleDoubleClick = useCallback(
|
const _handleDoubleClick = useCallback(
|
||||||
(e: React.MouseEvent) => {
|
(e: React.MouseEvent) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
if (hideRowActionButtons) return;
|
||||||
if (ownership === 'own' && provider.canRename?.(node)) {
|
if (ownership === 'own' && provider.canRename?.(node)) {
|
||||||
onStartRename(node.id);
|
onStartRename(node.id);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[ownership, provider, node, onStartRename],
|
[hideRowActionButtons, ownership, provider, node, onStartRename],
|
||||||
);
|
);
|
||||||
|
|
||||||
const _handleRowClick = useCallback(
|
const _handleRowClick = useCallback(
|
||||||
|
|
@ -298,11 +301,11 @@ const TreeNodeRow = React.memo(function TreeNodeRow<T>({
|
||||||
className={rowClasses}
|
className={rowClasses}
|
||||||
onClick={_handleRowClick}
|
onClick={_handleRowClick}
|
||||||
onDoubleClick={_handleDoubleClick}
|
onDoubleClick={_handleDoubleClick}
|
||||||
draggable
|
draggable={!hideRowActionButtons}
|
||||||
onDragStart={(e) => onDragStart(e, node)}
|
onDragStart={hideRowActionButtons ? undefined : (e) => onDragStart(e, node)}
|
||||||
onDragOver={(e) => onDragOver(e, node)}
|
onDragOver={hideRowActionButtons ? undefined : (e) => onDragOver(e, node)}
|
||||||
onDragLeave={onDragLeave}
|
onDragLeave={hideRowActionButtons ? undefined : onDragLeave}
|
||||||
onDrop={(e) => onDrop(e, node)}
|
onDrop={hideRowActionButtons ? undefined : (e) => onDrop(e, node)}
|
||||||
data-node-id={node.id}
|
data-node-id={node.id}
|
||||||
title={node.name}
|
title={node.name}
|
||||||
role="treeitem"
|
role="treeitem"
|
||||||
|
|
@ -312,7 +315,7 @@ const TreeNodeRow = React.memo(function TreeNodeRow<T>({
|
||||||
>
|
>
|
||||||
<div className={styles.indentSpacer} style={{ width: depth * INDENT_PX }} />
|
<div className={styles.indentSpacer} style={{ width: depth * INDENT_PX }} />
|
||||||
|
|
||||||
{selectable && (
|
{!hideRowActionButtons && (
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
className={styles.nodeCheckbox}
|
className={styles.nodeCheckbox}
|
||||||
|
|
@ -361,27 +364,22 @@ const TreeNodeRow = React.memo(function TreeNodeRow<T>({
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className={styles.nodeSizeGroup}>
|
{!hideRowActionButtons && (
|
||||||
<span className={styles.nodeSize}>
|
<span className={styles.nodeSize}>
|
||||||
{node.sizeBytes != null ? _formatSize(node.sizeBytes) : ''}
|
{node.sizeBytes != null ? _formatSize(node.sizeBytes) : ''}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<div className={styles.nodeActionsHover}>
|
|
||||||
{canCreateChild && onCreateChild && (
|
|
||||||
<button
|
|
||||||
className={styles.emojiBtn}
|
|
||||||
onClick={(e) => { e.stopPropagation(); onCreateChild(node.id); }}
|
|
||||||
title="Neuer Unterordner"
|
|
||||||
tabIndex={-1}
|
|
||||||
>
|
|
||||||
{'\u2795'}
|
|
||||||
</button>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{!hideRowActionButtons && (
|
||||||
|
<>
|
||||||
|
<div className={styles.nodeActionsHover}>
|
||||||
{canRename && (
|
{canRename && (
|
||||||
<button
|
<button
|
||||||
className={styles.emojiBtn}
|
className={styles.emojiBtn}
|
||||||
onClick={(e) => { e.stopPropagation(); onStartRename(node.id); }}
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onStartRename(node.id);
|
||||||
|
}}
|
||||||
title="Umbenennen"
|
title="Umbenennen"
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
>
|
>
|
||||||
|
|
@ -389,10 +387,13 @@ const TreeNodeRow = React.memo(function TreeNodeRow<T>({
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{node.type !== 'folder' && provider.downloadNode && (
|
{node.type !== 'folder' && (
|
||||||
<button
|
<button
|
||||||
className={styles.emojiBtn}
|
className={styles.emojiBtn}
|
||||||
onClick={(e) => { e.stopPropagation(); onDownload(node); }}
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onDownload(node);
|
||||||
|
}}
|
||||||
title="Datei herunterladen"
|
title="Datei herunterladen"
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
>
|
>
|
||||||
|
|
@ -403,7 +404,10 @@ const TreeNodeRow = React.memo(function TreeNodeRow<T>({
|
||||||
{canDelete && (
|
{canDelete && (
|
||||||
<button
|
<button
|
||||||
className={styles.emojiBtn}
|
className={styles.emojiBtn}
|
||||||
onClick={(e) => { e.stopPropagation(); onDelete(node.id); }}
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onDelete(node.id);
|
||||||
|
}}
|
||||||
title="Loeschen"
|
title="Loeschen"
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
>
|
>
|
||||||
|
|
@ -411,52 +415,8 @@ const TreeNodeRow = React.memo(function TreeNodeRow<T>({
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={styles.nodeActionsPersistent}>
|
<div className={styles.nodeActionsPersistent}>
|
||||||
{/* Order (left-to-right): extraActions (e.g. settings) -> RAG -> sendToChat -> scope -> neutralize. */}
|
|
||||||
{node.extraActions?.map((action) => (
|
|
||||||
<button
|
|
||||||
key={action.key}
|
|
||||||
className={`${styles.emojiBtn} ${action.disabled ? styles.emojiBtnReadonly : ''}`}
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
if (!action.disabled) onExtraAction(node.id, action);
|
|
||||||
}}
|
|
||||||
title={action.tooltip}
|
|
||||||
tabIndex={-1}
|
|
||||||
disabled={action.disabled}
|
|
||||||
>
|
|
||||||
{pendingActions.has(action.key)
|
|
||||||
? <span className={styles.flagSpinner} />
|
|
||||||
: action.value === 'mixed'
|
|
||||||
? <span className={styles.flagMixed}>{_MIXED_SYMBOL}</span>
|
|
||||||
: action.icon}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
|
|
||||||
{node.ragIndexEnabled !== undefined && (
|
|
||||||
<button
|
|
||||||
className={`${styles.emojiBtn} ${canPatchRagIndex ? '' : styles.emojiBtnReadonly}`}
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
if (canPatchRagIndex) onToggleRagIndex(node);
|
|
||||||
}}
|
|
||||||
title={node.ragIndexEnabled === 'mixed'
|
|
||||||
? 'Gemischt - Klick setzt explizit'
|
|
||||||
: node.ragIndexEnabled ? 'RAG-Indexierung an' : 'RAG-Indexierung aus'}
|
|
||||||
tabIndex={-1}
|
|
||||||
>
|
|
||||||
{pendingActions.has(_ACTION_RAG)
|
|
||||||
? <span className={styles.flagSpinner} />
|
|
||||||
: node.ragIndexEnabled === 'mixed'
|
|
||||||
? <span className={styles.flagMixed}>{_MIXED_SYMBOL}</span>
|
|
||||||
: node.ragIndexEnabled === true
|
|
||||||
? _RAG_ON_EMOJI
|
|
||||||
: <span style={_OFF_STATE_STYLE}>{_RAG_OFF_EMOJI}</span>}
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{onSendToChat && (
|
{onSendToChat && (
|
||||||
<button
|
<button
|
||||||
className={styles.emojiBtn}
|
className={styles.emojiBtn}
|
||||||
|
|
@ -478,14 +438,10 @@ const TreeNodeRow = React.memo(function TreeNodeRow<T>({
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (canPatchScope) onCycleScope(node);
|
if (canPatchScope) onCycleScope(node);
|
||||||
}}
|
}}
|
||||||
title={node.scope === 'mixed' ? 'Gemischt - Klick setzt explizit' : `Scope: ${node.scope}`}
|
title={`Scope: ${node.scope}`}
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
>
|
>
|
||||||
{pendingActions.has(_ACTION_SCOPE)
|
{_SCOPE_EMOJIS[node.scope] ?? _SCOPE_EMOJIS.personal}
|
||||||
? <span className={styles.flagSpinner} />
|
|
||||||
: node.scope === 'mixed'
|
|
||||||
? <span className={styles.flagMixed}>{_MIXED_SYMBOL}</span>
|
|
||||||
: (_SCOPE_EMOJIS[node.scope] ?? _SCOPE_EMOJIS.personal)}
|
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
@ -496,21 +452,16 @@ const TreeNodeRow = React.memo(function TreeNodeRow<T>({
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (canPatchNeutralize) onToggleNeutralize(node);
|
if (canPatchNeutralize) onToggleNeutralize(node);
|
||||||
}}
|
}}
|
||||||
title={node.neutralize === 'mixed'
|
title={node.neutralize ? 'Neutralisiert' : 'Nicht neutralisiert'}
|
||||||
? 'Gemischt - Klick setzt explizit'
|
|
||||||
: node.neutralize ? 'Neutralisiert' : 'Nicht neutralisiert'}
|
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
|
style={{ opacity: node.neutralize ? 1 : 0.35 }}
|
||||||
>
|
>
|
||||||
{pendingActions.has(_ACTION_NEUTRALIZE)
|
{_NEUTRALIZE_EMOJI}
|
||||||
? <span className={styles.flagSpinner} />
|
|
||||||
: node.neutralize === 'mixed'
|
|
||||||
? <span className={styles.flagMixed}>{_MIXED_SYMBOL}</span>
|
|
||||||
: node.neutralize === true
|
|
||||||
? _NEUTRALIZE_ON_EMOJI
|
|
||||||
: <span style={_OFF_STATE_STYLE}>{_NEUTRALIZE_OFF_EMOJI}</span>}
|
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}) as <T>(props: TreeNodeRowProps<T>) => React.ReactElement;
|
}) as <T>(props: TreeNodeRowProps<T>) => React.ReactElement;
|
||||||
|
|
@ -532,6 +483,8 @@ export function FormGeneratorTree<T = any>({
|
||||||
selectable = true,
|
selectable = true,
|
||||||
refreshAfterAction = false,
|
refreshAfterAction = false,
|
||||||
className,
|
className,
|
||||||
|
embedMaxHeight,
|
||||||
|
hideRowActionButtons = false,
|
||||||
}: FormGeneratorTreeProps<T>) {
|
}: FormGeneratorTreeProps<T>) {
|
||||||
const { t } = useLanguage();
|
const { t } = useLanguage();
|
||||||
const { confirm, ConfirmDialog } = useConfirm();
|
const { confirm, ConfirmDialog } = useConfirm();
|
||||||
|
|
@ -1097,6 +1050,7 @@ export function FormGeneratorTree<T = any>({
|
||||||
}
|
}
|
||||||
case 'F2': {
|
case 'F2': {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
if (hideRowActionButtons) break;
|
||||||
const node = nodes.find((n) => n.id === focusedId);
|
const node = nodes.find((n) => n.id === focusedId);
|
||||||
if (node && ownership === 'own' && provider.canRename?.(node)) {
|
if (node && ownership === 'own' && provider.canRename?.(node)) {
|
||||||
_handleStartRename(focusedId);
|
_handleStartRename(focusedId);
|
||||||
|
|
@ -1105,6 +1059,7 @@ export function FormGeneratorTree<T = any>({
|
||||||
}
|
}
|
||||||
case 'Delete': {
|
case 'Delete': {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
if (hideRowActionButtons) break;
|
||||||
const node = nodes.find((n) => n.id === focusedId);
|
const node = nodes.find((n) => n.id === focusedId);
|
||||||
if (node && ownership === 'own' && provider.canDelete?.(node)) {
|
if (node && ownership === 'own' && provider.canDelete?.(node)) {
|
||||||
_handleDelete(focusedId);
|
_handleDelete(focusedId);
|
||||||
|
|
@ -1124,6 +1079,7 @@ export function FormGeneratorTree<T = any>({
|
||||||
_handleToggleSelect,
|
_handleToggleSelect,
|
||||||
_handleStartRename,
|
_handleStartRename,
|
||||||
_handleDelete,
|
_handleDelete,
|
||||||
|
hideRowActionButtons,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -1163,13 +1119,21 @@ export function FormGeneratorTree<T = any>({
|
||||||
const wrapperClasses = [
|
const wrapperClasses = [
|
||||||
styles.formGeneratorTree,
|
styles.formGeneratorTree,
|
||||||
compact && styles.compactMode,
|
compact && styles.compactMode,
|
||||||
|
embedMaxHeight != null && styles.embeddedPicker,
|
||||||
className,
|
className,
|
||||||
]
|
]
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.join(' ');
|
.join(' ');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={wrapperClasses}>
|
<div
|
||||||
|
className={wrapperClasses}
|
||||||
|
style={
|
||||||
|
embedMaxHeight != null
|
||||||
|
? { height: embedMaxHeight, maxHeight: embedMaxHeight, flexShrink: 0 }
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
>
|
||||||
{title && (
|
{title && (
|
||||||
<div
|
<div
|
||||||
className={`${styles.sectionHeader} ${collapsible ? '' : styles.sectionHeaderNonCollapsible}`}
|
className={`${styles.sectionHeader} ${collapsible ? '' : styles.sectionHeaderNonCollapsible}`}
|
||||||
|
|
@ -1236,7 +1200,11 @@ export function FormGeneratorTree<T = any>({
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
{selectable && selectedIds.size > 0 && batchActions.length > 0 && (
|
{selectable && selectedIds.size > 0 && batchActions.length > 0 && (
|
||||||
|
=======
|
||||||
|
{selectedIds.size > 0 && batchActions.length > 0 && !hideRowActionButtons && (
|
||||||
|
>>>>>>> 7fb9645 (workign on folder location in file create node)
|
||||||
<div className={styles.batchToolbar}>
|
<div className={styles.batchToolbar}>
|
||||||
<span className={styles.batchCount}>{selectedIds.size} selected</span>
|
<span className={styles.batchCount}>{selectedIds.size} selected</span>
|
||||||
{batchActions.map((action: TreeBatchAction) => {
|
{batchActions.map((action: TreeBatchAction) => {
|
||||||
|
|
@ -1318,6 +1286,7 @@ export function FormGeneratorTree<T = any>({
|
||||||
onDragOver={_handleDragOver}
|
onDragOver={_handleDragOver}
|
||||||
onDragLeave={_handleDragLeave}
|
onDragLeave={_handleDragLeave}
|
||||||
onDrop={_handleDrop}
|
onDrop={_handleDrop}
|
||||||
|
hideRowActionButtons={hideRowActionButtons}
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -52,34 +52,8 @@ function _mapFileToNode(file: FileData, ownership: Ownership): TreeNode {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Stable synthetic root id per ownership scope. The real top-level
|
export function createFolderFileProvider(options: { includeFiles?: boolean } = {}): TreeNodeProvider {
|
||||||
* folders/files attach their `parentId` to this id once we re-parent them
|
const includeFiles = options.includeFiles !== false;
|
||||||
* in `loadChildren`. The id stays inside the FE provider; the backend
|
|
||||||
* never sees it. */
|
|
||||||
const _SYNTH_ROOT_ID = (ownership: Ownership): string => `__filesRoot:${ownership}`;
|
|
||||||
|
|
||||||
/** Build the synthetic root node. Its only job is to:
|
|
||||||
* - act as a drop-target for moving items back to top-level,
|
|
||||||
* - expose a global neutralize/scope toggle that cascades to every
|
|
||||||
* top-level descendant.
|
|
||||||
* Its scope/neutralize values are intentionally `undefined` (= "no own
|
|
||||||
* state") — the icons render an indeterminate state and a click sets the
|
|
||||||
* intent on every owned descendant. */
|
|
||||||
function _makeSyntheticRoot(ownership: Ownership): TreeNode {
|
|
||||||
return {
|
|
||||||
id: _SYNTH_ROOT_ID(ownership),
|
|
||||||
name: '/',
|
|
||||||
type: 'folder',
|
|
||||||
parentId: null,
|
|
||||||
ownership,
|
|
||||||
icon: <FaFolder style={{ color: '#666' }} />,
|
|
||||||
defaultExpanded: true,
|
|
||||||
scope: 'personal',
|
|
||||||
neutralize: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createFolderFileProvider(): TreeNodeProvider {
|
|
||||||
const ownerParam = (ownership: Ownership) => (ownership === 'own' ? 'me' : 'shared');
|
const ownerParam = (ownership: Ownership) => (ownership === 'own' ? 'me' : 'shared');
|
||||||
const typeMap = new Map<string, 'folder' | 'file'>();
|
const typeMap = new Map<string, 'folder' | 'file'>();
|
||||||
|
|
||||||
|
|
@ -157,10 +131,11 @@ export function createFolderFileProvider(): TreeNodeProvider {
|
||||||
}
|
}
|
||||||
nodes.push(...folderNodes);
|
nodes.push(...folderNodes);
|
||||||
|
|
||||||
|
if (includeFiles) {
|
||||||
try {
|
try {
|
||||||
const filters: Record<string, any> = {};
|
const filters: Record<string, any> = {};
|
||||||
if (apiParentId) {
|
if (parentId) {
|
||||||
filters.folderId = apiParentId;
|
filters.folderId = parentId;
|
||||||
}
|
}
|
||||||
const paginationParam = JSON.stringify({ filters, pageSize: 500 });
|
const paginationParam = JSON.stringify({ filters, pageSize: 500 });
|
||||||
const filesRes = await api.get('/api/files/list', {
|
const filesRes = await api.get('/api/files/list', {
|
||||||
|
|
@ -173,19 +148,16 @@ export function createFolderFileProvider(): TreeNodeProvider {
|
||||||
} else if (Array.isArray(data)) {
|
} else if (Array.isArray(data)) {
|
||||||
rawFiles = data;
|
rawFiles = data;
|
||||||
}
|
}
|
||||||
let matched = rawFiles.filter((f) => (f.folderId ?? null) === apiParentId);
|
let matched = rawFiles.filter((f) => (f.folderId ?? null) === parentId);
|
||||||
if (ownership === 'shared') {
|
if (ownership === 'shared') {
|
||||||
const myId = getUserDataCache()?.id;
|
const myId = getUserDataCache()?.id;
|
||||||
if (myId) matched = matched.filter((f) => f.sysCreatedBy !== myId);
|
if (myId) matched = matched.filter((f) => f.sysCreatedBy !== myId);
|
||||||
}
|
}
|
||||||
const fileNodes = matched.map((f) => _mapFileToNode(f, ownership));
|
nodes.push(...matched.map((f) => _mapFileToNode(f, ownership)));
|
||||||
if (apiParentId === null) {
|
|
||||||
for (const n of fileNodes) n.parentId = synthRootId;
|
|
||||||
}
|
|
||||||
nodes.push(...fileNodes);
|
|
||||||
} catch {
|
} catch {
|
||||||
// file list may fail for shared trees; folders still render
|
// file list may fail for shared trees; folders still render
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_trackTypes(nodes);
|
_trackTypes(nodes);
|
||||||
return nodes;
|
return nodes;
|
||||||
|
|
|
||||||
|
|
@ -122,4 +122,10 @@ export interface FormGeneratorTreeProps<T = any> {
|
||||||
* that rely on the optimistic-update path. */
|
* that rely on the optimistic-update path. */
|
||||||
refreshAfterAction?: boolean;
|
refreshAfterAction?: boolean;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
/** Embedded pickers (e.g. automation node config): constrain overall height so the tree scrolls inside. */
|
||||||
|
embedMaxHeight?: number;
|
||||||
|
/**
|
||||||
|
* Hides checkbox, size column, per-row emoji actions, drag-drop, and batch toolbar — saves space in pickers.
|
||||||
|
*/
|
||||||
|
hideRowActionButtons?: boolean;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue