From 58a460f2ba6a321e863ca8464ac3cf6e14b0e516 Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Wed, 7 May 2025 16:53:31 +0200 Subject: [PATCH 1/7] 130715: Created generate-decorator-registries script to generator decorator maps Co-authored-by: Nona Luypaert Co-authored-by: Alexandre Vryghem --- .gitignore | 3 + package.json | 1 + scripts/config/decorator-config.interface.ts | 9 + scripts/config/decorator-param.interface.ts | 21 ++ scripts/generate-decorator-registries.ts | 309 ++++++++++++++++++ src/app/core/shared/context.model.ts | 2 +- src/app/decorators.ts | 12 + .../generate-decorator-registries.plugin.ts | 23 ++ webpack/webpack.common.ts | 2 + 9 files changed, 381 insertions(+), 1 deletion(-) create mode 100644 scripts/config/decorator-config.interface.ts create mode 100644 scripts/config/decorator-param.interface.ts create mode 100644 scripts/generate-decorator-registries.ts create mode 100644 src/app/decorators.ts create mode 100644 webpack/plugins/generate-decorator-registries.plugin.ts diff --git a/.gitignore b/.gitignore index 7af424c50f3..ba7c09b7cdf 100644 --- a/.gitignore +++ b/.gitignore @@ -42,3 +42,6 @@ yarn-error.log junit.xml /src/mirador-viewer/config.local.js + +## ignore the auto-generated decorator registries +decorator-registries/ diff --git a/package.json b/package.json index 04a42e4ff40..e13088010d1 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "serve": "ts-node --project ./tsconfig.ts-node.json scripts/serve.ts", "serve:ssr": "node dist/server/main", "analyze": "webpack-bundle-analyzer dist/browser/stats.json", + "generate:decorator:registries": "ts-node --project ./tsconfig.ts-node.json scripts/generate-decorator-registries.ts", "build": "ng build --configuration development", "build:stats": "ng build --stats-json", "build:prod": "cross-env NODE_ENV=production npm run build:ssr", diff --git a/scripts/config/decorator-config.interface.ts b/scripts/config/decorator-config.interface.ts new file mode 100644 index 00000000000..6ba6a2c5b7e --- /dev/null +++ b/scripts/config/decorator-config.interface.ts @@ -0,0 +1,9 @@ +import { DecoratorParam } from './decorator-param.interface'; + +/** + * The configuration of dynamic component decorators. This is used to generate the registry files. + */ +export interface DecoratorConfig { + name: string; + params: DecoratorParam[]; +} diff --git a/scripts/config/decorator-param.interface.ts b/scripts/config/decorator-param.interface.ts new file mode 100644 index 00000000000..29ce21e9764 --- /dev/null +++ b/scripts/config/decorator-param.interface.ts @@ -0,0 +1,21 @@ +/** + * The configuration of a parameter from a decorator + */ +export interface DecoratorParam { + /** + * The name of the parameter + */ + name: string; + + /** + * The default value if any of the decorator param + */ + default?: string; + + /** + * The property of the provided value that should be used instead to generate the Map. So, for example, if the + * decorator value is a {@link ResourceType}, you may want to use the `ResourceType.value` instead of the whole + * {@link ResourceType} object. In this case the {@link DecoratorParam#property} would be `value`. + */ + property?: string; +} diff --git a/scripts/generate-decorator-registries.ts b/scripts/generate-decorator-registries.ts new file mode 100644 index 00000000000..d95af7e6a06 --- /dev/null +++ b/scripts/generate-decorator-registries.ts @@ -0,0 +1,309 @@ +import { + existsSync, + mkdirSync, + readdirSync, + readFileSync, + rmSync, + writeFileSync, +} from 'fs'; +import { sync } from 'glob'; +import { + basename, + dirname, + join, + relative, + resolve, +} from 'path'; +import { + createSourceFile, + forEachChild, + getDecorators, + Identifier, + ImportDeclaration, + isCallExpression, + isClassDeclaration, + isEnumDeclaration, + isExpressionWithTypeArguments, + isIdentifier, + isPropertyAccessExpression, + isStringLiteral, + ScriptTarget, + StringLiteral, + SyntaxKind, +} from 'typescript'; + +import { DECORATORS } from '../src/app/decorators'; +import { DecoratorConfig } from './config/decorator-config.interface'; + +const COMPONENTS_DIR = resolve(__dirname, '../src'); +const REGISTRY_OUTPUT_DIR = resolve(__dirname, '../src/decorator-registries'); + +/** + * Scans the code base for enums and extracts their values. + * + * @returns A nested map of the enums and their values. + */ +const generateEnumValues = () => { + const enumValues = {}; + + const fileNames = sync(`${COMPONENTS_DIR}/**/*.ts`, { ignore: `${COMPONENTS_DIR}/**/*.spec.ts` }); + + fileNames.forEach((filePath: string) => { + const fileName = basename(filePath); + const sourceFile = createSourceFile(fileName, readFileSync(filePath, 'utf8'), ScriptTarget.Latest); + + if (!sourceFile.isDeclarationFile) { + forEachChild(sourceFile, node => { + if (isEnumDeclaration(node)) { + const enumName = node.name.text; + enumValues[enumName] = {}; + + for (const value of node.members) { + const valueName = value.name.getText(sourceFile); + if (value.initializer && isStringLiteral(value.initializer)) { + enumValues[enumName][valueName] = value.initializer.text; + } + } + } + }); + } + }); + + return enumValues; +}; + +const enumValues = generateEnumValues(); + +const getDecoratorConstName = (decorator: string): string => { + return decorator + .replace(/([A-Z])/g, '_$1') + .toUpperCase() + .replace(/^_/, ''); +}; + +const getDecoratorFileName = (decorator: string): string => { + return decorator + .replace(/([A-Z])/g, '-$1') + .toLowerCase() + .replace(/^-/, '') + .concat('-registry.ts'); +}; + +/** + * Generates and writes a registry TypeScript file for decorator components. + * + * @param decoratorConfig - Decorator configuration that's currently being processed. + * @param {Array<{ name: string, filePath: string, args: any[], imports: Map }>} components - An array of objects, each representing a component. + * + * @returns {void} This function does not return a value. It writes a file to the output directory. + */ +const writeRegistryFile = ( + decoratorConfig: DecoratorConfig, + components: Array<{ name: string, filePath: string, args: any[], imports: Map }>, +): void => { + const mapName = getDecoratorConstName(decoratorConfig.name) + '_MAP'; + const functionName = `${decoratorConfig.name}CreateMap`; + const mapVarName = `${decoratorConfig.name}Map`; + let content = ''; + + const imports: Map> = new Map(); + components.forEach(component => { + for (const componentImport of component.imports.keys()) { + const importPath: string = component.imports.get(componentImport); + if (!imports.get(importPath)) { + imports.set(importPath, new Set()); + } + imports.get(importPath).add(componentImport); + } + }); + + if (imports.size > 0) { + content += `${ Array.from(imports.keys()).sort().map((path: string) => `import { ${Array.from(imports.get(path)).join(', ')} } from '${path}';`).join('\n')}\n\n`; + } + + content += `function ${functionName}(): Map {\n`; + content += ` const ${mapVarName} = new Map();\n\n`; + + const mapPathsSoFar = new Set(); + + for (const component of components) { + const argsArray = decoratorConfig.params.map((param, index) => (index < component.args.length && component.args[index] !== undefined) ? component.args[index] : param.default); + + let currentMapPath = mapVarName; + let currentPathKey = ''; + + for (let i = 0; i < argsArray.length - 1; i++) { + const key = argsArray[i]; + let keyString: string; + if (typeof key === 'string' && key.includes('${')) { + keyString = `\`${key}\``; + } else if (typeof key === 'string') { + keyString = `'${key.replace(/'/g, '\\\'')}'`; + } else if (key && typeof key === 'object' && 'classRef' in key) { + const param = decoratorConfig.params[argsArray.length - 1]; + if (param.property) { + keyString = `${key.classRef}.${param.property}`; + } else { + keyString = key.classRef; + } + } else { + keyString = String(key); + } + + const newPath = currentPathKey + '|' + (typeof key === 'object' && 'classRef' in key ? (key.classRef ? key.classRef : key) : String(key)); + + if (!mapPathsSoFar.has(newPath)) { + content += ` ${currentMapPath}.set(${keyString}, new Map());\n`; + mapPathsSoFar.add(newPath); + } + currentMapPath += `.get(${keyString})`; + currentPathKey = newPath; + } + + const finalKey = argsArray[argsArray.length - 1]; + let finalKeyString: string; + if (typeof finalKey === 'string' && finalKey.includes('${')) { + finalKeyString = `\`${finalKey}\``; + } else if (typeof finalKey === 'string') { + finalKeyString = `'${finalKey.replace(/'/g, '\\\'')}'`; + } else if (finalKey && typeof finalKey === 'object' && 'classRef' in finalKey) { + const param = decoratorConfig.params[argsArray.length - 1]; + if (param.property) { + finalKeyString = `${finalKey.classRef}.${param.property}`; + } else { + finalKeyString = finalKey.classRef; + } + } else { + finalKeyString = String(finalKey); + } + + const lazyImport = `() => import('${component.filePath}').then(c => c.${component.name})`; + content += ` ${currentMapPath}.set(${finalKeyString}, ${lazyImport});\n`; + } + + content += `\n return ${mapVarName};\n`; + content += `}\n\n`; + content += `export const ${mapName} = ${functionName}();\n`; + + + const filePath = join(REGISTRY_OUTPUT_DIR, getDecoratorFileName(decoratorConfig.name)); + if (!existsSync(filePath) || readFileSync(filePath, 'utf8') !== content) { + writeFileSync(filePath, content, 'utf8'); + } +}; + +const generateRegistries = ( + decoratorConfigs: DecoratorConfig[], +): Map }>> => { + // Initialize the map using decorator names as keys, and empty lists as values + const decoratorMap = new Map }>>(); + decoratorConfigs.forEach(config => { + decoratorMap.set(config.name, []); + }); + + // Get all TypeScript files recursively, excluding spec files + const fileNames = sync(`${COMPONENTS_DIR}/**/*.ts`, { ignore: `${COMPONENTS_DIR}/**/*.spec.ts` }); + + fileNames.forEach((filePath: string) => { + const fileName = basename(filePath); + const sourceFile = createSourceFile(fileName, readFileSync(filePath, 'utf8'), ScriptTarget.Latest); + + // The key of the map is the import name, and the value is the path + const imports: Map = new Map(); + forEachChild(sourceFile, (node: ImportDeclaration) => { + if (node.kind === SyntaxKind.ImportDeclaration && node.importClause?.namedBindings?.kind === SyntaxKind.NamedImports) { + node.importClause.namedBindings.elements.forEach(element => { + imports.set(element.name.text, (node.moduleSpecifier as StringLiteral).text); + }); + } + }); + + // Walk the AST to find class declarations with decorators + forEachChild(sourceFile, node => { + if (isClassDeclaration(node) && node.name) { + const decorators = getDecorators(node); + const componentName = node.name.text; + + decorators?.forEach((decorator) => { + if (isCallExpression(decorator.expression)) { + const currentDecoratorName = (decorator.expression.expression as Identifier).text; + + const decoratorConfig = decoratorConfigs.find(config => config.name === currentDecoratorName); + if (decoratorConfig) { + const args: any[] = []; + const argImports: Map = new Map(); + decorator.expression.arguments.forEach((arg) => { + // e.g. @decorator('range') + if (isStringLiteral(arg)) { + args.push(arg.text); + // e.g. @decorator(ItemSearchResult) + } else if (isIdentifier(arg)) { + args.push({ classRef: arg.text }); + + if (imports.has(arg.text)) { + let absoluteImportPath = imports.get(arg.text); + if (!absoluteImportPath.includes('src/app')) { + absoluteImportPath = resolve(dirname(filePath), imports.get(arg.text)); + } + const newRelativePath = relative(REGISTRY_OUTPUT_DIR, absoluteImportPath); + argImports.set(arg.text, newRelativePath); + } + // e.g. @decorator(Enum.property) + } else if (isPropertyAccessExpression(arg)) { + const propertyName = arg.name.text; + const objectName = (arg.expression as Identifier).text; + const enumValue = enumValues[objectName]?.[propertyName]; + args.push(enumValue || `${objectName}.${propertyName}`); + // e.g. @decorator(PaginatedList) + } else if (isExpressionWithTypeArguments(arg)) { + args.push(arg.typeArguments[0].getText(sourceFile)); + } else if (arg.kind === SyntaxKind.TrueKeyword) { + args.push(true); + } else if (arg.kind === SyntaxKind.FalseKeyword) { + args.push(false); + } + }); + + // Add to the map under the current decorator's name + decoratorMap.get(currentDecoratorName)?.push({ + name: componentName, + filePath: `../${relative(COMPONENTS_DIR, filePath).replace(/\.ts$/, '')}`, + args, + imports: argImports, + }); + } + } + }); + } + }); + }); + + return decoratorMap; +}; + +const main = (): void => { + mkdirSync(REGISTRY_OUTPUT_DIR, { recursive: true }); + const registriesToDelete: Set = new Set(readdirSync(REGISTRY_OUTPUT_DIR)); + + // Generate map with decorator names as keys, lists of component metadata objects as values + // 1 component metadata object contains: the name of the component, the full path of + // the component file, and the arguments used in the decorator on that component + const decoratorMap = generateRegistries(DECORATORS); + + // Write registry files for each decorator + DECORATORS.forEach(decoratorConfig => { + registriesToDelete.delete(getDecoratorFileName(decoratorConfig.name)); + const componentsForDecorator = decoratorMap.get(decoratorConfig.name); + if (componentsForDecorator && componentsForDecorator.length > 0) { + writeRegistryFile(decoratorConfig, componentsForDecorator); + } else { + console.warn(`No components found for decorator '${decoratorConfig.name}'`); + } + }); + + registriesToDelete.forEach((fileName: string) => rmSync(join(REGISTRY_OUTPUT_DIR, fileName))); + + console.debug(`Generated decorator registry files in ${REGISTRY_OUTPUT_DIR}`); +}; + +main(); diff --git a/src/app/core/shared/context.model.ts b/src/app/core/shared/context.model.ts index 8775272f34c..850df2513fd 100644 --- a/src/app/core/shared/context.model.ts +++ b/src/app/core/shared/context.model.ts @@ -4,7 +4,7 @@ export enum Context { /** Default context */ - Any = 'undefined', + Any = '*', /** General item page context */ ItemPage = 'itemPage', diff --git a/src/app/decorators.ts b/src/app/decorators.ts new file mode 100644 index 00000000000..d28db809a52 --- /dev/null +++ b/src/app/decorators.ts @@ -0,0 +1,12 @@ +import { DecoratorConfig } from '../../scripts/config/decorator-config.interface'; + +/** + * This list contains the dynamic components decorator configuration that will be used to generate the registry files + * in `src/decorator-registries`. + * + * If you want to create a new decorator, you need to extend this list and add your custom decorator to it. Afterwards + * a registry file will be generated inside the `src/decorator-registries` folder, which exports the Map that you can + * then use this inside your decorator file to dynamically retrieve the desired component. + */ +export const DECORATORS: DecoratorConfig[] = [ +]; diff --git a/webpack/plugins/generate-decorator-registries.plugin.ts b/webpack/plugins/generate-decorator-registries.plugin.ts new file mode 100644 index 00000000000..e4ff1dddb0c --- /dev/null +++ b/webpack/plugins/generate-decorator-registries.plugin.ts @@ -0,0 +1,23 @@ +import { execSync } from 'child_process'; +import { Compiler } from 'webpack'; + +/** + * Triggers the `generate:decorator:registries` script on every rebuild to synchronize the registry files. + */ +export class GenerateDecoratorRegistriesPlugin { + + apply(compiler: Compiler): void { + compiler.hooks.watchRun.tap('GenerateDecoratorRegistries', () => { + execSync('npm run generate:decorator:registries', { stdio: 'inherit' }); + }); + + let firstRun = true; + compiler.hooks.beforeCompile.tap('GenerateDecoratorRegistries', (c) => { + if (firstRun) { + execSync('npm run generate:decorator:registries', { stdio: 'inherit' }); + firstRun = false; + } + }); + } + +} diff --git a/webpack/webpack.common.ts b/webpack/webpack.common.ts index b1c42df8ad2..5b69487c48a 100644 --- a/webpack/webpack.common.ts +++ b/webpack/webpack.common.ts @@ -1,5 +1,6 @@ import { globalCSSImports, projectRoot, getFileHashes, calculateFileHash } from './helpers'; import { EnvironmentPlugin } from 'webpack'; +import { GenerateDecoratorRegistriesPlugin } from './plugins/generate-decorator-registries.plugin'; const CopyWebpackPlugin = require('copy-webpack-plugin'); const path = require('path'); @@ -75,6 +76,7 @@ const SCSS_LOADERS = [ export const commonExports = { plugins: [ + new GenerateDecoratorRegistriesPlugin(), new EnvironmentPlugin({ languageHashes: getFileHashes(path.join(__dirname, '..', 'src', 'assets', 'i18n'), /.*\.json5/g), }), From db9fa05820ff5cfc541178baafc29945b169f563 Mon Sep 17 00:00:00 2001 From: Nona Luypaert Date: Mon, 2 Jun 2025 11:37:03 +0200 Subject: [PATCH 2/7] 130715: Restored decorators Co-authored-by: Alexandre Vryghem --- .../ldn-itemfilters-data.service.ts | 3 + .../ldn-services-data.service.ts | 3 + .../admin-notify-search-result.component.ts | 4 + ...dmin-notify-message-search-result.model.ts | 2 + .../services/admin-notify-messages.service.ts | 3 + ...arch-result-grid-element.component.spec.ts | 2 - ...in-search-result-grid-element.component.ts | 43 +-- .../admin-sidebar-section.component.spec.ts | 34 ++- .../admin-sidebar-section.component.ts | 18 +- .../admin-sidebar.component.html | 8 +- .../admin-sidebar.component.spec.ts | 2 +- .../admin-sidebar/admin-sidebar.component.ts | 4 +- ...dable-admin-sidebar-section.component.html | 10 +- ...le-admin-sidebar-section.component.spec.ts | 17 +- ...andable-admin-sidebar-section.component.ts | 15 +- ...m-admin-workflow-actions.component.spec.ts | 12 +- ...e-item-admin-workflow-actions.component.ts | 12 +- ...in-workflow-grid-element.component.spec.ts | 11 +- ...t-admin-workflow-grid-element.component.ts | 25 +- ...in-workflow-grid-element.component.spec.ts | 12 +- ...t-admin-workflow-grid-element.component.ts | 17 +- src/app/app.config.ts | 18 -- .../browse-by-date.component.ts | 3 + .../browse-by-metadata.component.ts | 2 + .../browse-by-page.component.spec.ts | 10 +- .../browse-by-decorator.spec.ts | 27 +- .../browse-by-switcher/browse-by-decorator.ts | 60 +--- .../browse-by-switcher.component.spec.ts | 2 +- .../browse-by-switcher.component.ts | 8 +- .../browse-by-taxonomy.component.ts | 2 + .../browse-by-title.component.ts | 3 + ...page-sub-collection-list.component.spec.ts | 12 +- ...-page-sub-community-list.component.spec.ts | 2 + .../core/auth/auth-methods.service.spec.ts | 58 +--- src/app/core/auth/auth-methods.service.ts | 17 +- .../browse/browse-definition-data.service.ts | 3 + .../cache/builders/build-decorators.spec.ts | 14 - .../core/cache/builders/build-decorators.ts | 18 +- .../config/bulk-access-config-data.service.ts | 3 + ...submission-accesses-config-data.service.ts | 3 + .../submission-forms-config-data.service.ts | 3 + .../submission-uploads-config-data.service.ts | 3 + src/app/core/data-services-map.ts | 139 --------- .../core/data/access-status-data.service.ts | 3 + src/app/core/data/bitstream-data.service.ts | 3 + .../data/bitstream-format-data.service.ts | 3 + src/app/core/data/bundle-data.service.ts | 3 + src/app/core/data/collection-data.service.ts | 3 + src/app/core/data/community-data.service.ts | 3 + .../core/data/configuration-data.service.ts | 5 +- .../core/data/dspace-object-data.service.ts | 3 + .../authorization-data.service.ts | 5 + src/app/core/data/href-only-data.service.ts | 7 + src/app/core/data/identifier-data.service.ts | 3 + src/app/core/data/item-data.service.ts | 3 + .../core/data/metadata-field-data.service.ts | 3 + .../core/data/metadata-schema-data.service.ts | 3 + .../notify-services-status-data.service.ts | 3 + .../data/processes/process-data.service.ts | 3 + .../data/processes/script-data.service.ts | 3 + .../core/data/relationship-data.service.ts | 3 + .../data/relationship-type-data.service.ts | 3 + src/app/core/data/root-data.service.ts | 3 + src/app/core/data/site-data.service.ts | 3 + .../data/system-wide-alert-data.service.ts | 3 + src/app/core/data/version-data.service.ts | 3 + .../core/data/version-history-data.service.ts | 3 + .../core/data/workflow-action-data.service.ts | 3 + src/app/core/eperson/eperson-data.service.ts | 5 +- src/app/core/eperson/group-data.service.ts | 3 + .../core/feedback/feedback-data.service.ts | 3 + .../quality-assurance-event-data.service.ts | 3 + .../quality-assurance-source-data.service.ts | 3 + .../quality-assurance-topic-data.service.ts | 3 + .../source/suggestion-source-data.service.ts | 3 + .../suggestions/suggestion-data.service.ts | 3 + .../target/suggestion-target-data.service.ts | 3 + .../core/orcid/orcid-history-data.service.ts | 3 + .../core/orcid/orcid-queue-data.service.ts | 3 + .../researcher-profile-data.service.ts | 3 + src/app/core/provide-core.ts | 2 + .../resource-policy-data.service.ts | 3 + src/app/core/shared/listable.module.ts | 268 ------------------ src/app/core/shared/search/search.service.ts | 17 +- .../statistics/usage-report-data.service.ts | 3 + .../submission/correctiontype-data.service.ts | 3 + .../submission/models/correctiontype.model.ts | 3 +- .../models/correctiontype.resource-type.ts | 9 + .../submission-cc-license-data.service.ts | 3 + .../submission-cc-license-url-data.service.ts | 3 + .../submission-duplicate-data.service.ts | 4 +- .../vocabulary-entry-details.data.service.ts | 3 + .../vocabularies/vocabulary.data.service.ts | 3 + .../submission/workflowitem-data.service.ts | 3 + .../submission/workspaceitem-data.service.ts | 3 + .../supervision-order-data.service.ts | 3 + .../core/tasks/claimed-task-data.service.ts | 3 + .../models/workflow-action-object.model.ts | 3 +- src/app/core/tasks/pool-task-data.service.ts | 3 + src/app/decorators.ts | 126 ++++++++ ...edit-metadata-authority-field.component.ts | 3 + ...so-edit-metadata-entity-field.component.ts | 3 + .../dso-edit-metadata-text-field.component.ts | 3 + ...t-metadata-value-field-loader.component.ts | 2 +- ...dso-edit-metadata-value-field.decorator.ts | 64 ++--- ...it-item-metadata-list-element.component.ts | 3 + ...on-item-metadata-list-element.component.ts | 3 + ...ct-item-metadata-list-element.component.ts | 3 + .../external-log-in.methods-decorator.ts | 43 ++- .../external-log-in.component.html | 8 +- .../external-log-in.component.spec.ts | 17 +- .../external-log-in.component.ts | 47 +-- .../orcid-confirmation.component.ts | 9 +- .../external-login-page.component.html | 1 - .../external-login-page.component.ts | 7 +- ...top-level-community-list.component.spec.ts | 4 +- src/app/init.service.ts | 4 +- .../expandable-navbar-section.component.html | 10 +- ...xpandable-navbar-section.component.spec.ts | 13 +- .../expandable-navbar-section.component.ts | 15 +- ...med-expandable-navbar-section.component.ts | 30 -- .../navbar-section.component.html | 4 +- .../navbar-section.component.spec.ts | 8 +- .../navbar-section.component.ts | 21 +- src/app/navbar/navbar.component.html | 8 +- src/app/navbar/navbar.component.ts | 4 +- .../abstract-component-loader.component.ts | 10 +- .../browse-by/browse-by.component.spec.ts | 29 +- .../comcol-browse-by.component.spec.ts | 16 +- ...dit-menu-expandable-section.component.html | 6 +- ...-menu-expandable-section.component.spec.ts | 13 +- ...-edit-menu-expandable-section.component.ts | 19 +- .../dso-edit-menu-section.component.spec.ts | 30 +- .../dso-edit-menu-section.component.ts | 16 +- .../dso-edit-menu.component.html | 8 +- .../dso-edit-menu.component.spec.ts | 2 +- .../dso-edit-menu/dso-edit-menu.component.ts | 27 +- .../container/log-in-container.component.html | 8 +- .../log-in-container.component.spec.ts | 7 +- .../container/log-in-container.component.ts | 24 +- src/app/shared/log-in/log-in.component.ts | 3 +- .../log-in-external-provider.component.ts | 6 + .../methods/log-in.methods-decorator.ts | 39 +-- .../methods/log-in.methods-decorator.utils.ts | 15 - .../password/log-in-password.component.ts | 3 + .../menu-component-loader.component.ts | 53 ++++ src/app/shared/menu/menu-item-type.model.ts | 7 +- src/app/shared/menu/menu-item.decorator.ts | 50 ++-- .../external-link-menu-item.component.ts | 3 + .../menu-item/link-menu-item.component.ts | 3 + .../menu-item/onclick-menu-item.component.ts | 3 + .../menu-item/text-menu-item.component.ts | 3 + src/app/shared/menu/menu-section.decorator.ts | 57 +--- .../abstract-menu-section.component.ts | 84 ++++-- .../menu-section.component.spec.ts | 20 +- src/app/shared/menu/menu.component.spec.ts | 109 +++---- src/app/shared/menu/menu.component.ts | 85 ++---- ...etadata-representation-loader.component.ts | 8 +- .../metadata-representation.decorator.spec.ts | 76 +++-- .../metadata-representation.decorator.ts | 95 +------ .../claimed-task-actions-approve.component.ts | 7 +- .../claimed-task/claimed-task-type.ts | 12 + ...med-task-actions-decline-task.component.ts | 7 +- ...ed-task-actions-edit-metadata.component.ts | 5 +- ...aimed-task-action-rating.component.spec.ts | 4 +- ...ed-claimed-task-action-rating.component.ts | 7 +- ...imed-task-actions-reject.component.spec.ts | 4 +- .../claimed-task-actions-reject.component.ts | 7 +- ...d-task-actions-return-to-pool.component.ts | 5 +- ...k-action-select-reviewer.component.spec.ts | 4 +- ...d-task-action-select-reviewer.component.ts | 7 +- .../claimed-task-actions-decorator.spec.ts | 39 ++- .../claimed-task-actions-decorator.ts | 100 ++----- ...imed-task-actions-loader.component.spec.ts | 9 +- .../claimed-task-actions-loader.component.ts | 10 +- .../mydspace-reloadable-actions.ts | 13 +- .../claimed-task-search-result.model.ts | 2 + .../shared/collection-search-result.model.ts | 2 + .../shared/community-search-result.model.ts | 2 + .../shared/item-search-result.model.ts | 2 + ...-object-component-loader.component.spec.ts | 2 - ...table-object-component-loader.component.ts | 18 +- .../listable-object.decorator.spec.ts | 146 +++++++--- .../listable-object.decorator.ts | 124 +++++--- .../shared/pool-task-search-result.model.ts | 2 + .../tabulatable-objects-loader.component.html | 1 - ...bulatable-objects-loader.component.spec.ts | 107 ------- .../tabulatable-objects-loader.component.ts | 157 ++-------- .../tabulatable-objects.decorator.spec.ts | 4 +- .../tabulatable-objects.decorator.ts | 34 ++- .../tabulatable-objects.directive.ts | 15 - .../workflow-item-search-result.model.ts | 2 + .../workspace-item-search-result.model.ts | 2 + .../item/item-list-element.component.spec.ts | 4 +- ...-link-metadata-list-element.component.html | 1 - ...se-link-metadata-list-element.component.ts | 3 + .../item-metadata-list-element.component.ts | 3 + ...in-text-metadata-list-element.component.ts | 4 + .../object-table/object-table.component.html | 3 +- .../object-table/object-table.component.ts | 2 + .../search-authority-filter.component.ts | 3 + .../search-boolean-filter.component.ts | 3 + .../search-facet-filter-wrapper.component.ts | 4 +- .../search-filter-type-decorator.ts | 46 ++- .../search-hierarchy-filter.component.ts | 3 + .../search-range-filter.component.ts | 3 + .../search-text-filter.component.ts | 3 + .../search-label-loader.component.ts | 2 +- .../search-label-loader.decorator.ts | 43 ++- .../search-label-range.component.ts | 2 + .../search-label/search-label.component.html | 2 +- .../search-label/search-label.component.ts | 2 + .../search/search-result-element-decorator.ts | 45 +-- .../date/starts-with-date.component.ts | 3 + .../starts-with/starts-with-decorator.ts | 34 +-- .../starts-with-loader.component.spec.ts | 4 +- .../starts-with-loader.component.ts | 11 +- .../text/starts-with-text.component.ts | 3 + .../subscriptions-data.service.ts | 3 + .../accesses/section-accesses.component.ts | 4 +- ...ubmission-section-cc-licenses.component.ts | 2 + .../section-container.component.html | 4 +- .../section-container.component.spec.ts | 18 +- .../container/section-container.component.ts | 23 +- .../section-duplicates.component.ts | 4 +- .../sections/form/section-form.component.ts | 3 + .../section-identifiers.component.ts | 6 +- .../license/section-license.component.ts | 5 +- .../coar-notify-config-data.service.ts | 3 + .../section-coar-notify.component.ts | 3 + .../submission/sections/sections-decorator.ts | 46 +-- .../section-sherpa-policies.component.ts | 3 + .../upload/section-upload.component.ts | 3 + ...advanced-workflow-action-page.component.ts | 3 +- ...d-workflow-action-rating.component.spec.ts | 12 +- ...vanced-workflow-action-rating.component.ts | 11 +- ...w-action-select-reviewer.component.spec.ts | 10 +- ...rkflow-action-select-reviewer.component.ts | 11 +- .../advanced-workflow-action-type.ts | 7 + ...-workflow-actions-loader.component.spec.ts | 47 +-- ...anced-workflow-actions-loader.component.ts | 11 +- .../admin-sidebar/admin-sidebar.component.ts | 4 +- .../expandable-navbar-section.component.ts | 3 + .../custom/app/navbar/navbar.component.ts | 4 +- .../dspace/app/navbar/navbar.component.html | 8 +- .../dspace/app/navbar/navbar.component.ts | 4 +- 246 files changed, 1794 insertions(+), 2099 deletions(-) delete mode 100644 src/app/core/data-services-map.ts delete mode 100644 src/app/core/shared/listable.module.ts create mode 100644 src/app/core/submission/models/correctiontype.resource-type.ts delete mode 100644 src/app/navbar/expandable-navbar-section/themed-expandable-navbar-section.component.ts delete mode 100644 src/app/shared/log-in/methods/log-in.methods-decorator.utils.ts create mode 100644 src/app/shared/menu/menu-component-loader/menu-component-loader.component.ts create mode 100644 src/app/shared/mydspace-actions/claimed-task/claimed-task-type.ts delete mode 100644 src/app/shared/object-collection/shared/tabulatable-objects/tabulatable-objects-loader.component.html delete mode 100644 src/app/shared/object-collection/shared/tabulatable-objects/tabulatable-objects-loader.component.spec.ts delete mode 100644 src/app/shared/object-collection/shared/tabulatable-objects/tabulatable-objects.directive.ts create mode 100644 src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-type.ts diff --git a/src/app/admin/admin-ldn-services/ldn-services-data/ldn-itemfilters-data.service.ts b/src/app/admin/admin-ldn-services/ldn-services-data/ldn-itemfilters-data.service.ts index d64d2ed5cbc..8ed0a9dc81b 100644 --- a/src/app/admin/admin-ldn-services/ldn-services-data/ldn-itemfilters-data.service.ts +++ b/src/app/admin/admin-ldn-services/ldn-services-data/ldn-itemfilters-data.service.ts @@ -1,6 +1,7 @@ import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; +import { dataService } from '../../../core/cache/builders/build-decorators'; import { RemoteDataBuildService } from '../../../core/cache/builders/remote-data-build.service'; import { ObjectCacheService } from '../../../core/cache/object-cache.service'; import { @@ -15,12 +16,14 @@ import { RequestService } from '../../../core/data/request.service'; import { HALEndpointService } from '../../../core/shared/hal-endpoint.service'; import { NotificationsService } from '../../../shared/notifications/notifications.service'; import { FollowLinkConfig } from '../../../shared/utils/follow-link-config.model'; +import { LDN_SERVICE_CONSTRAINT_FILTERS } from '../ldn-services-model/ldn-service.resource-type'; import { Itemfilter } from '../ldn-services-model/ldn-service-itemfilters'; /** * A service responsible for fetching/sending data from/to the REST API on the itemfilters endpoint */ @Injectable({ providedIn: 'root' }) +@dataService(LDN_SERVICE_CONSTRAINT_FILTERS) export class LdnItemfiltersService extends IdentifiableDataService implements FindAllData { private findAllData: FindAllDataImpl; diff --git a/src/app/admin/admin-ldn-services/ldn-services-data/ldn-services-data.service.ts b/src/app/admin/admin-ldn-services/ldn-services-data/ldn-services-data.service.ts index 6e368dd6809..de40cfc1523 100644 --- a/src/app/admin/admin-ldn-services/ldn-services-data/ldn-services-data.service.ts +++ b/src/app/admin/admin-ldn-services/ldn-services-data/ldn-services-data.service.ts @@ -6,6 +6,7 @@ import { take, } from 'rxjs/operators'; +import { dataService } from '../../../core/cache/builders/build-decorators'; import { RemoteDataBuildService } from '../../../core/cache/builders/remote-data-build.service'; import { RequestParam } from '../../../core/cache/models/request-param.model'; import { ObjectCacheService } from '../../../core/cache/object-cache.service'; @@ -41,6 +42,7 @@ import { URLCombiner } from '../../../core/url-combiner/url-combiner'; import { NotificationsService } from '../../../shared/notifications/notifications.service'; import { FollowLinkConfig } from '../../../shared/utils/follow-link-config.model'; import { LdnServiceConstrain } from '../ldn-services-model/ldn-service.constrain.model'; +import { LDN_SERVICE } from '../ldn-services-model/ldn-service.resource-type'; import { LdnService } from '../ldn-services-model/ldn-services.model'; /** @@ -55,6 +57,7 @@ import { LdnService } from '../ldn-services-model/ldn-services.model'; * @implements {CreateData} */ @Injectable({ providedIn: 'root' }) +@dataService(LDN_SERVICE) export class LdnServicesService extends IdentifiableDataService implements FindAllData, DeleteData, PatchData, CreateData { createData: CreateDataImpl; private findAllData: FindAllDataImpl; diff --git a/src/app/admin/admin-notify-dashboard/admin-notify-search-result/admin-notify-search-result.component.ts b/src/app/admin/admin-notify-dashboard/admin-notify-search-result/admin-notify-search-result.component.ts index 7b0d3cd9335..c0ed3039133 100644 --- a/src/app/admin/admin-notify-dashboard/admin-notify-search-result/admin-notify-search-result.component.ts +++ b/src/app/admin/admin-notify-dashboard/admin-notify-search-result/admin-notify-search-result.component.ts @@ -17,8 +17,11 @@ import { } from 'rxjs'; import { PaginatedList } from '../../../core/data/paginated-list.model'; +import { Context } from '../../../core/shared/context.model'; import { SearchConfigurationService } from '../../../core/shared/search/search-configuration.service'; +import { ViewMode } from '../../../core/shared/view-mode.model'; import { SEARCH_CONFIG_SERVICE } from '../../../my-dspace-page/my-dspace-configuration.service'; +import { tabulatableObjectsComponent } from '../../../shared/object-collection/shared/tabulatable-objects/tabulatable-objects.decorator'; import { TabulatableResultListElementsComponent } from '../../../shared/object-list/search-result-list-element/tabulatable-search-result/tabulatable-result-list-elements.component'; import { TruncatableComponent } from '../../../shared/truncatable/truncatable.component'; import { TruncatablePartComponent } from '../../../shared/truncatable/truncatable-part/truncatable-part.component'; @@ -50,6 +53,7 @@ import { AdminNotifyMessagesService } from '../services/admin-notify-messages.se /** * Component for visualization in table format of the search results related to the AdminNotifyDashboardComponent */ +@tabulatableObjectsComponent(AdminNotifySearchResult, ViewMode.Table, Context.CoarNotify) export class AdminNotifySearchResultComponent extends TabulatableResultListElementsComponent, AdminNotifySearchResult> implements OnInit, OnDestroy{ public messagesSubject$: BehaviorSubject = new BehaviorSubject([]); public reprocessStatus = 'QUEUE_STATUS_QUEUED_FOR_RETRY'; diff --git a/src/app/admin/admin-notify-dashboard/models/admin-notify-message-search-result.model.ts b/src/app/admin/admin-notify-dashboard/models/admin-notify-message-search-result.model.ts index c4df75ef3ee..51151189936 100644 --- a/src/app/admin/admin-notify-dashboard/models/admin-notify-message-search-result.model.ts +++ b/src/app/admin/admin-notify-dashboard/models/admin-notify-message-search-result.model.ts @@ -1,5 +1,7 @@ import { SearchResult } from '../../../shared/search/models/search-result.model'; +import { searchResultFor } from '../../../shared/search/search-result-element-decorator'; import { AdminNotifyMessage } from './admin-notify-message.model'; +@searchResultFor(AdminNotifyMessage) export class AdminNotifySearchResult extends SearchResult { } diff --git a/src/app/admin/admin-notify-dashboard/services/admin-notify-messages.service.ts b/src/app/admin/admin-notify-dashboard/services/admin-notify-messages.service.ts index 2211facfc87..b6c7789ca90 100644 --- a/src/app/admin/admin-notify-dashboard/services/admin-notify-messages.service.ts +++ b/src/app/admin/admin-notify-dashboard/services/admin-notify-messages.service.ts @@ -13,6 +13,7 @@ import { tap, } from 'rxjs/operators'; +import { dataService } from '../../../core/cache/builders/build-decorators'; import { RemoteDataBuildService } from '../../../core/cache/builders/remote-data-build.service'; import { ObjectCacheService } from '../../../core/cache/object-cache.service'; import { IdentifiableDataService } from '../../../core/data/base/identifiable-data.service'; @@ -28,6 +29,7 @@ import { import { NotificationsService } from '../../../shared/notifications/notifications.service'; import { LdnServicesService } from '../../admin-ldn-services/ldn-services-data/ldn-services-data.service'; import { AdminNotifyMessage } from '../models/admin-notify-message.model'; +import { ADMIN_NOTIFY_MESSAGE } from '../models/admin-notify-message.resource-type'; /** * Injectable service responsible for fetching/sending data from/to the REST API on the messages' endpoint. @@ -37,6 +39,7 @@ import { AdminNotifyMessage } from '../models/admin-notify-message.model'; * @extends {IdentifiableDataService} */ @Injectable({ providedIn: 'root' }) +@dataService(ADMIN_NOTIFY_MESSAGE) export class AdminNotifyMessagesService extends IdentifiableDataService { protected reprocessEndpoint = 'enqueueretry'; diff --git a/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component.spec.ts b/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component.spec.ts index 7a6b58dea2f..3510dcfdf03 100644 --- a/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component.spec.ts +++ b/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component.spec.ts @@ -16,7 +16,6 @@ import { RemoteData } from '../../../../../core/data/remote-data'; import { Bitstream } from '../../../../../core/shared/bitstream.model'; import { FileService } from '../../../../../core/shared/file.service'; import { Item } from '../../../../../core/shared/item.model'; -import { ListableModule } from '../../../../../core/shared/listable.module'; import { ViewMode } from '../../../../../core/shared/view-mode.model'; import { mockTruncatableService } from '../../../../../shared/mocks/mock-trucatable.service'; import { getMockThemeService } from '../../../../../shared/mocks/theme-service.mock'; @@ -59,7 +58,6 @@ describe('ItemAdminSearchResultGridElementComponent', () => { NoopAnimationsModule, TranslateModule.forRoot(), RouterTestingModule.withRoutes([]), - ListableModule, ItemAdminSearchResultGridElementComponent, ], providers: [ diff --git a/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component.ts b/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component.ts index 362bd5d54e5..07c2a39bc6a 100644 --- a/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component.ts +++ b/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component.ts @@ -6,6 +6,11 @@ import { OnInit, ViewChild, } from '@angular/core'; +import { + from, + Observable, +} from 'rxjs'; +import { take } from 'rxjs/operators'; import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service'; import { BitstreamDataService } from '../../../../../core/data/bitstream-data.service'; @@ -60,25 +65,27 @@ export class ItemAdminSearchResultGridElementComponent extends SearchResultGridE */ ngOnInit(): void { super.ngOnInit(); - const component: GenericConstructor = this.getComponent(); + const component$: Observable> = from(this.getComponent()); - const viewContainerRef = this.dynamicComponentLoaderDirective.viewContainerRef; - viewContainerRef.clear(); + component$.pipe(take(1)).subscribe((component) => { + const viewContainerRef = this.dynamicComponentLoaderDirective.viewContainerRef; + viewContainerRef.clear(); - this.compRef = viewContainerRef.createComponent( - component, { - index: 0, - injector: undefined, - projectableNodes: [ - [this.badges.nativeElement], - [this.buttons.nativeElement], - ], - }, - ); - this.compRef.setInput('object',this.object); - this.compRef.setInput('index', this.index); - this.compRef.setInput('linkType', this.linkType); - this.compRef.setInput('listID', this.listID); + this.compRef = viewContainerRef.createComponent( + component, { + index: 0, + injector: undefined, + projectableNodes: [ + [this.badges.nativeElement], + [this.buttons.nativeElement], + ], + }, + ); + this.compRef.setInput('object',this.object); + this.compRef.setInput('index', this.index); + this.compRef.setInput('linkType', this.linkType); + this.compRef.setInput('listID', this.listID); + }); } ngOnDestroy(): void { @@ -92,7 +99,7 @@ export class ItemAdminSearchResultGridElementComponent extends SearchResultGridE * Fetch the component depending on the item's entity type, view mode and context * @returns {GenericConstructor} */ - private getComponent(): GenericConstructor { + private getComponent(): Promise> { return getListableObjectComponent(this.object.getRenderTypes(), ViewMode.GridElement, undefined, this.themeService.getThemeName()); } } diff --git a/src/app/admin/admin-sidebar/admin-sidebar-section/admin-sidebar-section.component.spec.ts b/src/app/admin/admin-sidebar/admin-sidebar-section/admin-sidebar-section.component.spec.ts index becdcbd17b0..418cb1da146 100644 --- a/src/app/admin/admin-sidebar/admin-sidebar-section/admin-sidebar-section.component.spec.ts +++ b/src/app/admin/admin-sidebar/admin-sidebar-section/admin-sidebar-section.component.spec.ts @@ -6,13 +6,16 @@ import { } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; -import { RouterTestingModule } from '@angular/router/testing'; +import { RouterModule } from '@angular/router'; import { TranslateModule } from '@ngx-translate/core'; import { MenuService } from '../../../shared/menu/menu.service'; +import { MenuSection } from '../../../shared/menu/menu-section.model'; +import { getMockThemeService } from '../../../shared/mocks/theme-service.mock'; import { CSSVariableService } from '../../../shared/sass-helper/css-variable.service'; import { CSSVariableServiceStub } from '../../../shared/testing/css-variable-service.stub'; import { MenuServiceStub } from '../../../shared/testing/menu-service.stub'; +import { ThemeService } from '../../../shared/theme-support/theme.service'; import { AdminSidebarSectionComponent } from './admin-sidebar-section.component'; describe('AdminSidebarSectionComponent', () => { @@ -25,11 +28,11 @@ describe('AdminSidebarSectionComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ - imports: [NoopAnimationsModule, RouterTestingModule, TranslateModule.forRoot(), AdminSidebarSectionComponent, TestComponent], + imports: [NoopAnimationsModule, RouterModule.forRoot([]), TranslateModule.forRoot(), AdminSidebarSectionComponent, TestComponent], providers: [ - { provide: 'sectionDataProvider', useValue: { model: { link: 'google.com' }, icon: iconString } }, { provide: MenuService, useValue: menuService }, { provide: CSSVariableService, useClass: CSSVariableServiceStub }, + { provide: ThemeService, useValue: getMockThemeService() }, ], }).compileComponents(); })); @@ -37,7 +40,14 @@ describe('AdminSidebarSectionComponent', () => { beforeEach(() => { fixture = TestBed.createComponent(AdminSidebarSectionComponent); component = fixture.componentInstance; - spyOn(component as any, 'getMenuItemComponent').and.returnValue(TestComponent); + component.section = { + model: { + link: 'google.com', + }, + icon: iconString, + } as MenuSection; + component.itemModel = component.section.model; + spyOn(component, 'getMenuItemComponent').and.returnValue(Promise.resolve(TestComponent)); fixture.detectChanges(); }); @@ -59,11 +69,11 @@ describe('AdminSidebarSectionComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ - imports: [NoopAnimationsModule, RouterTestingModule, TranslateModule.forRoot(), AdminSidebarSectionComponent, TestComponent], + imports: [NoopAnimationsModule, RouterModule.forRoot([]), TranslateModule.forRoot(), AdminSidebarSectionComponent, TestComponent], providers: [ - { provide: 'sectionDataProvider', useValue: { model: { link: 'google.com', disabled: true }, icon: iconString } }, { provide: MenuService, useValue: menuService }, { provide: CSSVariableService, useClass: CSSVariableServiceStub }, + { provide: ThemeService, useValue: getMockThemeService() }, ], }).compileComponents(); })); @@ -71,7 +81,15 @@ describe('AdminSidebarSectionComponent', () => { beforeEach(() => { fixture = TestBed.createComponent(AdminSidebarSectionComponent); component = fixture.componentInstance; - spyOn(component as any, 'getMenuItemComponent').and.returnValue(TestComponent); + component.section = { + model: { + link: 'google.com', + disabled: true, + }, + icon: iconString, + } as MenuSection; + component.itemModel = component.section.model; + spyOn(component, 'getMenuItemComponent').and.returnValue(Promise.resolve(TestComponent)); fixture.detectChanges(); }); @@ -97,7 +115,7 @@ describe('AdminSidebarSectionComponent', () => { template: ``, standalone: true, imports: [ - RouterTestingModule, + RouterModule, ], }) class TestComponent { diff --git a/src/app/admin/admin-sidebar/admin-sidebar-section/admin-sidebar-section.component.ts b/src/app/admin/admin-sidebar/admin-sidebar-section/admin-sidebar-section.component.ts index 1e0c4292b22..ebcb4741d62 100644 --- a/src/app/admin/admin-sidebar/admin-sidebar-section/admin-sidebar-section.component.ts +++ b/src/app/admin/admin-sidebar/admin-sidebar-section/admin-sidebar-section.component.ts @@ -1,8 +1,8 @@ import { NgClass } from '@angular/common'; import { Component, - Inject, Injector, + OnChanges, OnInit, } from '@angular/core'; import { @@ -14,9 +14,10 @@ import { TranslateModule } from '@ngx-translate/core'; import { isEmpty } from '../../../shared/empty.util'; import { MenuService } from '../../../shared/menu/menu.service'; import { MenuID } from '../../../shared/menu/menu-id.model'; -import { LinkMenuItemModel } from '../../../shared/menu/menu-item/models/link.model'; +import { rendersSectionForMenu } from '../../../shared/menu/menu-section.decorator'; import { MenuSection } from '../../../shared/menu/menu-section.model'; import { AbstractMenuSectionComponent } from '../../../shared/menu/menu-section/abstract-menu-section.component'; +import { ThemeService } from '../../../shared/theme-support/theme.service'; import { BrowserOnlyPipe } from '../../../shared/utils/browser-only.pipe'; /** @@ -35,13 +36,13 @@ import { BrowserOnlyPipe } from '../../../shared/utils/browser-only.pipe'; ], }) -export class AdminSidebarSectionComponent extends AbstractMenuSectionComponent implements OnInit { +@rendersSectionForMenu(MenuID.ADMIN, false) +export class AdminSidebarSectionComponent extends AbstractMenuSectionComponent implements OnInit, OnChanges { /** * This section resides in the Admin Sidebar */ menuID: MenuID = MenuID.ADMIN; - itemModel; /** * Boolean to indicate whether this section is disabled @@ -49,13 +50,16 @@ export class AdminSidebarSectionComponent extends AbstractMenuSectionComponent i isDisabled: boolean; constructor( - @Inject('sectionDataProvider') protected section: MenuSection, protected menuService: MenuService, protected injector: Injector, + protected themeService: ThemeService, protected router: Router, ) { - super(menuService, injector); - this.itemModel = section.model as LinkMenuItemModel; + super( + menuService, + injector, + themeService, + ); } ngOnInit(): void { diff --git a/src/app/admin/admin-sidebar/admin-sidebar.component.html b/src/app/admin/admin-sidebar/admin-sidebar.component.html index fc79c58b998..fc4d0ec08c3 100644 --- a/src/app/admin/admin-sidebar/admin-sidebar.component.html +++ b/src/app/admin/admin-sidebar/admin-sidebar.component.html @@ -26,9 +26,11 @@

