check images and dark mode

This commit is contained in:
polv committed 2026-03-22 00:41:56 +07:00
1 parent 978ebfa75f
commit 904cdd0413
10 files changed
+122 -19

No files matched your search

+27 -8
View File
@@ -113,6 +113,8 @@ impl YomitanWriter {
installed_at TEXT NOT NULL DEFAULT (datetime('now')), installed_at TEXT NOT NULL DEFAULT (datetime('now')),
bundle_name TEXT, -- custom bundle_name TEXT, -- custom
lang TEXT NOT NULL, -- custom lang TEXT NOT NULL, -- custom
asset_count INTEGER, -- custom
source TEXT, -- custom
UNIQUE (title, revision) UNIQUE (title, revision)
); );
CREATE TABLE IF NOT EXISTS glossaries ( -- not used. moved to a new file CREATE TABLE IF NOT EXISTS glossaries ( -- not used. moved to a new file
@@ -186,7 +188,7 @@ impl YomitanWriter {
self.ensure_db_attached(DbChild::YomitanGlossary)?; self.ensure_db_attached(DbChild::YomitanGlossary)?;
self.ensure_db_attached(DbChild::Search)?; self.ensure_db_attached(DbChild::Search)?;
let schema_version = "2026-03-08"; let schema_version = "2026-03-21";
let current_schema_version = { let current_schema_version = {
let mut stmt = self let mut stmt = self
.conn .conn
@@ -275,6 +277,15 @@ impl YomitanWriter {
)?; )?;
} }
if v < "2026-03-21" {
self.conn.execute_batch(
"
ALTER TABLE dictionaries ADD COLUMN asset_count INTEGER;
ALTER TABLE dictionaries ADD COLUMN source TEXT;
",
)?;
}
if v < schema_version { if v < schema_version {
self.conn self.conn
.prepare("UPDATE schema_meta SET value = ?2 WHERE key = ?1")? .prepare("UPDATE schema_meta SET value = ?2 WHERE key = ?1")?
@@ -454,6 +465,8 @@ impl YomitanWriter {
let cursor = Cursor::new(zip_file.bytes()?); let cursor = Cursor::new(zip_file.bytes()?);
let mut archive = ZipArchive::new(cursor)?; let mut archive = ZipArchive::new(cursor)?;
let mut asset_count = 0;
for i in 0..archive.len() { for i in 0..archive.len() {
// Metadata — borrow ends before by_index_raw // Metadata — borrow ends before by_index_raw
if let (Some(asset_name), is_dir) = { if let (Some(asset_name), is_dir) = {
@@ -481,6 +494,8 @@ impl YomitanWriter {
let mut entry = archive.by_index(i)?; let mut entry = archive.by_index(i)?;
let mut out = File::create(&out_path)?; let mut out = File::create(&out_path)?;
io::copy(&mut entry, &mut out)?; // writes plain bytes io::copy(&mut entry, &mut out)?; // writes plain bytes
asset_count += 1;
} }
} }
@@ -491,7 +506,6 @@ impl YomitanWriter {
serde_json::from_str::<Value>(&s)? serde_json::from_str::<Value>(&s)?
} }
Err(e) => { Err(e) => {
// TODO: proper error handling
eprintln!("{}", e); eprintln!("{}", e);
return Ok(YomitanZipImportResult { return Ok(YomitanZipImportResult {
exists: false, exists: false,
@@ -534,17 +548,21 @@ impl YomitanWriter {
.to_string(); .to_string();
{ {
let title: &str = &title; // Skip if already installed or older revision
// Skip if already installed
let mut stmt = self let mut stmt = self
.conn .conn
.prepare("SELECT (revision < ?2) FROM dictionaries WHERE title = ?1")?; .prepare("SELECT (revision < ?2) FROM dictionaries WHERE bundle_name = ?1")?;
let mut rows = stmt.query(params![title, revision])?; let mut rows = stmt.query(params![bundle_name, revision])?;
if let Some(row) = rows.next()? { if let Some(row) = rows.next()? {
let is_outdated: bool = row.get(0)?; let is_outdated: bool = row.get(0)?;
if is_outdated { if is_outdated {
self.drop_dictionary(bundle_name, lang)?; self.drop_dictionary(bundle_name, lang)?;
} else { } else {
self.conn.execute(
"UPDATE dictionaries SET asset_count = ?2 WHERE bundle_name = ?1",
params![bundle_name, asset_count],
)?;
return Ok(YomitanZipImportResult { return Ok(YomitanZipImportResult {
exists: true, exists: true,
load: false, load: false,
@@ -560,8 +578,8 @@ impl YomitanWriter {
let tx = self.conn.transaction()?; let tx = self.conn.transaction()?;
tx.execute( tx.execute(
"INSERT INTO dictionaries "INSERT INTO dictionaries
(title, revision, author, url, description, lang, bundle_name) VALUES (title, revision, author, url, description, lang, bundle_name, asset_count) VALUES
( ?1, ?2, ?3, ?4, ?5, ?6, ?7)", ( ?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)",
params![ params![
title, title,
revision, revision,
@@ -570,6 +588,7 @@ impl YomitanWriter {
index_file.get("description").and_then(Value::as_str), index_file.get("description").and_then(Value::as_str),
lang, lang,
bundle_name, bundle_name,
asset_count,
], ],
)?; )?;
+11 -7
View File
@@ -188,12 +188,21 @@ impl AppService {
let mut new_dicts: Vec<String> = vec![]; let mut new_dicts: Vec<String> = vec![];
let mut to_be_removed_dicts: Vec<String> = vec![]; let mut to_be_removed_dicts: Vec<String> = vec![];
{
let mut writer = YomitanWriter::new(self.db.dir.clone())?;
writer.create_schema(&import_callback)?;
}
let db_path = self.db.dir.join(DBFILE[DbChild::Yomitan]); let db_path = self.db.dir.join(DBFILE[DbChild::Yomitan]);
if db_path.exists() { if db_path.exists() {
let conn = Connection::open(&db_path)?; let conn = Connection::open(&db_path)?;
let mut stmt = let mut stmt = conn.prepare(
conn.prepare("SELECT DISTINCT bundle_name FROM dictionaries WHERE lang = ?1")?; "
SELECT DISTINCT bundle_name FROM dictionaries
WHERE lang = ?1 AND asset_count IS NOT NULL
",
)?;
let mut rows = stmt.query([lang])?; let mut rows = stmt.query([lang])?;
let mut db_zip_list: HashSet<String> = HashSet::new(); let mut db_zip_list: HashSet<String> = HashSet::new();
@@ -228,11 +237,6 @@ impl AppService {
to_be_removed_dicts: to_be_removed_dicts.clone(), to_be_removed_dicts: to_be_removed_dicts.clone(),
}); });
{
let mut writer = YomitanWriter::new(self.db.dir.clone())?;
writer.create_schema(&import_callback)?;
}
{ {
let conn = Connection::open(&db_path)?; let conn = Connection::open(&db_path)?;
for z in to_be_removed_dicts.clone().iter() { for z in to_be_removed_dicts.clone().iter() {
+10
View File
@@ -44,3 +44,13 @@ html {
height: 100vh; height: 100vh;
overflow-y: hidden; overflow-y: hidden;
} }
html.dark .glossaries {
span {
word-break: keep-all;
}
img {
filter: invert(1) hue-rotate(180deg);
}
}
+2 -3
View File
@@ -57,14 +57,13 @@ export default function Glossary({
function GlossaryImage({ img, yomitanURL }: { img: any; yomitanURL: string }) { function GlossaryImage({ img, yomitanURL }: { img: any; yomitanURL: string }) {
return ( return (
<img <img
className="glossary-image"
src={`${yomitanURL || ""}/${img.path}`} src={`${yomitanURL || ""}/${img.path}`}
width={img.width}
height={img.height}
title={img.title} title={img.title}
alt={img.alt} alt={img.alt}
style={{ style={{
imageRendering: img.imageRendering || "auto", imageRendering: img.imageRendering || "auto",
width: `${img.width}em`, width: `${img.width}em`, // unit seems to be em, not px
height: `${img.height}em`, height: `${img.height}em`,
}} }}
/> />
@@ -3,6 +3,7 @@ import { useEffect, useRef, useState } from "react";
import { Dialog, Portal } from "@chakra-ui/react"; import { Dialog, Portal } from "@chakra-ui/react";
import { invoke } from "@tauri-apps/api/core"; import { invoke } from "@tauri-apps/api/core";
import { listen } from "@tauri-apps/api/event"; import { listen } from "@tauri-apps/api/event";
import { fetch } from "@tauri-apps/plugin-http";
import { supabase } from "../lib/supabaseClient"; import { supabase } from "../lib/supabaseClient";
@@ -13,6 +14,13 @@ interface YomitanDictEntry {
lang: string; lang: string;
} }
interface YomitanStorageWorkerDictEntry {
key: string;
size: number;
uploaded: string;
url: string;
}
interface YomitanDownloadDictEntry { interface YomitanDownloadDictEntry {
url: string; url: string;
filepath: string; filepath: string;
@@ -119,6 +127,52 @@ function LoadingDialog() {
}); });
} }
} }
const { VITE_STORAGE_WORKER_URL, VITE_STORAGE_WORKER_TOKEN } =
import.meta.env;
try {
const prefix = `${lang}/`;
let entries: YomitanStorageWorkerDictEntry[] = [];
const LAST_API_CALL_KEY = "LAST_API_CALL_KEY_storageWorkerURL";
const LAST_API_JSON_KEY = "LAST_API_JSON_KEY_storageWorkerURL";
const lastCall = localStorage.getItem(LAST_API_CALL_KEY);
const lastJSON = localStorage.getItem(LAST_API_JSON_KEY);
if (lastJSON && lastCall) {
const d = new Date(lastCall);
d.setDate(d.getDate() + 1);
if (d > new Date()) {
entries = JSON.parse(lastJSON);
}
}
if (!entries.length) {
console.log(`visiting ${VITE_STORAGE_WORKER_URL}`);
entries = await fetch(VITE_STORAGE_WORKER_URL + "/files", {
headers: {
Authorization: `Bearer ${VITE_STORAGE_WORKER_TOKEN}`,
},
}).then((r) => r.json());
localStorage.setItem(LAST_API_CALL_KEY, new Date().toISOString());
localStorage.setItem(LAST_API_JSON_KEY, JSON.stringify(entries));
}
dicts.push(
...entries
.filter((o) => o.key.startsWith(prefix))
.map((o) => ({
filepath: `yomitan/${lang}/${o.key.substring(prefix.length)}`,
url: o.url,
})),
);
} catch (e) {
console.error(e);
}
} }
await invoke("init_yomitan", { dicts, lang }); await invoke("init_yomitan", { dicts, lang });
+1
View File
@@ -412,6 +412,7 @@ function SearchPage() {
</Card.Header> </Card.Header>
<Card.Body> <Card.Body>
<div <div
className="glossaries"
style={{ style={{
display: "flex", display: "flex",
flexDirection: "column", flexDirection: "column",
+7 -1
View File
@@ -8,7 +8,13 @@
"permissions": [ "permissions": [
"core:default", "core:default",
"opener:default", "opener:default",
"http:default", {
"identifier": "http:default",
"allow": [
{ "url": "https://api.github.com/repos/*/releases/latest" },
{ "url": "https://storage-worker.*.workers.dev" }
]
},
"fs:default", "fs:default",
"clipboard-manager:allow-read-text" "clipboard-manager:allow-read-text"
] ]
+3
View File
@@ -0,0 +1,3 @@
interface Env {
YOMITAN_STORAGE_API_TOKEN: string;
}
+6
View File
@@ -13,6 +13,12 @@
export default { export default {
async fetch(request, env) { async fetch(request, env) {
// Auth check first
const auth = request.headers.get("Authorization");
if (auth !== `Bearer ${env.YOMITAN_STORAGE_API_TOKEN}`) {
return new Response("Unauthorized", { status: 401 });
}
const url = new URL(request.url); const url = new URL(request.url);
// List files: GET /files // List files: GET /files
+1
View File
@@ -38,6 +38,7 @@
"skipLibCheck": true, "skipLibCheck": true,
"types": [ "types": [
"./worker-configuration.d.ts", "./worker-configuration.d.ts",
"./env.d.ts",
"node" "node"
] ]
}, },