Handle anki connect integration
This commit is contained in:
@@ -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')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user