-
Notifications
You must be signed in to change notification settings - Fork 247
Upload vrm file #232
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Upload vrm file #232
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -41,3 +41,8 @@ working/* | |
|
|
||
| # Sentry Config File | ||
| .env.sentry-build-plugin | ||
|
|
||
| # IDEs | ||
| .idea/ | ||
| .vscode/ | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,120 @@ | ||
| /** | ||
| * Вставка в консоль браузера (на странице приложения). | ||
| * База: AmicaVrmDatabase, store: vrms. | ||
| * Запись: { hash, saveType, vrmData, vrmUrl, thumbData }. | ||
| */ | ||
|
|
||
| // --------------- 1) Проверить, что в базе есть --------------- | ||
| function listVrms() { | ||
| return new Promise((resolve, reject) => { | ||
| const r = indexedDB.open('AmicaVrmDatabase'); | ||
| r.onsuccess = () => { | ||
| const db = r.result; | ||
| if (!db.objectStoreNames.contains('vrms')) { | ||
| db.close(); | ||
| return resolve([]); | ||
| } | ||
| const t = db.transaction('vrms', 'readonly'); | ||
| const s = t.objectStore('vrms'); | ||
| const req = s.getAll(); | ||
| req.onsuccess = () => { db.close(); resolve(req.result || []); }; | ||
| req.onerror = () => { db.close(); reject(req.error); }; | ||
| }; | ||
| r.onerror = () => reject(r.error); | ||
| }); | ||
| } | ||
| // В консоли: listVrms().then(console.log) | ||
|
|
||
| // --------------- 2) Добавить одну тестовую запись (минимальная, без реального VRM) --------------- | ||
| function addTestRecord() { | ||
| const record = { | ||
| hash: 'test-' + Date.now(), | ||
| saveType: 'local', | ||
| vrmData: 'data:application/octet-stream;base64,', // пустой base64 | ||
| vrmUrl: '', | ||
| thumbData: '' | ||
| }; | ||
| return new Promise((resolve, reject) => { | ||
| const r = indexedDB.open('AmicaVrmDatabase'); | ||
| r.onsuccess = () => { | ||
| const db = r.result; | ||
| const t = db.transaction('vrms', 'readwrite'); | ||
| const s = t.objectStore('vrms'); | ||
| const req = s.put(record); | ||
| req.onsuccess = () => { db.close(); resolve(record.hash); }; | ||
| req.onerror = () => { db.close(); reject(req.error); }; | ||
| }; | ||
| r.onerror = () => reject(r.error); | ||
| }); | ||
| } | ||
| // В консоли: addTestRecord().then(h => console.log('Добавлен hash:', h)) | ||
| // Затем обнови страницу (F5) — в списке моделей может появиться карточка (по клику будет ошибка загрузки — это нормально для теста). | ||
|
|
||
| // --------------- 3) Добавить реальный VRM из выбранного файла --------------- | ||
| function addVrmFromFile(file) { | ||
| if (!file || !file.name.toLowerCase().endsWith('.vrm')) { | ||
| return Promise.reject(new Error('Нужен файл .vrm')); | ||
| } | ||
| return new Promise((resolve, reject) => { | ||
| const reader = new FileReader(); | ||
| reader.onload = () => { | ||
| const vrmData = reader.result; | ||
| if (typeof vrmData !== 'string') { | ||
| reject(new Error('Ожидалась строка data URL')); | ||
| return; | ||
| } | ||
| const len = vrmData.length; | ||
| const S = 100000; | ||
| let h = 0; | ||
| for (let i = 0; i < Math.min(S, len); i++) h = ((h << 5) - h + vrmData.charCodeAt(i)) << 0; | ||
| for (let i = Math.max(0, len - S); i < len; i++) h = ((h << 5) - h + vrmData.charCodeAt(i)) << 0; | ||
| h = ((h << 5) - h + len) << 0; | ||
| const hash = String(h); | ||
| const record = { | ||
| hash, | ||
| saveType: 'local', | ||
| vrmData, | ||
| vrmUrl: '', | ||
| thumbData: '' | ||
| }; | ||
| const r = indexedDB.open('AmicaVrmDatabase'); | ||
| r.onsuccess = () => { | ||
| const db = r.result; | ||
| const t = db.transaction('vrms', 'readwrite'); | ||
| const s = t.objectStore('vrms'); | ||
| const req = s.put(record); | ||
| req.onsuccess = () => { db.close(); resolve({ hash, size: vrmData.length }); }; | ||
| req.onerror = () => { db.close(); reject(req.error); }; | ||
| }; | ||
| r.onerror = () => reject(r.error); | ||
| }; | ||
| reader.onerror = () => reject(reader.error); | ||
| reader.readAsDataURL(file); | ||
| }); | ||
| } | ||
|
|
||
| // Создать input и по выбору файла добавить в IndexedDB | ||
| function addVrmViaFilePicker() { | ||
| const input = document.createElement('input'); | ||
| input.type = 'file'; | ||
| input.accept = '.vrm,.VRM'; | ||
| input.onchange = () => { | ||
| const file = input.files && input.files[0]; | ||
| if (!file) return; | ||
| addVrmFromFile(file) | ||
| .then((r) => console.log('[VRM] Запись в IndexedDB:', r)) | ||
| .catch((e) => console.error('[VRM] Ошибка:', e)); | ||
| }; | ||
| input.click(); | ||
| } | ||
|
|
||
| // Экспорт в глобал, чтобы в консоли вызывать по имени | ||
| if (typeof window !== 'undefined') { | ||
| window.__vrmIndexedDB = { | ||
| list: listVrms, | ||
| addTest: addTestRecord, | ||
| addFromFile: addVrmFromFile, | ||
| pickAndAdd: addVrmViaFilePicker | ||
| }; | ||
| console.log('VRM IndexedDB helper: __vrmIndexedDB.list() | .addTest() | .pickAndAdd()'); | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -20,6 +20,8 @@ import { useKeyboardShortcut } from "@/hooks/useKeyboardShortcut"; | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { TextButton } from "@/components/textButton"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { ViewerContext } from "@/features/vrmViewer/viewerContext"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { config, updateConfig } from "@/utils/config"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { hashCodeLarge } from "./settings/common"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { vrmDataProvider } from "@/features/vrmStore/vrmDataProvider"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { Link } from "./settings/common"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -81,7 +83,7 @@ export const Settings = ({ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onClickClose: () => void; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { viewer } = useContext(ViewerContext); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { vrmList, vrmListAddFile } = useVrmStoreContext(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { vrmList, vrmListAddFile, addVrmFromStored } = useVrmStoreContext(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| useKeyboardShortcut("Escape", onClickClose); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [page, setPage] = useState('main_menu'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -187,9 +189,62 @@ export const Settings = ({ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [useWebGPU, setUseWebGPU] = useState<boolean>(config("use_webgpu") === 'true' ? true : false); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const vrmFileInputRef = useRef<HTMLInputElement>(null); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const handleClickOpenVrmFile = useCallback(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| vrmFileInputRef.current?.click(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, []); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const processVrmFile = useCallback( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (file: File) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!file?.name.toLowerCase().endsWith(".vrm")) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.warn("[VRM] Неверное расширение (ожидается .vrm)"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const reader = new FileReader(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| reader.onload = () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const dataUrl = reader.result; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (typeof dataUrl !== "string") return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const hash = hashCodeLarge(dataUrl); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const exists = vrmList.some((v) => v.getHash() === hash); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (exists) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| viewer.loadVrm(dataUrl, () => {}).catch((e) => console.error("[VRM]", e)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| updateConfig("vrm_save_type", "local"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| updateConfig("vrm_hash", hash); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| updateConfig("vrm_url", dataUrl); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+205
to
+211
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing React state updates when an already-stored VRM is selected. When the VRM already exists in the list (line 204), 🐛 Proposed fix if (exists) {
viewer.loadVrm(dataUrl, () => {}).catch((e) => console.error("[VRM]", e));
updateConfig("vrm_save_type", "local");
updateConfig("vrm_hash", hash);
updateConfig("vrm_url", dataUrl);
+ setVrmSaveType("local");
+ setVrmHash(hash);
+ setVrmUrl(dataUrl);
+ setSettingsUpdated(true);
return;
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| vrmDataProvider | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .addItem(hash, "local", dataUrl) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .then(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| updateConfig("vrm_save_type", "local"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| updateConfig("vrm_hash", hash); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| updateConfig("vrm_url", dataUrl); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| addVrmFromStored(hash, dataUrl); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return viewer.loadVrm(dataUrl, () => {}); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .then(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| viewer.getScreenshotBlob((thumbBlob: Blob | null) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (thumbBlob) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import("@/utils/blobDataUtils").then(({ BlobToBase64 }) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| BlobToBase64(thumbBlob).then((thumbData) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| vrmDataProvider.updateItemThumb(hash, thumbData); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .catch((e) => console.error("[VRM] Ошибка загрузки VRM:", e)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+212
to
+232
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing state updates after new VRM is stored and loaded. Similar to the "already exists" path, the "new VRM" path (lines 212-232) calls 🐛 Proposed fix — add state updates after addVrmFromStored vrmDataProvider
.addItem(hash, "local", dataUrl)
.then(() => {
updateConfig("vrm_save_type", "local");
updateConfig("vrm_hash", hash);
updateConfig("vrm_url", dataUrl);
addVrmFromStored(hash, dataUrl);
+ setVrmSaveType("local");
+ setVrmHash(hash);
+ setVrmUrl(dataUrl);
+ setSettingsUpdated(true);
return viewer.loadVrm(dataUrl, () => {});
})📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| reader.onerror = () => console.error("[VRM] FileReader ошибка:", reader.error); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| reader.readAsDataURL(file); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| [viewer, vrmList, addVrmFromStored] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const handleChangeVrmFile = useCallback( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (event: React.ChangeEvent<HTMLInputElement>) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const file = event.target.files?.[0]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (file) processVrmFile(file); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| event.target.value = ""; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| [processVrmFile] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const bgImgFileInputRef = useRef<HTMLInputElement>(null); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const handleClickOpenBgImgFile = useCallback(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -201,25 +256,6 @@ export const Settings = ({ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const mainMenuRef = useRef<HTMLDivElement>(null); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const notificationsRef = useRef<HTMLDivElement>(null); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const handleChangeVrmFile = useCallback( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (event: React.ChangeEvent<HTMLInputElement>) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const files = event.target.files; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!files) return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const file = files[0]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!file) return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const file_type = file.name.split(".").pop(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (file_type === "vrm") { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| vrmListAddFile(file, viewer); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| event.target.value = ""; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| [viewer] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| function handleChangeBgImgFile(event: React.ChangeEvent<HTMLInputElement>) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const files = event.target.files; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!files) return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -438,7 +474,7 @@ export const Settings = ({ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setVrmUrl={setVrmUrl} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setVrmSaveType={setVrmSaveType} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setSettingsUpdated={setSettingsUpdated} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| handleClickOpenVrmFile={handleClickOpenVrmFile} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onPickVrmFile={processVrmFile} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case 'character_animation': | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -897,15 +933,16 @@ export const Settings = ({ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <input | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| id="vrm-file-input" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type="file" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| className="hidden" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| accept=".vrm" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| className="sr-only" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| accept=".vrm,.VRM,model/gltf-binary" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ref={vrmFileInputRef} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onChange={handleChangeVrmFile} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <input | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type="file" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| className="hidden" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| className="sr-only" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| accept=".jpg,.jpeg,.png,.gif,.webp" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ref={bgImgFileInputRef} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onChange={handleChangeBgImgFile} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -88,6 +88,7 @@ export function FormRow({label, children}: { | |
| } | ||
|
|
||
| export function basename(path: string) { | ||
| if (!path || typeof path !== 'string') return ""; | ||
| const a = path.split("/"); | ||
| return a[a.length - 1]; | ||
| } | ||
|
|
@@ -106,6 +107,18 @@ export function hashCode(str: string): string { | |
| return hash.toString(); | ||
| } | ||
|
|
||
| /** Хеш для больших строк (VRM base64): берём начало, конец и длину, чтобы не блокировать UI. */ | ||
| const SAMPLE_SIZE = 100000; | ||
| export function hashCodeLarge(str: string): string { | ||
| const len = str.length; | ||
| if (len <= SAMPLE_SIZE * 2) return hashCode(str); | ||
| let h = 0; | ||
| for (let i = 0; i < SAMPLE_SIZE; i++) h = ((h << 5) - h + str.charCodeAt(i)) << 0; | ||
| for (let i = len - SAMPLE_SIZE; i < len; i++) h = ((h << 5) - h + str.charCodeAt(i)) << 0; | ||
| h = ((h << 5) - h + len) << 0; | ||
| return h.toString(); | ||
| } | ||
|
Comment on lines
+110
to
+120
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Weak hash with partial sampling creates a real collision risk for VRM files.
Consider using 🤖 Prompt for AI Agents |
||
|
|
||
| export type Link = { | ||
| key: string; | ||
| label: string; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Storing full VRM data URL in localStorage will exceed quota and fail.
Same critical issue as flagged in
vrmIndexedDBConsoleHelper.ts:updateConfig("vrm_url", dataUrl)persists the entire base64-encoded VRM file tolocalStorage, which has a ~5-10 MB limit. Real VRM files are typically 10-50+ MB, producing data URLs of 13-67+ MB. TheupdateConfigcall will throw aQuotaExceededErrorand corrupt the config flow.Store only the hash in config (e.g.,
vrm_hash) and resolve the VRM data from IndexedDB at load time. Thevrm_urlconfig should only hold a short URL or be left empty for local VRMs.Also applies to: 215-217
🤖 Prompt for AI Agents