+
diff --git a/frontend/app/shared/components/schema-category.component.scss b/frontend/app/shared/components/schema-category.component.scss
index 9429da6dc..fb98cb9c2 100644
--- a/frontend/app/shared/components/schema-category.component.scss
+++ b/frontend/app/shared/components/schema-category.component.scss
@@ -74,26 +74,31 @@ $drag-margin: -8px;
}
}
-:host {
- &:first-child {
- .nav-light {
- margin-top: 0;
- }
- }
-}
-
.item-published {
margin-bottom: 1px;
}
-ul.nav-light {
- margin-left: 1rem;
- margin-right: 0;
- font-size: 100%;
-}
-
-.category,
+.nav-category,
.nav-panel,
sqx-schema-category {
max-width: 100%;
+}
+
+.categories {
+ padding: 0;
+ padding-left: 1rem;
+ font-size: 1rem;
+
+ .nav-light {
+ margin-left: 0;
+ margin-right: 0;
+ }
+}
+
+:host {
+ &:first-child {
+ .nav-light {
+ margin-top: 0;
+ }
+ }
}
\ No newline at end of file
diff --git a/frontend/app/shared/components/schema-category.component.ts b/frontend/app/shared/components/schema-category.component.ts
index 262c1586c..02cd0b852 100644
--- a/frontend/app/shared/components/schema-category.component.ts
+++ b/frontend/app/shared/components/schema-category.component.ts
@@ -28,14 +28,16 @@ export class SchemaCategoryComponent implements OnChanges {
public schemaCategory: SchemaCategory;
@Input()
- public forContent?: boolean | null;
+ public schemaTarget?: 'Schema' | 'Contents';
public isCollapsed = false;
- public visibleCount = 0;
+ public get forContent() {
+ return this.schemaTarget === 'Contents';
+ }
public get schemas() {
- return this.schemaCategory.schemas;
+ return this.schemaCategory.schemasFiltered;
}
constructor(
@@ -51,19 +53,13 @@ export class SchemaCategoryComponent implements OnChanges {
}
public ngOnChanges() {
- this.visibleCount = this.getCount(this.schemaCategory);
- if (this.schemaCategory.schemas.length < this.schemaCategory.schemaTotalCount) {
+ if (this.schemaCategory.countSchemasInSubtreeFiltered < this.schemaCategory.countSchemasInSubtree) {
this.isCollapsed = false;
} else {
this.isCollapsed = this.localStore.getBoolean(this.configKey());
}
}
- private getCount(category: SchemaCategory): number {
- const childCount = category.categories.reduce((total, child) => total + this.getCount(child), 0);
- return childCount + category.schemas.length;
- }
-
public schemaRoute(schema: SchemaDto) {
if (schema.type === 'Singleton' && this.forContent) {
return [schema.name, schema.id, 'history'];
diff --git a/frontend/app/shared/services/schemas.service.spec.ts b/frontend/app/shared/services/schemas.service.spec.ts
index 0277c1bc3..585765878 100644
--- a/frontend/app/shared/services/schemas.service.spec.ts
+++ b/frontend/app/shared/services/schemas.service.spec.ts
@@ -818,15 +818,12 @@ function createSchemaProperties(id: number, suffix = '') {
);
}
-export function createSchema(id: number, suffix = '', category = '') {
+export function createSchema(id: number, suffix = '') {
const links: ResourceLinks = {
update: { method: 'PUT', href: `/schemas/${id}` },
};
const key = `${id}${suffix}`;
- if (category === '') {
- category = `schema-category${key}`;
- }
return new SchemaDto(links,
`id${id}`,
@@ -834,7 +831,7 @@ export function createSchema(id: number, suffix = '', category = '') {
DateTime.parseISO(`${id % 1000 + 2000}-11-11T10:10:00Z`), `modifier${id}`,
new Version(key),
`schema-name${key}`,
- category,
+ `schema-category${key}`,
id % 2 === 0 ? 'Default' : 'Singleton',
id % 3 === 0,
createSchemaProperties(id, suffix),
diff --git a/frontend/app/shared/state/schemas.state.spec.ts b/frontend/app/shared/state/schemas.state.spec.ts
index 2fc8a8144..48d5bfc36 100644
--- a/frontend/app/shared/state/schemas.state.spec.ts
+++ b/frontend/app/shared/state/schemas.state.spec.ts
@@ -494,14 +494,74 @@ describe('SchemasState', () => {
});
describe('Categories', () => {
- it('should be build from schemas', () => {
+ it('should be build from schemas with undefined categories', () => {
+ const schemaDefault = createSchema(6);
+ const schemaComponent = createSchema(7);
+
+ (schemaDefault as any)['category'] = '';
+ (schemaComponent as any)['category'] = '';
+ (schemaComponent as any)['type'] = 'Component';
+
+ const result = getCategoryTree([schemaDefault, schemaComponent], new Set
());
+
+ expect(result).toEqual([
+ {
+ displayName: 'i18n:common.components',
+ schemas: [schemaComponent],
+ schemasFiltered: [schemaComponent],
+ countSchemasInSubtree: 1,
+ countSchemasInSubtreeFiltered: 1,
+ categories: [],
+ },
+ {
+ displayName: 'i18n:common.schemas',
+ schemas: [schemaDefault],
+ schemasFiltered: [schemaDefault],
+ countSchemasInSubtree: 1,
+ countSchemasInSubtreeFiltered: 1,
+ categories: [],
+ },
+ ]);
+ });
+
+ it('should be build from schemas with defined categories', () => {
const result = getCategoryTree([schema1, schema2], new Set());
expect(result).toEqual([
- { displayName: 'i18n:common.components', schemas: [], schemaTotalCount: 0, categories: [] },
- { displayName: 'i18n:common.schemas', schemas: [], schemaTotalCount: 0, categories: [] },
- { displayName: 'schema-category1', name: 'schema-category1', schemas: [schema1], schemaTotalCount: 1, categories: [] },
- { displayName: 'schema-category2', name: 'schema-category2', schemas: [schema2], schemaTotalCount: 1, categories: [] },
+ {
+ displayName: 'i18n:common.components',
+ schemas: [],
+ schemasFiltered: [],
+ countSchemasInSubtree: 0,
+ countSchemasInSubtreeFiltered: 0,
+ categories: [],
+ },
+ {
+ displayName: 'i18n:common.schemas',
+ schemas: [],
+ schemasFiltered: [],
+ countSchemasInSubtree: 0,
+ countSchemasInSubtreeFiltered: 0,
+ categories: [],
+ },
+ {
+ displayName: 'schema-category1',
+ name: 'schema-category1',
+ schemas: [schema1],
+ schemasFiltered: [schema1],
+ countSchemasInSubtree: 1,
+ countSchemasInSubtreeFiltered: 1,
+ categories: [],
+ },
+ {
+ displayName: 'schema-category2',
+ name: 'schema-category2',
+ schemas: [schema2],
+ schemasFiltered: [schema2],
+ countSchemasInSubtree: 1,
+ countSchemasInSubtreeFiltered: 1,
+ categories: [],
+ },
]);
});
@@ -509,11 +569,49 @@ describe('SchemasState', () => {
const result = getCategoryTree([schema1, schema2], new Set(['schema-category3']));
expect(result).toEqual([
- { displayName: 'i18n:common.components', schemas: [], schemaTotalCount: 0, categories: [] },
- { displayName: 'i18n:common.schemas', schemas: [], schemaTotalCount: 0, categories: [] },
- { displayName: 'schema-category1', name: 'schema-category1', schemas: [schema1], schemaTotalCount: 1, categories: [] },
- { displayName: 'schema-category2', name: 'schema-category2', schemas: [schema2], schemaTotalCount: 1, categories: [] },
- { displayName: 'schema-category3', name: 'schema-category3', schemas: [], schemaTotalCount: 0, categories: [] },
+ {
+ displayName: 'i18n:common.components',
+ schemas: [],
+ schemasFiltered: [],
+ countSchemasInSubtree: 0,
+ countSchemasInSubtreeFiltered: 0,
+ categories: [],
+ },
+ {
+ displayName: 'i18n:common.schemas',
+ schemas: [],
+ schemasFiltered: [],
+ countSchemasInSubtree: 0,
+ countSchemasInSubtreeFiltered: 0,
+ categories: [],
+ },
+ {
+ displayName: 'schema-category1',
+ name: 'schema-category1',
+ schemas: [schema1],
+ schemasFiltered: [schema1],
+ countSchemasInSubtree: 1,
+ countSchemasInSubtreeFiltered: 1,
+ categories: [],
+ },
+ {
+ displayName: 'schema-category2',
+ name: 'schema-category2',
+ schemas: [schema2],
+ schemasFiltered: [schema2],
+ countSchemasInSubtree: 1,
+ countSchemasInSubtreeFiltered: 1,
+ categories: [],
+ },
+ {
+ displayName: 'schema-category3',
+ name: 'schema-category3',
+ schemas: [],
+ schemasFiltered: [],
+ countSchemasInSubtree: 0,
+ countSchemasInSubtreeFiltered: 0,
+ categories: [],
+ },
]);
});
@@ -521,24 +619,100 @@ describe('SchemasState', () => {
const result = getCategoryTree([schema1, schema2], new Set(), '1');
expect(result).toEqual([
- { displayName: 'i18n:common.components', schemas: [], schemaTotalCount: 0, categories: [] },
- { displayName: 'i18n:common.schemas', schemas: [], schemaTotalCount: 0, categories: [] },
- { displayName: 'schema-category1', name: 'schema-category1', schemas: [schema1], schemaTotalCount: 1, categories: [] },
- { displayName: 'schema-category2', name: 'schema-category2', schemas: [], schemaTotalCount: 1, categories: [] }, // Filtered out
+ {
+ displayName: 'i18n:common.components',
+ schemas: [],
+ schemasFiltered: [],
+ countSchemasInSubtree: 0,
+ countSchemasInSubtreeFiltered: 0,
+ categories: [],
+ },
+ {
+ displayName: 'i18n:common.schemas',
+ schemas: [],
+ schemasFiltered: [],
+ countSchemasInSubtree: 0,
+ countSchemasInSubtreeFiltered: 0,
+ categories: [],
+ },
+ {
+ displayName: 'schema-category1',
+ name: 'schema-category1',
+ schemas: [schema1],
+ schemasFiltered: [schema1],
+ countSchemasInSubtree: 1,
+ countSchemasInSubtreeFiltered: 1,
+ categories: [],
+ },
+ {
+ displayName: 'schema-category2',
+ name: 'schema-category2',
+ schemas: [schema2],
+ schemasFiltered: [],
+ countSchemasInSubtree: 1,
+ countSchemasInSubtreeFiltered: 0,
+ categories: [],
+ },
]);
});
it('should be build from schemas with nested categories', () => {
- const schema3 = createSchema(3, '', 'A');
- const schema4 = createSchema(4, '', 'A/B');
- const result = getCategoryTree([schema1, schema2, schema3, schema4], new Set());
+ const schemaA = createSchema(3);
+ const schemaAB = createSchema(4);
+
+ (schemaA as any)['category'] = 'A';
+ (schemaAB as any)['category'] = 'A/B';
+
+ const result = getCategoryTree([schema1, schema2, schemaA, schemaAB], new Set());
expect(result).toEqual([
- { displayName: 'i18n:common.components', schemas: [], schemaTotalCount: 0, categories: [] },
- { displayName: 'i18n:common.schemas', schemas: [], schemaTotalCount: 0, categories: [] },
- { displayName: 'A', name: 'A', schemas: [schema3], schemaTotalCount: 2, categories: [{ displayName: 'B', name: 'A/B', schemas: [schema4], schemaTotalCount: 1, categories: [] }] },
- { displayName: 'schema-category1', name: 'schema-category1', schemas: [schema1], schemaTotalCount: 1, categories: [] },
- { displayName: 'schema-category2', name: 'schema-category2', schemas: [schema2], schemaTotalCount: 1, categories: [] },
+ {
+ displayName: 'i18n:common.components',
+ schemas: [],
+ schemasFiltered: [],
+ countSchemasInSubtree: 0,
+ countSchemasInSubtreeFiltered: 0,
+ categories: [],
+ }, {
+ displayName: 'i18n:common.schemas',
+ schemas: [],
+ schemasFiltered: [],
+ countSchemasInSubtree: 0,
+ countSchemasInSubtreeFiltered: 0,
+ categories: [],
+ }, {
+ displayName: 'A',
+ name: 'A',
+ schemas: [schemaA],
+ schemasFiltered: [schemaA],
+ countSchemasInSubtree: 2,
+ countSchemasInSubtreeFiltered: 2,
+ categories: [{
+ displayName: 'B',
+ name: 'A/B',
+ schemas: [schemaAB],
+ schemasFiltered: [schemaAB],
+ countSchemasInSubtree: 1,
+ countSchemasInSubtreeFiltered: 1,
+ categories: [],
+ }],
+ }, {
+ displayName: 'schema-category1',
+ name: 'schema-category1',
+ schemas: [schema1],
+ schemasFiltered: [schema1],
+ countSchemasInSubtree: 1,
+ countSchemasInSubtreeFiltered: 1,
+ categories: [],
+ }, {
+ displayName: 'schema-category2',
+ name: 'schema-category2',
+ schemas: [schema2],
+ schemasFiltered: [schema2],
+ countSchemasInSubtree: 1,
+ countSchemasInSubtreeFiltered: 1,
+ categories: [],
+ },
]);
});
@@ -546,26 +720,115 @@ describe('SchemasState', () => {
const result = getCategoryTree([schema1, schema2], new Set(['A/B']));
expect(result).toEqual([
- { displayName: 'i18n:common.components', schemas: [], schemaTotalCount: 0, categories: [] },
- { displayName: 'i18n:common.schemas', schemas: [], schemaTotalCount: 0, categories: [] },
- { displayName: 'A', name: 'A', schemas: [], schemaTotalCount: 0, categories: [{ displayName: 'B', name: 'A/B', schemas: [], schemaTotalCount: 0, categories: [] }] },
- { displayName: 'schema-category1', name: 'schema-category1', schemas: [schema1], schemaTotalCount: 1, categories: [] },
- { displayName: 'schema-category2', name: 'schema-category2', schemas: [schema2], schemaTotalCount: 1, categories: [] },
+ {
+ displayName: 'i18n:common.components',
+ schemas: [],
+ schemasFiltered: [],
+ countSchemasInSubtree: 0,
+ countSchemasInSubtreeFiltered: 0,
+ categories: [],
+ }, {
+ displayName: 'i18n:common.schemas',
+ schemas: [],
+ schemasFiltered: [],
+ countSchemasInSubtree: 0,
+ countSchemasInSubtreeFiltered: 0,
+ categories: [],
+ }, {
+ displayName: 'A',
+ name: 'A',
+ schemas: [],
+ schemasFiltered: [],
+ countSchemasInSubtree: 0,
+ countSchemasInSubtreeFiltered: 0,
+ categories: [{
+ displayName: 'B',
+ name: 'A/B',
+ schemas: [],
+ schemasFiltered: [],
+ countSchemasInSubtree: 0,
+ countSchemasInSubtreeFiltered: 0,
+ categories: [],
+ }],
+ }, {
+ displayName: 'schema-category1',
+ name: 'schema-category1',
+ schemas: [schema1],
+ schemasFiltered: [schema1],
+ countSchemasInSubtree: 1,
+ countSchemasInSubtreeFiltered: 1,
+ categories: [],
+ }, {
+ displayName: 'schema-category2',
+ name: 'schema-category2',
+ schemas: [schema2],
+ schemasFiltered: [schema2],
+ countSchemasInSubtree: 1,
+ countSchemasInSubtreeFiltered: 1,
+ categories: [],
+ },
]);
});
});
it('should be build from schemas with nested categories and filter', () => {
- const schema3 = createSchema(3, '', 'A');
- const schema4 = createSchema(4, '', 'A/B');
- const result = getCategoryTree([schema1, schema2, schema3, schema4], new Set(), '4');
+ const schemaA = createSchema(3);
+ const schemaAB = createSchema(4);
+
+ (schemaA as any)['category'] = 'A';
+ (schemaAB as any)['category'] = 'A/B';
+
+ const result = getCategoryTree([schema1, schema2, schemaA, schemaAB], new Set(), '4');
expect(result).toEqual([
- { displayName: 'i18n:common.components', schemas: [], schemaTotalCount: 0, categories: [] },
- { displayName: 'i18n:common.schemas', schemas: [], schemaTotalCount: 0, categories: [] },
- { displayName: 'A', name: 'A', schemas: [], schemaTotalCount: 2, categories: [{ displayName: 'B', name: 'A/B', schemas: [schema4], schemaTotalCount: 1, categories: [] }] },
- { displayName: 'schema-category1', name: 'schema-category1', schemas: [], schemaTotalCount: 1, categories: [] },
- { displayName: 'schema-category2', name: 'schema-category2', schemas: [], schemaTotalCount: 1, categories: [] },
+ {
+ displayName: 'i18n:common.components',
+ schemas: [],
+ schemasFiltered: [],
+ countSchemasInSubtree: 0,
+ countSchemasInSubtreeFiltered: 0,
+ categories: [],
+ }, {
+ displayName: 'i18n:common.schemas',
+ schemas: [],
+ schemasFiltered: [],
+ countSchemasInSubtree: 0,
+ countSchemasInSubtreeFiltered: 0,
+ categories: [],
+ },
+ {
+ displayName: 'A',
+ name: 'A',
+ schemas: [schemaA],
+ schemasFiltered: [],
+ countSchemasInSubtree: 2,
+ countSchemasInSubtreeFiltered: 1,
+ categories: [{
+ displayName: 'B',
+ name: 'A/B',
+ schemas: [schemaAB],
+ schemasFiltered: [schemaAB],
+ countSchemasInSubtree: 1,
+ countSchemasInSubtreeFiltered: 1,
+ categories: [],
+ }],
+ }, {
+ displayName: 'schema-category1',
+ name: 'schema-category1',
+ schemas: [schema1],
+ schemasFiltered: [],
+ countSchemasInSubtree: 1,
+ countSchemasInSubtreeFiltered: 0,
+ categories: [],
+ }, {
+ displayName: 'schema-category2',
+ name: 'schema-category2',
+ schemas: [schema2],
+ schemasFiltered: [],
+ countSchemasInSubtree: 1,
+ countSchemasInSubtreeFiltered: 0,
+ categories: [],
+ },
]);
});
});
diff --git a/frontend/app/shared/state/schemas.state.ts b/frontend/app/shared/state/schemas.state.ts
index ba4ef3c3a..c1f1a839d 100644
--- a/frontend/app/shared/state/schemas.state.ts
+++ b/frontend/app/shared/state/schemas.state.ts
@@ -367,7 +367,9 @@ export type SchemaCategory = {
displayName: string;
name?: string;
schemas: SchemaDto[];
- schemaTotalCount: number;
+ schemasFiltered: SchemaDto[];
+ countSchemasInSubtree: number;
+ countSchemasInSubtreeFiltered: number;
categories: SchemaCategory[];
};
@@ -394,52 +396,72 @@ export function getCategoryTree(allSchemas: ReadonlyArray, categories
const schemas: SchemaCategory = {
displayName: SPECIAL_SCHEMAS,
schemas: [],
- schemaTotalCount: 0,
+ schemasFiltered: [],
+ countSchemasInSubtree: 0,
+ countSchemasInSubtreeFiltered: 0,
categories: [],
};
const components: SchemaCategory = {
displayName: SPECIAL_COMPONENTS,
schemas: [],
- schemaTotalCount: 0,
+ schemasFiltered: [],
+ countSchemasInSubtree: 0,
+ countSchemasInSubtreeFiltered: 0,
categories: [],
};
- const flatCategoryList: SchemaCategory[] = [schemas, components];
+ const categoryCache: { [name: string]: SchemaCategory } = {};
+ const categoryRoots: SchemaCategory[] = [];
+ // Sort the categories so that components and schemas stay top.
for (const name of categories) {
getOrCreateCategory(name);
}
function addSchemaToCategory(schema: SchemaDto, category: SchemaCategory) {
- category.schemaTotalCount++;
+ category.schemas.push(schema);
if (match(schema)) {
- category.schemas.push(schema);
+ category.schemasFiltered.push(schema);
}
}
function getOrCreateCategory(name: string): SchemaCategory {
- let category = flatCategoryList.find(x => x.name === name);
+ let category = categoryCache[name];
- const displayName = (name.indexOf(NESTED_CATEGORY_SEPARATOR) === -1) ? name : name.substr(name.lastIndexOf(NESTED_CATEGORY_SEPARATOR) + 1);
+ if (category) {
+ return category;
+ }
- if (!category) {
- category = {
- displayName,
- name,
- schemas: [],
- schemaTotalCount: 0,
- categories: [],
- };
+ let displayName = name;
- flatCategoryList.push(category);
+ const lastSeparatorIndex = name.lastIndexOf(NESTED_CATEGORY_SEPARATOR);
- if (name.indexOf(NESTED_CATEGORY_SEPARATOR) !== -1) {
- // Recurse back creating all the parents of this category
- const parentName = name.substr(0, name.lastIndexOf(NESTED_CATEGORY_SEPARATOR));
- getOrCreateCategory(parentName);
- }
+ if (lastSeparatorIndex >= 0) {
+ displayName = displayName.substr(lastSeparatorIndex + 1);
+ }
+
+ category = {
+ displayName,
+ name,
+ schemas: [],
+ schemasFiltered: [],
+ countSchemasInSubtree: 0,
+ countSchemasInSubtreeFiltered: 0,
+ categories: [],
+ };
+
+ categoryCache[name] = category;
+
+ if (lastSeparatorIndex >= 0) {
+ // Recurse back creating all the parents of this category
+ const parentName = name.substr(0, lastSeparatorIndex);
+ const parentCategory = getOrCreateCategory(parentName);
+
+ parentCategory.categories.push(category);
+ } else {
+ categoryRoots.push(category);
}
return category;
@@ -449,8 +471,7 @@ export function getCategoryTree(allSchemas: ReadonlyArray, categories
const name = schema.category;
if (name) {
- const category = getOrCreateCategory(name);
- addSchemaToCategory(schema, category);
+ addSchemaToCategory(schema, getOrCreateCategory(name));
} else if (schema.type === 'Component') {
addSchemaToCategory(schema, components);
} else {
@@ -458,28 +479,29 @@ export function getCategoryTree(allSchemas: ReadonlyArray, categories
}
}
- // Sort by name and than DisplayName so that children get correctly sorted under their parents but component and schema still sort correctly
- flatCategoryList.sortByString(x => `${x.name ?? ''} - ${x.displayName}`);
-
- const result: SchemaCategory[] = [];
- // Child categories by necessity come after their parents alphabetically so processing in reverse lets us roll up all categories into their parents nicely.
- // Because we're processing in reverse we unshift rather than push to the results array to get everything in the right order at the end.
- for (const category of flatCategoryList.reverse()) {
- if (category.name) {
- if (category.name?.indexOf(NESTED_CATEGORY_SEPARATOR) !== -1) {
- const parentName = category.name?.substr(0, category.name.lastIndexOf(NESTED_CATEGORY_SEPARATOR));
- const parentCategory = flatCategoryList.find(x => x.name === parentName);
- if (parentCategory) {
- parentCategory.categories.unshift(category);
- parentCategory.schemaTotalCount += category.schemaTotalCount;
- }
- } else {
- result.unshift(category);
- }
- } else {
- result.unshift(category);
+ function update(category: SchemaCategory) {
+ category.countSchemasInSubtree = category.schemas.length;
+ category.countSchemasInSubtreeFiltered = category.schemasFiltered.length;
+
+ // Add up the total count of the whole tree.
+ for (const child of category.categories) {
+ update(child);
+
+ category.countSchemasInSubtree += child.countSchemasInSubtree;
+ category.countSchemasInSubtreeFiltered += child.countSchemasInSubtreeFiltered;
}
+
+ // Sort each category category individually because sorting is O(log N).
+ category.categories.sortByString(x => x.displayName);
+ }
+
+ // Sort by name and then add components and schemas, so that both categories are on top.
+ categoryRoots.sortByString(x => x.displayName);
+ categoryRoots.unshift(components, schemas);
+
+ for (const child of categoryRoots) {
+ update(child);
}
- return result;
+ return categoryRoots;
}
diff --git a/frontend/app/theme/_panels2.scss b/frontend/app/theme/_panels2.scss
index 6d7ece1e6..bff9a1516 100644
--- a/frontend/app/theme/_panels2.scss
+++ b/frontend/app/theme/_panels2.scss
@@ -304,7 +304,7 @@
.nav-heading {
color: $color-text-decent;
- margin-bottom: .5rem;
+ margin-bottom: 0;
margin-top: 1rem;
}
}
\ No newline at end of file