fix: node inhalt extrahieren nimmt jetzt context, files page formgenerator und folder tree zeigen jetzt die gleichen elemente
All checks were successful
Deploy Nyla Frontend INT / build-and-deploy (push) Successful in 10m27s
All checks were successful
Deploy Nyla Frontend INT / build-and-deploy (push) Successful in 10m27s
This commit is contained in:
parent
fe7321c84d
commit
eb35e4b463
5 changed files with 143 additions and 27 deletions
|
|
@ -36,6 +36,7 @@ export interface PaginationParams {
|
|||
search?: string;
|
||||
viewKey?: string;
|
||||
groupByLevels?: Array<{ field: string; nullLabel?: string; direction?: 'asc' | 'desc' }>;
|
||||
owner?: 'all' | 'me' | 'shared';
|
||||
}
|
||||
|
||||
export interface PaginatedResponse<T> {
|
||||
|
|
@ -109,6 +110,7 @@ export async function fetchFiles(
|
|||
if (params.search) paginationObj.search = params.search;
|
||||
if (params.viewKey) paginationObj.viewKey = params.viewKey;
|
||||
if (params.groupByLevels !== undefined) paginationObj.groupByLevels = params.groupByLevels;
|
||||
if (params.owner) requestParams.owner = params.owner;
|
||||
|
||||
if (Object.keys(paginationObj).length > 0) {
|
||||
requestParams.pagination = JSON.stringify(paginationObj);
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import type { GraphDefinedSchemaRef, NodeType, NodeTypeParameter, PortSchema } f
|
|||
import type { ApiRequestFunction } from '../../../api/workflowApi';
|
||||
import { getLabel } from '../nodes/shared/utils';
|
||||
import { FRONTEND_TYPE_RENDERERS } from '../nodes/frontendTypeRenderers';
|
||||
import { ContextBuilderRenderer } from '../nodes/frontendTypeRenderers/ContextBuilderRenderer';
|
||||
import { RequiredAttributePicker } from '../nodes/shared/RequiredAttributePicker';
|
||||
import { findRequiredErrors } from '../nodes/shared/paramValidation';
|
||||
import { useAutomation2DataFlow } from '../context/Automation2DataFlowContext';
|
||||
|
|
@ -253,6 +254,7 @@ export const NodeConfigPanel: React.FC<NodeConfigPanelProps> = ({ node,
|
|||
|
||||
for (const param of sortedParameters) {
|
||||
if (param.frontendType === 'hidden') continue;
|
||||
if (param.name === 'context') continue;
|
||||
if (CONTEXT_EXTRACT_CHUNK_SET.has(param.name)) continue;
|
||||
if (!parameterVisibleForFrontendOptions(param, params, nodeType)) continue;
|
||||
|
||||
|
|
@ -378,6 +380,15 @@ export const NodeConfigPanel: React.FC<NodeConfigPanelProps> = ({ node,
|
|||
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;
|
||||
|
||||
const isTrigger = node.type.startsWith('trigger.');
|
||||
|
|
@ -483,11 +494,71 @@ export const NodeConfigPanel: React.FC<NodeConfigPanelProps> = ({ node,
|
|||
</div>
|
||||
)}
|
||||
{extractContentAccordionItems !== null ? (
|
||||
<AccordionList<string>
|
||||
key={`${node.id}-extract-accordion`}
|
||||
defaultOpenId={null}
|
||||
items={extractContentAccordionItems}
|
||||
/>
|
||||
<>
|
||||
{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>
|
||||
key={`${node.id}-extract-accordion`}
|
||||
defaultOpenId={null}
|
||||
items={extractContentAccordionItems}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
) : (
|
||||
parameters.map((param: NodeTypeParameter) => {
|
||||
// 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 type { TreeNodeProvider, TreeNode, Ownership, ScopeValue, TreeBatchAction } from '../types';
|
||||
import api from '../../../../api';
|
||||
import { getUserDataCache } from '../../../../utils/userCache';
|
||||
|
||||
interface FolderData {
|
||||
id: string;
|
||||
|
|
@ -117,7 +116,7 @@ export function createFolderFileProvider(options: { includeFiles?: boolean } = {
|
|||
if ((f.parentId ?? null) === null) out.add(f.id);
|
||||
}
|
||||
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 rawFiles: FileData[] = (data && typeof data === 'object' && 'items' in data)
|
||||
? (Array.isArray(data.items) ? data.items : [])
|
||||
|
|
@ -169,7 +168,7 @@ export function createFolderFileProvider(options: { includeFiles?: boolean } = {
|
|||
}
|
||||
const paginationParam = JSON.stringify({ filters, pageSize: 500 });
|
||||
const filesRes = await api.get('/api/files/list', {
|
||||
params: { pagination: paginationParam },
|
||||
params: { pagination: paginationParam, owner },
|
||||
});
|
||||
const data = filesRes.data;
|
||||
let rawFiles: FileData[] = [];
|
||||
|
|
@ -179,10 +178,6 @@ export function createFolderFileProvider(options: { includeFiles?: boolean } = {
|
|||
rawFiles = data;
|
||||
}
|
||||
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));
|
||||
if (apiParentId === null) {
|
||||
for (const n of fileNodes) n.parentId = synthRootId;
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ export interface PaginationParams {
|
|||
filters?: Record<string, any>;
|
||||
search?: string;
|
||||
viewKey?: string;
|
||||
owner?: 'all' | 'me' | 'shared';
|
||||
}
|
||||
|
||||
// Files list hook
|
||||
|
|
@ -150,6 +151,7 @@ export function useUserFiles() {
|
|||
groupField: string;
|
||||
groupDirection?: 'asc' | 'desc';
|
||||
groupByLevels?: Array<{ field: string; nullLabel?: string; direction?: string }>;
|
||||
owner?: 'all' | 'me' | 'shared';
|
||||
}) => {
|
||||
const levels = base.groupByLevels?.length
|
||||
? base.groupByLevels
|
||||
|
|
@ -164,7 +166,11 @@ export function useUserFiles() {
|
|||
if (base.sort?.length) (pObj as { sort: typeof base.sort }).sort = base.sort;
|
||||
if (base.viewKey) (pObj as { viewKey: string }).viewKey = base.viewKey;
|
||||
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 : [];
|
||||
},
|
||||
|
|
@ -192,7 +198,10 @@ export function useUserFiles() {
|
|||
if (paginationParams.search) (pObj as { search: string }).search = paginationParams.search;
|
||||
if (paginationParams.viewKey) (pObj as { viewKey: string }).viewKey = paginationParams.viewKey;
|
||||
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) {
|
||||
return { items: data.items, pagination: data.pagination };
|
||||
|
|
|
|||
|
|
@ -31,6 +31,17 @@ interface UserFile {
|
|||
}
|
||||
|
||||
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 = () => {
|
||||
const { t } = useLanguage();
|
||||
|
|
@ -74,6 +85,7 @@ export const FilesPage: React.FC = () => {
|
|||
const [editingFile, setEditingFile] = useState<UserFile | null>(null);
|
||||
const [selectedFiles, setSelectedFiles] = useState<UserFile[]>([]);
|
||||
const [selectedFolderId, setSelectedFolderId] = useState<string | null>(null);
|
||||
const [selectedOwnership, setSelectedOwnership] = useState<'own' | 'shared' | null>('own');
|
||||
const [highlightedFileId, setHighlightedFileId] = useState<string | null>(null);
|
||||
|
||||
const [treeWidth, setTreeWidth] = useState(300);
|
||||
|
|
@ -103,14 +115,24 @@ export const FilesPage: React.FC = () => {
|
|||
const _tableRefetch = useCallback(async (params?: any) => {
|
||||
const nextParams = { ...(params || {}) };
|
||||
const nextFilters = { ...(nextParams.filters || {}) };
|
||||
if (viewMode === 'folder' && selectedFolderId) {
|
||||
nextFilters.folderId = selectedFolderId;
|
||||
const normalizedFolderId = normalizeFolderFilterId(selectedFolderId);
|
||||
const rootSelected = isSyntheticRootFolderId(selectedFolderId);
|
||||
const owner: FileOwnerScope =
|
||||
selectedOwnership === 'own'
|
||||
? 'me'
|
||||
: selectedOwnership === 'shared'
|
||||
? 'shared'
|
||||
: 'all';
|
||||
if (viewMode === 'folder' && selectedFolderId && !rootSelected) {
|
||||
nextFilters.folderId = normalizedFolderId;
|
||||
} else {
|
||||
delete nextFilters.folderId;
|
||||
}
|
||||
nextParams.filters = nextFilters;
|
||||
if (owner !== 'all') nextParams.owner = owner;
|
||||
else delete nextParams.owner;
|
||||
await tableRefetch(nextParams);
|
||||
}, [tableRefetch, selectedFolderId, viewMode]);
|
||||
}, [tableRefetch, selectedFolderId, selectedOwnership, viewMode]);
|
||||
|
||||
const fetchGroupSectionSummaries = useCallback(
|
||||
async (base: {
|
||||
|
|
@ -122,12 +144,20 @@ export const FilesPage: React.FC = () => {
|
|||
groupDirection?: 'asc' | 'desc';
|
||||
}) => {
|
||||
const filters = { ...(base.filters || {}) };
|
||||
if (viewMode === 'folder' && selectedFolderId) {
|
||||
filters.folderId = selectedFolderId;
|
||||
const normalizedFolderId = normalizeFolderFilterId(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(
|
||||
|
|
@ -137,12 +167,20 @@ export const FilesPage: React.FC = () => {
|
|||
parentColumnFilters?: Record<string, unknown>,
|
||||
) => {
|
||||
const merged = { ...(parentColumnFilters || {}) };
|
||||
if (viewMode === 'folder' && selectedFolderId) {
|
||||
merged.folderId = selectedFolderId;
|
||||
const normalizedFolderId = normalizeFolderFilterId(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 () => {
|
||||
|
|
@ -152,14 +190,15 @@ export const FilesPage: React.FC = () => {
|
|||
|
||||
useEffect(() => {
|
||||
_tableRefetch({ page: 1, pageSize: 25 });
|
||||
}, [selectedFolderId, viewMode, _tableRefetch]);
|
||||
}, [selectedFolderId, selectedOwnership, viewMode, _tableRefetch]);
|
||||
|
||||
// ── Tree interaction ──────────────────────────────────────────────────
|
||||
const _handleTreeNodeClick = useCallback((node: TreeNode) => {
|
||||
setSelectedOwnership(node.ownership);
|
||||
if (node.type === 'folder') {
|
||||
setSelectedFolderId(node.id);
|
||||
} else if (node.type === 'file') {
|
||||
setSelectedFolderId(node.parentId);
|
||||
setSelectedFolderId(node.parentId ?? null);
|
||||
setHighlightedFileId(node.id);
|
||||
requestAnimationFrame(() => {
|
||||
const row = document.querySelector('tr[data-highlighted="true"]');
|
||||
|
|
|
|||
Loading…
Reference in a new issue