Handle anki connect integration
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
import { settings } from "$lib/settings";
|
||||
import { showSnackbar } from "$lib/util"
|
||||
import { get } from "svelte/store";
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
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')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import TextBoxes from './TextBoxes.svelte';
|
||||
|
||||
export let page: Page;
|
||||
export let src: Blob;
|
||||
export let src: File;
|
||||
</script>
|
||||
|
||||
<div
|
||||
@@ -13,5 +13,5 @@
|
||||
style:background-image={`url(${URL.createObjectURL(src)})`}
|
||||
class="relative"
|
||||
>
|
||||
<TextBoxes {page} />
|
||||
<TextBoxes {page} {src} />
|
||||
</div>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { clamp, showSnackbar } from '$lib/util';
|
||||
import { clamp, promptConfirmation } from '$lib/util';
|
||||
import type { Page } from '$lib/types';
|
||||
import { settings } from '$lib/settings';
|
||||
import { CirclePlusSolid } from 'flowbite-svelte-icons';
|
||||
import { getLastCardInfo } from '$lib/anki-connect';
|
||||
import { updateLastCard } from '$lib/anki-connect';
|
||||
|
||||
export let page: Page;
|
||||
export let src: File;
|
||||
|
||||
$: textBoxes = page.blocks.map((block) => {
|
||||
const { img_height, img_width } = page;
|
||||
@@ -39,15 +39,17 @@
|
||||
$: border = $settings.textBoxBorders ? '1px solid red' : 'none';
|
||||
$: contenteditable = $settings.textEditable;
|
||||
|
||||
async function onUpdateCard() {
|
||||
const res = await getLastCardInfo();
|
||||
|
||||
showSnackbar(res.fields.Word.value);
|
||||
async function onUpdateCard(lines: string[]) {
|
||||
if ($settings.ankiConnectSettings.enabled) {
|
||||
promptConfirmation('Add image to last created anki card?', () =>
|
||||
updateLastCard(src, lines.join(' '))
|
||||
);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#each textBoxes as { fontSize, height, left, lines, top, width, writingMode }, index (`text-box-${index}`)}
|
||||
<div
|
||||
<button
|
||||
class="text-box"
|
||||
style:width
|
||||
style:height
|
||||
@@ -58,17 +60,13 @@
|
||||
style:font-weight={fontWeight}
|
||||
style:display
|
||||
style:border
|
||||
on:dblclick={() => onUpdateCard(lines)}
|
||||
{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}
|
||||
<p>{line}</p>
|
||||
{/each}
|
||||
</div>
|
||||
</button>
|
||||
{/each}
|
||||
|
||||
<style>
|
||||
@@ -100,10 +98,6 @@
|
||||
font-weight: var(--bold);
|
||||
}
|
||||
|
||||
.text-box:hover button {
|
||||
opacity: 100;
|
||||
}
|
||||
|
||||
.text-box:focus p,
|
||||
.text-box:hover p {
|
||||
display: table;
|
||||
|
||||
@@ -4,6 +4,10 @@ import { showSnackbar } from '$lib/util/snackbar';
|
||||
import { requestPersistentStorage } from '$lib/util/upload';
|
||||
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) {
|
||||
const zipFileReader = new BlobReader(file);
|
||||
const zipReader = new ZipReader(zipFileReader);
|
||||
@@ -13,7 +17,7 @@ export async function unzipManga(file: File) {
|
||||
|
||||
for (const entry of entries) {
|
||||
const mime = getMimeType(entry.filename);
|
||||
if (mime === 'image/jpeg' || mime === 'image/png') {
|
||||
if (imageTypes.includes(mime)) {
|
||||
const blob = await entry.getData?.(new BlobWriter(mime));
|
||||
if (blob) {
|
||||
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[]) {
|
||||
const zipTypes = ['zip', 'cbz', 'ZIP', 'CBZ'];
|
||||
const volumes: Record<string, Volume> = {};
|
||||
const mangas: string[] = [];
|
||||
|
||||
@@ -86,7 +89,7 @@ export async function processFiles(files: File[]) {
|
||||
|
||||
const mimeType = type || getMimeType(file.name);
|
||||
|
||||
if (mimeType === 'image/jpeg' || mimeType === 'image/png') {
|
||||
if (imageTypes.includes(mimeType)) {
|
||||
if (webkitRelativePath) {
|
||||
const imageName = webkitRelativePath.split('/').at(-1);
|
||||
const vol = webkitRelativePath.split('/').at(-2);
|
||||
|
||||
Reference in New Issue
Block a user