workign on folder location in file create node
This commit is contained in:
parent
25b56f585e
commit
47b3c1ab23
7 changed files with 297 additions and 120 deletions
|
|
@ -320,6 +320,7 @@ const _LEGACY_RENDERERS_THAT_HANDLE_BINDINGS = new Set([
|
|||
'featureInstance',
|
||||
'sharepointFolder',
|
||||
'sharepointFile',
|
||||
'userFileFolder',
|
||||
'clickupList',
|
||||
'clickupTask',
|
||||
'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 { ContextBuilderRenderer } from './ContextBuilderRenderer';
|
||||
import { FeatureInstancePicker } from './FeatureInstancePicker';
|
||||
import { UserFileFolderPicker } from './UserFileFolderPicker';
|
||||
import { TemplateTextareaRenderer } from './TemplateTextareaRenderer';
|
||||
import { getApiBaseUrl } from '../../../../../config/config';
|
||||
|
||||
|
|
@ -917,6 +918,7 @@ export const FRONTEND_TYPE_RENDERERS: Record<string, FieldRendererComponent> = {
|
|||
featureInstance: FeatureInstancePicker,
|
||||
sharepointFolder: SharepointPathPicker,
|
||||
sharepointFile: SharepointPathPicker,
|
||||
userFileFolder: UserFileFolderPicker,
|
||||
clickupList: FolderPicker,
|
||||
clickupTask: FolderPicker,
|
||||
caseList: CaseListEditor,
|
||||
|
|
|
|||
|
|
@ -503,6 +503,22 @@
|
|||
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 */
|
||||
.compactMode .sectionHeader {
|
||||
padding: 6px 8px;
|
||||
|
|
|
|||
|
|
@ -133,6 +133,7 @@ interface TreeNodeRowProps<T = any> {
|
|||
onDragOver: (e: React.DragEvent, node: TreeNode<T>) => void;
|
||||
onDragLeave: (e: React.DragEvent) => void;
|
||||
onDrop: (e: React.DragEvent, node: TreeNode<T>) => void;
|
||||
hideRowActionButtons?: boolean;
|
||||
}
|
||||
|
||||
const TreeNodeRow = React.memo(function TreeNodeRow<T>({
|
||||
|
|
@ -161,6 +162,7 @@ const TreeNodeRow = React.memo(function TreeNodeRow<T>({
|
|||
onDragOver,
|
||||
onDragLeave,
|
||||
onDrop,
|
||||
hideRowActionButtons = false,
|
||||
}: TreeNodeRowProps<T>) {
|
||||
const { node, depth, hasChildren } = entry;
|
||||
const renameRef = useRef<HTMLInputElement>(null);
|
||||
|
|
@ -194,11 +196,12 @@ const TreeNodeRow = React.memo(function TreeNodeRow<T>({
|
|||
const _handleDoubleClick = useCallback(
|
||||
(e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
if (hideRowActionButtons) return;
|
||||
if (ownership === 'own' && provider.canRename?.(node)) {
|
||||
onStartRename(node.id);
|
||||
}
|
||||
},
|
||||
[ownership, provider, node, onStartRename],
|
||||
[hideRowActionButtons, ownership, provider, node, onStartRename],
|
||||
);
|
||||
|
||||
const _handleRowClick = useCallback(
|
||||
|
|
@ -240,11 +243,11 @@ const TreeNodeRow = React.memo(function TreeNodeRow<T>({
|
|||
className={rowClasses}
|
||||
onClick={_handleRowClick}
|
||||
onDoubleClick={_handleDoubleClick}
|
||||
draggable
|
||||
onDragStart={(e) => onDragStart(e, node)}
|
||||
onDragOver={(e) => onDragOver(e, node)}
|
||||
onDragLeave={onDragLeave}
|
||||
onDrop={(e) => onDrop(e, node)}
|
||||
draggable={!hideRowActionButtons}
|
||||
onDragStart={hideRowActionButtons ? undefined : (e) => onDragStart(e, node)}
|
||||
onDragOver={hideRowActionButtons ? undefined : (e) => onDragOver(e, node)}
|
||||
onDragLeave={hideRowActionButtons ? undefined : onDragLeave}
|
||||
onDrop={hideRowActionButtons ? undefined : (e) => onDrop(e, node)}
|
||||
data-node-id={node.id}
|
||||
title={node.name}
|
||||
role="treeitem"
|
||||
|
|
@ -254,17 +257,19 @@ const TreeNodeRow = React.memo(function TreeNodeRow<T>({
|
|||
>
|
||||
<div className={styles.indentSpacer} style={{ width: depth * INDENT_PX }} />
|
||||
|
||||
<input
|
||||
type="checkbox"
|
||||
className={styles.nodeCheckbox}
|
||||
checked={isSelected}
|
||||
onChange={() => {}}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onToggleSelect(node.id, e as unknown as React.MouseEvent);
|
||||
}}
|
||||
tabIndex={-1}
|
||||
/>
|
||||
{!hideRowActionButtons && (
|
||||
<input
|
||||
type="checkbox"
|
||||
className={styles.nodeCheckbox}
|
||||
checked={isSelected}
|
||||
onChange={() => {}}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onToggleSelect(node.id, e as unknown as React.MouseEvent);
|
||||
}}
|
||||
tabIndex={-1}
|
||||
/>
|
||||
)}
|
||||
|
||||
{hasChildren ? (
|
||||
<span
|
||||
|
|
@ -301,89 +306,104 @@ const TreeNodeRow = React.memo(function TreeNodeRow<T>({
|
|||
</span>
|
||||
)}
|
||||
|
||||
<span className={styles.nodeSize}>
|
||||
{node.sizeBytes != null ? _formatSize(node.sizeBytes) : ''}
|
||||
</span>
|
||||
{!hideRowActionButtons && (
|
||||
<span className={styles.nodeSize}>
|
||||
{node.sizeBytes != null ? _formatSize(node.sizeBytes) : ''}
|
||||
</span>
|
||||
)}
|
||||
|
||||
<div className={styles.nodeActionsHover}>
|
||||
{canRename && (
|
||||
<button
|
||||
className={styles.emojiBtn}
|
||||
onClick={(e) => { e.stopPropagation(); onStartRename(node.id); }}
|
||||
title="Umbenennen"
|
||||
tabIndex={-1}
|
||||
>
|
||||
{'\u270F\uFE0F'}
|
||||
</button>
|
||||
)}
|
||||
{!hideRowActionButtons && (
|
||||
<>
|
||||
<div className={styles.nodeActionsHover}>
|
||||
{canRename && (
|
||||
<button
|
||||
className={styles.emojiBtn}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onStartRename(node.id);
|
||||
}}
|
||||
title="Umbenennen"
|
||||
tabIndex={-1}
|
||||
>
|
||||
{'\u270F\uFE0F'}
|
||||
</button>
|
||||
)}
|
||||
|
||||
{node.type !== 'folder' && (
|
||||
<button
|
||||
className={styles.emojiBtn}
|
||||
onClick={(e) => { e.stopPropagation(); onDownload(node); }}
|
||||
title="Datei herunterladen"
|
||||
tabIndex={-1}
|
||||
>
|
||||
{'\u{1F4E5}'}
|
||||
</button>
|
||||
)}
|
||||
{node.type !== 'folder' && (
|
||||
<button
|
||||
className={styles.emojiBtn}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onDownload(node);
|
||||
}}
|
||||
title="Datei herunterladen"
|
||||
tabIndex={-1}
|
||||
>
|
||||
{'\u{1F4E5}'}
|
||||
</button>
|
||||
)}
|
||||
|
||||
{canDelete && (
|
||||
<button
|
||||
className={styles.emojiBtn}
|
||||
onClick={(e) => { e.stopPropagation(); onDelete(node.id); }}
|
||||
title="Loeschen"
|
||||
tabIndex={-1}
|
||||
>
|
||||
{'\u{1F5D1}\uFE0F'}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{canDelete && (
|
||||
<button
|
||||
className={styles.emojiBtn}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onDelete(node.id);
|
||||
}}
|
||||
title="Loeschen"
|
||||
tabIndex={-1}
|
||||
>
|
||||
{'\u{1F5D1}\uFE0F'}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className={styles.nodeActionsPersistent}>
|
||||
{onSendToChat && (
|
||||
<button
|
||||
className={styles.emojiBtn}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onSendToChat(node);
|
||||
}}
|
||||
title="In Chat senden"
|
||||
tabIndex={-1}
|
||||
>
|
||||
{'\u{1F4AC}'}
|
||||
</button>
|
||||
)}
|
||||
<div className={styles.nodeActionsPersistent}>
|
||||
{onSendToChat && (
|
||||
<button
|
||||
className={styles.emojiBtn}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onSendToChat(node);
|
||||
}}
|
||||
title="In Chat senden"
|
||||
tabIndex={-1}
|
||||
>
|
||||
{'\u{1F4AC}'}
|
||||
</button>
|
||||
)}
|
||||
|
||||
{node.scope !== undefined && (
|
||||
<button
|
||||
className={`${styles.emojiBtn} ${canPatchScope ? '' : styles.emojiBtnReadonly}`}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (canPatchScope) onCycleScope(node);
|
||||
}}
|
||||
title={`Scope: ${node.scope}`}
|
||||
tabIndex={-1}
|
||||
>
|
||||
{_SCOPE_EMOJIS[node.scope] ?? _SCOPE_EMOJIS.personal}
|
||||
</button>
|
||||
)}
|
||||
{node.scope !== undefined && (
|
||||
<button
|
||||
className={`${styles.emojiBtn} ${canPatchScope ? '' : styles.emojiBtnReadonly}`}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (canPatchScope) onCycleScope(node);
|
||||
}}
|
||||
title={`Scope: ${node.scope}`}
|
||||
tabIndex={-1}
|
||||
>
|
||||
{_SCOPE_EMOJIS[node.scope] ?? _SCOPE_EMOJIS.personal}
|
||||
</button>
|
||||
)}
|
||||
|
||||
{node.neutralize !== undefined && (
|
||||
<button
|
||||
className={`${styles.emojiBtn} ${canPatchNeutralize ? '' : styles.emojiBtnReadonly}`}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (canPatchNeutralize) onToggleNeutralize(node);
|
||||
}}
|
||||
title={node.neutralize ? 'Neutralisiert' : 'Nicht neutralisiert'}
|
||||
tabIndex={-1}
|
||||
style={{ opacity: node.neutralize ? 1 : 0.35 }}
|
||||
>
|
||||
{_NEUTRALIZE_EMOJI}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{node.neutralize !== undefined && (
|
||||
<button
|
||||
className={`${styles.emojiBtn} ${canPatchNeutralize ? '' : styles.emojiBtnReadonly}`}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (canPatchNeutralize) onToggleNeutralize(node);
|
||||
}}
|
||||
title={node.neutralize ? 'Neutralisiert' : 'Nicht neutralisiert'}
|
||||
tabIndex={-1}
|
||||
style={{ opacity: node.neutralize ? 1 : 0.35 }}
|
||||
>
|
||||
{_NEUTRALIZE_EMOJI}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}) as <T>(props: TreeNodeRowProps<T>) => React.ReactElement;
|
||||
|
|
@ -403,6 +423,8 @@ export function FormGeneratorTree<T = any>({
|
|||
onSendToChat,
|
||||
allowCreateFolder = true,
|
||||
className,
|
||||
embedMaxHeight,
|
||||
hideRowActionButtons = false,
|
||||
}: FormGeneratorTreeProps<T>) {
|
||||
const { prompt, PromptDialog } = usePrompt();
|
||||
const [nodes, setNodes] = useState<TreeNode<T>[]>([]);
|
||||
|
|
@ -784,6 +806,7 @@ export function FormGeneratorTree<T = any>({
|
|||
}
|
||||
case 'F2': {
|
||||
e.preventDefault();
|
||||
if (hideRowActionButtons) break;
|
||||
const node = nodes.find((n) => n.id === focusedId);
|
||||
if (node && ownership === 'own' && provider.canRename?.(node)) {
|
||||
_handleStartRename(focusedId);
|
||||
|
|
@ -792,6 +815,7 @@ export function FormGeneratorTree<T = any>({
|
|||
}
|
||||
case 'Delete': {
|
||||
e.preventDefault();
|
||||
if (hideRowActionButtons) break;
|
||||
const node = nodes.find((n) => n.id === focusedId);
|
||||
if (node && ownership === 'own' && provider.canDelete?.(node)) {
|
||||
_handleDelete(focusedId);
|
||||
|
|
@ -811,6 +835,7 @@ export function FormGeneratorTree<T = any>({
|
|||
_handleToggleSelect,
|
||||
_handleStartRename,
|
||||
_handleDelete,
|
||||
hideRowActionButtons,
|
||||
],
|
||||
);
|
||||
|
||||
|
|
@ -850,13 +875,21 @@ export function FormGeneratorTree<T = any>({
|
|||
const wrapperClasses = [
|
||||
styles.formGeneratorTree,
|
||||
compact && styles.compactMode,
|
||||
embedMaxHeight != null && styles.embeddedPicker,
|
||||
className,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ');
|
||||
|
||||
return (
|
||||
<div className={wrapperClasses}>
|
||||
<div
|
||||
className={wrapperClasses}
|
||||
style={
|
||||
embedMaxHeight != null
|
||||
? { height: embedMaxHeight, maxHeight: embedMaxHeight, flexShrink: 0 }
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
{title && (
|
||||
<div
|
||||
className={`${styles.sectionHeader} ${collapsible ? '' : styles.sectionHeaderNonCollapsible}`}
|
||||
|
|
@ -923,7 +956,7 @@ export function FormGeneratorTree<T = any>({
|
|||
</div>
|
||||
)}
|
||||
|
||||
{selectedIds.size > 0 && batchActions.length > 0 && (
|
||||
{selectedIds.size > 0 && batchActions.length > 0 && !hideRowActionButtons && (
|
||||
<div className={styles.batchToolbar}>
|
||||
<span className={styles.batchCount}>{selectedIds.size} selected</span>
|
||||
{batchActions.map((action: TreeBatchAction) => {
|
||||
|
|
@ -996,6 +1029,7 @@ export function FormGeneratorTree<T = any>({
|
|||
onDragOver={_handleDragOver}
|
||||
onDragLeave={_handleDragLeave}
|
||||
onDrop={_handleDrop}
|
||||
hideRowActionButtons={hideRowActionButtons}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -52,7 +52,8 @@ function _mapFileToNode(file: FileData, ownership: Ownership): TreeNode {
|
|||
};
|
||||
}
|
||||
|
||||
export function createFolderFileProvider(): TreeNodeProvider {
|
||||
export function createFolderFileProvider(options: { includeFiles?: boolean } = {}): TreeNodeProvider {
|
||||
const includeFiles = options.includeFiles !== false;
|
||||
const ownerParam = (ownership: Ownership) => (ownership === 'own' ? 'me' : 'shared');
|
||||
const typeMap = new Map<string, 'folder' | 'file'>();
|
||||
|
||||
|
|
@ -78,30 +79,32 @@ export function createFolderFileProvider(): TreeNodeProvider {
|
|||
const childFolders = allFolders.filter((f) => (f.parentId ?? null) === parentId);
|
||||
nodes.push(...childFolders.map((f) => _mapFolderToNode(f, ownership)));
|
||||
|
||||
try {
|
||||
const filters: Record<string, any> = {};
|
||||
if (parentId) {
|
||||
filters.folderId = parentId;
|
||||
if (includeFiles) {
|
||||
try {
|
||||
const filters: Record<string, any> = {};
|
||||
if (parentId) {
|
||||
filters.folderId = parentId;
|
||||
}
|
||||
const paginationParam = JSON.stringify({ filters, pageSize: 500 });
|
||||
const filesRes = await api.get('/api/files/list', {
|
||||
params: { pagination: paginationParam },
|
||||
});
|
||||
const data = filesRes.data;
|
||||
let rawFiles: FileData[] = [];
|
||||
if (data && typeof data === 'object' && 'items' in data) {
|
||||
rawFiles = Array.isArray(data.items) ? data.items : [];
|
||||
} else if (Array.isArray(data)) {
|
||||
rawFiles = data;
|
||||
}
|
||||
let matched = rawFiles.filter((f) => (f.folderId ?? null) === parentId);
|
||||
if (ownership === 'shared') {
|
||||
const myId = getUserDataCache()?.id;
|
||||
if (myId) matched = matched.filter((f) => f.sysCreatedBy !== myId);
|
||||
}
|
||||
nodes.push(...matched.map((f) => _mapFileToNode(f, ownership)));
|
||||
} catch {
|
||||
// file list may fail for shared trees; folders still render
|
||||
}
|
||||
const paginationParam = JSON.stringify({ filters, pageSize: 500 });
|
||||
const filesRes = await api.get('/api/files/list', {
|
||||
params: { pagination: paginationParam },
|
||||
});
|
||||
const data = filesRes.data;
|
||||
let rawFiles: FileData[] = [];
|
||||
if (data && typeof data === 'object' && 'items' in data) {
|
||||
rawFiles = Array.isArray(data.items) ? data.items : [];
|
||||
} else if (Array.isArray(data)) {
|
||||
rawFiles = data;
|
||||
}
|
||||
let matched = rawFiles.filter((f) => (f.folderId ?? null) === parentId);
|
||||
if (ownership === 'shared') {
|
||||
const myId = getUserDataCache()?.id;
|
||||
if (myId) matched = matched.filter((f) => f.sysCreatedBy !== myId);
|
||||
}
|
||||
nodes.push(...matched.map((f) => _mapFileToNode(f, ownership)));
|
||||
} catch {
|
||||
// file list may fail for shared trees; folders still render
|
||||
}
|
||||
|
||||
_trackTypes(nodes);
|
||||
|
|
|
|||
|
|
@ -63,4 +63,10 @@ export interface FormGeneratorTreeProps<T = any> {
|
|||
/** When false, hides "Neuer Ordner" (e.g. map from table file permissions). Default true. */
|
||||
allowCreateFolder?: boolean;
|
||||
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