mirror of
https://github.com/patarapolw/cjdic2.git
synced 2026-11-09 17:39:35 +00:00
check images and dark mode
This commit is contained in:
1 parent
978ebfa75f
commit
904cdd0413
10 files changed
+122
-19
No files matched your search
@@ -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
@@ -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() {
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 });
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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"
|
||||||
]
|
]
|
||||||
|
|||||||
Vendored
+3
@@ -0,0 +1,3 @@
|
|||||||
|
interface Env {
|
||||||
|
YOMITAN_STORAGE_API_TOKEN: string;
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -38,6 +38,7 @@
|
|||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"types": [
|
"types": [
|
||||||
"./worker-configuration.d.ts",
|
"./worker-configuration.d.ts",
|
||||||
|
"./env.d.ts",
|
||||||
"node"
|
"node"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in new issue
Block a user