generalize activity bar

This commit is contained in:
Laila Los
2024-12-03 10:25:11 -05:00
committed by Dannon Baker
parent f5e146db3d
commit 06d870e447
8 changed files with 101 additions and 34 deletions
@@ -45,7 +45,7 @@ describe("ActivityBar", () => {
beforeEach(async () => {
const pinia = createTestingPinia({ stubActions: false });
activityStore = useActivityStore();
activityStore = useActivityStore("default");
eventStore = useEventStore();
wrapper = shallowMount(mountTarget, {
localVue,
@@ -1,11 +1,10 @@
<script setup lang="ts">
import { storeToRefs } from "pinia";
import { computed, type Ref, ref, watch } from "vue";
import { computed, type Ref, ref } from "vue";
import { useRoute } from "vue-router/composables";
import draggable from "vuedraggable";
import { useConfig } from "@/composables/config";
import { useHashedUserId } from "@/composables/hashedUserId";
import { convertDropData } from "@/stores/activitySetup";
import { type Activity, useActivityStore } from "@/stores/activityStore";
import { useEventStore } from "@/stores/eventStore";
@@ -24,6 +23,19 @@ import NotificationsPanel from "@/components/Panels/NotificationsPanel.vue";
import SettingsPanel from "@/components/Panels/SettingsPanel.vue";
import ToolPanel from "@/components/Panels/ToolPanel.vue";
const props = withDefaults(
defineProps<{
defaultActivities?: Activity[];
activityBarId?: string;
specialActivities?: Activity[];
}>(),
{
defaultActivities: undefined,
activityBarId: "default",
specialActivities: () => [],
}
);
// require user to long click before dragging
const DRAG_DELAY = 50;
@@ -32,10 +44,8 @@ const { config, isConfigLoaded } = useConfig();
const route = useRoute();
const userStore = useUserStore();
const { hashedUserId } = useHashedUserId();
const eventStore = useEventStore();
const activityStore = useActivityStore();
const activityStore = useActivityStore(props.activityBarId);
const { isAdmin, isAnonymous } = storeToRefs(userStore);
const emit = defineEmits(["dragstart"]);
@@ -50,9 +60,6 @@ const dragItem: Ref<Activity | null> = ref(null);
// drag state
const isDragging = ref(false);
// sync built-in activities with cached activities
activityStore.sync();
/**
* Checks if the route of an activity is currently being visited and panels are collapsed
*/
@@ -132,12 +139,9 @@ function onToggleSidebar(toggle: string = "", to: string | null = null) {
userStore.toggleSideBar(toggle);
}
watch(
() => hashedUserId.value,
() => {
activityStore.sync();
}
);
defineExpose({
isActiveSideBar,
});
</script>
<template>
@@ -226,6 +230,28 @@ watch(
tooltip="Administer this Galaxy"
variant="danger"
@click="onToggleSidebar('admin')" />
<template v-for="activity in props.specialActivities">
<ActivityItem
v-if="activity.panel"
:id="`activity-${activity.id}`"
:key="activity.id"
:icon="activity.icon"
:is-active="panelActivityIsActive(activity)"
:title="activity.title"
:tooltip="activity.tooltip"
:to="activity.to || ''"
@click="onToggleSidebar(activity.id, activity.to)" />
<ActivityItem
v-else-if="activity.to"
:id="`activity-${activity.id}`"
:key="activity.id"
:icon="activity.icon"
:is-active="isActiveRoute(activity.to)"
:title="activity.title"
:tooltip="activity.tooltip"
:to="activity.to"
@click="onToggleSidebar()" />
</template>
</b-nav>
</div>
<FlexPanel v-if="isSideBarOpen" side="left" :collapsible="false">
@@ -234,8 +260,9 @@ watch(
<VisualizationPanel v-else-if="isActiveSideBar('visualizations')" />
<MultiviewPanel v-else-if="isActiveSideBar('multiview')" />
<NotificationsPanel v-else-if="isActiveSideBar('notifications')" />
<SettingsPanel v-else-if="isActiveSideBar('settings')" />
<SettingsPanel v-else-if="isActiveSideBar('settings')" :activity-bar-scope="props.activityBarId" />
<AdminPanel v-else-if="isActiveSideBar('admin')" />
<slot name="side-panel"></slot>
</FlexPanel>
</div>
</template>
@@ -40,13 +40,14 @@ describe("ActivitySettings", () => {
beforeEach(async () => {
const pinia = createTestingPinia({ stubActions: false });
activityStore = useActivityStore();
activityStore = useActivityStore("default");
activityStore.sync();
wrapper = mount(mountTarget, {
localVue,
pinia,
props: {
query: "",
activityBarScope: "default",
},
stubs: {
icon: { template: "<div></div>" },
@@ -19,10 +19,11 @@ library.add({
});
const props = defineProps<{
activityBarScope: string;
query: string;
}>();
const activityStore = useActivityStore();
const activityStore = useActivityStore(props.activityBarScope);
const { activities } = storeToRefs(activityStore);
const activityAction = useActivityAction();
@@ -10,7 +10,11 @@ import ActivitySettings from "@/components/ActivityBar/ActivitySettings.vue";
import DelayedInput from "@/components/Common/DelayedInput.vue";
import ActivityPanel from "@/components/Panels/ActivityPanel.vue";
const activityStore = useActivityStore();
const props = defineProps<{
activityBarScope: string;
}>();
const activityStore = useActivityStore(props.activityBarScope);
const confirmRestore = ref(false);
const query = ref("");
@@ -37,7 +41,7 @@ function onQuery(newQuery: string) {
<FontAwesomeIcon :icon="faUndo" fixed-width />
</BButton>
</template>
<ActivitySettings :query="query" />
<ActivitySettings :query="query" :activity-bar-scope="props.activityBarScope" />
<BModal
v-model="confirmRestore"
title="Restore Activity Bar Defaults"
+2 -2
View File
@@ -4,7 +4,7 @@
import { type Activity } from "@/stores/activityStore";
import { type EventData } from "@/stores/eventStore";
export const Activities = [
export const defaultActivities = [
{
anonymous: false,
description: "Displays currently running interactive tools (ITs), if these are enabled by the administrator.",
@@ -148,7 +148,7 @@ export const Activities = [
to: "/libraries",
visible: true,
},
];
] as const;
export function convertDropData(data: EventData): Activity | null {
if (data.history_content_type === "dataset") {
+3 -3
View File
@@ -56,14 +56,14 @@ describe("Activity Store", () => {
});
it("initialize store", () => {
const activityStore = useActivityStore();
const activityStore = useActivityStore("default");
expect(activityStore.getAll().length).toBe(0);
activityStore.sync();
expect(activityStore.getAll().length).toBe(1);
});
it("add activity", () => {
const activityStore = useActivityStore();
const activityStore = useActivityStore("default");
activityStore.sync();
const initialActivities = activityStore.getAll();
expect(initialActivities[0]?.visible).toBeTruthy();
@@ -81,7 +81,7 @@ describe("Activity Store", () => {
});
it("remove activity", () => {
const activityStore = useActivityStore();
const activityStore = useActivityStore("default");
activityStore.sync();
const initialActivities = activityStore.getAll();
expect(initialActivities.length).toEqual(1);
+43 -9
View File
@@ -1,13 +1,14 @@
/**
* Stores the Activity Bar state
*/
import { watchImmediate } from "@vueuse/core";
import { computed, type Ref, ref } from "vue";
import { defineStore } from "pinia";
import { type Ref } from "vue";
import { useHashedUserId } from "@/composables/hashedUserId";
import { useUserLocalStorage } from "@/composables/userLocalStorage";
import { Activities } from "./activitySetup";
import { defaultActivities } from "./activitySetup";
import { defineScopedStore } from "./scopedStore";
export interface Activity {
// determine wether an anonymous user can access this activity
@@ -34,14 +35,36 @@ export interface Activity {
visible: boolean;
}
export const useActivityStore = defineStore("activityStore", () => {
const activities: Ref<Array<Activity>> = useUserLocalStorage("activity-store-activities", []);
export const useActivityStore = defineScopedStore("activityStore", (scope) => {
const activities: Ref<Array<Activity>> = useUserLocalStorage(`activity-store-activities-${scope}`, []);
const { hashedUserId } = useHashedUserId();
watchImmediate(
() => hashedUserId.value,
() => {
sync();
}
);
const customDefaultActivities = ref<Activity[] | null>(null);
const currentDefaultActivities = computed(() => customDefaultActivities.value ?? defaultActivities);
function overrideDefaultActivities(activities: Activity[]) {
customDefaultActivities.value = activities;
sync();
}
function resetDefaultActivities() {
customDefaultActivities.value = null;
sync();
}
/**
* Restores the default activity bar items
*/
function restore() {
activities.value = Activities.slice();
activities.value = currentDefaultActivities.value.slice();
}
/**
@@ -52,12 +75,15 @@ export const useActivityStore = defineStore("activityStore", () => {
function sync() {
// create a map of built-in activities
const activitiesMap: Record<string, Activity> = {};
Activities.forEach((a) => {
currentDefaultActivities.value.forEach((a) => {
activitiesMap[a.id] = a;
});
// create an updated array of activities
const newActivities: Array<Activity> = [];
const foundActivity = new Set();
activities.value.forEach((a: Activity) => {
if (a.mutable) {
// existing custom activity
@@ -66,6 +92,7 @@ export const useActivityStore = defineStore("activityStore", () => {
// update existing built-in activity attributes
// skip legacy built-in activities
const sourceActivity = activitiesMap[a.id];
if (sourceActivity) {
foundActivity.add(a.id);
newActivities.push({
@@ -75,12 +102,14 @@ export const useActivityStore = defineStore("activityStore", () => {
}
}
});
// add new built-in activities
Activities.forEach((a) => {
currentDefaultActivities.value.forEach((a) => {
if (!foundActivity.has(a.id)) {
newActivities.push({ ...a });
}
});
// update activities stored in local cache only if changes were applied
if (JSON.stringify(activities.value) !== JSON.stringify(newActivities)) {
activities.value = newActivities;
@@ -97,6 +126,7 @@ export const useActivityStore = defineStore("activityStore", () => {
function remove(activityId: string) {
const findIndex = activities.value.findIndex((a: Activity) => a.id === activityId);
if (findIndex !== -1) {
activities.value.splice(findIndex, 1);
}
@@ -109,5 +139,9 @@ export const useActivityStore = defineStore("activityStore", () => {
setAll,
restore,
sync,
customDefaultActivities,
currentDefaultActivities,
overrideDefaultActivities,
resetDefaultActivities,
};
});