Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ width: 128px;
border-radius: 128px;
" />

## v2.5.4

- Features
- Online tagging matches to review show a toolbar Review button and a
tab-title count instead of popping up a dialog.

## v2.5.3

- Features
Expand Down
14 changes: 14 additions & 0 deletions frontend/src/app.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import DockerHubDeprecatedSnackbar from "@/components/docker-hub-deprecated-snac
import SessionErrorSnackbar from "@/components/session-error-snackbar.vue";
import { useAuthStore } from "@/stores/auth";
import { useFavoritesStore } from "@/stores/favorites";
import { pendingTitle, useOnlineTagStore } from "@/stores/online-tag";
import { useSocketStore } from "@/stores/socket";

export default {
Expand All @@ -21,7 +22,20 @@ export default {
DockerHubDeprecatedSnackbar,
SessionErrorSnackbar,
},
head() {
/*
* "(N) " on whatever title the page sets, so a background tab shows
* matches waiting for review. Read the count here, not inside the
* template function: unhead calls that at render time, outside this
* effect, so only what head() itself reads re-runs it.
*/
const count = this.isUserAdmin ? this.pendingComicCount : 0;
if (!count) return {};
return { titleTemplate: (title) => pendingTitle(count, title) };
},
computed: {
...mapState(useOnlineTagStore, ["pendingComicCount"]),
...mapState(useAuthStore, ["isUserAdmin"]),
...mapState(useAuthStore, {
user: (state) => state.user,
/*
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/components/admin/admin-header.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<v-toolbar id="titleBar" flat density="compact">
<span id="buttonSpacer" />
<v-toolbar-title id="adminTitle"> Codex Administration </v-toolbar-title>
<OnlineTagReviewButton />
<SettingsDrawerButton
:key="mdAndDown"
class="adminSettingsButton"
Expand All @@ -28,12 +29,14 @@ import { mapWritableState } from "pinia";
import { capitalCase } from "text-case";

import AppBanner from "@/components/banner.vue";
import OnlineTagReviewButton from "@/components/online-tag/review-button.vue";
import SettingsDrawerButton from "@/components/settings/button.vue";
import { TABS, useAdminStore } from "@/stores/admin";
export default {
name: "AdminTitleToolbar",
components: {
AppBanner,
OnlineTagReviewButton,
SettingsDrawerButton,
},
data() {
Expand Down
19 changes: 6 additions & 13 deletions frontend/src/components/admin/drawer/admin-menu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ import AdminStatusList from "@/components/admin/drawer/status-list.vue";
import CodexListItem from "@/components/codex-list-item.vue";
import { useAdminStore } from "@/stores/admin";
import { useAuthStore } from "@/stores/auth";
import { promptComics, useOnlineTagStore } from "@/stores/online-tag";
import { useOnlineTagStore } from "@/stores/online-tag";

export default {
name: "AdminMenu",
Expand All @@ -79,7 +79,7 @@ export default {
"hasUnseenFailedImports",
"tagWriteErrors",
]),
...mapState(useOnlineTagStore, ["pendingPrompts"]),
...mapState(useOnlineTagStore, ["pendingComicCount", "pendingPrompts"]),
...mapWritableState(useOnlineTagStore, ["promptDialogOpen"]),
showTagWriteErrors() {
return this.tagWriteErrors.length > 0;
Expand All @@ -97,14 +97,7 @@ export default {
return this.pendingPrompts.length > 0;
},
promptsLabel() {
// Comics, not questions: one question can hold a whole series, and the
// Tagging tab counts the rows it will mark for review. A prompt that
// names no comic at all still counts as one thing to look at, so a
// malformed cache entry can't make a visible queue read as empty.
const count = this.pendingPrompts.reduce(
(total, prompt) => total + Math.max(promptComics(prompt).length, 1),
0,
);
const count = this.pendingComicCount;
return `${count} Match${count === 1 ? "" : "es"} to Review`;
},
showAdminPanelLink() {
Expand All @@ -127,9 +120,9 @@ export default {
this.librarianTask("poll");
},
openPrompts() {
// The OnlineTagPromptPopup (mounted in browser.vue + admin.vue) watches
// this flag; flipping it opens the same Match Review dialog the browser
// toolbar button does.
// The OnlineTagPromptPopup (mounted in browser.vue, admin.vue and
// reader.vue) watches this flag; flipping it opens the same Match
// Review dialog the toolbar Review button does.
this.promptDialogOpen = true;
},
},
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
<BrowserSearchButton />
</v-toolbar-items>
<v-spacer />
<OnlineTagReviewButton />
<v-toolbar-items>
<BrowserColumnsButton />
<BrowserViewModeToggle />
Expand All @@ -27,6 +28,7 @@ import BrowserOrderReverseButton from "@/components/browser/toolbars/top/order-r
import BrowserSearchButton from "@/components/browser/toolbars/top/search-button.vue";
import BrowserTopCollectionSelect from "@/components/browser/toolbars/top/top-collection-select.vue";
import BrowserViewModeToggle from "@/components/browser/toolbars/top/view-mode-toggle.vue";
import OnlineTagReviewButton from "@/components/online-tag/review-button.vue";
import SettingsDrawerButton from "@/components/settings/button.vue";
import { useBrowserStore } from "@/stores/browser";

Expand All @@ -40,6 +42,7 @@ export default {
BrowserOrderBySelect,
BrowserOrderReverseButton,
BrowserViewModeToggle,
OnlineTagReviewButton,
SettingsDrawerButton,
},
data() {
Expand Down
74 changes: 74 additions & 0 deletions frontend/src/components/online-tag/review-button.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<!--
Opens the online-tagging Match Review dialog while matches wait for an
admin. The dialog never opens on its own, so this button is the cue in the
browser and reader top toolbars and the admin title bar; the settings drawer
item is the second path. Phones show the icon and count only.

A plain v-btn, not ScaleButton: compact density on phones stacks with
size="small" and shrinks the button to 16px tall.
-->
<template>
<v-btn
v-if="show"
class="reviewButton"
size="small"
variant="tonal"
color="warning"
:prepend-icon="mdiTagMultiple"
:title="label"
:aria-label="label"
@click="onClick"
>
<span class="d-none d-sm-inline">Review&nbsp;</span>{{ countText }}
</v-btn>
</template>

<script>
import { mdiTagMultiple } from "@mdi/js";
import { mapState, mapWritableState } from "pinia";

import { NUMBER_FORMAT } from "@/datetime";
import { useAuthStore } from "@/stores/auth";
import { useOnlineTagStore } from "@/stores/online-tag";

export default {
name: "OnlineTagReviewButton",
data() {
return {
mdiTagMultiple,
};
},
computed: {
...mapState(useAuthStore, ["isUserAdmin"]),
...mapState(useOnlineTagStore, ["pendingComicCount"]),
...mapWritableState(useOnlineTagStore, ["promptDialogOpen"]),
show() {
return Boolean(this.isUserAdmin) && this.pendingComicCount > 0;
},
countText() {
return NUMBER_FORMAT.format(this.pendingComicCount);
},
label() {
const count = this.pendingComicCount;
return `Review ${this.countText} online tagging match${count === 1 ? "" : "es"}`;
},
},
methods: {
onClick() {
// The OnlineTagPromptPopup (mounted in browser.vue, admin.vue and
// reader.vue) watches this flag.
this.promptDialogOpen = true;
},
},
};
</script>

<style scoped lang="scss">
/* Layered: these rules beat Vuetify's component CSS by position,
* and lose to a `color`/utility prop, which is the intended order. */
@layer codex-components {
.reviewButton {
margin-right: 8px;
}
}
</style>
15 changes: 15 additions & 0 deletions frontend/src/components/reader/toolbars/top/reader-toolbar-top.vue
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
/>
</v-toolbar-items>
<v-spacer />
<OnlineTagReviewButton class="readerReviewButton" />
<v-toolbar-items v-if="!empty">
<ReaderArcSelect />
<FavoriteToggle
Expand Down Expand Up @@ -61,6 +62,7 @@ import { mapActions, mapState } from "pinia";
import AppBanner from "@/components/banner.vue";
import FavoriteToggle from "@/components/favorite-toggle.vue";
import MetadataDialog from "@/components/metadata/metadata-dialog.vue";
import OnlineTagReviewButton from "@/components/online-tag/review-button.vue";
import ReaderArcSelect from "@/components/reader/toolbars/top/reader-arc-select.vue";
import SettingsDrawerButton from "@/components/settings/button.vue";
import { useAuthStore } from "@/stores/auth";
Expand All @@ -74,6 +76,7 @@ export default {
AppBanner,
FavoriteToggle,
MetadataDialog,
OnlineTagReviewButton,
ReaderArcSelect,
SettingsDrawerButton,
},
Expand Down Expand Up @@ -224,6 +227,18 @@ export default {
padding-bottom: 10px;
}

/*
* Nothing else in this toolbar can shrink, so below 360px the Review button
* would push the settings button off-screen (a three-digit reading order
* needs 360px with it, 302px without). The settings drawer item still offers
* the review there.
*/
@media (max-width: 359.98px) {
.readerReviewButton {
display: none;
}
}

@media #{map.get(vuetify.$display-breakpoints, 'sm-and-down')} {
.closeBook {
font-size: small;
Expand Down
9 changes: 0 additions & 9 deletions frontend/src/components/settings/button.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
</v-icon>
<component :is="AdminSettingsButtonProgress" v-if="isUserAdmin" />
<component :is="AdminSettingsButtonErrors" v-if="isUserAdmin" />
<component :is="AdminSettingsButtonPrompts" v-if="isUserAdmin" />
</ScaleButton>
</template>

Expand All @@ -37,26 +36,18 @@ const AdminSettingsButtonErrors = markRaw(
),
);

const AdminSettingsButtonPrompts = markRaw(
defineAsyncComponent(
() => import("@/components/admin/drawer/admin-settings-button-prompts.vue"),
),
);

export default {
name: "SettingsDrawerButton",
components: {
AdminSettingsButtonErrors,
AdminSettingsButtonProgress,
AdminSettingsButtonPrompts,
ScaleButton,
},
data() {
return {
mdiMenu,
AdminSettingsButtonProgress,
AdminSettingsButtonErrors,
AdminSettingsButtonPrompts,
};
},
computed: {
Expand Down
Loading