Handle anki connect integration

This commit is contained in:
ZXY101
2023-09-24 07:20:49 +02:00
parent 43b5199bff
commit 4d6f5bd7ae
4 changed files with 102 additions and 26 deletions

View File

@@ -1,4 +1,6 @@
import { settings } from "$lib/settings";
import { showSnackbar } from "$lib/util" import { showSnackbar } from "$lib/util"
import { get } from "svelte/store";
export async function ankiConnect(action: string, params: Record<string, any>) { export async function ankiConnect(action: string, params: Record<string, any>) {
@@ -40,8 +42,85 @@ export function getCardAgeInMin(id: number) {
return Math.floor((Date.now() - id) / 60000); return Math.floor((Date.now() - id) / 60000);
} }
export async function updateLastCard() { async function blobToBase64(blob: Blob) {
return new Promise<string | null>((resolve) => {
const reader = new FileReader();
reader.onloadend = () => resolve(reader.result as string);
reader.readAsDataURL(blob);
});
}
export async function imageToWebp(source: File) {
const image = await createImageBitmap(source);
const canvas = new OffscreenCanvas(image.width, image.height);
const context = canvas.getContext("2d");
if (context) {
context.drawImage(image, 0, 0);
const blob = await canvas.convertToBlob({ type: 'image/webp' });
image.close();
return await blobToBase64(blob);
}
}
export async function updateLastCard(image: File, sentence: string) {
const {
overwriteImage,
enabled,
cropImage,
grabSentence,
pictureField,
sentenceField
} = get(settings).ankiConnectSettings;
if (!enabled) {
return
}
showSnackbar('Updating last card...', 10000)
const id = await getLastCardId() const id = await getLastCardId()
return Math.floor((Date.now() - id) / 60000); if (getCardAgeInMin(id) >= 5) {
showSnackbar('Error: Card created over 5 minutes ago');
return;
}
const fields: Record<string, any> = {};
if (grabSentence) {
fields[sentenceField] = sentence;
}
if (overwriteImage) {
fields[pictureField] = ''
}
if (cropImage) {
console.log('image cropping here');
}
const picture = await imageToWebp(image)
if (picture) {
ankiConnect('updateNoteFields', {
note: {
id,
fields,
picture: {
filename: `_${id}.webp`,
data: picture.split(';base64,')[1],
fields: [pictureField],
},
},
}).then(() => {
showSnackbar('Card updated!')
}).catch((e) => {
showSnackbar(e)
})
} else {
showSnackbar('Something went wrong')
}
} }

View File

@@ -3,7 +3,7 @@
import TextBoxes from './TextBoxes.svelte'; import TextBoxes from './TextBoxes.svelte';
export let page: Page; export let page: Page;
export let src: Blob; export let src: File;
</script> </script>
<div <div
@@ -13,5 +13,5 @@
style:background-image={`url(${URL.createObjectURL(src)})`} style:background-image={`url(${URL.createObjectURL(src)})`}
class="relative" class="relative"
> >
<TextBoxes {page} /> <TextBoxes {page} {src} />
</div> </div>

View File

@@ -1,11 +1,11 @@
<script lang="ts"> <script lang="ts">
import { clamp, showSnackbar } from '$lib/util'; import { clamp, promptConfirmation } from '$lib/util';
import type { Page } from '$lib/types'; import type { Page } from '$lib/types';
import { settings } from '$lib/settings'; import { settings } from '$lib/settings';
import { CirclePlusSolid } from 'flowbite-svelte-icons'; import { updateLastCard } from '$lib/anki-connect';
import { getLastCardInfo } from '$lib/anki-connect';
export let page: Page; export let page: Page;
export let src: File;
$: textBoxes = page.blocks.map((block) => { $: textBoxes = page.blocks.map((block) => {
const { img_height, img_width } = page; const { img_height, img_width } = page;
@@ -39,15 +39,17 @@
$: border = $settings.textBoxBorders ? '1px solid red' : 'none'; $: border = $settings.textBoxBorders ? '1px solid red' : 'none';
$: contenteditable = $settings.textEditable; $: contenteditable = $settings.textEditable;
async function onUpdateCard() { async function onUpdateCard(lines: string[]) {
const res = await getLastCardInfo(); if ($settings.ankiConnectSettings.enabled) {
promptConfirmation('Add image to last created anki card?', () =>
showSnackbar(res.fields.Word.value); updateLastCard(src, lines.join(' '))
);
}
} }
</script> </script>
{#each textBoxes as { fontSize, height, left, lines, top, width, writingMode }, index (`text-box-${index}`)} {#each textBoxes as { fontSize, height, left, lines, top, width, writingMode }, index (`text-box-${index}`)}
<div <button
class="text-box" class="text-box"
style:width style:width
style:height style:height
@@ -58,17 +60,13 @@
style:font-weight={fontWeight} style:font-weight={fontWeight}
style:display style:display
style:border style:border
on:dblclick={() => onUpdateCard(lines)}
{contenteditable} {contenteditable}
> >
{#if $settings.ankiConnectSettings.enabled}
<button class="absolute -m-8 opacity-0 hover:block p-4" on:click={onUpdateCard}>
<CirclePlusSolid class="text-primary-500 hover:text-primary-600" />
</button>
{/if}
{#each lines as line} {#each lines as line}
<p>{line}</p> <p>{line}</p>
{/each} {/each}
</div> </button>
{/each} {/each}
<style> <style>
@@ -100,10 +98,6 @@
font-weight: var(--bold); font-weight: var(--bold);
} }
.text-box:hover button {
opacity: 100;
}
.text-box:focus p, .text-box:focus p,
.text-box:hover p { .text-box:hover p {
display: table; display: table;

View File

@@ -4,6 +4,10 @@ import { showSnackbar } from '$lib/util/snackbar';
import { requestPersistentStorage } from '$lib/util/upload'; import { requestPersistentStorage } from '$lib/util/upload';
import { BlobReader, ZipReader, BlobWriter, getMimeType } from '@zip.js/zip.js'; import { BlobReader, ZipReader, BlobWriter, getMimeType } from '@zip.js/zip.js';
const zipTypes = ['zip', 'cbz', 'ZIP', 'CBZ'];
const imageTypes = ['image/jpeg', 'image/png', 'image/webp'];
export async function unzipManga(file: File) { export async function unzipManga(file: File) {
const zipFileReader = new BlobReader(file); const zipFileReader = new BlobReader(file);
const zipReader = new ZipReader(zipFileReader); const zipReader = new ZipReader(zipFileReader);
@@ -13,7 +17,7 @@ export async function unzipManga(file: File) {
for (const entry of entries) { for (const entry of entries) {
const mime = getMimeType(entry.filename); const mime = getMimeType(entry.filename);
if (mime === 'image/jpeg' || mime === 'image/png') { if (imageTypes.includes(mime)) {
const blob = await entry.getData?.(new BlobWriter(mime)); const blob = await entry.getData?.(new BlobWriter(mime));
if (blob) { if (blob) {
const file = new File([blob], entry.filename, { type: mime }); const file = new File([blob], entry.filename, { type: mime });
@@ -61,7 +65,6 @@ export async function scanFiles(item: FileSystemEntry, files: Promise<File | und
} }
export async function processFiles(files: File[]) { export async function processFiles(files: File[]) {
const zipTypes = ['zip', 'cbz', 'ZIP', 'CBZ'];
const volumes: Record<string, Volume> = {}; const volumes: Record<string, Volume> = {};
const mangas: string[] = []; const mangas: string[] = [];
@@ -86,7 +89,7 @@ export async function processFiles(files: File[]) {
const mimeType = type || getMimeType(file.name); const mimeType = type || getMimeType(file.name);
if (mimeType === 'image/jpeg' || mimeType === 'image/png') { if (imageTypes.includes(mimeType)) {
if (webkitRelativePath) { if (webkitRelativePath) {
const imageName = webkitRelativePath.split('/').at(-1); const imageName = webkitRelativePath.split('/').at(-1);
const vol = webkitRelativePath.split('/').at(-2); const vol = webkitRelativePath.split('/').at(-2);