mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-24 16:30:27 +08:00
generalize activity bar
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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") {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user