-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
diff --git a/arches_references/src/arches_references/components/MainSplitter.vue b/arches_references/src/arches_references/components/MainSplitter.vue
new file mode 100644
index 0000000..f36822c
--- /dev/null
+++ b/arches_references/src/arches_references/components/MainSplitter.vue
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/arches_references/src/arches_references/components/tree/AddDeleteControls.vue b/arches_references/src/arches_references/components/tree/AddDeleteControls.vue
index 59b7071..84d49c1 100644
--- a/arches_references/src/arches_references/components/tree/AddDeleteControls.vue
+++ b/arches_references/src/arches_references/components/tree/AddDeleteControls.vue
@@ -5,7 +5,6 @@ import { useGettext } from "vue3-gettext";
import { useConfirm } from "primevue/useconfirm";
import { useToast } from "primevue/usetoast";
import Button from "primevue/button";
-import ConfirmDialog from "primevue/confirmdialog";
import SplitButton from "primevue/splitbutton";
import {
@@ -113,7 +112,6 @@ const createList = () => {
};
nextNewList.value = newList;
- newListFormValue.value = "";
newListCounter.value += 1;
tree.value.push(listAsNode(newList, selectedLanguage.value));
@@ -262,29 +260,6 @@ await fetchListsAndPopulateTree();
:severity="shouldUseContrast() ? CONTRAST : PRIMARY"
@click="createList"
/>
-
-import { computed, inject, ref } from "vue";
+import { computed, inject, ref, watch } from "vue";
+import { useRoute } from "vue-router";
import { useGettext } from "vue3-gettext";
import Tree from "primevue/tree";
@@ -8,12 +9,18 @@ import {
displayedRowKey,
selectedLanguageKey,
} from "@/arches_references/constants.ts";
-import { bestLabel, nodeIsList } from "@/arches_references/utils.ts";
+import { routeNames } from "@/arches_references/routes.ts";
+import {
+ bestLabel,
+ findNodeInTree,
+ nodeIsList,
+} from "@/arches_references/utils.ts";
import LetterCircle from "@/arches_references/components/misc/LetterCircle.vue";
import ListTreeControls from "@/arches_references/components/tree/ListTreeControls.vue";
import TreeRow from "@/arches_references/components/tree/TreeRow.vue";
import type { ComponentPublicInstance, Ref } from "vue";
+import type { RouteLocationNormalizedLoadedGeneric } from "vue-router";
import type { TreePassThroughMethodOptions } from "primevue/tree";
import type { TreeExpandedKeys, TreeSelectionKeys } from "primevue/tree";
import type { TreeNode } from "primevue/treenode";
@@ -57,7 +64,89 @@ const { setDisplayedRow } = inject(displayedRowKey) as unknown as {
setDisplayedRow: RowSetter;
};
+const route = useRoute();
+
+const navigate = (newRoute: RouteLocationNormalizedLoadedGeneric) => {
+ switch (newRoute.name) {
+ case routeNames.splash:
+ setDisplayedRow(null);
+ expandedKeys.value = {};
+ selectedKeys.value = {};
+ break;
+ case routeNames.list: {
+ if (!tree.value.length) {
+ return;
+ }
+ const list = tree.value.find(
+ (node) => node.data.id === newRoute.params.id,
+ );
+ if (list) {
+ setDisplayedRow(list.data);
+ expandedKeys.value = {
+ ...expandedKeys.value,
+ [list.data.id]: true,
+ };
+ selectedKeys.value = { [list.data.id]: true };
+ } else {
+ setDisplayedRow(null);
+ }
+ break;
+ }
+ case routeNames.item: {
+ if (!tree.value.length) {
+ return;
+ }
+ const { found, path } = findNodeInTree(
+ tree.value,
+ newRoute.params.id as string,
+ );
+ if (found) {
+ setDisplayedRow(found.data);
+ const itemsToExpandIds = path.map(
+ (itemInPath: TreeNode) => itemInPath.key,
+ );
+ expandedKeys.value = {
+ ...expandedKeys.value,
+ ...Object.fromEntries(
+ [
+ found.data.controlled_list_id,
+ ...itemsToExpandIds,
+ ].map((x) => [x, true]),
+ ),
+ };
+ selectedKeys.value = { [found.data.id]: true };
+ }
+ break;
+ }
+ }
+};
+
+// React to route changes.
+watch(
+ [
+ () => {
+ return { ...route };
+ },
+ ],
+ ([newRoute]) => {
+ navigate(newRoute);
+ },
+);
+
+// Navigate on initial load of the tree.
+watch(
+ tree,
+ () => {
+ navigate(route);
+ },
+ { once: true },
+);
+
const updateSelectedAndExpanded = (node: TreeNode) => {
+ if (isMultiSelecting.value || movingItem.value?.key) {
+ return;
+ }
+
setDisplayedRow(node.data);
expandedKeys.value = {
...expandedKeys.value,
@@ -169,7 +258,7 @@ const ptNodeContent = ({ instance }: TreePassThroughMethodOptions) => {
({ required: true });
+const controlledListItemsTree = defineModel("tree", {
+ required: true,
+});
const rerenderTree = defineModel("rerenderTree", { required: true });
const expandedKeys = defineModel("expandedKeys", {
required: true,
diff --git a/arches_references/src/arches_references/components/tree/MoveRow.vue b/arches_references/src/arches_references/components/tree/MoveRow.vue
index 5bb90e9..e20319e 100644
--- a/arches_references/src/arches_references/components/tree/MoveRow.vue
+++ b/arches_references/src/arches_references/components/tree/MoveRow.vue
@@ -69,9 +69,6 @@ const selectedKeys = defineModel("selectedKeys", {
});
const movingItem = defineModel("movingItem");
const nextNewItem = defineModel("nextNewItem");
-const newLabelFormValue = defineModel("newLabelFormValue", {
- required: true,
-});
const newLabelCounter = ref(1);
const shouldRefocusUpArrow = ref(false);
const shouldRefocusDownArrow = ref(false);
@@ -83,8 +80,8 @@ watch(displayedRow, () => {
const isFirstItem = (item: ControlledListItem) => {
const siblings: TreeNode[] = item.parent_id
- ? findNodeInTree(tree.value, item.parent_id).data.children
- : findNodeInTree(tree.value, item.list_id).data.items;
+ ? findNodeInTree(tree.value, item.parent_id).found!.data.children
+ : findNodeInTree(tree.value, item.list_id).found!.data.items;
if (!siblings.length) {
throw new Error();
}
@@ -93,8 +90,8 @@ const isFirstItem = (item: ControlledListItem) => {
const isLastItem = (item: ControlledListItem) => {
const siblings: TreeNode[] = item.parent_id
- ? findNodeInTree(tree.value, item.parent_id).data.children
- : findNodeInTree(tree.value, item.list_id).data.items;
+ ? findNodeInTree(tree.value, item.parent_id).found!.data.children
+ : findNodeInTree(tree.value, item.list_id).found!.data.items;
if (!siblings.length) {
throw new Error();
}
@@ -113,7 +110,7 @@ const setMovingItem = (node: TreeNode) => {
),
],
node.key,
- );
+ ).found;
};
const addItem = (parent: TreeNode) => {
@@ -139,7 +136,6 @@ const addItem = (parent: TreeNode) => {
};
nextNewItem.value = newItem;
- newLabelFormValue.value = "";
newLabelCounter.value += 1;
parent.children!.push(itemAsNode(newItem, selectedLanguage.value));
@@ -153,13 +149,15 @@ const addItem = (parent: TreeNode) => {
};
const reorder = async (item: ControlledListItem, up: boolean) => {
- const list: ControlledList = findNodeInTree(tree.value, item.list_id).data;
+ const list: ControlledList = findNodeInTree(tree.value, item.list_id).found!
+ .data;
let siblings: ControlledListItem[];
if (item.parent_id) {
- siblings = findNodeInTree(tree.value, item.parent_id).children!.map(
- (child: TreeNode) => child.data,
- );
+ siblings = findNodeInTree(
+ tree.value,
+ item.parent_id,
+ ).found!.children!.map((child: TreeNode) => child.data);
} else {
siblings = list.items;
}
diff --git a/arches_references/src/arches_references/components/tree/TreeRow.vue b/arches_references/src/arches_references/components/tree/TreeRow.vue
index 98a8bcd..107ce86 100644
--- a/arches_references/src/arches_references/components/tree/TreeRow.vue
+++ b/arches_references/src/arches_references/components/tree/TreeRow.vue
@@ -138,7 +138,7 @@ const setParent = async (parentNode: TreeNode) => {
siblings.push(item);
} else {
item.parent_id = parentNode.key;
- list = findNodeInTree(tree.value, item.list_id).data;
+ list = findNodeInTree(tree.value, item.list_id).found!.data;
siblings = parentNode.data.children;
siblings.push(item);
}
@@ -213,7 +213,11 @@ const acceptNewItemShortcutEntry = async () => {
const parent = findNodeInTree(
tree.value,
newItem.parent_id ?? newItem.list_id,
- );
+ ).found;
+ if (!parent) {
+ throw new Error();
+ }
+
parent.children = [
...parent.children!.filter((child: TreeNode) => !dataIsNew(child.data)),
itemAsNode(newItem, selectedLanguage.value),
@@ -226,6 +230,7 @@ const acceptNewItemShortcutEntry = async () => {
selectedKeys.value = { [newItem.id]: true };
setDisplayedRow(newItem);
+ newLabelFormValue.value = "";
};
const triggerAcceptNewItemShortcut = () => {
@@ -259,6 +264,7 @@ const acceptNewListShortcutEntry = async () => {
];
selectedKeys.value = { [newList.id]: true };
setDisplayedRow(newList);
+ newLabelFormValue.value = "";
};
@@ -332,7 +338,6 @@ const acceptNewListShortcutEntry = async () => {
v-model:selected-keys="selectedKeys"
v-model:moving-item="movingItem"
v-model:next-new-item="nextNewItem"
- v-model:new-label-form-value="newLabelFormValue"
:node
:move-labels
/>
diff --git a/arches_references/src/arches_references/plugins/ControlledListManager.vue b/arches_references/src/arches_references/plugins/ControlledListManager.vue
index a15c439..efacaa2 100644
--- a/arches_references/src/arches_references/plugins/ControlledListManager.vue
+++ b/arches_references/src/arches_references/plugins/ControlledListManager.vue
@@ -1,31 +1,3 @@
-
-
-
-
-
+
-
-
diff --git a/arches_references/src/arches_references/routes.ts b/arches_references/src/arches_references/routes.ts
new file mode 100644
index 0000000..113a120
--- /dev/null
+++ b/arches_references/src/arches_references/routes.ts
@@ -0,0 +1,25 @@
+import ControlledListsMain from "@/arches_references/components/ControlledListsMain.vue";
+
+export const routes = [
+ {
+ path: "/plugins/controlled-list-manager",
+ name: "splash",
+ component: ControlledListsMain,
+ },
+ {
+ path: "/plugins/controlled-list-manager/list/:id",
+ name: "list",
+ component: ControlledListsMain,
+ },
+ {
+ path: "/plugins/controlled-list-manager/item/:id",
+ name: "item",
+ component: ControlledListsMain,
+ },
+];
+
+export const routeNames = {
+ splash: "splash",
+ list: "list",
+ item: "item",
+};
diff --git a/arches_references/src/arches_references/utils.ts b/arches_references/src/arches_references/utils.ts
index 545b1a8..31fa110 100644
--- a/arches_references/src/arches_references/utils.ts
+++ b/arches_references/src/arches_references/utils.ts
@@ -53,26 +53,36 @@ export const languageNameFromCode = (code: string) => {
return arches.languages.find((lang: Language) => lang.code === code).name;
};
-export const findNodeInTree = (tree: TreeNode[], itemId: string) => {
+export const findNodeInTree = (
+ tree: TreeNode[],
+ itemId: string,
+): {
+ found: TreeNode | undefined;
+ path: TreeNode[];
+} => {
+ const path: TreeNode[] = [];
+
function recurse(items: TreeNode[]): TreeNode | undefined {
for (const item of items) {
if (item.data.id === itemId) {
return item;
}
for (const child of item.items ?? item.children) {
- const maybeFound = recurse([child]);
- if (maybeFound) {
- return maybeFound;
+ const found = recurse([child]);
+ if (found) {
+ path.push(item);
+ return found;
}
}
}
}
- const result = recurse(tree);
- if (!result) {
+ const found = recurse(tree);
+ if (!found) {
throw new Error();
}
- return result;
+
+ return { found, path };
};
// Shapers
diff --git a/arches_references/urls.py b/arches_references/urls.py
index dd56bc6..8d04c44 100644
--- a/arches_references/urls.py
+++ b/arches_references/urls.py
@@ -13,50 +13,50 @@
)
urlpatterns = [
- path("api/controlled_lists/", ListsView.as_view(), name="controlled_lists"),
+ path("api/controlled_lists", ListsView.as_view(), name="controlled_lists"),
path(
- "api/controlled_list//",
+ "api/controlled_list/",
ListView.as_view(),
name="controlled_list",
),
- path("api/controlled_list/", ListView.as_view(), name="controlled_list_add"),
+ path("api/controlled_list", ListView.as_view(), name="controlled_list_add"),
path(
- "api/controlled_list_item//",
+ "api/controlled_list_item/",
ListItemView.as_view(),
name="controlled_list_item",
),
path(
- "api/controlled_list_item/",
+ "api/controlled_list_item",
ListItemView.as_view(),
name="controlled_list_item_add",
),
path(
- "api/controlled_list_item_value//",
+ "api/controlled_list_item_value/",
ListItemValueView.as_view(),
name="controlled_list_item_value",
),
path(
- "api/controlled_list_item_value/",
+ "api/controlled_list_item_value",
ListItemValueView.as_view(),
name="controlled_list_item_value_add",
),
path(
- "api/controlled_list_item_image//",
+ "api/controlled_list_item_image/",
ListItemImageView.as_view(),
name="controlled_list_item_image",
),
path(
- "api/controlled_list_item_image/",
+ "api/controlled_list_item_image",
ListItemImageView.as_view(),
name="controlled_list_item_image_add",
),
path(
- "api/controlled_list_item_image_metadata//",
+ "api/controlled_list_item_image_metadata/",
ListItemImageMetadataView.as_view(),
name="controlled_list_item_image_metadata",
),
path(
- "api/controlled_list_item_image_metadata/",
+ "api/controlled_list_item_image_metadata",
ListItemImageMetadataView.as_view(),
name="controlled_list_item_image_metadata_add",
),
diff --git a/arches_references/views.py b/arches_references/views.py
index 8a2d0ca..5c60bda 100644
--- a/arches_references/views.py
+++ b/arches_references/views.py
@@ -1,13 +1,12 @@
-import logging
from http import HTTPStatus
from uuid import UUID
from django.core.exceptions import ValidationError
from django.db import transaction
from django.db.models import Max
-from django.views.generic import View
from django.utils.decorators import method_decorator
from django.utils.translation import gettext as _
+from django.views.generic import View
from arches.app.models.utils import field_names
from arches.app.utils.betterJSONSerializer import JSONDeserializer
@@ -25,8 +24,6 @@
NodeProxy,
)
-logger = logging.getLogger(__name__)
-
def _prefetch_terms(request):
"""Children at arbitrary depth will still be returned, but tell
@@ -90,7 +87,7 @@ def get(self, request):
@method_decorator(
group_required("RDM Administrator", raise_exception=True), name="dispatch"
)
-class ListView(View):
+class ListView(APIBase):
def get(self, request, list_id):
"""Returns either a flat representation (?flat=true) or a tree (default)."""
try:
@@ -176,7 +173,7 @@ def delete(self, request, list_id):
@method_decorator(
group_required("RDM Administrator", raise_exception=True), name="dispatch"
)
-class ListItemView(View):
+class ListItemView(APIBase):
def post(self, request):
data = JSONDeserializer().deserialize(request.body)
try:
@@ -247,7 +244,7 @@ def delete(self, request, item_id):
@method_decorator(
group_required("RDM Administrator", raise_exception=True), name="dispatch"
)
-class ListItemValueView(View):
+class ListItemValueView(APIBase):
def post(self, request):
data = JSONDeserializer().deserialize(request.body)
value = ListItemValue(**data)
@@ -301,7 +298,7 @@ def delete(self, request, value_id):
@method_decorator(
group_required("RDM Administrator", raise_exception=True), name="dispatch"
)
-class ListItemImageView(View):
+class ListItemImageView(APIBase):
def post(self, request):
uploaded_file = request.FILES["item_image"]
img = ListItemImage(
@@ -328,7 +325,7 @@ def delete(self, request, image_id):
@method_decorator(
group_required("RDM Administrator", raise_exception=True), name="dispatch"
)
-class ListItemImageMetadataView(View):
+class ListItemImageMetadataView(APIBase):
def post(self, request):
data = JSONDeserializer().deserialize(request.body)
data.pop("metadata_label", None)
diff --git a/package-lock.json b/package-lock.json
index 69d1c37..99e88ab 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -7,10 +7,11 @@
"name": "arches_references",
"license": "AGPL-3.0-only",
"dependencies": {
- "arches": "archesproject/arches#dev/7.6.x"
+ "arches": "archesproject/arches#dev/8.0.x",
+ "vue-router": "^4.4.0"
},
"devDependencies": {
- "arches-dev-dependencies": "archesproject/arches-dev-dependencies#dev/7.6.x"
+ "arches-dev-dependencies": "archesproject/arches-dev-dependencies#dev/8.0.x"
}
},
"node_modules/@ampproject/remapping": {
@@ -5546,6 +5547,11 @@
"he": "^1.2.0"
}
},
+ "node_modules/@vue/devtools-api": {
+ "version": "6.6.3",
+ "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.3.tgz",
+ "integrity": "sha512-0MiMsFma/HqA6g3KLKn+AGpL1kgKhFWszC9U29NfpWK5LE7bjeXxySWJrOJ77hBz+TBrBQ7o4QJqbPbqbs8rJw=="
+ },
"node_modules/@vue/language-core": {
"version": "2.0.29",
"resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.0.29.tgz",
@@ -6023,8 +6029,8 @@
}
},
"node_modules/arches": {
- "version": "7.6.0",
- "resolved": "git+ssh://git@github.com/archesproject/arches.git#017862edff660c4a7bb58c4d5e9c9e969ab1008e",
+ "version": "8.0.0",
+ "resolved": "git+ssh://git@github.com/archesproject/arches.git#d093f8f386ccb50a80709b13dc5802618cf262a4",
"dependencies": {
"@babel/runtime": "^7.17.2",
"@mapbox/geojson-extent": "~1.0.1",
@@ -6092,8 +6098,8 @@
}
},
"node_modules/arches-dev-dependencies": {
- "version": "7.6.0",
- "resolved": "git+ssh://git@github.com/archesproject/arches-dev-dependencies.git#d75232c50957237421aece034456b785ad05bb85",
+ "version": "8.0.0",
+ "resolved": "git+ssh://git@github.com/archesproject/arches-dev-dependencies.git#c5d4097acc341980e327d4cb12534a3a2765107f",
"dev": true,
"dependencies": {
"@babel/core": "^7.24.4",
@@ -16715,6 +16721,20 @@
}
}
},
+ "node_modules/vue-router": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.4.3.tgz",
+ "integrity": "sha512-sv6wmNKx2j3aqJQDMxLFzs/u/mjA9Z5LCgy6BE0f7yFWMjrPLnS/sPNn8ARY/FXw6byV18EFutn5lTO6+UsV5A==",
+ "dependencies": {
+ "@vue/devtools-api": "^6.6.3"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/posva"
+ },
+ "peerDependencies": {
+ "vue": "^3.2.0"
+ }
+ },
"node_modules/vue-template-compiler": {
"version": "2.7.16",
"resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.16.tgz",
diff --git a/package.json b/package.json
index ca939de..8b8e1f9 100644
--- a/package.json
+++ b/package.json
@@ -18,17 +18,17 @@
"vitest": "vitest --run --coverage"
},
"dependencies": {
- "arches": "archesproject/arches#dev/7.6.x"
+ "arches": "archesproject/arches#dev/8.0.x",
+ "vue-router": "^4.4.0"
},
"devDependencies": {
- "arches-dev-dependencies": "archesproject/arches-dev-dependencies#dev/7.6.x"
- },
- "nodeModulesPaths": {
+ "arches-dev-dependencies": "archesproject/arches-dev-dependencies#dev/8.0.x"
},
+ "nodeModulesPaths": {},
"overrides": {
"moment-timezone": "^0.5.45",
"nomnom": "npm:@gerhobbelt/nomnom",
- "rimraf": "^5.0.7",
+ "rimraf": "^5.0.7",
"underscore": "^1.13.6"
}
}