fix: node inhalt extrahieren nimmt jetzt context, files page formgenerator und folder tree zeigen jetzt die gleichen elemente
This commit is contained in:
parent
1c539076e5
commit
9d081e8819
5 changed files with 143 additions and 27 deletions
|
|
@ -36,6 +36,7 @@ export interface PaginationParams {
|
||||||
search?: string;
|
search?: string;
|
||||||
viewKey?: string;
|
viewKey?: string;
|
||||||
groupByLevels?: Array<{ field: string; nullLabel?: string; direction?: 'asc' | 'desc' }>;
|
groupByLevels?: Array<{ field: string; nullLabel?: string; direction?: 'asc' | 'desc' }>;
|
||||||
|
owner?: 'all' | 'me' | 'shared';
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PaginatedResponse<T> {
|
export interface PaginatedResponse<T> {
|
||||||
|
|
@ -109,6 +110,7 @@ export async function fetchFiles(
|
||||||
if (params.search) paginationObj.search = params.search;
|
if (params.search) paginationObj.search = params.search;
|
||||||
if (params.viewKey) paginationObj.viewKey = params.viewKey;
|
if (params.viewKey) paginationObj.viewKey = params.viewKey;
|
||||||
if (params.groupByLevels !== undefined) paginationObj.groupByLevels = params.groupByLevels;
|
if (params.groupByLevels !== undefined) paginationObj.groupByLevels = params.groupByLevels;
|
||||||
|
if (params.owner) requestParams.owner = params.owner;
|
||||||
|
|
||||||
if (Object.keys(paginationObj).length > 0) {
|
if (Object.keys(paginationObj).length > 0) {
|
||||||
requestParams.pagination = JSON.stringify(paginationObj);
|
requestParams.pagination = JSON.stringify(paginationObj);
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import type { GraphDefinedSchemaRef, NodeType, NodeTypeParameter, PortSchema } f
|
||||||
import type { ApiRequestFunction } from '../../../api/workflowApi';
|
import type { ApiRequestFunction } from '../../../api/workflowApi';
|
||||||
import { getLabel } from '../nodes/shared/utils';
|
import { getLabel } from '../nodes/shared/utils';
|
||||||
import { FRONTEND_TYPE_RENDERERS } from '../nodes/frontendTypeRenderers';
|
import { FRONTEND_TYPE_RENDERERS } from '../nodes/frontendTypeRenderers';
|
||||||
|
import { ContextBuilderRenderer } from '../nodes/frontendTypeRenderers/ContextBuilderRenderer';
|
||||||
import { RequiredAttributePicker } from '../nodes/shared/RequiredAttributePicker';
|
import { RequiredAttributePicker } from '../nodes/shared/RequiredAttributePicker';
|
||||||
import { findRequiredErrors } from '../nodes/shared/paramValidation';
|
import { findRequiredErrors } from '../nodes/shared/paramValidation';
|
||||||
import { useAutomation2DataFlow } from '../context/Automation2DataFlowContext';
|
import { useAutomation2DataFlow } from '../context/Automation2DataFlowContext';
|
||||||
|
|
@ -253,6 +254,7 @@ export const NodeConfigPanel: React.FC<NodeConfigPanelProps> = ({ node,
|
||||||
|
|
||||||
for (const param of sortedParameters) {
|
for (const param of sortedParameters) {
|
||||||
if (param.frontendType === 'hidden') continue;
|
if (param.frontendType === 'hidden') continue;
|
||||||
|
if (param.name === 'context') continue;
|
||||||
if (CONTEXT_EXTRACT_CHUNK_SET.has(param.name)) continue;
|
if (CONTEXT_EXTRACT_CHUNK_SET.has(param.name)) continue;
|
||||||
if (!parameterVisibleForFrontendOptions(param, params, nodeType)) continue;
|
if (!parameterVisibleForFrontendOptions(param, params, nodeType)) continue;
|
||||||
|
|
||||||
|
|
@ -378,6 +380,15 @@ export const NodeConfigPanel: React.FC<NodeConfigPanelProps> = ({ node,
|
||||||
t,
|
t,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const extractContentContextParam = useMemo((): NodeTypeParameter | null => {
|
||||||
|
if (!node || !nodeType || node.type !== CONTEXT_EXTRACT_CONTENT_NODE_TYPE) return null;
|
||||||
|
const param = sortedParameters.find((p) => p.name === 'context') ?? null;
|
||||||
|
if (!param) return null;
|
||||||
|
if (param.frontendType === 'hidden') return null;
|
||||||
|
if (!parameterVisibleForFrontendOptions(param, params, nodeType)) return null;
|
||||||
|
return param;
|
||||||
|
}, [node, nodeType, sortedParameters, params]);
|
||||||
|
|
||||||
if (!node || !nodeType) return null;
|
if (!node || !nodeType) return null;
|
||||||
|
|
||||||
const isTrigger = node.type.startsWith('trigger.');
|
const isTrigger = node.type.startsWith('trigger.');
|
||||||
|
|
@ -483,11 +494,71 @@ export const NodeConfigPanel: React.FC<NodeConfigPanelProps> = ({ node,
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{extractContentAccordionItems !== null ? (
|
{extractContentAccordionItems !== null ? (
|
||||||
|
<>
|
||||||
|
{extractContentContextParam ? (
|
||||||
|
<div
|
||||||
|
key={`${node.id}-${extractContentContextParam.name}`}
|
||||||
|
style={{ marginBottom: 8, minWidth: 0, maxWidth: '100%' }}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 6,
|
||||||
|
marginBottom: 2,
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
minWidth: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{extractContentContextParam.required && (
|
||||||
|
<span
|
||||||
|
title={t('Pflichtfeld')}
|
||||||
|
style={{ color: 'var(--danger-color, #dc3545)', fontWeight: 700, flexShrink: 0 }}
|
||||||
|
>
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{verboseSchema && extractContentContextParam.type && (
|
||||||
|
<span
|
||||||
|
title={t('Parameter-Typ')}
|
||||||
|
style={{
|
||||||
|
fontSize: 10,
|
||||||
|
fontWeight: 600,
|
||||||
|
color: 'var(--text-secondary)',
|
||||||
|
background: 'var(--bg-secondary)',
|
||||||
|
border: '1px solid var(--border-color)',
|
||||||
|
borderRadius: 4,
|
||||||
|
padding: '1px 6px',
|
||||||
|
maxWidth: '100%',
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{extractContentContextParam.type}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<ContextBuilderRenderer
|
||||||
|
param={extractContentContextParam}
|
||||||
|
value={workflowParamUiValue(params, extractContentContextParam)}
|
||||||
|
onChange={(val: unknown) => updateParam(extractContentContextParam.name, val)}
|
||||||
|
allParams={params}
|
||||||
|
instanceId={instanceId}
|
||||||
|
request={request}
|
||||||
|
nodeType={node.type}
|
||||||
|
onPatchParams={patchParams}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
{extractContentAccordionItems.length > 0 ? (
|
||||||
<AccordionList<string>
|
<AccordionList<string>
|
||||||
key={`${node.id}-extract-accordion`}
|
key={`${node.id}-extract-accordion`}
|
||||||
defaultOpenId={null}
|
defaultOpenId={null}
|
||||||
items={extractContentAccordionItems}
|
items={extractContentAccordionItems}
|
||||||
/>
|
/>
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
) : (
|
) : (
|
||||||
parameters.map((param: NodeTypeParameter) => {
|
parameters.map((param: NodeTypeParameter) => {
|
||||||
// Safety net: hidden params have no UI footprint at all — no row,
|
// Safety net: hidden params have no UI footprint at all — no row,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import { FaFolder, FaFile, FaTrash } from 'react-icons/fa';
|
import { FaFolder, FaFile, FaTrash } from 'react-icons/fa';
|
||||||
import type { TreeNodeProvider, TreeNode, Ownership, ScopeValue, TreeBatchAction } from '../types';
|
import type { TreeNodeProvider, TreeNode, Ownership, ScopeValue, TreeBatchAction } from '../types';
|
||||||
import api from '../../../../api';
|
import api from '../../../../api';
|
||||||
import { getUserDataCache } from '../../../../utils/userCache';
|
|
||||||
|
|
||||||
interface FolderData {
|
interface FolderData {
|
||||||
id: string;
|
id: string;
|
||||||
|
|
@ -137,7 +136,7 @@ export function createFolderFileProvider(options: { includeFiles?: boolean } = {
|
||||||
if ((f.parentId ?? null) === null) out.add(f.id);
|
if ((f.parentId ?? null) === null) out.add(f.id);
|
||||||
}
|
}
|
||||||
const paginationParam = JSON.stringify({ filters: { folderId: null }, pageSize: 500 });
|
const paginationParam = JSON.stringify({ filters: { folderId: null }, pageSize: 500 });
|
||||||
const filesRes = await api.get('/api/files/list', { params: { pagination: paginationParam } });
|
const filesRes = await api.get('/api/files/list', { params: { pagination: paginationParam, owner } });
|
||||||
const data = filesRes.data;
|
const data = filesRes.data;
|
||||||
const rawFiles: FileData[] = (data && typeof data === 'object' && 'items' in data)
|
const rawFiles: FileData[] = (data && typeof data === 'object' && 'items' in data)
|
||||||
? (Array.isArray(data.items) ? data.items : [])
|
? (Array.isArray(data.items) ? data.items : [])
|
||||||
|
|
@ -193,7 +192,7 @@ export function createFolderFileProvider(options: { includeFiles?: boolean } = {
|
||||||
}
|
}
|
||||||
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', {
|
||||||
params: { pagination: paginationParam },
|
params: { pagination: paginationParam, owner },
|
||||||
});
|
});
|
||||||
const data = filesRes.data;
|
const data = filesRes.data;
|
||||||
let rawFiles: FileData[] = [];
|
let rawFiles: FileData[] = [];
|
||||||
|
|
@ -203,10 +202,6 @@ export function createFolderFileProvider(options: { includeFiles?: boolean } = {
|
||||||
rawFiles = data;
|
rawFiles = data;
|
||||||
}
|
}
|
||||||
let matched = rawFiles.filter((f) => (f.folderId ?? null) === apiParentId);
|
let matched = rawFiles.filter((f) => (f.folderId ?? null) === apiParentId);
|
||||||
if (ownership === 'shared') {
|
|
||||||
const myId = getUserDataCache()?.id;
|
|
||||||
if (myId) matched = matched.filter((f) => f.sysCreatedBy !== myId);
|
|
||||||
}
|
|
||||||
const fileNodes = matched.map((f) => _mapFileToNode(f, ownership));
|
const fileNodes = matched.map((f) => _mapFileToNode(f, ownership));
|
||||||
if (apiParentId === null) {
|
if (apiParentId === null) {
|
||||||
for (const n of fileNodes) n.parentId = synthRootId;
|
for (const n of fileNodes) n.parentId = synthRootId;
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,7 @@ export interface PaginationParams {
|
||||||
filters?: Record<string, any>;
|
filters?: Record<string, any>;
|
||||||
search?: string;
|
search?: string;
|
||||||
viewKey?: string;
|
viewKey?: string;
|
||||||
|
owner?: 'all' | 'me' | 'shared';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Files list hook
|
// Files list hook
|
||||||
|
|
@ -150,6 +151,7 @@ export function useUserFiles() {
|
||||||
groupField: string;
|
groupField: string;
|
||||||
groupDirection?: 'asc' | 'desc';
|
groupDirection?: 'asc' | 'desc';
|
||||||
groupByLevels?: Array<{ field: string; nullLabel?: string; direction?: string }>;
|
groupByLevels?: Array<{ field: string; nullLabel?: string; direction?: string }>;
|
||||||
|
owner?: 'all' | 'me' | 'shared';
|
||||||
}) => {
|
}) => {
|
||||||
const levels = base.groupByLevels?.length
|
const levels = base.groupByLevels?.length
|
||||||
? base.groupByLevels
|
? base.groupByLevels
|
||||||
|
|
@ -164,7 +166,11 @@ export function useUserFiles() {
|
||||||
if (base.sort?.length) (pObj as { sort: typeof base.sort }).sort = base.sort;
|
if (base.sort?.length) (pObj as { sort: typeof base.sort }).sort = base.sort;
|
||||||
if (base.viewKey) (pObj as { viewKey: string }).viewKey = base.viewKey;
|
if (base.viewKey) (pObj as { viewKey: string }).viewKey = base.viewKey;
|
||||||
const { data } = await api.get('/api/files/list', {
|
const { data } = await api.get('/api/files/list', {
|
||||||
params: { mode: 'groupSummary', pagination: JSON.stringify(pObj) },
|
params: {
|
||||||
|
mode: 'groupSummary',
|
||||||
|
pagination: JSON.stringify(pObj),
|
||||||
|
...(base.owner ? { owner: base.owner } : {}),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
return Array.isArray(data?.groups) ? data.groups : [];
|
return Array.isArray(data?.groups) ? data.groups : [];
|
||||||
},
|
},
|
||||||
|
|
@ -192,7 +198,10 @@ export function useUserFiles() {
|
||||||
if (paginationParams.search) (pObj as { search: string }).search = paginationParams.search;
|
if (paginationParams.search) (pObj as { search: string }).search = paginationParams.search;
|
||||||
if (paginationParams.viewKey) (pObj as { viewKey: string }).viewKey = paginationParams.viewKey;
|
if (paginationParams.viewKey) (pObj as { viewKey: string }).viewKey = paginationParams.viewKey;
|
||||||
const { data } = await api.get('/api/files/list', {
|
const { data } = await api.get('/api/files/list', {
|
||||||
params: { pagination: JSON.stringify(pObj) },
|
params: {
|
||||||
|
pagination: JSON.stringify(pObj),
|
||||||
|
...(paginationParams.owner ? { owner: paginationParams.owner } : {}),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
if (data && typeof data === 'object' && 'items' in data) {
|
if (data && typeof data === 'object' && 'items' in data) {
|
||||||
return { items: data.items, pagination: data.pagination };
|
return { items: data.items, pagination: data.pagination };
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,17 @@ interface UserFile {
|
||||||
}
|
}
|
||||||
|
|
||||||
type ViewMode = 'folder' | 'all';
|
type ViewMode = 'folder' | 'all';
|
||||||
|
type FileOwnerScope = 'all' | 'me' | 'shared';
|
||||||
|
|
||||||
|
function normalizeFolderFilterId(folderId: string | null): string | null {
|
||||||
|
if (!folderId) return null;
|
||||||
|
if (folderId.startsWith('__filesRoot:')) return null;
|
||||||
|
return folderId;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSyntheticRootFolderId(folderId: string | null): boolean {
|
||||||
|
return Boolean(folderId && folderId.startsWith('__filesRoot:'));
|
||||||
|
}
|
||||||
|
|
||||||
export const FilesPage: React.FC = () => {
|
export const FilesPage: React.FC = () => {
|
||||||
const { t } = useLanguage();
|
const { t } = useLanguage();
|
||||||
|
|
@ -74,6 +85,7 @@ export const FilesPage: React.FC = () => {
|
||||||
const [editingFile, setEditingFile] = useState<UserFile | null>(null);
|
const [editingFile, setEditingFile] = useState<UserFile | null>(null);
|
||||||
const [selectedFiles, setSelectedFiles] = useState<UserFile[]>([]);
|
const [selectedFiles, setSelectedFiles] = useState<UserFile[]>([]);
|
||||||
const [selectedFolderId, setSelectedFolderId] = useState<string | null>(null);
|
const [selectedFolderId, setSelectedFolderId] = useState<string | null>(null);
|
||||||
|
const [selectedOwnership, setSelectedOwnership] = useState<'own' | 'shared' | null>('own');
|
||||||
const [highlightedFileId, setHighlightedFileId] = useState<string | null>(null);
|
const [highlightedFileId, setHighlightedFileId] = useState<string | null>(null);
|
||||||
|
|
||||||
const [treeWidth, setTreeWidth] = useState(300);
|
const [treeWidth, setTreeWidth] = useState(300);
|
||||||
|
|
@ -103,14 +115,24 @@ export const FilesPage: React.FC = () => {
|
||||||
const _tableRefetch = useCallback(async (params?: any) => {
|
const _tableRefetch = useCallback(async (params?: any) => {
|
||||||
const nextParams = { ...(params || {}) };
|
const nextParams = { ...(params || {}) };
|
||||||
const nextFilters = { ...(nextParams.filters || {}) };
|
const nextFilters = { ...(nextParams.filters || {}) };
|
||||||
if (viewMode === 'folder' && selectedFolderId) {
|
const normalizedFolderId = normalizeFolderFilterId(selectedFolderId);
|
||||||
nextFilters.folderId = selectedFolderId;
|
const rootSelected = isSyntheticRootFolderId(selectedFolderId);
|
||||||
|
const owner: FileOwnerScope =
|
||||||
|
selectedOwnership === 'own'
|
||||||
|
? 'me'
|
||||||
|
: selectedOwnership === 'shared'
|
||||||
|
? 'shared'
|
||||||
|
: 'all';
|
||||||
|
if (viewMode === 'folder' && selectedFolderId && !rootSelected) {
|
||||||
|
nextFilters.folderId = normalizedFolderId;
|
||||||
} else {
|
} else {
|
||||||
delete nextFilters.folderId;
|
delete nextFilters.folderId;
|
||||||
}
|
}
|
||||||
nextParams.filters = nextFilters;
|
nextParams.filters = nextFilters;
|
||||||
|
if (owner !== 'all') nextParams.owner = owner;
|
||||||
|
else delete nextParams.owner;
|
||||||
await tableRefetch(nextParams);
|
await tableRefetch(nextParams);
|
||||||
}, [tableRefetch, selectedFolderId, viewMode]);
|
}, [tableRefetch, selectedFolderId, selectedOwnership, viewMode]);
|
||||||
|
|
||||||
const fetchGroupSectionSummaries = useCallback(
|
const fetchGroupSectionSummaries = useCallback(
|
||||||
async (base: {
|
async (base: {
|
||||||
|
|
@ -122,12 +144,20 @@ export const FilesPage: React.FC = () => {
|
||||||
groupDirection?: 'asc' | 'desc';
|
groupDirection?: 'asc' | 'desc';
|
||||||
}) => {
|
}) => {
|
||||||
const filters = { ...(base.filters || {}) };
|
const filters = { ...(base.filters || {}) };
|
||||||
if (viewMode === 'folder' && selectedFolderId) {
|
const normalizedFolderId = normalizeFolderFilterId(selectedFolderId);
|
||||||
filters.folderId = selectedFolderId;
|
const rootSelected = isSyntheticRootFolderId(selectedFolderId);
|
||||||
|
if (viewMode === 'folder' && selectedFolderId && !rootSelected) {
|
||||||
|
filters.folderId = normalizedFolderId;
|
||||||
}
|
}
|
||||||
return fetchGroupSectionSummariesFromHook({ ...base, filters });
|
const owner: FileOwnerScope =
|
||||||
|
selectedOwnership === 'own'
|
||||||
|
? 'me'
|
||||||
|
: selectedOwnership === 'shared'
|
||||||
|
? 'shared'
|
||||||
|
: 'all';
|
||||||
|
return fetchGroupSectionSummariesFromHook({ ...base, filters, owner });
|
||||||
},
|
},
|
||||||
[fetchGroupSectionSummariesFromHook, viewMode, selectedFolderId],
|
[fetchGroupSectionSummariesFromHook, viewMode, selectedFolderId, selectedOwnership],
|
||||||
);
|
);
|
||||||
|
|
||||||
const refetchForSection = useCallback(
|
const refetchForSection = useCallback(
|
||||||
|
|
@ -137,12 +167,20 @@ export const FilesPage: React.FC = () => {
|
||||||
parentColumnFilters?: Record<string, unknown>,
|
parentColumnFilters?: Record<string, unknown>,
|
||||||
) => {
|
) => {
|
||||||
const merged = { ...(parentColumnFilters || {}) };
|
const merged = { ...(parentColumnFilters || {}) };
|
||||||
if (viewMode === 'folder' && selectedFolderId) {
|
const normalizedFolderId = normalizeFolderFilterId(selectedFolderId);
|
||||||
merged.folderId = selectedFolderId;
|
const rootSelected = isSyntheticRootFolderId(selectedFolderId);
|
||||||
|
if (viewMode === 'folder' && selectedFolderId && !rootSelected) {
|
||||||
|
merged.folderId = normalizedFolderId;
|
||||||
}
|
}
|
||||||
return refetchForSectionFromHook(paginationParams, sectionFilter, merged);
|
const owner: FileOwnerScope =
|
||||||
|
selectedOwnership === 'own'
|
||||||
|
? 'me'
|
||||||
|
: selectedOwnership === 'shared'
|
||||||
|
? 'shared'
|
||||||
|
: 'all';
|
||||||
|
return refetchForSectionFromHook({ ...paginationParams, owner }, sectionFilter, merged);
|
||||||
},
|
},
|
||||||
[refetchForSectionFromHook, viewMode, selectedFolderId],
|
[refetchForSectionFromHook, viewMode, selectedFolderId, selectedOwnership],
|
||||||
);
|
);
|
||||||
|
|
||||||
const _refreshAll = useCallback(async () => {
|
const _refreshAll = useCallback(async () => {
|
||||||
|
|
@ -152,14 +190,15 @@ export const FilesPage: React.FC = () => {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
_tableRefetch({ page: 1, pageSize: 25 });
|
_tableRefetch({ page: 1, pageSize: 25 });
|
||||||
}, [selectedFolderId, viewMode, _tableRefetch]);
|
}, [selectedFolderId, selectedOwnership, viewMode, _tableRefetch]);
|
||||||
|
|
||||||
// ── Tree interaction ──────────────────────────────────────────────────
|
// ── Tree interaction ──────────────────────────────────────────────────
|
||||||
const _handleTreeNodeClick = useCallback((node: TreeNode) => {
|
const _handleTreeNodeClick = useCallback((node: TreeNode) => {
|
||||||
|
setSelectedOwnership(node.ownership);
|
||||||
if (node.type === 'folder') {
|
if (node.type === 'folder') {
|
||||||
setSelectedFolderId(node.id);
|
setSelectedFolderId(node.id);
|
||||||
} else if (node.type === 'file') {
|
} else if (node.type === 'file') {
|
||||||
setSelectedFolderId(node.parentId);
|
setSelectedFolderId(node.parentId ?? null);
|
||||||
setHighlightedFileId(node.id);
|
setHighlightedFileId(node.id);
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
const row = document.querySelector('tr[data-highlighted="true"]');
|
const row = document.querySelector('tr[data-highlighted="true"]');
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue