+1
public/icon/user-solid.svg
+1
public/icon/user-solid.svg
···
1
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M224 256A128 128 0 1 0 224 0a128 128 0 1 0 0 256zm-45.7 48C79.8 304 0 383.8 0 482.3C0 498.7 13.3 512 29.7 512l388.6 0c16.4 0 29.7-13.3 29.7-29.7C448 383.8 368.2 304 269.7 304l-91.4 0z"/></svg>
+4
-4
src/Components/Managers/PhotoListRenderingManager.tsx
+4
-4
src/Components/Managers/PhotoListRenderingManager.tsx
···
93
93
// and then render that text
94
94
95
95
// === DEBUG ===
96
-
// ctx.strokeStyle = '#f00';
97
-
// ctx.strokeRect(0, currentY - scroll, canvas.width, row.Height);
96
+
ctx.strokeStyle = '#f00';
97
+
ctx.strokeRect(0, currentY - scroll, canvas.width, row.Height);
98
98
99
99
ctx.textAlign = 'center';
100
100
ctx.textBaseline = 'middle';
···
108
108
let photo = (el as PhotoListPhoto).Photo;
109
109
110
110
// === DEBUG ===
111
-
// ctx.strokeStyle = '#f00';
112
-
// ctx.strokeRect((rowXPos - row.Width / 2) + canvas.width / 2, currentY - scroll, photo.scaledWidth!, row.Height);
111
+
ctx.strokeStyle = '#f00';
112
+
ctx.strokeRect((rowXPos - row.Width / 2) + canvas.width / 2, currentY - scroll, photo.scaledWidth!, row.Height);
113
113
114
114
if(!photo.loaded)
115
115
// If the photo is not loaded, start a new task and load it in that task
+71
-33
src/Components/PhotoViewer.tsx
+71
-33
src/Components/PhotoViewer.tsx
···
23
23
let allowedToOpenTray = false;
24
24
let trayInAnimation = false;
25
25
26
+
let authorProfileButton: HTMLDivElement;
27
+
26
28
let switchPhotoWithKey = ( e: KeyboardEvent ) => {
27
29
switch(e.key){
28
30
case 'Escape':
···
240
242
if(photo.metadata){
241
243
photo.onMetaLoaded = () => {}
242
244
243
-
let meta = JSON.parse(photo.metadata);
245
+
try{
246
+
// Try JSON format ( VRCX )
247
+
let meta = JSON.parse(photo.metadata);
244
248
245
-
allowedToOpenTray = true;
246
-
trayButton.style.display = 'flex';
247
-
248
-
photoTray.innerHTML = '';
249
-
photoTray.appendChild(
250
-
<div class="photo-tray-columns">
251
-
<div class="photo-tray-column" style={{ width: '20%' }}><br />
252
-
<div class="tray-heading">People</div>
253
-
254
-
<For each={meta.players}>
255
-
{( item ) =>
256
-
<div>
257
-
{ item.displayName }
258
-
<Show when={item.id}>
259
-
<img width="15" src="/icon/up-right-from-square-solid.svg" onClick={() => invoke('open_url', { url: 'https://vrchat.com/home/user/' + item.id })} style={{ "margin-left": '10px', "font-size": '12px', 'color': '#bbb', cursor: 'pointer' }} />
260
-
</Show>
261
-
</div>
262
-
}
263
-
</For><br />
264
-
</div>
265
-
<div class="photo-tray-column"><br />
266
-
<div class="tray-heading">World</div>
267
-
268
-
<div ref={( el ) => worldInfoContainer = el}>Loading World Data...</div>
269
-
</div>
270
-
</div> as Node
271
-
);
249
+
allowedToOpenTray = true;
250
+
trayButton.style.display = 'flex';
272
251
273
-
window.WorldCacheManager.getWorldById(meta.world.id)
274
-
.then(worldData => {
275
-
if(worldData)
276
-
loadWorldData(worldData);
277
-
});
252
+
authorProfileButton!.style.display = 'none';
253
+
254
+
photoTray.innerHTML = '';
255
+
photoTray.appendChild(
256
+
<div class="photo-tray-columns">
257
+
<div class="photo-tray-column" style={{ width: '20%' }}><br />
258
+
<div class="tray-heading">People</div>
259
+
260
+
<For each={meta.players}>
261
+
{( item ) =>
262
+
<div>
263
+
{ item.displayName }
264
+
<Show when={item.id}>
265
+
<img width="15" src="/icon/up-right-from-square-solid.svg" onClick={() => invoke('open_url', { url: 'https://vrchat.com/home/user/' + item.id })} style={{ "margin-left": '10px', "font-size": '12px', 'color': '#bbb', cursor: 'pointer' }} />
266
+
</Show>
267
+
</div>
268
+
}
269
+
</For><br />
270
+
</div>
271
+
<div class="photo-tray-column"><br />
272
+
<div class="tray-heading">World</div>
273
+
274
+
<div ref={( el ) => worldInfoContainer = el}>Loading World Data...</div>
275
+
</div>
276
+
</div> as Node
277
+
);
278
+
279
+
window.WorldCacheManager.getWorldById(meta.world.id)
280
+
.then(worldData => {
281
+
if(worldData)
282
+
loadWorldData(worldData);
283
+
});
284
+
} catch(e){
285
+
try{
286
+
// Not json lets try XML (vrc prints)
287
+
let parser = new DOMParser();
288
+
let doc = parser.parseFromString(photo.metadata, "text/xml");
289
+
290
+
let id = doc.getElementsByTagName('xmp:Author')[0]!.innerHTML;
291
+
292
+
authorProfileButton!.style.display = 'flex';
293
+
authorProfileButton!.onclick = () =>
294
+
invoke('open_url', { url: 'https://vrchat.com/home/user/' + id });
295
+
} catch(e){
296
+
console.error(e);
297
+
console.log('Couldn\'t decode metadata')
298
+
299
+
authorProfileButton!.style.display = 'none';
300
+
}
301
+
302
+
trayButton.style.display = 'none';
303
+
closeTray();
304
+
}
278
305
} else{
279
306
trayButton.style.display = 'none';
280
307
closeTray();
···
428
455
<img draggable="false" src="/icon/angle-up-solid.svg"></img>
429
456
</div>
430
457
</div>
458
+
459
+
<div class="viewer-button"
460
+
ref={authorProfileButton!}
461
+
onMouseOver={( el ) => anime({ targets: el.currentTarget, width: '40px', height: '40px', 'margin-left': '15px', 'margin-right': '15px', 'margin-top': '-10px' })}
462
+
onMouseLeave={( el ) => anime({ targets: el.currentTarget, width: '30px', height: '30px', 'margin-left': '20px', 'margin-right': '20px', 'margin-top': '0px' })}
463
+
>
464
+
<div class="icon" style={{ width: '12px', margin: '0' }}>
465
+
<img draggable="false" src="/icon/user-solid.svg"></img>
466
+
</div>
467
+
</div>
468
+
431
469
<div class="viewer-button"
432
470
onMouseOver={( el ) => anime({ targets: el.currentTarget, width: '40px', height: '40px', 'margin-left': '15px', 'margin-right': '15px', 'margin-top': '-10px' })}
433
471
onMouseLeave={( el ) => anime({ targets: el.currentTarget, width: '30px', height: '30px', 'margin-left': '20px', 'margin-right': '20px', 'margin-top': '0px' })}
+1
-1
vite.config.ts
+1
-1
vite.config.ts
···
11
11
12
12
// https://vitejs.dev/config/
13
13
export default defineConfig(async () => ({
14
-
plugins: [solid(), fullReloadAlways],
14
+
plugins: [solid(),], //fullReloadAlways],
15
15
16
16
// Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
17
17
//