{{ 'menu.header.admin' | translate }}

diff --git a/src/app/admin/admin-sidebar/admin-sidebar.component.spec.ts b/src/app/admin/admin-sidebar/admin-sidebar.component.spec.ts index 68f3cc550f0..5c0bc2eb5f1 100644 --- a/src/app/admin/admin-sidebar/admin-sidebar.component.spec.ts +++ b/src/app/admin/admin-sidebar/admin-sidebar.component.spec.ts @@ -101,7 +101,7 @@ describe('AdminSidebarComponent', () => { spyOn(menuService, 'getMenuTopSections').and.returnValue(of([])); fixture = TestBed.createComponent(AdminSidebarComponent); comp = fixture.componentInstance; // SearchPageComponent test instance - comp.sections = of([]); + comp.sectionDTOs$ = of([]); fixture.detectChanges(); }); diff --git a/src/app/admin/admin-sidebar/admin-sidebar.component.ts b/src/app/admin/admin-sidebar/admin-sidebar.component.ts index 6300c80c9d2..4d001eef862 100644 --- a/src/app/admin/admin-sidebar/admin-sidebar.component.ts +++ b/src/app/admin/admin-sidebar/admin-sidebar.component.ts @@ -1,7 +1,6 @@ import { AsyncPipe, NgClass, - NgComponentOutlet, } from '@angular/common'; import { Component, @@ -31,6 +30,7 @@ import { AuthorizationDataService } from '../../core/data/feature-authorization/ import { slideSidebar } from '../../shared/animations/slide'; import { MenuComponent } from '../../shared/menu/menu.component'; import { MenuService } from '../../shared/menu/menu.service'; +import { MenuComponentLoaderComponent } from '../../shared/menu/menu-component-loader/menu-component-loader.component'; import { MenuID } from '../../shared/menu/menu-id.model'; import { CSSVariableService } from '../../shared/sass-helper/css-variable.service'; import { ThemeService } from '../../shared/theme-support/theme.service'; @@ -48,9 +48,9 @@ import { BrowserOnlyPipe } from '../../shared/utils/browser-only.pipe'; imports: [ AsyncPipe, BrowserOnlyPipe, + MenuComponentLoaderComponent, NgbDropdownModule, NgClass, - NgComponentOutlet, TranslatePipe, ], }) diff --git a/src/app/admin/admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component.html b/src/app/admin/admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component.html index f3bf20fc343..edbbc477ae4 100644 --- a/src/app/admin/admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component.html +++ b/src/app/admin/admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component.html @@ -22,8 +22,9 @@