Skip to content

Commit 7c2ef44

Browse files
v1.11.8: gọn popup đổi avatar & cấu hình hệ thống dễ đọc
- Bỏ tab "Linh vật Cục bộ" và "Tự nhập" khỏi popup đổi avatar — chỉ giữ Codex Pets + Emojis; avatar cũ dạng URL/tên file vẫn render đúng - Redesign menu trái popup Cấu hình hệ thống: nền sáng hơn, label/icon inactive màu slate-300 (trước xám đậm khó đọc) - Bump version 1.11.7 → 1.11.8 (package.json, tauri.conf.json, Cargo.toml) 🤖 Generated with Codebuff Co-Authored-By: Codebuff <noreply@codebuff.com>
1 parent 460b6c3 commit 7c2ef44

9 files changed

Lines changed: 40 additions & 156 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "server-dashboard-tauri",
33
"private": true,
4-
"version": "1.11.7",
4+
"version": "1.11.8",
55
"type": "module",
66
"scripts": {
77
"dev": "vite",

src-tauri/Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src-tauri/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "multitool-pro"
3-
version = "1.11.7"
3+
version = "1.11.8"
44
description = "MultiTool Pro - All-in-one utility"
55
authors = ["you"]
66
edition = "2021"

src-tauri/tauri.conf.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"productName": "MultiTool Pro",
3-
"version": "1.11.7",
3+
"version": "1.11.8",
44
"identifier": "com.multitoolpro.desktop",
55
"build": {
66
"frontendDist": "../dist",

src/App.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,15 @@ interface PendingSize {
6868
}
6969

7070
const CHANGELOGS: ChangelogEntry[] = [
71+
{
72+
version: '1.11.8',
73+
title: 'v1.11.8 - Popup đổi avatar gọn hơn & Cấu hình hệ thống dễ đọc',
74+
items: [
75+
'Popup đổi avatar: bỏ tab "Linh vật Cục bộ" và "Tự nhập" — chỉ giữ Codex Pets + Emojis (gọn, ít phức tạp hơn).',
76+
'Popup Cấu hình hệ thống: menu trái sáng rõ hơn, label + icon chưa chọn đổi sang màu sáng dễ đọc (trước đây xám đậm khó nhìn).',
77+
'Sửa avatar cũ dạng URL/tên file vẫn hiển thị đúng (chỉ bỏ phần chọn mới, không phá dữ liệu đã lưu).',
78+
]
79+
},
7180
{
7281
version: '1.11.7',
7382
title: 'v1.11.7 - Đổi avatar từ sidebar & sửa màn hình login',
@@ -210,7 +219,7 @@ function App() {
210219
const [openPrinter, setOpenPrinter] = useState<string | null>(null)
211220
const [statusText, setStatusText] = useState('Sẵn sàng')
212221
const [autostart, setAutostart] = useState(false)
213-
const [appVersion, setAppVersion] = useState('1.11.7')
222+
const [appVersion, setAppVersion] = useState('1.11.8')
214223
const [changelogOpen, setChangelogOpen] = useState(false)
215224
const [changelogAnim, setChangelogAnim] = useState<'enter' | 'exit'>('enter')
216225
// WHY: Popup auto-update chuyên nghiệp — thay cho window.confirm cũ. Một popup duy

src/components/AvatarPickerDialog.tsx

Lines changed: 13 additions & 140 deletions
Original file line numberDiff line numberDiff line change
@@ -7,41 +7,35 @@ import {
77
safeAvatarBg,
88
emojiFallbackFor,
99
isImageFileName,
10-
localMascotUrl,
1110
resolveAvatarValue,
1211
fetchCodexPets,
13-
fetchLocalMascots,
14-
MASCOTS_DIR_DEFAULT,
1512
} from '../lib/avatars'
1613

1714
// WHY: Popup đổi avatar — tham khảo AvatarPickerDialog của web english-topics:
18-
// 3 tab (Codex Pets / Emojis / URL tự nhập) + preview lớn + chọn màu nền đại diện.
19-
// Lưu 2 thứ về Supabase: user_profiles.avatar_emoji (giá trị avatar — emoji,
20-
// codex:<slug> hoặc URL) và user_settings.settings_json.avatar_bg (class màu nền,
21-
// đúng cấu trúc bên web để 2 app đọc/ghi tương thích lẫn nhau).
15+
// 2 tab (Codex Pets / Emojis) + preview lớn + chọn màu nền đại diện.
16+
// Lưu 2 thứ về Supabase: user_profiles.avatar_emoji (giá trị avatar — emoji hoặc
17+
// codex:<slug>) và user_settings.settings_json.avatar_bg (class màu nền, đúng cấu
18+
// trúc bên web để 2 app đọc/ghi tương thích lẫn nhau).
2219
interface AvatarPickerDialogProps {
2320
open: boolean
2421
userId: string
2522
// WHY: Giá trị avatar hiện tại (avatar_emoji) để khởi tạo lựa chọn — có thể là
26-
// emoji, `codex:<slug>` hoặc URL ảnh.
23+
// emoji hoặc `codex:<slug>`.
2724
currentValue: string | null
2825
// WHY: Class màu nền hiện tại (settings_json.avatar_bg) — null nếu chưa từng lưu.
2926
currentBg: string | null
30-
// WHY: Thư mục linh vật CỤC BỘ hiện tại (settings_json.mascots_dir) — dùng để khởi
31-
// tạo ô nhập thư mục trong tab "Cục bộ" và preview tên file ảnh.
32-
currentMascotsDir: string | null
3327
onClose: () => void
3428
// WHY: Báo UserMenu biết đã lưu xong (kèm giá trị mới) để refresh avatar sidebar.
3529
onSaved: (value: string, bg: string) => void
3630
}
3731

38-
type PickerTab = 'codex' | 'emoji' | 'local' | 'url'
32+
type PickerTab = 'codex' | 'emoji'
3933

4034
// WHY: Giá trị mặc định khi chưa có avatar — biểu tượng 👤 như bên web.
4135
const DEFAULT_AVATAR = '👤'
4236

4337
export default function AvatarPickerDialog({
44-
open, userId, currentValue, currentBg, currentMascotsDir, onClose, onSaved,
38+
open, userId, currentValue, currentBg, onClose, onSaved,
4539
}: AvatarPickerDialogProps) {
4640
const [tab, setTab] = useState<PickerTab>('codex')
4741
// WHY: Giá trị avatar đang chọn (chưa lưu) — khởi tạo từ avatar hiện tại.
@@ -54,41 +48,21 @@ export default function AvatarPickerDialog({
5448
// WHY: Các slug linh vật bị 404 khi tải ảnh (bị gỡ khỏi upstream) → fallback emoji
5549
// thay vì hiện khung vỡ (giống logic getEmojiFallback bên web).
5650
const [brokenPets, setBrokenPets] = useState<Set<string>>(new Set())
57-
// WHY: Thư mục linh vật CỤC BỘ — mặc định public/mascots của web english-topics; user
58-
// có thể sửa đường dẫn máy thật. Lưu vào settings_json.mascots_dir khi save.
59-
const [mascotsDir, setMascotsDir] = useState<string>(() => currentMascotsDir || MASCOTS_DIR_DEFAULT)
60-
const [localMascots, setLocalMascots] = useState<string[]>([])
61-
const [localLoading, setLocalLoading] = useState(false)
62-
// WHY: File ảnh cục bộ bị lỗi khi tải (file hỏng/thiếu) → fallback emoji theo tên file.
63-
const [localBroken, setLocalBroken] = useState<Set<string>>(new Set())
6451
const [previewError, setPreviewError] = useState(false)
6552
const [saving, setSaving] = useState(false)
6653
const [error, setError] = useState<string | null>(null)
6754
const [saved, setSaved] = useState(false)
6855

69-
// WHY: (Re)load danh sách linh vật cục bộ từ thư mục (qua backend) — dùng cho cả lúc
70-
// mở popup lẫn nút "Làm mới" khi user sửa đường dẫn thư mục.
71-
const reloadLocal = async (dir: string) => {
72-
setLocalLoading(true)
73-
setLocalBroken(new Set())
74-
const files = await fetchLocalMascots(dir)
75-
setLocalMascots(files)
76-
setLocalLoading(false)
77-
}
78-
7956
// WHY: Mỗi lần mở popup — reset trạng thái + nạp lại danh sách Codex Pets (fresh),
8057
// đồng thời đồng bộ lựa chọn với giá trị hiện tại (user có thể đã đổi từ nơi khác).
8158
useEffect(() => {
8259
if (!open) return
8360
setValue(currentValue || DEFAULT_AVATAR)
8461
setBg(safeAvatarBg(currentBg))
85-
const dir = currentMascotsDir || MASCOTS_DIR_DEFAULT
86-
setMascotsDir(dir)
8762
setPreviewError(false)
8863
setError(null)
8964
setSaved(false)
9065
setBrokenPets(new Set())
91-
setLocalBroken(new Set())
9266
let disposed = false
9367
;(async () => {
9468
setLoadingPets(true)
@@ -99,9 +73,6 @@ export default function AvatarPickerDialog({
9973
setPetsError(list.length === 0)
10074
setLoadingPets(false)
10175
})()
102-
// WHY: Load luôn danh sách linh vật cục bộ (thư mục mặc định/đã lưu) — tránh
103-
// chờ user bấm "Làm mới" mới thấy có gì trong tab.
104-
reloadLocal(dir)
10576
return () => { disposed = true }
10677
// eslint-disable-next-line react-hooks/exhaustive-deps
10778
}, [open])
@@ -117,9 +88,9 @@ export default function AvatarPickerDialog({
11788
const trimmedValue = value.trim()
11889

11990
// WHY: Resolve giá trị avatar đang chọn sang cách hiển thị (ảnh/emoji) — xử lý cả
120-
// codex:<slug> → R2, URL/path tự nhập, tên file ảnh (dog.png) → linh vật CỤC BỘ qua
121-
// backend, còn lại là emoji (dùng chung resolveAvatarValue với sidebar).
122-
const resolvedPreview = resolveAvatarValue(trimmedValue, mascotsDir)
91+
// codex:<slug> → R2 và emoji (dùng chung resolveAvatarValue với sidebar, vẫn hỗ trợ
92+
// URL/tên file nếu avatar cũ đang lưu dạng đó).
93+
const resolvedPreview = resolveAvatarValue(trimmedValue)
12394
const isImage = resolvedPreview.isImage && !!resolvedPreview.src
12495
const previewSrc = resolvedPreview.src
12596

@@ -172,9 +143,6 @@ export default function AvatarPickerDialog({
172143
const nextJson = {
173144
...((settings?.settings_json as Record<string, unknown>) || {}),
174145
avatar_bg: bg,
175-
// WHY: Lưu thư mục linh vật cục bộ cùng avatar — sidebar cần biết thư mục
176-
// để render avatar dạng tên file (dog.png) khi mở lại app.
177-
mascots_dir: mascotsDir,
178146
}
179147
if (settings) {
180148
await supabase
@@ -202,8 +170,8 @@ export default function AvatarPickerDialog({
202170

203171
if (!open) return null
204172

205-
// WHY: Nút chuyển tab (Codex Pets / Emojis / Tự nhập) — tab đang chọn nổi bật màu
206-
// emerald, tab khác mờ; dùng chung cho cả 3 tab để tránh lặp JSX.
173+
// WHY: Nút chuyển tab (Codex Pets / Emojis) — tab đang chọn nổi bật màu emerald,
174+
// tab khác mờ; dùng chung cho cả 2 tab để tránh lặp JSX.
207175
const tabBtn = (t: PickerTab, label: string, icon: string) => (
208176
<button
209177
type="button"
@@ -234,7 +202,7 @@ export default function AvatarPickerDialog({
234202
<div className="w-7 h-7 rounded-lg bg-emerald-500/10 border border-emerald-500/30 flex items-center justify-center text-sm">🖼️</div>
235203
<div>
236204
<h3 className="text-sm font-bold" style={{ color: 'var(--fg)' }}>Đổi avatar</h3>
237-
<p className="text-[10px]" style={{ color: 'var(--fg-dim)' }}>Chọn linh vật Codex Pets, emoji hoặc dán link ảnh</p>
205+
<p className="text-[10px]" style={{ color: 'var(--fg-dim)' }}>Chọn linh vật Codex Pets hoặc emoji</p>
238206
</div>
239207
</div>
240208
<button
@@ -303,8 +271,6 @@ export default function AvatarPickerDialog({
303271
<div className="flex gap-1.5 p-1 rounded-xl shrink-0" style={{ background: 'rgba(255,255,255,0.04)' }}>
304272
{tabBtn('codex', 'Codex Pets', '🐾')}
305273
{tabBtn('emoji', 'Emojis', '😀')}
306-
{tabBtn('local', 'Cục bộ', '🗂️')}
307-
{tabBtn('url', 'Tự nhập', '🔗')}
308274
</div>
309275

310276
<div className="flex-1 min-h-0 mt-3 overflow-y-auto rounded-xl border p-2" style={{ borderColor: 'var(--border)' }}>
@@ -380,99 +346,6 @@ export default function AvatarPickerDialog({
380346
</div>
381347
)}
382348

383-
{tab === 'local' && (
384-
<div className="flex flex-col gap-2.5 min-h-40">
385-
{/* WHY: Ô nhập đường dẫn thư mục linh vật CỤC BỘ — mặc định public/
386-
mascots của web english-topics trên cùng máy; user sửa đường dẫn
387-
máy thật rồi bấm Làm mới (giống web chỉ đọc public/mascots). */}
388-
<div className="flex gap-2 shrink-0">
389-
<input
390-
type="text"
391-
value={mascotsDir}
392-
onChange={e => setMascotsDir(e.target.value)}
393-
onKeyDown={e => { if (e.key === 'Enter') reloadLocal(mascotsDir) }}
394-
placeholder="Đường dẫn thư mục linh vật (vd .../english-topics_v2_nextjs/public/mascots)"
395-
className="flex-1 min-w-0 px-3 py-2 text-[11px] rounded-lg border transition-all focus:outline-none focus:ring-2 bg-slate-800/60 border-slate-700 text-slate-100 placeholder-slate-500 focus:border-emerald-500 focus:ring-emerald-500/20"
396-
/>
397-
<button
398-
type="button"
399-
onClick={() => reloadLocal(mascotsDir)}
400-
className="px-3.5 py-2 text-[11px] font-semibold rounded-lg border cursor-pointer transition-colors hover:bg-white/5 shrink-0"
401-
style={{ color: 'var(--fg-secondary)', borderColor: 'var(--border)' }}
402-
>
403-
🔄 Làm mới
404-
</button>
405-
</div>
406-
407-
{localLoading ? (
408-
<div className="flex-1 min-h-32 flex flex-col items-center justify-center gap-2 text-xs" style={{ color: 'var(--fg-dim)' }}>
409-
<span className="w-5 h-5 border-2 border-emerald-400/20 border-t-emerald-400 rounded-full animate-spin" />
410-
Đang đọc thư mục...
411-
</div>
412-
) : localMascots.length === 0 ? (
413-
<div className="flex-1 min-h-32 flex flex-col items-center justify-center gap-1.5 text-xs text-center px-4" style={{ color: 'var(--fg-dim)' }}>
414-
<span className="text-xl">🗂️</span>
415-
Không tìm thấy linh vật nào trong thư mục.
416-
<span className="text-[10px] max-w-xs">Đưa file ảnh (png/jpg/gif/webp/svg) vào thư mục rồi bấm Làm mới.</span>
417-
</div>
418-
) : (
419-
<div className="grid grid-cols-4 sm:grid-cols-5 gap-2">
420-
{localMascots.map(file => {
421-
const selected = trimmedValue === file
422-
const broken = localBroken.has(file)
423-
return (
424-
<button
425-
type="button"
426-
key={file}
427-
onClick={() => { setValue(file); setPreviewError(false) }}
428-
title={file}
429-
className={`w-full aspect-square rounded-xl border-2 transition-all duration-150 cursor-pointer flex items-center justify-center overflow-hidden p-0.5 ${
430-
selected
431-
? 'border-emerald-400 ring-2 ring-emerald-400/30'
432-
: 'border-transparent hover:border-white/20 hover:bg-white/5'
433-
}`}
434-
>
435-
{broken ? (
436-
<span className="text-2xl select-none">{emojiFallbackFor(file)}</span>
437-
) : (
438-
<img
439-
src={localMascotUrl(mascotsDir, file)}
440-
alt={file}
441-
loading="lazy"
442-
className="w-full h-full object-contain"
443-
draggable={false}
444-
onError={() => setLocalBroken(prev => new Set(prev).add(file))}
445-
/>
446-
)}
447-
</button>
448-
)
449-
})}
450-
</div>
451-
)}
452-
</div>
453-
)}
454-
455-
{tab === 'url' && (
456-
<div className="h-full min-h-40 flex flex-col justify-center">
457-
<div className="max-w-md mx-auto w-full space-y-3 p-4">
458-
<div className="text-center">
459-
<span className="text-2xl">🔗</span>
460-
<h4 className="text-sm font-bold mt-1" style={{ color: 'var(--fg)' }}>Dán link ảnh tùy chỉnh</h4>
461-
<p className="text-[11px] mt-1" style={{ color: 'var(--fg-dim)' }}>Bất kỳ link ảnh png, jpg, gif, webp nào từ internet</p>
462-
</div>
463-
<input
464-
type="url"
465-
value={value}
466-
onChange={e => { setValue(e.target.value); setPreviewError(false) }}
467-
placeholder="https://.../avatar.png"
468-
className="w-full px-3.5 py-2.5 text-xs rounded-xl border transition-all focus:outline-none focus:ring-2 bg-slate-800/60 border-slate-700 text-slate-100 placeholder-slate-500 focus:border-emerald-500 focus:ring-emerald-500/20"
469-
/>
470-
<p className="text-[10px] px-2" style={{ color: 'var(--fg-dim)' }}>
471-
⚠️ Đảm bảo link ảnh hoạt động và cho phép hiển thị (CORS enabled). Vd: <span className="font-mono">{CODEX_PETS_BASE_URL}/firefly--lingxiaotian/webp/idle.webp</span>
472-
</p>
473-
</div>
474-
</div>
475-
)}
476349
</div>
477350
</div>
478351
</div>

0 commit comments

Comments
 (0)