Refactor executable IDs to use strings #262
This commit unifies executable ID structure across categories and scripts, paving the way for more complex ID solutions for #262. It also refactors related code to adapt to the changes. Key changes: - Change numeric IDs to string IDs for categories - Use named types for string IDs to improve code clarity - Add unit tests to verify ID uniqueness Other supporting changes: - Separate concerns in entities for data access and executables by using separate abstractions (`Identifiable` and `RepositoryEntity`) - Simplify usage and construction of entities. - Remove `BaseEntity` for simplicity. - Move creation of categories/scripts to domain layer - Refactor CategoryCollection for better validation logic isolation - Rename some categories to keep the names (used as pseudo-IDs) unique on Windows.
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import type { Script } from '@/domain/Executables/Script/Script';
|
||||
import { RecommendationLevel } from '@/domain/Executables/Script/RecommendationLevel';
|
||||
import { scrambledEqual } from '@/application/Common/Array';
|
||||
import type { ICategoryCollection } from '@/domain/ICategoryCollection';
|
||||
import type { ICategoryCollection } from '@/domain/Collection/ICategoryCollection';
|
||||
import type { ReadonlyScriptSelection, ScriptSelection } from '@/application/Context/State/Selection/Script/ScriptSelection';
|
||||
import type { SelectedScript } from '@/application/Context/State/Selection/Script/SelectedScript';
|
||||
import { RecommendationStatusType } from './RecommendationStatusType';
|
||||
@@ -99,6 +99,6 @@ function areAllSelected(
|
||||
if (expectedScripts.length < selectedScriptIds.length) {
|
||||
return false;
|
||||
}
|
||||
const expectedScriptIds = expectedScripts.map((script) => script.id);
|
||||
const expectedScriptIds = expectedScripts.map((script) => script.executableId);
|
||||
return scrambledEqual(selectedScriptIds, expectedScriptIds);
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ import {
|
||||
} from 'vue';
|
||||
import { injectKey } from '@/presentation/injectionSymbols';
|
||||
import TooltipWrapper from '@/presentation/components/Shared/TooltipWrapper.vue';
|
||||
import type { ICategoryCollection } from '@/domain/ICategoryCollection';
|
||||
import type { ICategoryCollection } from '@/domain/Collection/ICategoryCollection';
|
||||
import MenuOptionList from '../MenuOptionList.vue';
|
||||
import MenuOptionListItem from '../MenuOptionListItem.vue';
|
||||
import { setCurrentRecommendationStatus, getCurrentRecommendationStatus } from './RecommendationStatusHandler';
|
||||
|
||||
@@ -44,6 +44,7 @@ import {
|
||||
} from 'vue';
|
||||
import { injectKey } from '@/presentation/injectionSymbols';
|
||||
import SizeObserver from '@/presentation/components/Shared/SizeObserver.vue';
|
||||
import type { ExecutableId } from '@/domain/Executables/Identifiable';
|
||||
import { hasDirective } from './NonCollapsingDirective';
|
||||
import CardListItem from './CardListItem.vue';
|
||||
|
||||
@@ -58,12 +59,12 @@ export default defineComponent({
|
||||
|
||||
const width = ref<number | undefined>();
|
||||
|
||||
const categoryIds = computed<readonly number[]>(
|
||||
() => currentState.value.collection.actions.map((category) => category.id),
|
||||
const categoryIds = computed<readonly ExecutableId[]>(
|
||||
() => currentState.value.collection.actions.map((category) => category.executableId),
|
||||
);
|
||||
const activeCategoryId = ref<number | undefined>(undefined);
|
||||
const activeCategoryId = ref<ExecutableId | undefined>(undefined);
|
||||
|
||||
function onSelected(categoryId: number, isExpanded: boolean) {
|
||||
function onSelected(categoryId: ExecutableId, isExpanded: boolean) {
|
||||
activeCategoryId.value = isExpanded ? categoryId : undefined;
|
||||
}
|
||||
|
||||
|
||||
@@ -56,12 +56,14 @@
|
||||
<script lang="ts">
|
||||
import {
|
||||
defineComponent, computed, shallowRef,
|
||||
type PropType,
|
||||
} from 'vue';
|
||||
import AppIcon from '@/presentation/components/Shared/Icon/AppIcon.vue';
|
||||
import FlatButton from '@/presentation/components/Shared/FlatButton.vue';
|
||||
import { injectKey } from '@/presentation/injectionSymbols';
|
||||
import ScriptsTree from '@/presentation/components/Scripts/View/Tree/ScriptsTree.vue';
|
||||
import { sleep } from '@/infrastructure/Threading/AsyncSleep';
|
||||
import type { ExecutableId } from '@/domain/Executables/Identifiable';
|
||||
import CardSelectionIndicator from './CardSelectionIndicator.vue';
|
||||
import CardExpandTransition from './CardExpandTransition.vue';
|
||||
import CardExpansionArrow from './CardExpansionArrow.vue';
|
||||
@@ -77,11 +79,11 @@ export default defineComponent({
|
||||
},
|
||||
props: {
|
||||
categoryId: {
|
||||
type: Number,
|
||||
type: String as PropType<ExecutableId>,
|
||||
required: true,
|
||||
},
|
||||
activeCategoryId: {
|
||||
type: Number,
|
||||
type: String as PropType<ExecutableId>,
|
||||
default: undefined,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -12,11 +12,12 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed } from 'vue';
|
||||
import { defineComponent, computed, type PropType } from 'vue';
|
||||
import AppIcon from '@/presentation/components/Shared/Icon/AppIcon.vue';
|
||||
import { injectKey } from '@/presentation/injectionSymbols';
|
||||
import type { Category } from '@/domain/Executables/Category/Category';
|
||||
import type { ICategoryCollection } from '@/domain/ICategoryCollection';
|
||||
import type { ICategoryCollection } from '@/domain/Collection/ICategoryCollection';
|
||||
import type { ExecutableId } from '@/domain/Executables/Identifiable';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
@@ -24,7 +25,7 @@ export default defineComponent({
|
||||
},
|
||||
props: {
|
||||
categoryId: {
|
||||
type: Number,
|
||||
type: String as PropType<ExecutableId>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import type { ExecutableId } from '@/domain/Executables/Identifiable';
|
||||
|
||||
export enum NodeType {
|
||||
Script,
|
||||
Category,
|
||||
}
|
||||
|
||||
export interface NodeMetadata {
|
||||
readonly id: string;
|
||||
readonly executableId: ExecutableId;
|
||||
readonly text: string;
|
||||
readonly isReversible: boolean;
|
||||
readonly docs: ReadonlyArray<string>;
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
} from 'vue';
|
||||
import { injectKey } from '@/presentation/injectionSymbols';
|
||||
import type { NodeMetadata } from '@/presentation/components/Scripts/View/Tree/NodeContent/NodeMetadata';
|
||||
import type { ICategoryCollection } from '@/domain/ICategoryCollection';
|
||||
import type { ICategoryCollection } from '@/domain/Collection/ICategoryCollection';
|
||||
import { getReverter } from './Reverter/ReverterFactory';
|
||||
import ToggleSwitch from './ToggleSwitch.vue';
|
||||
import type { Reverter } from './Reverter/Reverter';
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
import type { UserSelection } from '@/application/Context/State/Selection/UserSelection';
|
||||
import type { ICategoryCollection } from '@/domain/ICategoryCollection';
|
||||
import type { ICategoryCollection } from '@/domain/Collection/ICategoryCollection';
|
||||
import type { SelectedScript } from '@/application/Context/State/Selection/Script/SelectedScript';
|
||||
import { getCategoryId } from '../../TreeViewAdapter/CategoryNodeMetadataConverter';
|
||||
import type { ExecutableId } from '@/domain/Executables/Identifiable';
|
||||
import { createExecutableIdFromNodeId } from '../../TreeViewAdapter/CategoryNodeMetadataConverter';
|
||||
import { ScriptReverter } from './ScriptReverter';
|
||||
import type { Reverter } from './Reverter';
|
||||
import type { TreeNodeId } from '../../TreeView/Node/TreeNode';
|
||||
|
||||
export class CategoryReverter implements Reverter {
|
||||
private readonly categoryId: number;
|
||||
private readonly categoryId: ExecutableId;
|
||||
|
||||
private readonly scriptReverters: ReadonlyArray<ScriptReverter>;
|
||||
|
||||
constructor(nodeId: string, collection: ICategoryCollection) {
|
||||
this.categoryId = getCategoryId(nodeId);
|
||||
constructor(nodeId: TreeNodeId, collection: ICategoryCollection) {
|
||||
this.categoryId = createExecutableIdFromNodeId(nodeId);
|
||||
this.scriptReverters = createScriptReverters(this.categoryId, collection);
|
||||
}
|
||||
|
||||
@@ -37,12 +39,12 @@ export class CategoryReverter implements Reverter {
|
||||
}
|
||||
|
||||
function createScriptReverters(
|
||||
categoryId: number,
|
||||
categoryId: ExecutableId,
|
||||
collection: ICategoryCollection,
|
||||
): ScriptReverter[] {
|
||||
const category = collection.getCategory(categoryId);
|
||||
const scripts = category
|
||||
.getAllScriptsRecursively()
|
||||
.filter((script) => script.canRevert());
|
||||
return scripts.map((script) => new ScriptReverter(script.id));
|
||||
return scripts.map((script) => new ScriptReverter(script.executableId));
|
||||
}
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
import type { ICategoryCollection } from '@/domain/ICategoryCollection';
|
||||
import type { ICategoryCollection } from '@/domain/Collection/ICategoryCollection';
|
||||
import { type NodeMetadata, NodeType } from '../NodeMetadata';
|
||||
import { ScriptReverter } from './ScriptReverter';
|
||||
import { CategoryReverter } from './CategoryReverter';
|
||||
import type { Reverter } from './Reverter';
|
||||
|
||||
export function getReverter(node: NodeMetadata, collection: ICategoryCollection): Reverter {
|
||||
export function getReverter(
|
||||
node: NodeMetadata,
|
||||
collection: ICategoryCollection,
|
||||
): Reverter {
|
||||
switch (node.type) {
|
||||
case NodeType.Category:
|
||||
return new CategoryReverter(node.id, collection);
|
||||
return new CategoryReverter(node.executableId, collection);
|
||||
case NodeType.Script:
|
||||
return new ScriptReverter(node.id);
|
||||
return new ScriptReverter(node.executableId);
|
||||
default:
|
||||
throw new Error('Unknown script type');
|
||||
}
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import type { UserSelection } from '@/application/Context/State/Selection/UserSelection';
|
||||
import type { SelectedScript } from '@/application/Context/State/Selection/Script/SelectedScript';
|
||||
import { getScriptId } from '../../TreeViewAdapter/CategoryNodeMetadataConverter';
|
||||
import type { ExecutableId } from '@/domain/Executables/Identifiable';
|
||||
import { createExecutableIdFromNodeId } from '../../TreeViewAdapter/CategoryNodeMetadataConverter';
|
||||
import type { Reverter } from './Reverter';
|
||||
import type { TreeNodeId } from '../../TreeView/Node/TreeNode';
|
||||
|
||||
export class ScriptReverter implements Reverter {
|
||||
private readonly scriptId: string;
|
||||
private readonly scriptId: ExecutableId;
|
||||
|
||||
constructor(nodeId: string) {
|
||||
this.scriptId = getScriptId(nodeId);
|
||||
constructor(nodeId: TreeNodeId) {
|
||||
this.scriptId = createExecutableIdFromNodeId(nodeId);
|
||||
}
|
||||
|
||||
public getState(selectedScripts: ReadonlyArray<SelectedScript>): boolean {
|
||||
|
||||
@@ -24,8 +24,9 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, toRef } from 'vue';
|
||||
import { defineComponent, toRef, type PropType } from 'vue';
|
||||
import { injectKey } from '@/presentation/injectionSymbols';
|
||||
import type { ExecutableId } from '@/domain/Executables/Identifiable';
|
||||
import TreeView from './TreeView/TreeView.vue';
|
||||
import NodeContent from './NodeContent/NodeContent.vue';
|
||||
import { useTreeViewFilterEvent } from './TreeViewAdapter/UseTreeViewFilterEvent';
|
||||
@@ -41,7 +42,7 @@ export default defineComponent({
|
||||
},
|
||||
props: {
|
||||
categoryId: {
|
||||
type: [Number],
|
||||
type: String as PropType<ExecutableId>,
|
||||
default: undefined,
|
||||
},
|
||||
hasTopPadding: {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
export type TreeInputNodeDataId = string;
|
||||
|
||||
export interface TreeInputNodeData {
|
||||
readonly id: string;
|
||||
readonly id: TreeInputNodeDataId;
|
||||
readonly children?: readonly TreeInputNodeData[];
|
||||
readonly parent?: TreeInputNodeData | null;
|
||||
readonly data?: object;
|
||||
|
||||
@@ -56,7 +56,7 @@ import { useNodeState } from './UseNodeState';
|
||||
import LeafTreeNode from './LeafTreeNode.vue';
|
||||
import InteractableNode from './InteractableNode.vue';
|
||||
import type { TreeRoot } from '../TreeRoot/TreeRoot';
|
||||
import type { TreeNode } from './TreeNode';
|
||||
import type { TreeNode, TreeNodeId } from './TreeNode';
|
||||
import type { NodeRenderingStrategy } from '../Rendering/Scheduling/NodeRenderingStrategy';
|
||||
import type { PropType } from 'vue';
|
||||
|
||||
@@ -69,7 +69,7 @@ export default defineComponent({
|
||||
},
|
||||
props: {
|
||||
nodeId: {
|
||||
type: String,
|
||||
type: String as PropType<TreeNodeId>,
|
||||
required: true,
|
||||
},
|
||||
treeRoot: {
|
||||
|
||||
@@ -18,13 +18,13 @@ import { useCurrentTreeNodes } from '../UseCurrentTreeNodes';
|
||||
import { useNodeState } from './UseNodeState';
|
||||
import { useKeyboardInteractionState } from './UseKeyboardInteractionState';
|
||||
import type { TreeRoot } from '../TreeRoot/TreeRoot';
|
||||
import type { TreeNode } from './TreeNode';
|
||||
import type { TreeNode, TreeNodeId } from './TreeNode';
|
||||
import type { PropType } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
nodeId: {
|
||||
type: String,
|
||||
type: String as PropType<TreeNodeId>,
|
||||
required: true,
|
||||
},
|
||||
treeRoot: {
|
||||
|
||||
@@ -28,7 +28,7 @@ import { defineComponent, computed, toRef } from 'vue';
|
||||
import { useCurrentTreeNodes } from '../UseCurrentTreeNodes';
|
||||
import NodeCheckbox from './NodeCheckbox.vue';
|
||||
import InteractableNode from './InteractableNode.vue';
|
||||
import type { TreeNode } from './TreeNode';
|
||||
import type { TreeNode, TreeNodeId } from './TreeNode';
|
||||
import type { TreeRoot } from '../TreeRoot/TreeRoot';
|
||||
import type { PropType } from 'vue';
|
||||
|
||||
@@ -39,7 +39,7 @@ export default defineComponent({
|
||||
},
|
||||
props: {
|
||||
nodeId: {
|
||||
type: String,
|
||||
type: String as PropType<TreeNodeId>,
|
||||
required: true,
|
||||
},
|
||||
treeRoot: {
|
||||
|
||||
@@ -14,13 +14,13 @@ import { useCurrentTreeNodes } from '../UseCurrentTreeNodes';
|
||||
import { useNodeState } from './UseNodeState';
|
||||
import { TreeNodeCheckState } from './State/CheckState';
|
||||
import type { TreeRoot } from '../TreeRoot/TreeRoot';
|
||||
import type { TreeNode } from './TreeNode';
|
||||
import type { TreeNode, TreeNodeId } from './TreeNode';
|
||||
import type { PropType } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
nodeId: {
|
||||
type: String,
|
||||
type: String as PropType<TreeNodeId>,
|
||||
required: true,
|
||||
},
|
||||
treeRoot: {
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import type { HierarchyAccess, HierarchyReader } from './Hierarchy/HierarchyAccess';
|
||||
import type { TreeNodeStateAccess, TreeNodeStateReader } from './State/StateAccess';
|
||||
|
||||
export type TreeNodeId = string;
|
||||
|
||||
export interface ReadOnlyTreeNode {
|
||||
readonly id: string;
|
||||
readonly id: TreeNodeId;
|
||||
readonly state: TreeNodeStateReader;
|
||||
readonly hierarchy: HierarchyReader;
|
||||
readonly metadata?: object;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { TreeNodeHierarchy } from './Hierarchy/TreeNodeHierarchy';
|
||||
import { TreeNodeState } from './State/TreeNodeState';
|
||||
import type { TreeNode } from './TreeNode';
|
||||
import type { TreeNode, TreeNodeId } from './TreeNode';
|
||||
import type { TreeNodeStateAccess } from './State/StateAccess';
|
||||
import type { HierarchyAccess } from './Hierarchy/HierarchyAccess';
|
||||
|
||||
@@ -9,7 +9,7 @@ export class TreeNodeManager implements TreeNode {
|
||||
|
||||
public readonly hierarchy: HierarchyAccess;
|
||||
|
||||
constructor(public readonly id: string, public readonly metadata?: object) {
|
||||
constructor(public readonly id: TreeNodeId, public readonly metadata?: object) {
|
||||
if (!id) {
|
||||
throw new Error('missing id');
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import type { ReadOnlyTreeNode, TreeNode } from '../../../Node/TreeNode';
|
||||
import type { ReadOnlyTreeNode, TreeNode, TreeNodeId } from '../../../Node/TreeNode';
|
||||
|
||||
export interface ReadOnlyQueryableNodes {
|
||||
readonly rootNodes: readonly ReadOnlyTreeNode[];
|
||||
readonly flattenedNodes: readonly ReadOnlyTreeNode[];
|
||||
|
||||
getNodeById(id: string): ReadOnlyTreeNode;
|
||||
getNodeById(nodeId: TreeNodeId): ReadOnlyTreeNode;
|
||||
}
|
||||
|
||||
export interface QueryableNodes extends ReadOnlyQueryableNodes {
|
||||
readonly rootNodes: readonly TreeNode[];
|
||||
readonly flattenedNodes: readonly TreeNode[];
|
||||
|
||||
getNodeById(id: string): TreeNode;
|
||||
getNodeById(nodeId: TreeNodeId): TreeNode;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { QueryableNodes } from './QueryableNodes';
|
||||
import type { TreeNode } from '../../../Node/TreeNode';
|
||||
import type { TreeNode, TreeNodeId } from '../../../Node/TreeNode';
|
||||
|
||||
export class TreeNodeNavigator implements QueryableNodes {
|
||||
public readonly flattenedNodes: readonly TreeNode[];
|
||||
@@ -8,10 +8,10 @@ export class TreeNodeNavigator implements QueryableNodes {
|
||||
this.flattenedNodes = flattenNodes(rootNodes);
|
||||
}
|
||||
|
||||
public getNodeById(id: string): TreeNode {
|
||||
const foundNode = this.flattenedNodes.find((node) => node.id === id);
|
||||
public getNodeById(nodeId: TreeNodeId): TreeNode {
|
||||
const foundNode = this.flattenedNodes.find((node) => node.id === nodeId);
|
||||
if (!foundNode) {
|
||||
throw new Error(`Node could not be found: ${id}`);
|
||||
throw new Error(`Node could not be found: ${nodeId}`);
|
||||
}
|
||||
return foundNode;
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
} from 'vue';
|
||||
import HierarchicalTreeNode from '../Node/HierarchicalTreeNode.vue';
|
||||
import { useCurrentTreeNodes } from '../UseCurrentTreeNodes';
|
||||
import { type TreeNodeId } from '../Node/TreeNode';
|
||||
import type { NodeRenderingStrategy } from '../Rendering/Scheduling/NodeRenderingStrategy';
|
||||
import type { TreeRoot } from './TreeRoot';
|
||||
import type { PropType } from 'vue';
|
||||
@@ -43,7 +44,7 @@ export default defineComponent({
|
||||
setup(props) {
|
||||
const { nodes } = useCurrentTreeNodes(toRef(props, 'treeRoot'));
|
||||
|
||||
const renderedNodeIds = computed<string[]>(() => {
|
||||
const renderedNodeIds = computed<TreeNodeId[]>(() => {
|
||||
return nodes
|
||||
.value
|
||||
.rootNodes
|
||||
|
||||
@@ -26,6 +26,7 @@ import { useLeafNodeCheckedStateUpdater } from './UseLeafNodeCheckedStateUpdater
|
||||
import { useAutoUpdateParentCheckState } from './UseAutoUpdateParentCheckState';
|
||||
import { useAutoUpdateChildrenCheckState } from './UseAutoUpdateChildrenCheckState';
|
||||
import { useGradualNodeRendering, type NodeRenderingControl } from './Rendering/UseGradualNodeRendering';
|
||||
import { type TreeNodeId } from './Node/TreeNode';
|
||||
import type { TreeNodeStateChangedEmittedEvent } from './Bindings/TreeNodeStateChangedEmittedEvent';
|
||||
import type { TreeInputNodeData } from './Bindings/TreeInputNodeData';
|
||||
import type { TreeViewFilterEvent } from './Bindings/TreeInputFilterEvent';
|
||||
@@ -45,7 +46,7 @@ export default defineComponent({
|
||||
default: () => undefined,
|
||||
},
|
||||
selectedLeafNodeIds: {
|
||||
type: Array as PropType<ReadonlyArray<string>>,
|
||||
type: Array as PropType<ReadonlyArray<TreeNodeId>>,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
import type { Category } from '@/domain/Executables/Category/Category';
|
||||
import type { ICategoryCollection } from '@/domain/ICategoryCollection';
|
||||
import type { ICategoryCollection } from '@/domain/Collection/ICategoryCollection';
|
||||
import type { Script } from '@/domain/Executables/Script/Script';
|
||||
import type { ExecutableId } from '@/domain/Executables/Identifiable';
|
||||
import type { Executable } from '@/domain/Executables/Executable';
|
||||
import { type NodeMetadata, NodeType } from '../NodeContent/NodeMetadata';
|
||||
import type { TreeNodeId } from '../TreeView/Node/TreeNode';
|
||||
|
||||
export function parseAllCategories(collection: ICategoryCollection): NodeMetadata[] {
|
||||
return createCategoryNodes(collection.actions);
|
||||
}
|
||||
|
||||
export function parseSingleCategory(
|
||||
categoryId: number,
|
||||
categoryId: ExecutableId,
|
||||
collection: ICategoryCollection,
|
||||
): NodeMetadata[] {
|
||||
const category = collection.getCategory(categoryId);
|
||||
@@ -16,27 +19,19 @@ export function parseSingleCategory(
|
||||
return tree;
|
||||
}
|
||||
|
||||
export function getScriptNodeId(script: Script): string {
|
||||
return script.id;
|
||||
export function createNodeIdForExecutable(executable: Executable): TreeNodeId {
|
||||
return executable.executableId;
|
||||
}
|
||||
|
||||
export function getScriptId(nodeId: string): string {
|
||||
export function createExecutableIdFromNodeId(nodeId: TreeNodeId): ExecutableId {
|
||||
return nodeId;
|
||||
}
|
||||
|
||||
export function getCategoryId(nodeId: string): number {
|
||||
return +nodeId;
|
||||
}
|
||||
|
||||
export function getCategoryNodeId(category: Category): string {
|
||||
return `${category.id}`;
|
||||
}
|
||||
|
||||
function parseCategoryRecursively(
|
||||
parentCategory: Category,
|
||||
): NodeMetadata[] {
|
||||
return [
|
||||
...createCategoryNodes(parentCategory.subCategories),
|
||||
...createCategoryNodes(parentCategory.subcategories),
|
||||
...createScriptNodes(parentCategory.scripts),
|
||||
];
|
||||
}
|
||||
@@ -57,7 +52,7 @@ function convertCategoryToNode(
|
||||
children: readonly NodeMetadata[],
|
||||
): NodeMetadata {
|
||||
return {
|
||||
id: getCategoryNodeId(category),
|
||||
executableId: createNodeIdForExecutable(category),
|
||||
type: NodeType.Category,
|
||||
text: category.name,
|
||||
children,
|
||||
@@ -68,7 +63,7 @@ function convertCategoryToNode(
|
||||
|
||||
function convertScriptToNode(script: Script): NodeMetadata {
|
||||
return {
|
||||
id: getScriptNodeId(script),
|
||||
executableId: createNodeIdForExecutable(script),
|
||||
type: NodeType.Script,
|
||||
text: script.name,
|
||||
children: [],
|
||||
|
||||
@@ -14,7 +14,7 @@ export function getNodeMetadata(
|
||||
|
||||
export function convertToNodeInput(metadata: NodeMetadata): TreeInputNodeData {
|
||||
return {
|
||||
id: metadata.id,
|
||||
id: metadata.executableId,
|
||||
children: convertChildren(metadata.children, convertToNodeInput),
|
||||
data: metadata,
|
||||
};
|
||||
|
||||
@@ -2,20 +2,21 @@ import {
|
||||
computed, shallowReadonly,
|
||||
} from 'vue';
|
||||
import type { useUserSelectionState } from '@/presentation/components/Shared/Hooks/UseUserSelectionState';
|
||||
import { getScriptNodeId } from './CategoryNodeMetadataConverter';
|
||||
import { createNodeIdForExecutable } from './CategoryNodeMetadataConverter';
|
||||
import type { TreeNodeId } from '../TreeView/Node/TreeNode';
|
||||
|
||||
export function useSelectedScriptNodeIds(
|
||||
useSelectionStateHook: ReturnType<typeof useUserSelectionState>,
|
||||
scriptNodeIdParser = getScriptNodeId,
|
||||
convertToNodeId = createNodeIdForExecutable,
|
||||
) {
|
||||
const { currentSelection } = useSelectionStateHook;
|
||||
|
||||
const selectedNodeIds = computed<readonly string[]>(() => {
|
||||
const selectedNodeIds = computed<readonly TreeNodeId[]>(() => {
|
||||
return currentSelection
|
||||
.value
|
||||
.scripts
|
||||
.selectedScripts
|
||||
.map((selected) => scriptNodeIdParser(selected.script));
|
||||
.map((selected) => convertToNodeId(selected.script));
|
||||
});
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
import {
|
||||
type Ref, shallowReadonly, shallowRef,
|
||||
} from 'vue';
|
||||
import type { Script } from '@/domain/Executables/Script/Script';
|
||||
import type { Category } from '@/domain/Executables/Category/Category';
|
||||
import { injectKey } from '@/presentation/injectionSymbols';
|
||||
import type { ReadonlyFilterContext } from '@/application/Context/State/Filter/FilterContext';
|
||||
import type { FilterResult } from '@/application/Context/State/Filter/Result/FilterResult';
|
||||
import type { ExecutableId } from '@/domain/Executables/Identifiable';
|
||||
import type { Executable } from '@/domain/Executables/Executable';
|
||||
import { type TreeViewFilterEvent, createFilterRemovedEvent, createFilterTriggeredEvent } from '../TreeView/Bindings/TreeInputFilterEvent';
|
||||
import { getNodeMetadata } from './TreeNodeMetadataConverter';
|
||||
import { getCategoryNodeId, getScriptNodeId } from './CategoryNodeMetadataConverter';
|
||||
import type { NodeMetadata } from '../NodeContent/NodeMetadata';
|
||||
import type { ReadOnlyTreeNode } from '../TreeView/Node/TreeNode';
|
||||
import { createExecutableIdFromNodeId } from './CategoryNodeMetadataConverter';
|
||||
import type { ReadOnlyTreeNode, TreeNodeId } from '../TreeView/Node/TreeNode';
|
||||
|
||||
type TreeNodeFilterResultPredicate = (
|
||||
node: ReadOnlyTreeNode,
|
||||
@@ -24,7 +23,7 @@ export function useTreeViewFilterEvent() {
|
||||
const latestFilterEvent = shallowRef<TreeViewFilterEvent | undefined>(undefined);
|
||||
|
||||
const treeNodePredicate: TreeNodeFilterResultPredicate = (node, filterResult) => filterMatches(
|
||||
getNodeMetadata(node),
|
||||
node.id,
|
||||
filterResult,
|
||||
);
|
||||
|
||||
@@ -71,15 +70,17 @@ function createFilterEvent(
|
||||
);
|
||||
}
|
||||
|
||||
function filterMatches(node: NodeMetadata, filter: FilterResult): boolean {
|
||||
return containsScript(node, filter.scriptMatches)
|
||||
|| containsCategory(node, filter.categoryMatches);
|
||||
function filterMatches(nodeId: TreeNodeId, filter: FilterResult): boolean {
|
||||
const executableId = createExecutableIdFromNodeId(nodeId);
|
||||
return containsExecutable(executableId, filter.scriptMatches)
|
||||
|| containsExecutable(executableId, filter.categoryMatches);
|
||||
}
|
||||
|
||||
function containsScript(expected: NodeMetadata, scripts: readonly Script[]) {
|
||||
return scripts.some((existing: Script) => expected.id === getScriptNodeId(existing));
|
||||
}
|
||||
|
||||
function containsCategory(expected: NodeMetadata, categories: readonly Category[]) {
|
||||
return categories.some((existing: Category) => expected.id === getCategoryNodeId(existing));
|
||||
function containsExecutable(
|
||||
expectedId: ExecutableId,
|
||||
executables: readonly Executable[],
|
||||
): boolean {
|
||||
return executables.some(
|
||||
(existing: Category) => existing.executableId === expectedId,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import {
|
||||
type Ref, computed, shallowReadonly,
|
||||
} from 'vue';
|
||||
import type { ICategoryCollection } from '@/domain/ICategoryCollection';
|
||||
import type { ICategoryCollection } from '@/domain/Collection/ICategoryCollection';
|
||||
import { injectKey } from '@/presentation/injectionSymbols';
|
||||
import type { ExecutableId } from '@/domain/Executables/Identifiable';
|
||||
import { parseSingleCategory, parseAllCategories } from './CategoryNodeMetadataConverter';
|
||||
import { convertToNodeInput } from './TreeNodeMetadataConverter';
|
||||
import type { TreeInputNodeData } from '../TreeView/Bindings/TreeInputNodeData';
|
||||
import type { NodeMetadata } from '../NodeContent/NodeMetadata';
|
||||
|
||||
export function useTreeViewNodeInput(
|
||||
categoryIdRef: Readonly<Ref<number | undefined>>,
|
||||
categoryIdRef: Readonly<Ref<ExecutableId | undefined>>,
|
||||
parser: CategoryNodeParser = {
|
||||
parseSingle: parseSingleCategory,
|
||||
parseAll: parseAllCategories,
|
||||
@@ -30,7 +31,7 @@ export function useTreeViewNodeInput(
|
||||
}
|
||||
|
||||
function parseNodes(
|
||||
categoryId: number | undefined,
|
||||
categoryId: ExecutableId | undefined,
|
||||
categoryCollection: ICategoryCollection,
|
||||
parser: CategoryNodeParser,
|
||||
): NodeMetadata[] {
|
||||
|
||||
Reference in New Issue
Block a user