mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-24 16:30:27 +08:00
Merge pull request #18265 from ahmedhamidawan/multiview_top_bar
Move history multiview item actions to top
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
faBurn,
|
||||
faExchangeAlt,
|
||||
faEye,
|
||||
faPlus,
|
||||
@@ -10,7 +11,7 @@ import {
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { useEventBus } from "@vueuse/core";
|
||||
|
||||
import { deleteHistories, deleteHistory, historiesFetcher, undeleteHistories, undeleteHistory } from "@/api/histories";
|
||||
import { historiesFetcher } from "@/api/histories";
|
||||
import { updateTags } from "@/api/tags";
|
||||
import { useHistoryStore } from "@/stores/historyStore";
|
||||
import Filtering, { contains, equals, expandNameTag, toBool, type ValidFilter } from "@/utils/filtering";
|
||||
@@ -70,7 +71,8 @@ const batch: BatchOperationArray = [
|
||||
if (confirm(_l(`Are you sure that you want to delete the selected histories?`))) {
|
||||
try {
|
||||
const historyIds = data.map((x) => String(x.id));
|
||||
await deleteHistories({ ids: historyIds });
|
||||
const historyStore = useHistoryStore();
|
||||
await historyStore.deleteHistories(historyIds);
|
||||
return {
|
||||
status: "success",
|
||||
message: `Deleted ${data.length} histories.`,
|
||||
@@ -92,7 +94,8 @@ const batch: BatchOperationArray = [
|
||||
if (confirm(_l(`Are you sure that you want to restore the selected histories?`))) {
|
||||
try {
|
||||
const historyIds = data.map((x) => String(x.id));
|
||||
await undeleteHistories({ ids: historyIds });
|
||||
const historyStore = useHistoryStore();
|
||||
await historyStore.restoreHistories(historyIds);
|
||||
return {
|
||||
status: "success",
|
||||
message: `Restored ${data.length} histories.`,
|
||||
@@ -108,13 +111,14 @@ const batch: BatchOperationArray = [
|
||||
},
|
||||
{
|
||||
title: "Purge",
|
||||
icon: faTrash,
|
||||
icon: faBurn,
|
||||
condition: (data: Array<HistoryEntry>) => !data.some((x) => x.purged),
|
||||
handler: async (data: Array<HistoryEntry>) => {
|
||||
if (confirm(_l(`Are you sure that you want to permanently delete the selected histories?`))) {
|
||||
try {
|
||||
const historyIds = data.map((x) => String(x.id));
|
||||
await deleteHistories({ ids: historyIds, purge: true });
|
||||
const historyStore = useHistoryStore();
|
||||
await historyStore.deleteHistories(historyIds, true);
|
||||
return {
|
||||
status: "success",
|
||||
message: `Purged ${data.length} histories.`,
|
||||
@@ -185,7 +189,8 @@ const fields: FieldArray = [
|
||||
handler: async (data: HistoryEntry) => {
|
||||
if (confirm(_l("Are you sure that you want to delete this history?"))) {
|
||||
try {
|
||||
await deleteHistory({ history_id: String(data.id) });
|
||||
const historyStore = useHistoryStore();
|
||||
await historyStore.deleteHistory(String(data.id));
|
||||
return {
|
||||
status: "success",
|
||||
message: `'${data.name}' has been deleted.`,
|
||||
@@ -201,12 +206,13 @@ const fields: FieldArray = [
|
||||
},
|
||||
{
|
||||
title: "Delete Permanently",
|
||||
icon: faTrash,
|
||||
icon: faBurn,
|
||||
condition: (data: HistoryEntry) => !data.purged,
|
||||
handler: async (data: HistoryEntry) => {
|
||||
if (confirm(_l("Are you sure that you want to permanently delete this history?"))) {
|
||||
try {
|
||||
await deleteHistory({ history_id: String(data.id), purge: true });
|
||||
const historyStore = useHistoryStore();
|
||||
await historyStore.deleteHistory(String(data.id), true);
|
||||
return {
|
||||
status: "success",
|
||||
message: `'${data.name}' has been permanently deleted.`,
|
||||
@@ -226,7 +232,8 @@ const fields: FieldArray = [
|
||||
condition: (data: HistoryEntry) => !!data.deleted && !data.purged,
|
||||
handler: async (data: HistoryEntry) => {
|
||||
try {
|
||||
await undeleteHistory({ history_id: String(data.id) });
|
||||
const historyStore = useHistoryStore();
|
||||
await historyStore.restoreHistory(String(data.id));
|
||||
return {
|
||||
status: "success",
|
||||
message: `'${data.name}' has been restored.`,
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
<script setup lang="ts">
|
||||
import { BBadge } from "bootstrap-vue";
|
||||
|
||||
import type { HistorySummary } from "@/api";
|
||||
import { useHistoryStore } from "@/stores/historyStore";
|
||||
|
||||
import type { DetailsLayoutSummarized } from "../Layout/types";
|
||||
|
||||
import HistoryIndicators from "../HistoryIndicators.vue";
|
||||
import TextSummary from "@/components/Common/TextSummary.vue";
|
||||
import DetailsLayout from "@/components/History/Layout/DetailsLayout.vue";
|
||||
import UtcDate from "@/components/UtcDate.vue";
|
||||
|
||||
interface Props {
|
||||
history: HistorySummary;
|
||||
writeable: boolean;
|
||||
summarized: boolean;
|
||||
summarized?: DetailsLayoutSummarized;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
writeable: true,
|
||||
summarized: false,
|
||||
summarized: undefined,
|
||||
});
|
||||
|
||||
const historyStore = useHistoryStore();
|
||||
@@ -49,10 +49,7 @@ function onSave(newDetails: HistorySummary) {
|
||||
no-expand />
|
||||
</template>
|
||||
<template v-if="summarized" v-slot:update-time>
|
||||
<BBadge v-b-tooltip pill>
|
||||
<span v-localize>last edited </span>
|
||||
<UtcDate v-if="history.update_time" :date="history.update_time" mode="elapsed" />
|
||||
</BBadge>
|
||||
<HistoryIndicators :history="history" detailed-time />
|
||||
</template>
|
||||
</DetailsLayout>
|
||||
</template>
|
||||
|
||||
@@ -32,7 +32,7 @@ const currentUserOwnsHistory = computed(() => {
|
||||
<div v-if="hasMessages" class="mx-3 mt-2" data-description="history messages">
|
||||
<BAlert v-if="history.purged" :show="history.purged" variant="warning">
|
||||
<FontAwesomeIcon :icon="faBurn" fixed-width />
|
||||
{{ localize("History has been purged") }}
|
||||
{{ localize("History has been permanently deleted") }}
|
||||
</BAlert>
|
||||
<BAlert v-else-if="history.deleted" :show="history.deleted" variant="warning">
|
||||
<FontAwesomeIcon :icon="faTrash" fixed-width />
|
||||
|
||||
@@ -3,6 +3,7 @@ import { library } from "@fortawesome/fontawesome-svg-core";
|
||||
import {
|
||||
faArchive,
|
||||
faBars,
|
||||
faBurn,
|
||||
faColumns,
|
||||
faCopy,
|
||||
faExchangeAlt,
|
||||
@@ -44,6 +45,7 @@ import SelectorModal from "@/components/History/Modals/SelectorModal.vue";
|
||||
library.add(
|
||||
faArchive,
|
||||
faBars,
|
||||
faBurn,
|
||||
faColumns,
|
||||
faCopy,
|
||||
faExchangeAlt,
|
||||
@@ -62,16 +64,21 @@ library.add(
|
||||
interface Props {
|
||||
histories: HistorySummary[];
|
||||
history: HistorySummary;
|
||||
title?: string;
|
||||
historiesLoading?: boolean;
|
||||
minimal?: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
title: "Histories",
|
||||
historiesLoading: false,
|
||||
minimal: false,
|
||||
});
|
||||
|
||||
// modal refs
|
||||
const showSwitchModal = ref(false);
|
||||
const showDeleteModal = ref(false);
|
||||
const showPrivacyModal = ref(false);
|
||||
const showCopyModal = ref(false);
|
||||
|
||||
const purgeHistory = ref(false);
|
||||
|
||||
const userStore = useUserStore();
|
||||
@@ -84,6 +91,10 @@ const canEditHistory = computed(() => {
|
||||
return canMutateHistory(props.history);
|
||||
});
|
||||
|
||||
const isDeletedNotPurged = computed(() => {
|
||||
return props.history.deleted && !props.history.purged;
|
||||
});
|
||||
|
||||
const historyState = computed(() => {
|
||||
if (props.history.purged) {
|
||||
return "purged";
|
||||
@@ -115,11 +126,14 @@ function userTitle(title: string) {
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<nav class="d-flex justify-content-between mx-3 my-2" aria-label="current history management">
|
||||
<h2 class="m-1 h-sm">History</h2>
|
||||
<nav
|
||||
:class="{ 'd-flex justify-content-between mx-3 my-2': !props.minimal }"
|
||||
aria-label="current history management">
|
||||
<h2 v-if="!props.minimal" class="m-1 h-sm">History</h2>
|
||||
|
||||
<BButtonGroup>
|
||||
<BButton
|
||||
v-if="!props.minimal"
|
||||
v-b-tooltip.top.hover.noninteractive
|
||||
class="create-hist-btn"
|
||||
data-description="create new history"
|
||||
@@ -132,6 +146,7 @@ function userTitle(title: string) {
|
||||
</BButton>
|
||||
|
||||
<BButton
|
||||
v-if="!props.minimal"
|
||||
v-b-tooltip.top.hover.noninteractive
|
||||
data-description="switch to another history"
|
||||
size="sm"
|
||||
@@ -146,10 +161,11 @@ function userTitle(title: string) {
|
||||
v-b-tooltip.top.hover.noninteractive
|
||||
no-caret
|
||||
size="sm"
|
||||
variant="link"
|
||||
:variant="props.minimal ? 'outline-info' : 'link'"
|
||||
toggle-class="text-decoration-none"
|
||||
menu-class="history-options-button-menu"
|
||||
title="History options"
|
||||
right
|
||||
data-description="history options">
|
||||
<template v-slot:button-content>
|
||||
<FontAwesomeIcon fixed-width :icon="faBars" />
|
||||
@@ -162,10 +178,12 @@ function userTitle(title: string) {
|
||||
<span>Fetching histories from server</span>
|
||||
</div>
|
||||
|
||||
<span v-else>You have {{ totalHistoryCount }} histories.</span>
|
||||
<span v-else-if="!props.minimal">You have {{ totalHistoryCount }} histories.</span>
|
||||
<span v-else>Manage History</span>
|
||||
</BDropdownText>
|
||||
|
||||
<BDropdownItem
|
||||
v-if="!props.minimal"
|
||||
data-description="switch to multi history view"
|
||||
:disabled="isAnonymous"
|
||||
:title="userTitle('Open History Multiview')"
|
||||
@@ -174,7 +192,7 @@ function userTitle(title: string) {
|
||||
<span v-localize>Show Histories Side-by-Side</span>
|
||||
</BDropdownItem>
|
||||
|
||||
<BDropdownDivider />
|
||||
<BDropdownDivider v-if="!props.minimal" />
|
||||
|
||||
<BDropdownText v-if="!canEditHistory">
|
||||
This history has been <span class="font-weight-bold">{{ historyState }}</span
|
||||
@@ -195,19 +213,20 @@ function userTitle(title: string) {
|
||||
<BDropdownDivider />
|
||||
|
||||
<BDropdownItem
|
||||
v-b-modal:copy-current-history-modal
|
||||
:disabled="isAnonymous"
|
||||
:title="userTitle('Copy History to a New History')">
|
||||
:title="userTitle('Copy History to a New History')"
|
||||
@click="showCopyModal = !showCopyModal">
|
||||
<FontAwesomeIcon fixed-width :icon="faCopy" class="mr-1" />
|
||||
<span v-localize>Copy this History</span>
|
||||
</BDropdownItem>
|
||||
|
||||
<BDropdownItem
|
||||
v-b-modal:delete-history-modal
|
||||
:disabled="!canEditHistory"
|
||||
:title="localize('Permanently Delete History')">
|
||||
<FontAwesomeIcon fixed-width :icon="faTrash" class="mr-1" />
|
||||
<span v-localize>Delete this History</span>
|
||||
:title="localize(isDeletedNotPurged ? 'Permanently Delete History' : 'Delete History')"
|
||||
@click="showDeleteModal = !showDeleteModal">
|
||||
<FontAwesomeIcon fixed-width :icon="isDeletedNotPurged ? faBurn : faTrash" class="mr-1" />
|
||||
<span v-if="isDeletedNotPurged" v-localize>Permanently Delete History</span>
|
||||
<span v-else v-localize>Delete this History</span>
|
||||
</BDropdownItem>
|
||||
|
||||
<BDropdownItem
|
||||
@@ -271,9 +290,9 @@ function userTitle(title: string) {
|
||||
</BDropdownItem>
|
||||
|
||||
<BDropdownItem
|
||||
v-b-modal:history-privacy-modal
|
||||
:disabled="isAnonymous || !canEditHistory"
|
||||
:title="userTitle('Make this History Private')">
|
||||
:title="userTitle('Make this History Private')"
|
||||
@click="showPrivacyModal = !showPrivacyModal">
|
||||
<FontAwesomeIcon fixed-width :icon="faLock" class="mr-1" />
|
||||
<span v-localize>Make Private</span>
|
||||
</BDropdownItem>
|
||||
@@ -282,6 +301,7 @@ function userTitle(title: string) {
|
||||
</nav>
|
||||
|
||||
<SelectorModal
|
||||
v-if="!props.minimal"
|
||||
v-show="showSwitchModal"
|
||||
id="selector-history-modal"
|
||||
:histories="histories"
|
||||
@@ -289,13 +309,19 @@ function userTitle(title: string) {
|
||||
:show-modal.sync="showSwitchModal"
|
||||
@selectHistory="historyStore.setCurrentHistory($event.id)" />
|
||||
|
||||
<CopyModal id="copy-current-history-modal" :history="history" />
|
||||
<CopyModal :history="history" :show-modal.sync="showCopyModal" />
|
||||
|
||||
<BModal
|
||||
id="history-privacy-modal"
|
||||
v-model="showPrivacyModal"
|
||||
title="Make History Private"
|
||||
title-tag="h2"
|
||||
@ok="historyStore.secureHistory(history)">
|
||||
<h4>
|
||||
History:
|
||||
<b>
|
||||
<i>{{ history.name }}</i>
|
||||
</b>
|
||||
</h4>
|
||||
<p v-localize>
|
||||
This will make all the data in this history private (excluding library datasets), and will set
|
||||
permissions such that all new data is created as private. Any datasets within that are currently shared
|
||||
@@ -304,15 +330,15 @@ function userTitle(title: string) {
|
||||
</BModal>
|
||||
|
||||
<BModal
|
||||
id="delete-history-modal"
|
||||
title="Delete History?"
|
||||
v-model="showDeleteModal"
|
||||
:title="isDeletedNotPurged ? 'Permanently Delete History?' : 'Delete History?'"
|
||||
title-tag="h2"
|
||||
@ok="onDelete"
|
||||
@show="purgeHistory = false">
|
||||
@show="purgeHistory = isDeletedNotPurged">
|
||||
<p v-localize>
|
||||
Do you also want to permanently delete the history <i class="ml-1">{{ history.name }}</i>
|
||||
</p>
|
||||
<BFormCheckbox id="purge-history" v-model="purgeHistory">
|
||||
<BFormCheckbox id="purge-history" v-model="purgeHistory" :disabled="isDeletedNotPurged">
|
||||
<span v-localize>Yes, permanently delete this history.</span>
|
||||
</BFormCheckbox>
|
||||
</BModal>
|
||||
|
||||
+13
-1
@@ -12,6 +12,7 @@ library.add(faCheckSquare, faCompress);
|
||||
interface Props {
|
||||
history: HistorySummaryExtended;
|
||||
hasMatches: boolean;
|
||||
editable: boolean;
|
||||
expandedCount: number;
|
||||
showSelection: boolean;
|
||||
isMultiViewItem: boolean;
|
||||
@@ -32,7 +33,7 @@ function onUpdateOperationStatus(updateTime: number) {
|
||||
|
||||
<template>
|
||||
<section>
|
||||
<nav class="content-operations d-flex justify-content-between bg-secondary">
|
||||
<nav v-if="editable" class="content-operations d-flex justify-content-between bg-secondary">
|
||||
<BButtonGroup>
|
||||
<BButton
|
||||
title="Select Items"
|
||||
@@ -66,6 +67,17 @@ function onUpdateOperationStatus(updateTime: number) {
|
||||
:history="history"
|
||||
@update:operation-running="onUpdateOperationStatus" />
|
||||
</nav>
|
||||
<nav v-else-if="isMultiViewItem" class="content-operations bg-secondary">
|
||||
<BButton
|
||||
title="Collapse Items"
|
||||
class="rounded-0"
|
||||
size="sm"
|
||||
variant="link"
|
||||
:disabled="!expandedCount"
|
||||
@click="$emit('collapse-all')">
|
||||
<FontAwesomeIcon :icon="faCompress" fixed-width />
|
||||
</BButton>
|
||||
</nav>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ const contentItemRefs = computed(() => {
|
||||
const currItemFocused = useActiveElement();
|
||||
const lastItemId = ref<string | null>(null);
|
||||
|
||||
const { currentFilterText, currentHistoryId } = storeToRefs(useHistoryStore());
|
||||
const { currentFilterText, currentHistoryId, pinnedHistories, storedHistories } = storeToRefs(useHistoryStore());
|
||||
const { lastCheckedTime, totalMatchesCount, isWatching } = storeToRefs(useHistoryItemsStore());
|
||||
|
||||
const historyStore = useHistoryStore();
|
||||
@@ -143,6 +143,47 @@ const formattedSearchError = computed(() => {
|
||||
};
|
||||
});
|
||||
|
||||
/** Returns a string that indicates whether all the pinned histories have annotations,
|
||||
* tags, both, or none of them.
|
||||
* This is used to format the `DetailsLayout` header uniformly for multi-view histories.
|
||||
*/
|
||||
const detailsSummarized = computed(() => {
|
||||
if (!props.isMultiViewItem) {
|
||||
return undefined;
|
||||
}
|
||||
if (pinnedHistories.value.length === 0) {
|
||||
return "hidden";
|
||||
}
|
||||
|
||||
let annotation = false;
|
||||
let tags = false;
|
||||
let loaded = true;
|
||||
|
||||
for (const h of pinnedHistories.value) {
|
||||
const history = storedHistories.value[h.id];
|
||||
if (!history) {
|
||||
loaded = false;
|
||||
break;
|
||||
}
|
||||
if (history.annotation) {
|
||||
annotation = true;
|
||||
}
|
||||
if (history.tags.length > 0) {
|
||||
tags = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!loaded || (annotation && tags)) {
|
||||
return "both";
|
||||
} else if (annotation) {
|
||||
return "annotation";
|
||||
} else if (tags) {
|
||||
return "tags";
|
||||
} else {
|
||||
return "none";
|
||||
}
|
||||
});
|
||||
|
||||
const storeFilterText = computed(() => {
|
||||
if (props.history.id !== currentHistoryId.value) {
|
||||
return "";
|
||||
@@ -523,10 +564,10 @@ function setItemDragstart(
|
||||
<HistoryDetails
|
||||
:history="history"
|
||||
:writeable="canEditHistory"
|
||||
:summarized="isMultiViewItem"
|
||||
:summarized="detailsSummarized"
|
||||
@update:history="historyStore.updateHistory($event)" />
|
||||
|
||||
<HistoryMessages :history="history" :current-user="currentUser" />
|
||||
<HistoryMessages v-if="!isMultiViewItem" :history="history" :current-user="currentUser" />
|
||||
|
||||
<HistoryCounter
|
||||
:history="history"
|
||||
@@ -539,8 +580,8 @@ function setItemDragstart(
|
||||
@reloadContents="reloadContents" />
|
||||
|
||||
<HistoryOperations
|
||||
v-if="canEditHistory"
|
||||
:history="history"
|
||||
:editable="canEditHistory"
|
||||
:is-multi-view-item="isMultiViewItem"
|
||||
:show-selection="showSelection"
|
||||
:expanded-count="expandedCount"
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
<script setup lang="ts">
|
||||
import { library } from "@fortawesome/fontawesome-svg-core";
|
||||
import { faArchive, faBurn, faTrash } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
|
||||
import { BBadge } from "bootstrap-vue";
|
||||
import { storeToRefs } from "pinia";
|
||||
|
||||
import { type HistorySummary, userOwnsHistory } from "@/api";
|
||||
import { useUserStore } from "@/stores/userStore";
|
||||
import localize from "@/utils/localization";
|
||||
|
||||
import UtcDate from "../UtcDate.vue";
|
||||
|
||||
library.add(faArchive, faBurn, faTrash);
|
||||
|
||||
const userStore = useUserStore();
|
||||
const { currentUser } = storeToRefs(userStore);
|
||||
|
||||
const props = defineProps<{
|
||||
history: HistorySummary;
|
||||
includeCount?: boolean;
|
||||
detailedTime?: boolean;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="d-flex align-items-center flex-gapx-1">
|
||||
<BBadge v-if="props.includeCount" pill :title="localize('Amount of items in history')">
|
||||
{{ history.count }} {{ localize("items") }}
|
||||
</BBadge>
|
||||
<BBadge v-if="history.update_time" pill :title="localize('Last edited')">
|
||||
<span v-if="props.detailedTime" v-localize>last edited </span>
|
||||
<UtcDate :date="history.update_time" mode="elapsed" />
|
||||
</BBadge>
|
||||
<BBadge v-if="props.history.purged" pill class="alert-warning" title="Permanently deleted">
|
||||
<FontAwesomeIcon :icon="faBurn" fixed-width />
|
||||
</BBadge>
|
||||
<BBadge v-else-if="history.deleted" pill class="alert-danger" title="Deleted">
|
||||
<FontAwesomeIcon :icon="faTrash" fixed-width />
|
||||
</BBadge>
|
||||
<BBadge v-if="history.archived && userOwnsHistory(currentUser, props.history)" pill title="Archived">
|
||||
<FontAwesomeIcon :icon="faArchive" fixed-width />
|
||||
</BBadge>
|
||||
</div>
|
||||
</template>
|
||||
@@ -19,10 +19,10 @@ import localize from "@/utils/localization";
|
||||
import { HistoriesFilters } from "./HistoriesFilters";
|
||||
|
||||
import TextSummary from "../Common/TextSummary.vue";
|
||||
import HistoryIndicators from "./HistoryIndicators.vue";
|
||||
import Heading from "@/components/Common/Heading.vue";
|
||||
import StatelessTags from "@/components/TagsMultiselect/StatelessTags.vue";
|
||||
import ScrollToTopButton from "@/components/ToolsList/ScrollToTopButton.vue";
|
||||
import UtcDate from "@/components/UtcDate.vue";
|
||||
|
||||
type AdditionalOptions = "set-current" | "multi" | "center";
|
||||
type PinnedHistory = { id: string };
|
||||
@@ -277,18 +277,7 @@ async function loadMore(noScroll = false) {
|
||||
</i>
|
||||
</div>
|
||||
<TextSummary v-else component="h4" :description="history.name" one-line-summary />
|
||||
<div class="d-flex align-items-center flex-gapx-1">
|
||||
<BBadge pill :title="localize('Amount of items in history')">
|
||||
{{ history.count }} {{ localize("items") }}
|
||||
</BBadge>
|
||||
<BBadge
|
||||
v-if="history.update_time"
|
||||
v-b-tooltip.noninteractive.hover
|
||||
pill
|
||||
:title="localize('Last edited')">
|
||||
<UtcDate :date="history.update_time" mode="elapsed" />
|
||||
</BBadge>
|
||||
</div>
|
||||
<HistoryIndicators :history="history" include-count />
|
||||
</div>
|
||||
|
||||
<p v-if="!isMultiviewPanel && history.annotation" class="my-1">{{ history.annotation }}</p>
|
||||
|
||||
@@ -185,7 +185,9 @@ describe("History center panel View", () => {
|
||||
expect(storageDashboardButtonDisabled(wrapper)).toBeFalsy();
|
||||
|
||||
// instead we have an alert
|
||||
expect(wrapper.find("[data-description='history messages']").text()).toBe("History has been purged");
|
||||
expect(wrapper.find("[data-description='history messages']").text()).toBe(
|
||||
"History has been permanently deleted"
|
||||
);
|
||||
});
|
||||
|
||||
it("should not display archived message and should be importable when user is not owner and history is archived", async () => {
|
||||
|
||||
@@ -40,8 +40,7 @@ function onViewCollection(collection: CollectionEntry, currentOffset?: number) {
|
||||
<HistoryNavigation
|
||||
:history="currentHistory"
|
||||
:histories="histories"
|
||||
:histories-loading="historiesLoading"
|
||||
title="Histories" />
|
||||
:histories-loading="historiesLoading" />
|
||||
</template>
|
||||
</HistoryPanel>
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@ import { computed, ref } from "vue";
|
||||
import { useUserStore } from "@/stores/userStore";
|
||||
import l from "@/utils/localization";
|
||||
|
||||
import type { DetailsLayoutSummarized } from "./types";
|
||||
|
||||
import TextSummary from "@/components/Common/TextSummary.vue";
|
||||
import StatelessTags from "@/components/TagsMultiselect/StatelessTags.vue";
|
||||
|
||||
@@ -20,7 +22,7 @@ interface Props {
|
||||
writeable?: boolean;
|
||||
annotation?: string;
|
||||
showAnnotation?: boolean;
|
||||
summarized?: boolean;
|
||||
summarized?: DetailsLayoutSummarized;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
@@ -29,7 +31,7 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
writeable: true,
|
||||
annotation: undefined,
|
||||
showAnnotation: true,
|
||||
summarized: false,
|
||||
summarized: undefined,
|
||||
});
|
||||
|
||||
const emit = defineEmits(["save"]);
|
||||
@@ -47,6 +49,20 @@ const localProps = ref<{ name: string; annotation: string | null; tags: string[]
|
||||
tags: [],
|
||||
});
|
||||
|
||||
const detailsClass = computed(() => {
|
||||
const classes: Record<string, boolean> = {
|
||||
details: true,
|
||||
"summarized-details": props.summarized && !editing.value,
|
||||
"m-3": !props.summarized || editing.value,
|
||||
};
|
||||
|
||||
if (props.summarized) {
|
||||
classes[props.summarized] = true;
|
||||
}
|
||||
|
||||
return classes;
|
||||
});
|
||||
|
||||
const editButtonTitle = computed(() => {
|
||||
if (isAnonymous.value) {
|
||||
return l("Log in to Rename History");
|
||||
@@ -90,10 +106,7 @@ function selectText() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section
|
||||
class="details"
|
||||
:class="summarized && !editing ? 'summarized-details' : 'm-3'"
|
||||
data-description="edit details">
|
||||
<section :class="detailsClass" data-description="edit details">
|
||||
<BButton
|
||||
:disabled="isAnonymous || !writeable"
|
||||
class="edit-button ml-1 float-right"
|
||||
@@ -114,7 +127,9 @@ function selectText() {
|
||||
v-short="annotation"
|
||||
class="mt-2"
|
||||
data-description="annotation value" />
|
||||
<div v-else-if="summarized" style="min-height: 2rem">
|
||||
<div
|
||||
v-else-if="summarized"
|
||||
:class="{ annotation: ['both', 'annotation'].includes(summarized), hidden: summarized === 'hidden' }">
|
||||
<TextSummary
|
||||
v-if="annotation"
|
||||
:description="annotation"
|
||||
@@ -124,8 +139,11 @@ function selectText() {
|
||||
</div>
|
||||
<StatelessTags
|
||||
v-if="tags"
|
||||
class="tags"
|
||||
:class="!summarized && 'mt-2'"
|
||||
:class="{
|
||||
'mt-2': !summarized,
|
||||
tags: ['both', 'tags'].includes(summarized),
|
||||
hidden: summarized === 'hidden',
|
||||
}"
|
||||
:value="tags"
|
||||
disabled
|
||||
:max-visible-tags="summarized ? 1 : 5" />
|
||||
@@ -184,12 +202,20 @@ function selectText() {
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.summarized-details {
|
||||
min-height: 8.5em;
|
||||
margin: 1rem 1rem 0 1rem;
|
||||
margin-left: 0.5rem;
|
||||
max-width: 15rem;
|
||||
|
||||
&.both {
|
||||
min-height: 8.5em;
|
||||
}
|
||||
.tags {
|
||||
min-height: 2rem;
|
||||
}
|
||||
.annotation {
|
||||
min-height: 2rem;
|
||||
}
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export type DetailsLayoutSummarized = "both" | "annotation" | "tags" | "none" | "hidden";
|
||||
@@ -20,9 +20,16 @@ import localize from "@/utils/localization";
|
||||
|
||||
interface Props {
|
||||
history: HistorySummary;
|
||||
showModal?: boolean;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
showModal: false,
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "update:show-modal", value: boolean): void;
|
||||
}>();
|
||||
|
||||
const userStore = useUserStore();
|
||||
const historyStore = useHistoryStore();
|
||||
@@ -32,6 +39,7 @@ const { currentUser, isAnonymous } = storeToRefs(userStore);
|
||||
const name = ref("");
|
||||
const copyAll = ref(false);
|
||||
const loading = ref(false);
|
||||
const localShowModal = ref(props.showModal);
|
||||
|
||||
const title = computed(() => {
|
||||
return `Copying History: ${props.history.name}`;
|
||||
@@ -55,6 +63,18 @@ const formValid = computed(() => {
|
||||
return newNameValid.value;
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.showModal,
|
||||
(newVal) => {
|
||||
localShowModal.value = newVal;
|
||||
}
|
||||
);
|
||||
watch(
|
||||
() => localShowModal.value,
|
||||
(newVal) => {
|
||||
emit("update:show-modal", newVal);
|
||||
}
|
||||
);
|
||||
watch(
|
||||
() => props.history,
|
||||
(newHistory) => {
|
||||
@@ -74,7 +94,7 @@ async function copy(close: () => void) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<BModal v-bind="$attrs" :title="title" title-tag="h2" v-on="$listeners">
|
||||
<BModal v-model="localShowModal" v-bind="$attrs" :title="title" title-tag="h2" v-on="$listeners">
|
||||
<transition name="fade">
|
||||
<BAlert v-localize :show="isAnonymous" variant="warning">
|
||||
As an anonymous user, unless you log in or register, you will lose your current history after copying
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { library } from "@fortawesome/fontawesome-svg-core";
|
||||
import { faTimes } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
|
||||
import { BButton } from "bootstrap-vue";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { computed, ref } from "vue";
|
||||
@@ -6,10 +9,13 @@ import { computed, ref } from "vue";
|
||||
import { useExtendedHistory } from "@/composables/detailedHistory";
|
||||
import { useHistoryStore } from "@/stores/historyStore";
|
||||
|
||||
import HistoryNavigation from "../CurrentHistory/HistoryNavigation.vue";
|
||||
import CollectionPanel from "@/components/History/CurrentCollection/CollectionPanel.vue";
|
||||
import HistoryPanel from "@/components/History/CurrentHistory/HistoryPanel.vue";
|
||||
import LoadingSpan from "@/components/LoadingSpan.vue";
|
||||
|
||||
library.add(faTimes);
|
||||
|
||||
interface Props {
|
||||
source: {
|
||||
id: string;
|
||||
@@ -20,7 +26,7 @@ interface Props {
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const historyStore = useHistoryStore();
|
||||
const { currentHistoryId, pinnedHistories } = storeToRefs(historyStore);
|
||||
const { currentHistoryId, histories, historiesLoading, pinnedHistories } = storeToRefs(historyStore);
|
||||
|
||||
const { history } = useExtendedHistory(props.source.id);
|
||||
|
||||
@@ -42,7 +48,38 @@ function onViewCollection(collection: object) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else id="list-item" class="d-flex flex-column align-items-center w-100">
|
||||
<div v-else id="list-item" class="d-flex flex-column w-100">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<BButton
|
||||
size="sm"
|
||||
class="my-1"
|
||||
:disabled="sameToCurrent"
|
||||
:variant="sameToCurrent ? 'disabled' : 'outline-info'"
|
||||
:title="sameToCurrent ? 'Current History' : 'Switch to this history'"
|
||||
@click="historyStore.setCurrentHistory(source.id)">
|
||||
{{ sameToCurrent ? "Current History" : "Switch to" }}
|
||||
</BButton>
|
||||
<BButton
|
||||
v-if="Object.keys(pinnedHistories).length > 0"
|
||||
size="sm"
|
||||
class="my-1"
|
||||
variant="outline-danger"
|
||||
title="Hide this history from the list"
|
||||
@click="historyStore.unpinHistories([source.id])">
|
||||
<FontAwesomeIcon :icon="faTimes" />
|
||||
Hide
|
||||
</BButton>
|
||||
</div>
|
||||
<HistoryNavigation
|
||||
:history="history"
|
||||
:histories="histories"
|
||||
:histories-loading="historiesLoading"
|
||||
minimal />
|
||||
</div>
|
||||
|
||||
<hr class="w-100 m-1" />
|
||||
|
||||
<CollectionPanel
|
||||
v-if="selectedCollections.length && selectedCollections[0]?.history_id === source.id"
|
||||
:history="history"
|
||||
@@ -57,28 +94,5 @@ function onViewCollection(collection: object) {
|
||||
:show-controls="false"
|
||||
is-multi-view-item
|
||||
@view-collection="onViewCollection" />
|
||||
|
||||
<hr class="w-100 m-2" />
|
||||
|
||||
<div class="flex-row flex-grow-0">
|
||||
<BButton
|
||||
size="sm"
|
||||
class="my-1"
|
||||
:disabled="sameToCurrent"
|
||||
:variant="sameToCurrent ? 'disabled' : 'outline-info'"
|
||||
:title="sameToCurrent ? 'Current History' : 'Switch to this history'"
|
||||
@click="historyStore.setCurrentHistory(source.id)">
|
||||
{{ sameToCurrent ? "Current History" : "Switch to" }}
|
||||
</BButton>
|
||||
<BButton
|
||||
v-if="Object.keys(pinnedHistories).length > 0"
|
||||
size="sm"
|
||||
class="my-1"
|
||||
variant="outline-danger"
|
||||
title="Hide this history from the list"
|
||||
@click="historyStore.unpinHistories([source.id])">
|
||||
Hide
|
||||
</BButton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -8,14 +8,19 @@ import {
|
||||
type HistorySummary,
|
||||
type HistorySummaryExtended,
|
||||
} from "@/api";
|
||||
import { historyFetcher } from "@/api/histories";
|
||||
import {
|
||||
deleteHistories as deleteHistoriesByIds,
|
||||
deleteHistory as deleteHistoryById,
|
||||
historyFetcher,
|
||||
undeleteHistories,
|
||||
undeleteHistory,
|
||||
} from "@/api/histories";
|
||||
import { archiveHistory, unarchiveHistory } from "@/api/histories.archived";
|
||||
import { HistoryFilters } from "@/components/History/HistoryFilters";
|
||||
import { useUserLocalStorage } from "@/composables/userLocalStorage";
|
||||
import {
|
||||
cloneHistory,
|
||||
createAndSelectNewHistory,
|
||||
deleteHistoryById,
|
||||
getCurrentHistoryFromServer,
|
||||
getHistoryByIdFromServer,
|
||||
getHistoryCount,
|
||||
@@ -190,17 +195,64 @@ export const useHistoryStore = defineStore("historyStore", () => {
|
||||
return filteredHistoryIds[0];
|
||||
}
|
||||
|
||||
async function deleteHistory(historyId: string, purge: boolean) {
|
||||
const deletedHistory = (await deleteHistoryById(historyId, purge)) as HistorySummary;
|
||||
const nextAvailableHistoryId = getNextAvailableHistoryId([deletedHistory.id]);
|
||||
if (nextAvailableHistoryId) {
|
||||
await setCurrentHistory(nextAvailableHistoryId);
|
||||
} else {
|
||||
await createNewHistory();
|
||||
async function setNextAvailableHistoryId(excludedIds: string[]) {
|
||||
if (currentHistoryId.value && excludedIds.includes(currentHistoryId.value)) {
|
||||
const nextAvailableHistoryId = getNextAvailableHistoryId(excludedIds);
|
||||
if (nextAvailableHistoryId) {
|
||||
await setCurrentHistory(nextAvailableHistoryId);
|
||||
} else {
|
||||
await createNewHistory();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteHistory(historyId: string, purge = false) {
|
||||
try {
|
||||
const { data } = await deleteHistoryById({ history_id: historyId, purge });
|
||||
const deletedHistory = data as AnyHistory;
|
||||
await setNextAvailableHistoryId([deletedHistory.id]);
|
||||
del(storedHistories.value, deletedHistory.id);
|
||||
await handleTotalCountChange(1, true);
|
||||
} catch (error) {
|
||||
rethrowSimple(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteHistories(ids: string[], purge = false) {
|
||||
try {
|
||||
const { data } = await deleteHistoriesByIds({ ids, purge });
|
||||
const deletedHistories = data as AnyHistory[];
|
||||
const historyIds = deletedHistories.map((x) => String(x.id));
|
||||
await setNextAvailableHistoryId(historyIds);
|
||||
deletedHistories.forEach((history) => {
|
||||
del(storedHistories.value, history.id);
|
||||
});
|
||||
await handleTotalCountChange(deletedHistories.length, true);
|
||||
} catch (error) {
|
||||
rethrowSimple(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function restoreHistory(historyId: string) {
|
||||
try {
|
||||
const { data } = await undeleteHistory({ history_id: historyId });
|
||||
const restoredHistory = data as AnyHistory;
|
||||
await handleTotalCountChange(1);
|
||||
setHistory(restoredHistory);
|
||||
} catch (error) {
|
||||
rethrowSimple(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function restoreHistories(ids: string[]) {
|
||||
try {
|
||||
const { data } = await undeleteHistories({ ids });
|
||||
const restoredHistories = data as AnyHistory[];
|
||||
await handleTotalCountChange(restoredHistories.length);
|
||||
setHistories(restoredHistories);
|
||||
} catch (error) {
|
||||
rethrowSimple(error);
|
||||
}
|
||||
del(storedHistories.value, deletedHistory.id);
|
||||
unpinHistories([deletedHistory.id]);
|
||||
await handleTotalCountChange(1, true);
|
||||
}
|
||||
|
||||
async function loadCurrentHistory(since?: string): Promise<HistoryDevDetailed | undefined> {
|
||||
@@ -334,6 +386,7 @@ export const useHistoryStore = defineStore("historyStore", () => {
|
||||
currentHistoryId,
|
||||
currentFilterText,
|
||||
pinnedHistories,
|
||||
storedHistories,
|
||||
getHistoryById,
|
||||
getHistoryNameById,
|
||||
setCurrentHistory,
|
||||
@@ -348,6 +401,10 @@ export const useHistoryStore = defineStore("historyStore", () => {
|
||||
copyHistory,
|
||||
createNewHistory,
|
||||
deleteHistory,
|
||||
deleteHistories,
|
||||
restoreHistory,
|
||||
restoreHistories,
|
||||
handleTotalCountChange,
|
||||
loadCurrentHistory,
|
||||
loadHistories,
|
||||
loadHistoryById,
|
||||
|
||||
+37
-51
@@ -1,14 +1,17 @@
|
||||
import axios from "axios";
|
||||
import { prependPath } from "utils/redirect";
|
||||
import axios, { type AxiosResponse } from "axios";
|
||||
import type { ApiResponse } from "openapi-typescript-fetch";
|
||||
|
||||
import type { AnyHistory } from "@/api";
|
||||
import { prependPath } from "@/utils/redirect";
|
||||
|
||||
/**
|
||||
* Generic json getter
|
||||
* @param {*} response
|
||||
* @return {Object} response.data or throws an error if response status is not 200
|
||||
* @param response
|
||||
* @return response.data or throws an error if response status is not 200
|
||||
*/
|
||||
function doResponse(response) {
|
||||
function doResponse(response: AxiosResponse | ApiResponse) {
|
||||
if (response.status !== 200) {
|
||||
throw new Error(response);
|
||||
throw new Error(response.statusText);
|
||||
}
|
||||
return response.data;
|
||||
}
|
||||
@@ -16,18 +19,15 @@ function doResponse(response) {
|
||||
/**
|
||||
* Some current endpoints don't accept JSON, so we need to
|
||||
* do some massaging to send in old form post data.
|
||||
* @param {Object} fields
|
||||
*/
|
||||
function formData(fields = {}) {
|
||||
function formData(fields = {} as Record<string, any>) {
|
||||
return Object.keys(fields).reduce((result, fieldName) => {
|
||||
result.set(fieldName, fields[fieldName]);
|
||||
return result;
|
||||
}, new FormData());
|
||||
}
|
||||
|
||||
/**
|
||||
* Default history request parameters.
|
||||
*/
|
||||
/** Default history request parameters. */
|
||||
const stdHistoryParams = {
|
||||
view: "summary",
|
||||
};
|
||||
@@ -43,7 +43,7 @@ const extendedHistoryParams = {
|
||||
|
||||
/**
|
||||
* Create a new history, select it as the current history, and return it if successful.
|
||||
* @return {Object} the new history or throws an error if new history creation fails
|
||||
* @return the new history or throws an error if new history creation fails
|
||||
*/
|
||||
export async function createAndSelectNewHistory() {
|
||||
const url = "history/create_new_current";
|
||||
@@ -57,11 +57,11 @@ export async function createAndSelectNewHistory() {
|
||||
|
||||
/**
|
||||
* Generates copy of history on server.
|
||||
* @param {Object} history Source history
|
||||
* @param {String} name New history name
|
||||
* @param {Boolean} copyAll Copy existing contents
|
||||
* @param history Source history
|
||||
* @param name New history name
|
||||
* @param copyAll Copy existing contents
|
||||
*/
|
||||
export async function cloneHistory(history, name, copyAll) {
|
||||
export async function cloneHistory(history: AnyHistory, name: string, copyAll: boolean) {
|
||||
const url = "api/histories";
|
||||
const payload = {
|
||||
name,
|
||||
@@ -73,23 +73,12 @@ export async function cloneHistory(history, name, copyAll) {
|
||||
return doResponse(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete history on server and return the deleted history.
|
||||
* @param {String} id Encoded history id
|
||||
* @param {Boolean} [purge=false] Permanent delete
|
||||
*/
|
||||
export async function deleteHistoryById(id, purge = false) {
|
||||
const url = `api/histories/${id}` + (purge ? "?purge=True" : "");
|
||||
const response = await axios.delete(prependPath(url), { params: stdHistoryParams });
|
||||
return doResponse(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current history from server and return it.
|
||||
* @param {String} since timestamp to get histories since
|
||||
* @return {Object} the current history
|
||||
* @param since timestamp to get histories since
|
||||
* @return the current history
|
||||
*/
|
||||
export async function getCurrentHistoryFromServer(since = undefined) {
|
||||
export async function getCurrentHistoryFromServer(since: string | undefined = undefined) {
|
||||
const url = "history/current_history_json";
|
||||
const response = await axios.get(prependPath(url), { params: { since: since } });
|
||||
return doResponse(response);
|
||||
@@ -97,10 +86,10 @@ export async function getCurrentHistoryFromServer(since = undefined) {
|
||||
|
||||
/**
|
||||
* Set current history on server and return it.
|
||||
* @param {String} historyId Encoded history id
|
||||
* @return {Object} the current history
|
||||
* @param historyId Encoded history id
|
||||
* @return the current history
|
||||
*/
|
||||
export async function setCurrentHistoryOnServer(historyId) {
|
||||
export async function setCurrentHistoryOnServer(historyId: string) {
|
||||
const url = "history/set_as_current";
|
||||
const response = await axios.get(prependPath(url), { params: { id: historyId } });
|
||||
return doResponse(response);
|
||||
@@ -108,12 +97,12 @@ export async function setCurrentHistoryOnServer(historyId) {
|
||||
|
||||
/**
|
||||
* Get list of histories from server and return them.
|
||||
* @param {Number} offset to start from (default = 0)
|
||||
* @param {Number | null} limit of histories to load (default = null; in which case no limit)
|
||||
* @param {String} queryString to append to url in the form `q=filter&qv=val&q=...`
|
||||
* @return {Promise.<Array>} list of histories
|
||||
* @param offset to start from (default = 0)
|
||||
* @param limit of histories to load (default = null; in which case no limit)
|
||||
* @param queryString to append to url in the form `q=filter&qv=val&q=...`
|
||||
* @return list of histories
|
||||
*/
|
||||
export async function getHistoryList(offset = 0, limit = null, queryString = "") {
|
||||
export async function getHistoryList(offset = 0, limit: number | null = null, queryString = "") {
|
||||
const params = `view=summary&order=update_time&offset=${offset}`;
|
||||
let url = `api/histories?${params}`;
|
||||
if (limit !== null) {
|
||||
@@ -128,7 +117,7 @@ export async function getHistoryList(offset = 0, limit = null, queryString = "")
|
||||
|
||||
/**
|
||||
* Get number of histories for current user from server and return them.
|
||||
* @return {Promise.<Number>} number of histories
|
||||
* @return number of histories
|
||||
*/
|
||||
export async function getHistoryCount() {
|
||||
// This url is temp. for this PR, waiting on:
|
||||
@@ -138,11 +127,8 @@ export async function getHistoryCount() {
|
||||
return doResponse(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load one history by id
|
||||
* @param {String} id
|
||||
*/
|
||||
export async function getHistoryByIdFromServer(id) {
|
||||
/** Load one history by id */
|
||||
export async function getHistoryByIdFromServer(id: string) {
|
||||
const path = `api/histories/${id}`;
|
||||
const response = await axios.get(prependPath(path), { params: extendedHistoryParams });
|
||||
return doResponse(response);
|
||||
@@ -151,10 +137,10 @@ export async function getHistoryByIdFromServer(id) {
|
||||
/**
|
||||
* Set permissions to private for indicated history
|
||||
* TODO: rewrite API endpoint for this
|
||||
* @param {Object} history the history to secure
|
||||
* @return {Object} the secured history
|
||||
* @param history the history to secure
|
||||
* @return the secured history
|
||||
*/
|
||||
export async function secureHistoryOnServer(history) {
|
||||
export async function secureHistoryOnServer(history: AnyHistory) {
|
||||
const { id } = history;
|
||||
const url = "history/make_private";
|
||||
const response = await axios.post(prependPath(url), formData({ history_id: id }));
|
||||
@@ -166,11 +152,11 @@ export async function secureHistoryOnServer(history) {
|
||||
|
||||
/**
|
||||
* Update specific fields in history
|
||||
* @param {Object} historyId the history id to update
|
||||
* @param {Object} payload fields to update
|
||||
* @return {Object} the updated history
|
||||
* @param historyId the history id to update
|
||||
* @param payload fields to update
|
||||
* @return the updated history
|
||||
*/
|
||||
export async function updateHistoryFields(historyId, payload) {
|
||||
export async function updateHistoryFields(historyId: string, payload: Record<string, any>) {
|
||||
const url = `api/histories/${historyId}`;
|
||||
const response = await axios.put(prependPath(url), payload, { params: extendedHistoryParams });
|
||||
return doResponse(response);
|
||||
Reference in New Issue
Block a user