Skip to content

Commit

Permalink
dumili: Extract TableOfContentsEntry and TableOfContentsPage, hide up…
Browse files Browse the repository at this point in the history
…load widget once we are done uploading images, allow to disassociate image URLs from their page
  • Loading branch information
bperel committed Dec 2, 2024
1 parent b10e371 commit 73ae5b8
Show file tree
Hide file tree
Showing 14 changed files with 284 additions and 223 deletions.
15 changes: 14 additions & 1 deletion apps/dumili/api/services/indexation/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,20 @@ export default (io: Server) => {

indexationSocket.on("runKumikoOnPage", async (pageId, callback) => {
const { indexation } = indexationSocket.data;
const page = indexation.pages.find(({ id }) => id === pageId)!;
const page = await prisma.page.findUnique({
where: {
id: pageId,
indexationId: indexation.id,
},
});

if (!page) {
callback({
error: `This indexation does not have any page with this ID`,
errorDetails: JSON.stringify({ pageId }),
});
return;
}

await setKumikoInferredPageStoryKinds([page]);
const entry = getEntryFromPage(indexation, pageId)!;
Expand Down
4 changes: 2 additions & 2 deletions apps/dumili/api/services/indexation/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export default abstract class {

abstract setPageUrl: (
id: number,
url: string,
url: string|null,
callback: () => void,
) => void;

Expand Down Expand Up @@ -138,7 +138,7 @@ export default abstract class {
abstract runKumikoOnPage: (
pageId: page["id"],
callback: (
data: Errorable<{ status: "OK" }, "Kumiko output could not be parsed">,
data: Errorable<{ status: "OK" }, "This indexation does not have any page with this ID"|"Kumiko output could not be parsed">,
) => void,
) => void;

Expand Down
8 changes: 5 additions & 3 deletions apps/dumili/src/components/AiTooltip.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
@mouseout="() => (showRepeat = false)"
@mouseover="() => (showRepeat = true)"
>
<b-tooltip :target="id" click @show="emit('click')" @hide="emit('blur')"
><slot
/></b-tooltip>
<Teleport to="body">
<b-tooltip :target="id" click @show="emit('click')" @hide="emit('blur')"
><slot
/></b-tooltip>
</Teleport>
<AiSuggestionIcon
:id="disabled ? `${id}-disabled` : id"
button
Expand Down
72 changes: 1 addition & 71 deletions apps/dumili/src/components/Entry.vue
Original file line number Diff line number Diff line change
Expand Up @@ -29,54 +29,9 @@
<template #unknown-text>{{
$t("Type inconnu")
}}</template></suggestion-list
><ai-tooltip
:id="`ai-results-entry-story-kind-${entry.id}`"
:value="storyKindAiSuggestion?.kind"
:on-click-rerun="() => runKumiko(entry.id)"
@click="showAiDetectionsOn = { type: 'entry', id: entry.id }"
@blur="showAiDetectionsOn = undefined"
>
<b>{{ $t("Types d'entrées déduits pour les pages") }}</b>
<table-results
:data="
pagesWithInferredKinds.map(({ page, ...inferredData }) => ({
page: page.pageNumber,
...inferredData,
}))
"
/><br />
<b>{{ $t("Type d'entrée déduit") }}</b>
{{
storyKindAiSuggestion
? storyKinds[storyKindAiSuggestion?.kind]
: $t("Non calculé")
}}</ai-tooltip
></b-col
><b-col col cols="4" class="d-flex flex-column align-items-center">
<StorySuggestionList v-model="entry" />
<ai-tooltip
v-if="entry.acceptedStoryKind?.kind === STORY"
:id="`ai-results-entry-story-${entry.id}`"
:value="storyAiSuggestions.map(({ storycode }) => storycode!)"
:on-click-rerun="() => runStorycodeOcr(entry.id)"
@click="showAiDetectionsOn = { type: 'entry', id: entry.id }"
@blur="showAiDetectionsOn = undefined"
>
<template v-if="storyAiSuggestions.length">
{{ $t("Résultats OCR pour la première case:") }}
<table-results :data="pages[0].aiOcrResults" />
{{ $t("Histoires potentielles:") }}
<table-results
:data="
storyAiSuggestions.map(({ storycode }) => ({
storycode,
title: storyDetails[storycode!].title,
}))
" /></template
><template v-else>{{
$t("Pas de résultats OCR")
}}</template></ai-tooltip
>
</b-col>
<b-col col cols="5" class="flex-column">
<b-form-input
Expand Down Expand Up @@ -119,12 +74,10 @@
</template>
<script setup lang="ts">
import { watchDebounced } from "@vueuse/core";
import useAi from "~/composables/useAi";
import { dumiliSocketInjectionKey } from "~/composables/useDumiliSocket";
import { suggestions } from "~/stores/suggestions";
import { ui } from "~/stores/ui";
import { FullEntry } from "~dumili-services/indexation/types";
import { COVER, STORY, storyKinds } from "~dumili-types/storyKinds";
import { COVER, storyKinds } from "~dumili-types/storyKinds";
import { getEntryPages } from "~dumili-utils/entryPages";
import type { storyKind } from "~prisma/client_dumili";
Expand All @@ -135,19 +88,9 @@ defineProps<{
const { indexationSocket } = inject(dumiliSocketInjectionKey)!;
const { indexation } = storeToRefs(suggestions());
const { runStorycodeOcr, runKumiko } = useAi();
const entry = defineModel<FullEntry>({ required: true });
const pagesWithInferredKinds = computed(() =>
getEntryPages(indexation.value!, entry.value.id).map((page) => ({
page,
[$t("Type d'entrée déduit pour la page")]: page.aiKumikoInferredStoryKind
? storyKinds[page.aiKumikoInferredStoryKind]
: $t("Non calculé"),
})),
);
watch(
() => entry.value.acceptedStoryKind?.id,
(storyKindId) => {
Expand Down Expand Up @@ -179,19 +122,6 @@ watchDebounced(
{ debounce: 500, maxWait: 1000 },
);
const { storyDetails } = storeToRefs(coa());
const { showAiDetectionsOn } = storeToRefs(ui());
const pages = computed(() => getEntryPages(indexation.value!, entry.value.id));
const storyKindAiSuggestion = computed(() =>
entry.value.storyKindSuggestions.find(({ isChosenByAi }) => isChosenByAi),
);
const storyAiSuggestions = computed(() =>
entry.value.storySuggestions.filter(({ ocrDetails }) => ocrDetails),
);
const storycode = computed(() => entry.value.acceptedStory?.storycode);
const title = computed(() => entry.value.title || $t("Sans titre"));
Expand Down
16 changes: 14 additions & 2 deletions apps/dumili/src/components/Gallery.vue
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@
md="4"
@click="selectedId = id"
>
<b-button
variant="danger"
class="position-absolute top-0 mt-2 text-center opacity-50 opacity-100-hover"
@click="disconnectPageUrl(id)"
>
{{ $t("Désassocier l'image") }}
</b-button>
<b-img
v-if="url"
v-element-visibility="[
Expand All @@ -42,7 +49,7 @@
>{{ $t("Ajouter") }}</b-button
>
<div class="position-absolute bottom-0 text-center">
Page {{ pageNumber }} ({{ id }})
{{ $t("Page {pageNumber} (ID: {id})", { pageNumber, id }) }}
</div>
</b-col>
</b-row>
Expand All @@ -58,7 +65,7 @@
maxUploadableImagesFromPageNumber(uploadPageNumber!),
)
"
folder-name="20241025T171702824"
@done="loadIndexation"
/>
</b-container>
</template>
Expand Down Expand Up @@ -119,6 +126,11 @@ useSortable(imagesRef, images, {
const selectedId = ref<number | undefined>(undefined);
const disconnectPageUrl = async (id: number) => {
await indexationSocket.value!.services.setPageUrl(id, null);
await loadIndexation();
};
watch(selectedId, (id) => {
if (id) {
emit("selected", id);
Expand Down
134 changes: 15 additions & 119 deletions apps/dumili/src/components/TableOfContents.vue
Original file line number Diff line number Diff line change
Expand Up @@ -48,87 +48,21 @@
>
<b-col :cols="1" style="padding: 0">
<b-row
v-for="{
id,
pageNumber,
aiKumikoResultPanels,
aiKumikoInferredStoryKind,
} in indexation.pages"
:key="id"
v-for="page in indexation.pages"
:key="page.id"
:style="{ height: `${pageHeight}px` }"
class="g-0 px-0 py-0 align-items-center page"
>
<b-col
role="button"
:class="{
'fw-bold': visiblePages?.has(id),
}"
@click="currentPage = pageNumber - 1"
>{{ $t("Page") }} {{ pageNumber }}<br /><ai-tooltip
v-if="aiKumikoResultPanels"
:id="`ai-results-page-${pageNumber}`"
:value="aiKumikoInferredStoryKind"
:on-click-rerun="() => runKumikoOnPage(id)"
@click="showAiDetectionsOn = { type: 'page', id }"
>
<b>{{ $t("Cases détectées") }}</b>
<table-results
:data="
aiKumikoResultPanels.map(({ x, y, width, height }) => ({
x,
y,
width,
height,
}))
"
/>
<b>{{ $t("Type d'entrée déduit pour la page") }}</b>
{{
aiKumikoInferredStoryKind
? storyKinds[aiKumikoInferredStoryKind] || $t("Non calculé")
: $t("Non calculé")
}}
</ai-tooltip>
</b-col>
<TableOfContentsPage :page="page" />
</b-row>
</b-col>
<b-col :cols="1" class="position-relative p-0">
<template v-for="(entry, idx) in indexation.entries" :key="entry.id">
<vue-draggable-resizable
:active="hoveredEntry === entry"
:parent="true"
prevent-deactivation
:resizable="true"
:draggable="false"
:handles="['bm']"
:grid="[1, pageHeight]"
:h="entry.entirepages * pageHeight"
:min-height="pageHeight - 1"
:class-name="`entry col w-100 border kind-${entry.acceptedStoryKind?.kind} ${(hoveredEntry === entry && 'striped') || ''} ${(currentEntry?.id === entry.id && 'border-2') || 'border-1'}`"
:title="`${entry.title || 'Inconnu'} (${getUserFriendlyPageCount(
entry,
)})`"
@mouseover="hoveredEntry = entry"
@mouseout="hoveredEntry = null"
@resize-stop="
(_left: number, _top: number, _width: number, height: number) =>
onEntryResizeStop(idx, height)
"
@click="
currentPage = getFirstPageOfEntry(indexation.entries, entry.id)
"
></vue-draggable-resizable>
<b-button
v-if="
indexation.entries.length - 1 === idx &&
lastHoveredEntry?.id === entry.id
"
class="create-entry fw-bold position-absolute mt-n1 d-flex justify-content-center align-items-center"
title="Create an entry here"
variant="info"
@click="createEntry()"
>{{ $t("Ajouter une entrée") }}</b-button
>
<TableOfContentsEntry
:entry="entry"
@on-entry-resize-stop="onEntryResizeStop(idx, $event)"
@create-entry-after="createEntry"
/>
</template>
</b-col>
<b-col :cols="10" class="d-flex flex-column" style="padding: 0">
Expand All @@ -155,34 +89,25 @@
<script setup lang="ts">
import useAi from "~/composables/useAi";
import { dumiliSocketInjectionKey } from "~/composables/useDumiliSocket";
import {
getEntryFromPage,
getEntryPages,
getFirstPageOfEntry,
} from "~dumili-utils/entryPages";
import { getEntryFromPage, getEntryPages } from "~dumili-utils/entryPages";
import { suggestions } from "~/stores/suggestions";
import { ui } from "~/stores/ui";
import { FullEntry, FullIndexation } from "~dumili-services/indexation/types";
import { entry as entryModel } from "~prisma/client_dumili";
import { storyKinds } from "~dumili-types/storyKinds";
import { FullIndexation } from "~dumili-services/indexation/types";
import { watchDebounced } from "@vueuse/core";
import TableOfContentsEntry from "./TableOfContentsEntry.vue";
const { indexationSocket } = inject(dumiliSocketInjectionKey)!;
const { loadIndexation } = suggestions();
const { hoveredEntry, currentEntry, showAiDetectionsOn } = storeToRefs(ui());
const { hoveredEntry, currentEntry } = storeToRefs(ui());
const indexation = storeToRefs(suggestions()).indexation as Ref<FullIndexation>;
const { currentPage, visiblePages } = storeToRefs(ui());
const { currentPage, pageHeight } = storeToRefs(ui());
const { runKumikoOnPage, runKumiko } = useAi();
const lastHoveredEntry = ref<entryModel | null>(null);
const { runKumiko } = useAi();
const { status: aiStatus } = useAi();
const { t: $t } = useI18n();
const pageHeight = 50;
const numberOfPages = computed({
get: () => indexation.value.pages.length,
set: async (value) => {
Expand All @@ -206,37 +131,17 @@ const issueAiSuggestion = computed(() =>
indexation.value.issueSuggestions.find(({ isChosenByAi }) => isChosenByAi),
);
watch(hoveredEntry, (entry) => {
if (entry) {
lastHoveredEntry.value = entry;
}
});
const updateNumberOfPages = (event: Event) => {
numberOfPages.value = parseInt((event.target as HTMLInputElement).value);
};
const onEntryResizeStop = (entryIdx: number, height: number) => {
indexation.value!.entries[entryIdx].entirepages = Math.max(
0,
Math.round(height / pageHeight),
Math.round(height / pageHeight.value),
);
};
const getUserFriendlyPageCount = (entry: FullEntry) => {
const fraction = entry.brokenpagenumerator
? `${entry.brokenpagenumerator}/${entry.brokenpagedenominator}`
: "";
if (entry.entirepages === 0) {
if (fraction) {
return `${fraction} page`;
} else {
return "0 page";
}
}
return `${entry.entirepages}${fraction ? `+ ${fraction}` : ""} pages`;
};
const createEntry = async () => {
await indexationSocket.value!.services.createEntry();
return loadIndexation();
Expand Down Expand Up @@ -312,15 +217,6 @@ watch(
}
}
:deep(.entry) {
margin-top: 1px;
cursor: pointer;
&:first-child {
margin-top: 0;
}
}
.entry-details {
&.current {
width: 100%;
Expand Down
Loading

0 comments on commit 73ae5b8

Please sign in to comment.