feat: add reusable private and shared account icons
This commit is contained in:
1 parent
d2b2681698
commit
a4e9d56b8a
44 files changed
+1443
-20
No files matched your search
@@ -0,0 +1,14 @@
|
||||
"""Optional catalog maintenance: convert downloaded ICO files using Pillow.
|
||||
|
||||
Normal installs use the committed PNG assets and do not need Python.
|
||||
Run after download-icons.cjs, before icons:seed.
|
||||
"""
|
||||
from pathlib import Path
|
||||
from PIL import Image
|
||||
|
||||
folder = Path(__file__).resolve().parent.parent / 'assets' / 'icons'
|
||||
for source in folder.glob('*.ico'):
|
||||
with Image.open(source) as image:
|
||||
image.convert('RGBA').save(source.with_suffix('.png'))
|
||||
source.unlink()
|
||||
print('Catalog ICO sources converted to PNG.')
|
||||
@@ -3,7 +3,7 @@ const tsc = require.resolve('typescript/bin/tsc');
|
||||
if (spawnSync(process.execPath, [tsc], { stdio: 'inherit' }).status !== 0) process.exit(1);
|
||||
const children = [
|
||||
spawn(process.execPath, [tsc, '--watch', '--preserveWatchOutput'], { stdio: 'inherit' }),
|
||||
spawn(process.execPath, ['--watch', 'dist/main.js'], { stdio: 'inherit' }),
|
||||
spawn(process.execPath, ['--watch', '--watch-path=dist', 'dist/main.js'], { stdio: 'inherit' }),
|
||||
];
|
||||
function stop() {
|
||||
for (const p of children) p.kill();
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
// Explicit, fixed public sources only. No user account information is sent.
|
||||
const fs = require('node:fs/promises');
|
||||
const path = require('node:path');
|
||||
const sharp = require('sharp');
|
||||
const { createHash } = require('node:crypto');
|
||||
const dir = path.join(__dirname, '../assets/icons');
|
||||
async function main() {
|
||||
await fs.mkdir(dir, { recursive: true });
|
||||
const response = await fetch('https://api.github.com/repos/cellier/bank-icon-cn/contents/png/72');
|
||||
if (!response.ok) throw Error();
|
||||
const banks = await response.json();
|
||||
const names = [
|
||||
'中国工商银行',
|
||||
'中国农业银行',
|
||||
'中国建设银行',
|
||||
'中国银行',
|
||||
'交通银行',
|
||||
'招商银行',
|
||||
'中信银行',
|
||||
'中国光大银行',
|
||||
'中国民生银行',
|
||||
'兴业银行',
|
||||
'平安银行',
|
||||
'上海浦东发展银行',
|
||||
'广发银行',
|
||||
'华夏银行',
|
||||
'北京银行',
|
||||
'上海银行',
|
||||
'浙商银行',
|
||||
];
|
||||
const sources = names.map((name) => ({
|
||||
name,
|
||||
source: banks.find((b) => b.name === name + '@3x.png')?.download_url,
|
||||
}));
|
||||
sources.push(
|
||||
{ name: '支付宝', source: 'https://i.alipayobjects.com/common/favicon/favicon.ico' },
|
||||
{ name: '微信', source: 'https://res.wx.qq.com/a/wx_fed/assets/res/NTI4MWU5.ico' },
|
||||
{ name: '京东金融', source: 'https://jr.jd.com/logo.png' },
|
||||
);
|
||||
const manifest = [];
|
||||
for (const item of sources) {
|
||||
if (!item.source) throw Error('Missing catalog source');
|
||||
const res = await fetch(item.source, { signal: AbortSignal.timeout(20000) });
|
||||
if (!res.ok) throw Error('Public icon download failed');
|
||||
const raw = Buffer.from(await res.arrayBuffer());
|
||||
const file =
|
||||
String(manifest.length + 1).padStart(2, '0') +
|
||||
(item.source.endsWith('.ico') ? '.ico' : '.png');
|
||||
if (file.endsWith('.ico')) await fs.writeFile(path.join(dir, file), raw);
|
||||
else
|
||||
await fs.writeFile(
|
||||
path.join(dir, file),
|
||||
await sharp(raw)
|
||||
.resize(256, 256, { fit: 'inside', withoutEnlargement: true })
|
||||
.png()
|
||||
.toBuffer(),
|
||||
);
|
||||
manifest.push({
|
||||
...item,
|
||||
file: file.replace('.ico', '.png'),
|
||||
sha256: createHash('sha256').update(raw).digest('hex'),
|
||||
});
|
||||
}
|
||||
await fs.writeFile(path.join(dir, 'sources.json'), JSON.stringify(manifest, null, 2) + '\n');
|
||||
console.log(`Downloaded ${manifest.length} public icons; convert ICO sources before seeding.`);
|
||||
}
|
||||
main().catch(() => {
|
||||
console.error('Icon source download failed; existing database icons were not changed.');
|
||||
process.exitCode = 1;
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
require('dotenv/config');
|
||||
const { PrismaClient } = require('@prisma/client');
|
||||
const { readFile } = require('node:fs/promises');
|
||||
const { join } = require('node:path');
|
||||
const { createHash } = require('node:crypto');
|
||||
const sharp = require('sharp');
|
||||
const db = new PrismaClient();
|
||||
async function main() {
|
||||
const dir = join(__dirname, '../assets/icons');
|
||||
const sources = JSON.parse(await readFile(join(dir, 'sources.json'), 'utf8'));
|
||||
// Deterministic IDs make repeated runs safe, without overwriting existing records.
|
||||
for (const item of sources) {
|
||||
const data = await sharp(await readFile(join(dir, item.file)))
|
||||
.resize(256, 256, { fit: 'inside', withoutEnlargement: true })
|
||||
.png()
|
||||
.toBuffer();
|
||||
const hex = createHash('sha256')
|
||||
.update('worthpath-builtin:' + item.name)
|
||||
.digest('hex');
|
||||
const id = `${hex.slice(0, 8)}-${hex.slice(8, 12)}-4${hex.slice(13, 16)}-a${hex.slice(17, 20)}-${hex.slice(20, 32)}`;
|
||||
await db.icon.upsert({
|
||||
where: { id },
|
||||
create: {
|
||||
id,
|
||||
name: item.name,
|
||||
shared: true,
|
||||
data,
|
||||
source: item.source,
|
||||
hash: createHash('sha256').update(data).digest('hex'),
|
||||
},
|
||||
update: {},
|
||||
});
|
||||
}
|
||||
console.log(`Shared icon catalog ready: ${sources.length} icons.`);
|
||||
}
|
||||
main()
|
||||
.catch(() => {
|
||||
console.error('Icon seeding failed; check local database configuration.');
|
||||
process.exitCode = 1;
|
||||
})
|
||||
.finally(() => db.$disconnect());
|
||||
Reference in new issue
Block a user