Skip to content

Commit

Permalink
dumili: Extract DumiliBook, show visible pages when scrolling through…
Browse files Browse the repository at this point in the history
… the gallery

Bump dependencies
  • Loading branch information
bperel committed Nov 27, 2024
1 parent 116fa3f commit 6468e12
Show file tree
Hide file tree
Showing 14 changed files with 444 additions and 348 deletions.
2 changes: 1 addition & 1 deletion apps/dumili/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
},
"dependencies": {
"@prisma/client": "^5.22.0",
"@sentry/node": "^8.40.0",
"@sentry/node": "^8.41.0",
"axios": "^1.7.8",
"busboy": "^1.6.0",
"cloudinary": "^1.41.3",
Expand Down
15 changes: 8 additions & 7 deletions apps/dumili/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,16 @@
},
"dependencies": {
"@iconify-json/bi": "^1.2.1",
"@sentry/browser": "^8.40.0",
"@sentry/vue": "^8.40.0",
"@sentry/browser": "^8.41.0",
"@sentry/vue": "^8.41.0",
"@unhead/vue": "^1.11.13",
"@vueuse/core": "^11.3.0",
"@vueuse/components": "^12.0.0",
"@vueuse/integrations": "^11.3.0",
"bootstrap": "^5.3.3",
"bootstrap-vue-next": "^0.25.15",
"js-cookie": "^3.0.5",
"pinia": "^2.2.6",
"pinia": "^2.2.7",
"sortablejs": "^1.15.4",
"vue": "^3.5.13",
"vue-draggable-resizable": "^3.0.0",
Expand All @@ -40,7 +41,7 @@
"~web": "workspace:*"
},
"devDependencies": {
"@antfu/eslint-config": "^3.10.0",
"@antfu/eslint-config": "^3.11.0",
"@eslint/eslintrc": "^3.2.0",
"@eslint/js": "^9.15.0",
"@intlify/eslint-plugin-vue-i18n": "^3.1.0",
Expand All @@ -57,9 +58,9 @@
"sass": "^1.81.0",
"typescript": "^5.7.2",
"typescript-eslint": "^8.16.0",
"unplugin-auto-import": "^0.18.5",
"unplugin-icons": "^0.20.1",
"unplugin-vue-components": "^0.27.4",
"unplugin-auto-import": "^0.18.6",
"unplugin-icons": "^0.20.2",
"unplugin-vue-components": "^0.27.5",
"vite": "^5.4.11",
"vite-plugin-eslint": "^1.8.1",
"vite-plugin-pages": "^0.32.4",
Expand Down
132 changes: 132 additions & 0 deletions apps/dumili/src/components/DumiliBook.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
<template>
<Book
v-if="coverRatio"
v-model:book="book"
v-model:current-page="currentPage"
v-model:opened="isBookOpened"
:cover-ratio="coverRatio"
:pages="indexation.pages"
>
<template #page-overlay="{ index, page }">
<template v-if="hoveredEntryPageNumbers?.includes(index + 1)">
<div
:class="`overlay kind-${hoveredEntry!.acceptedStoryKind?.kind} striped`"
></div>
</template>
<template
v-if="
showAiDetectionsOn?.type === 'page' &&
showAiDetectionsOn.id === page.id
"
>
<div
v-for="panel in page.aiKumikoResultPanels"
:key="`kumiko-match-${panel.id}`"
class="overlay translucent"
:style="getPanelCss(panel)"
></div>
</template>
<template
v-if="
showAiDetectionsOn?.type === 'entry' &&
getEntryPages(indexation, showAiDetectionsOn.id).includes(page)
"
>
<div
v-for="ocrDetection of page.aiOcrResults || []"
:key="`ocr-match-${ocrDetection.id}`"
class="position-absolute ocr-match text w-100 h-100"
:style="getOcrDetectionCss(ocrDetection, index + 1)"
>
{{ ocrDetection.text }}
</div>
</template>
</template>
</Book>
</template>

<script setup lang="ts">
import { components as webComponents, type PageFlip } from "~web";
import { getEntryPages } from "~dumili-utils/entryPages";
import type { FullIndexation } from "~dumili-services/indexation/types";
import type { aiKumikoResultPanel, aiOcrResult } from "~prisma/client_dumili";
import { ref } from "vue";
import { ui } from "~/stores/ui";
const { Book } = webComponents;
const { indexation, firstPageDimensions } = defineProps<{
indexation: FullIndexation;
firstPageDimensions: { width: number; height: number };
}>();
const {
hoveredEntry,
hoveredEntryPageNumbers,
currentPage,
visiblePages,
showAiDetectionsOn,
} = storeToRefs(ui());
const book = ref<PageFlip>();
const isBookOpened = ref(true);
defineEmits<{
"update:book": [value: PageFlip | undefined];
"update:currentPage": [value: number];
"update:opened": [value: boolean];
}>();
watch(
[() => book.value?.getPageCollection(), currentPage],
([pageCollection, currentPage]) => {
if (pageCollection) {
visiblePages.value = new Set(
[currentPage, pageCollection.getCurrentSpreadIndex() * 2].map(
(pageIndex) => indexation.pages[pageIndex].id,
),
);
}
},
);
const coverRatio = computed(
() => firstPageDimensions.height / firstPageDimensions.width,
);
const getPanelCss = (panel: aiKumikoResultPanel) => {
const { width: pageWidth, height: pageHeight } = firstPageDimensions!;
const { x, y, width, height } = panel;
return {
left: `${(x * 100) / pageWidth}%`,
top: `${(y * 100) / pageHeight}%`,
width: `${(width * 100) / pageWidth}%`,
height: `${(height * 100) / pageHeight}%`,
};
};
const getOcrDetectionCss = (
{ x1, x2, x3, x4, y1, y2, y3, y4 }: aiOcrResult,
bookPageIndex: number,
) => {
const pageElement = (
book.value!.getPage(bookPageIndex) as unknown as { element: HTMLDivElement }
).element;
const naturalToRenderedRatio =
pageElement.clientWidth / firstPageDimensions!.width;
return {
clipPath: `polygon(${[
[x1, y1],
[x2, y2],
[x3, y3],
[x4, y4],
]
.map(([x, y]) =>
(["width", "height"] as const)
.map((_, idx) => `${[x, y][idx] * naturalToRenderedRatio}px`)
.join(" "),
)
.join(",")})`,
};
};
</script>
33 changes: 29 additions & 4 deletions apps/dumili/src/components/Gallery.vue
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,22 @@
md="4"
@click="selectedId = id"
>
<b-img v-if="url" :src="url" fluid />
<b-img
v-if="url"
v-element-visibility="[
(visible) => {
if (visible) {
imagesInViewport.add(id);
} else {
imagesInViewport.delete(id);
}
},
{ threshold: 1 },
]"
lazy
:src="url"
fluid
/>
<div class="position-absolute bottom-0 text-center">
{{ id }}
</div>
Expand All @@ -33,12 +48,15 @@ import {
moveArrayElement,
useSortable,
} from "@vueuse/integrations/useSortable";
import { vElementVisibility } from "@vueuse/components";
import { dumiliSocketInjectionKey } from "~/composables/useDumiliSocket";
import { ui } from "~/stores/ui";
const { indexationSocket } = inject(dumiliSocketInjectionKey)!;
const imagesInViewport = ref(new Set<number>());
const { images } = defineProps<{
images: { url: string | null; id: number }[];
selectable?: boolean;
Expand All @@ -54,6 +72,8 @@ const emit = defineEmits<{
const { t: $t } = useI18n();
const { visiblePages, currentPage } = storeToRefs(ui());
const imagesRef = ref<HTMLElement | null>(null);
useSortable(imagesRef, images, {
multiDrag: true,
Expand All @@ -77,11 +97,16 @@ watch(selectedId, (id) => {
}
});
watch(currentPage, (value) => {
document.getElementById(`page-image-${value}`)?.scrollIntoView();
});
watch(
() => ui().currentPage,
(currentPage) => {
document.getElementById(`page-image-${currentPage}`)?.scrollIntoView();
imagesInViewport,
(value) => {
visiblePages.value = value;
},
{ immediate: true },
);
</script>
<style scoped lang="scss">
Expand Down
8 changes: 2 additions & 6 deletions apps/dumili/src/components/TableOfContents.vue
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
<b-col
role="button"
:class="{
'fw-bold': shownPages.includes(pageNumber - 1),
'fw-bold': visiblePages?.has(id),
}"
@click="currentPage = pageNumber - 1"
>{{ $t("Page") }} {{ pageNumber }}<br /><ai-tooltip
Expand Down Expand Up @@ -154,16 +154,12 @@ import { entry as entryModel } from "~prisma/client_dumili";
import { storyKinds } from "~dumili-types/storyKinds";
import { watchDebounced } from "@vueuse/core";
defineProps<{
shownPages: number[];
}>();
const { indexationSocket } = inject(dumiliSocketInjectionKey)!;
const { loadIndexation } = suggestions();
const { hoveredEntry, currentEntry, showAiDetectionsOn } = storeToRefs(ui());
const indexation = storeToRefs(suggestions()).indexation as Ref<FullIndexation>;
const { currentPage } = storeToRefs(ui());
const { currentPage, visiblePages } = storeToRefs(ui());
const { runKumikoOnPage, runKumiko } = useAi();
Expand Down
Loading

0 comments on commit 6468e12

Please sign in to comment.