+1
-1
build-release.sh
+1
-1
build-release.sh
+10
-1
changelog
+10
-1
changelog
···
121
122
Hotfix 1:
123
- Fixed loading when an image file is corrupted
124
+
- Fixed update prompt when not connected to internet
125
+
126
+
v0.2.7:
127
+
- Fixed image resizing when window is thinner than image
128
+
- Fixed closing settings with keybinds
129
+
- Fixed the behaviour of changing the photo path
130
+
- Fixed loading photos in folders that aren't VRChat folders
131
+
132
+
Hotfix 1:
133
+
- Fixed resizing images (again)
+25
-2
src/Components/App.tsx
+25
-2
src/Components/App.tsx
···
1
-
import { onMount } from "solid-js";
2
3
import PhotoList from "./PhotoList";
4
import PhotoViewer from "./PhotoViewer";
5
import SettingsMenu from "./SettingsMenu";
6
-
import { utils } from "animejs";
7
8
let App = () => {
9
onMount(() => {
10
utils.set('.settings',
11
{
···
13
opacity: 0,
14
translateX: '500px'
15
})
16
})
17
18
return (
···
23
<SettingsMenu />
24
25
<div class="copy-notif">Image Copied!</div>
26
</div>
27
);
28
}
···
1
+
import { createSignal, onMount } from "solid-js";
2
3
import PhotoList from "./PhotoList";
4
import PhotoViewer from "./PhotoViewer";
5
import SettingsMenu from "./SettingsMenu";
6
+
import { animate, utils } from "animejs";
7
+
import { listen } from "@tauri-apps/api/event";
8
9
let App = () => {
10
+
let [ errorText, setErrorText ] = createSignal('');
11
+
12
onMount(() => {
13
utils.set('.settings',
14
{
···
16
opacity: 0,
17
translateX: '500px'
18
})
19
+
20
+
listen<string>('vrcpm-error', ( ev ) => {
21
+
setErrorText(ev.payload);
22
+
23
+
utils.set('.error-notif', { translateX: '-50%', translateY: '-100px' });
24
+
animate('.error-notif', {
25
+
ease: 'outElastic',
26
+
opacity: 1,
27
+
translateY: '0px'
28
+
});
29
+
30
+
setTimeout(() => {
31
+
animate('.error-notif', {
32
+
ease: 'outElastic',
33
+
opacity: 0,
34
+
translateY: '-100px'
35
+
});
36
+
}, 2000);
37
+
});
38
})
39
40
return (
···
45
<SettingsMenu />
46
47
<div class="copy-notif">Image Copied!</div>
48
+
<div class="error-notif">{ errorText() }</div>
49
</div>
50
);
51
}
+4
-5
src/Components/PhotoViewer.tsx
+4
-5
src/Components/PhotoViewer.tsx
···
154
let dstWidth;
155
let dstHeight;
156
157
-
let imgHeight = imageViewer.height;
158
-
let imgWidth = imageViewer.width;
159
160
if(
161
imgWidth / window.innerWidth <
···
178
utils.set(photoLayerManager, { translateY: '20px', opacity: 0, display: 'none' });
179
180
window.addEventListener('keyup', switchPhotoWithKey);
181
-
182
-
resizeImage();
183
-
184
window.addEventListener('resize', () => resizeImage());
185
186
let contextMenuOpen = false;
···
262
if(photo){
263
imageViewer.src = (window.OS === "windows" ? "http://photo.localhost/" : 'photo://localhost/') + window.PhotoViewerManager.CurrentPhoto()?.path.split('\\').join('/') + "?full";
264
imageViewer.crossOrigin = 'anonymous';
265
266
animate(imageViewer, {
267
opacity: 1,
···
154
let dstWidth;
155
let dstHeight;
156
157
+
let imgHeight = imageViewer.naturalHeight;
158
+
let imgWidth = imageViewer.naturalWidth;
159
160
if(
161
imgWidth / window.innerWidth <
···
178
utils.set(photoLayerManager, { translateY: '20px', opacity: 0, display: 'none' });
179
180
window.addEventListener('keyup', switchPhotoWithKey);
181
window.addEventListener('resize', () => resizeImage());
182
183
let contextMenuOpen = false;
···
259
if(photo){
260
imageViewer.src = (window.OS === "windows" ? "http://photo.localhost/" : 'photo://localhost/') + window.PhotoViewerManager.CurrentPhoto()?.path.split('\\').join('/') + "?full";
261
imageViewer.crossOrigin = 'anonymous';
262
+
263
+
imageViewer.onload = () => { resizeImage(); }
264
265
animate(imageViewer, {
266
opacity: 1,
+24
-18
src/Components/SettingsMenu.tsx
+24
-18
src/Components/SettingsMenu.tsx
···
6
7
let SettingsMenu = () => {
8
// let sliderBar: HTMLElement;
9
-
let settingsContainer: HTMLElement;
10
// let currentButton = 0;
11
// let lastClickedButton = -1;
12
let finalPathConfirm: HTMLElement;
···
17
let closeWithKey = ( e: KeyboardEvent ) => {
18
if(e.key === 'Escape'){
19
window.ViewManager.ChangeState(ViewState.PHOTO_LIST);
20
-
animate('.settings', {
21
opacity: 0,
22
translateX: '500px',
23
easing: 'easeInOutQuad',
24
duration: 250,
25
onComplete: () => {
26
utils.set('.settings', { display: 'none' });
27
}
28
})
···
77
// }
78
// })
79
80
-
// window.addEventListener('keyup', closeWithKey);
81
82
// window.addEventListener('touchend', ( e: TouchEvent ) => {
83
// if(sliderMouseDown){
···
194
}}>
195
<div class="icon"><img draggable="false" src="/icon/x-solid.svg"></img></div>
196
</div>
197
-
<div class="settings-container" ref={( el ) => settingsContainer = el}>
198
<div class="settings-block">
199
<h1>Storage Settings</h1>
200
<p>{ window.PhotoManager.PhotoCount() } Photos ({ bytesToFormatted(window.PhotoManager.PhotoSize(), 0) })</p>
···
311
</span>
312
<span style={{ display: 'none' }} ref={( el ) => finalPathConfirm = el}>
313
<span class="path" style={{ color: 'green' }} onClick={async () => {
314
-
finalPathPreviousData = finalPathData;
315
-
finalPathConfirm.style.display = 'none';
316
317
-
await invoke('change_final_path', { newPath: finalPathData });
318
-
window.location.reload();
319
320
-
animate('.settings', {
321
-
opacity: 0,
322
-
translateX: '500px',
323
-
easing: 'easeInOutQuad',
324
-
duration: 250,
325
-
onComplete: () => {
326
-
utils.set('.settings', { display: 'none' });
327
-
}
328
-
})
329
330
-
window.location.reload();
331
}}>
332
Save
333
</span>
···
6
7
let SettingsMenu = () => {
8
// let sliderBar: HTMLElement;
9
+
// let settingsContainer: HTMLElement;
10
// let currentButton = 0;
11
// let lastClickedButton = -1;
12
let finalPathConfirm: HTMLElement;
···
17
let closeWithKey = ( e: KeyboardEvent ) => {
18
if(e.key === 'Escape'){
19
window.ViewManager.ChangeState(ViewState.PHOTO_LIST);
20
+
console.log('h');
21
+
animate('.settings',{
22
opacity: 0,
23
translateX: '500px',
24
easing: 'easeInOutQuad',
25
duration: 250,
26
onComplete: () => {
27
+
console.log('h');
28
utils.set('.settings', { display: 'none' });
29
}
30
})
···
79
// }
80
// })
81
82
+
window.addEventListener('keyup', closeWithKey);
83
84
// window.addEventListener('touchend', ( e: TouchEvent ) => {
85
// if(sliderMouseDown){
···
196
}}>
197
<div class="icon"><img draggable="false" src="/icon/x-solid.svg"></img></div>
198
</div>
199
+
{/* <div class="settings-container" ref={( el ) => settingsContainer = el}> */}
200
+
<div class="settings-container">
201
<div class="settings-block">
202
<h1>Storage Settings</h1>
203
<p>{ window.PhotoManager.PhotoCount() } Photos ({ bytesToFormatted(window.PhotoManager.PhotoSize(), 0) })</p>
···
314
</span>
315
<span style={{ display: 'none' }} ref={( el ) => finalPathConfirm = el}>
316
<span class="path" style={{ color: 'green' }} onClick={async () => {
317
+
let changed = await invoke('change_final_path', { newPath: finalPathData });
318
319
+
if(changed){
320
+
finalPathPreviousData = finalPathData;
321
+
finalPathConfirm.style.display = 'none';
322
323
+
window.location.reload();
324
+
325
+
animate('.settings', {
326
+
opacity: 0,
327
+
translateX: '500px',
328
+
easing: 'easeInOutQuad',
329
+
duration: 250,
330
+
onComplete: () => {
331
+
utils.set('.settings', { display: 'none' });
332
+
}
333
+
})
334
335
+
window.location.reload();
336
+
}
337
}}>
338
Save
339
</span>
+17
src/styles.css
+17
src/styles.css
···
100
img{
101
max-width: 100%;
102
max-height: 100%;
103
+
}
104
+
105
+
.error-notif{
106
+
position: fixed;
107
+
top: 40px;
108
+
left: 50%;
109
+
color: white;
110
+
transform: translateX(-50%) translateY(-100px);
111
+
background: rgba(43, 43, 43, 0.76);
112
+
padding: 10px 40px;
113
+
backdrop-filter: blur(10px);
114
+
-webkit-backdrop-filter: blur(10px);
115
+
border-radius: 50px;
116
+
box-shadow: #000 0 0 10px;
117
+
z-index: 12;
118
+
opacity: 0;
119
+
pointer-events: none;
120
}
+1
-1
src-tauri/Cargo.lock
+1
-1
src-tauri/Cargo.lock
+1
-1
src-tauri/Cargo.toml
+1
-1
src-tauri/Cargo.toml
+50
-2
src-tauri/gen/schemas/windows-schema.json
+50
-2
src-tauri/gen/schemas/windows-schema.json
···
519
"markdownDescription": "Default core plugins set.\n#### This default permission set includes:\n\n- `core:path:default`\n- `core:event:default`\n- `core:window:default`\n- `core:webview:default`\n- `core:app:default`\n- `core:image:default`\n- `core:resources:default`\n- `core:menu:default`\n- `core:tray:default`"
520
},
521
{
522
-
"description": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-version`\n- `allow-name`\n- `allow-tauri-version`\n- `allow-identifier`\n- `allow-bundle-type`",
523
"type": "string",
524
"const": "core:app:default",
525
-
"markdownDescription": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-version`\n- `allow-name`\n- `allow-tauri-version`\n- `allow-identifier`\n- `allow-bundle-type`"
526
},
527
{
528
"description": "Enables the app_hide command without any pre-configured scope.",
···
567
"markdownDescription": "Enables the name command without any pre-configured scope."
568
},
569
{
570
"description": "Enables the remove_data_store command without any pre-configured scope.",
571
"type": "string",
572
"const": "core:app:allow-remove-data-store",
573
"markdownDescription": "Enables the remove_data_store command without any pre-configured scope."
574
},
575
{
576
"description": "Enables the set_app_theme command without any pre-configured scope.",
577
"type": "string",
578
"const": "core:app:allow-set-app-theme",
···
639
"markdownDescription": "Denies the name command without any pre-configured scope."
640
},
641
{
642
"description": "Denies the remove_data_store command without any pre-configured scope.",
643
"type": "string",
644
"const": "core:app:deny-remove-data-store",
645
"markdownDescription": "Denies the remove_data_store command without any pre-configured scope."
646
},
647
{
648
"description": "Denies the set_app_theme command without any pre-configured scope.",
···
1827
"markdownDescription": "Enables the set_focus command without any pre-configured scope."
1828
},
1829
{
1830
"description": "Enables the set_fullscreen command without any pre-configured scope.",
1831
"type": "string",
1832
"const": "core:window:allow-set-fullscreen",
···
1897
"type": "string",
1898
"const": "core:window:allow-set-shadow",
1899
"markdownDescription": "Enables the set_shadow command without any pre-configured scope."
1900
},
1901
{
1902
"description": "Enables the set_size command without any pre-configured scope.",
···
2271
"markdownDescription": "Denies the set_focus command without any pre-configured scope."
2272
},
2273
{
2274
"description": "Denies the set_fullscreen command without any pre-configured scope.",
2275
"type": "string",
2276
"const": "core:window:deny-set-fullscreen",
···
2341
"type": "string",
2342
"const": "core:window:deny-set-shadow",
2343
"markdownDescription": "Denies the set_shadow command without any pre-configured scope."
2344
},
2345
{
2346
"description": "Denies the set_size command without any pre-configured scope.",
···
519
"markdownDescription": "Default core plugins set.\n#### This default permission set includes:\n\n- `core:path:default`\n- `core:event:default`\n- `core:window:default`\n- `core:webview:default`\n- `core:app:default`\n- `core:image:default`\n- `core:resources:default`\n- `core:menu:default`\n- `core:tray:default`"
520
},
521
{
522
+
"description": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-version`\n- `allow-name`\n- `allow-tauri-version`\n- `allow-identifier`\n- `allow-bundle-type`\n- `allow-register-listener`\n- `allow-remove-listener`",
523
"type": "string",
524
"const": "core:app:default",
525
+
"markdownDescription": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-version`\n- `allow-name`\n- `allow-tauri-version`\n- `allow-identifier`\n- `allow-bundle-type`\n- `allow-register-listener`\n- `allow-remove-listener`"
526
},
527
{
528
"description": "Enables the app_hide command without any pre-configured scope.",
···
567
"markdownDescription": "Enables the name command without any pre-configured scope."
568
},
569
{
570
+
"description": "Enables the register_listener command without any pre-configured scope.",
571
+
"type": "string",
572
+
"const": "core:app:allow-register-listener",
573
+
"markdownDescription": "Enables the register_listener command without any pre-configured scope."
574
+
},
575
+
{
576
"description": "Enables the remove_data_store command without any pre-configured scope.",
577
"type": "string",
578
"const": "core:app:allow-remove-data-store",
579
"markdownDescription": "Enables the remove_data_store command without any pre-configured scope."
580
},
581
{
582
+
"description": "Enables the remove_listener command without any pre-configured scope.",
583
+
"type": "string",
584
+
"const": "core:app:allow-remove-listener",
585
+
"markdownDescription": "Enables the remove_listener command without any pre-configured scope."
586
+
},
587
+
{
588
"description": "Enables the set_app_theme command without any pre-configured scope.",
589
"type": "string",
590
"const": "core:app:allow-set-app-theme",
···
651
"markdownDescription": "Denies the name command without any pre-configured scope."
652
},
653
{
654
+
"description": "Denies the register_listener command without any pre-configured scope.",
655
+
"type": "string",
656
+
"const": "core:app:deny-register-listener",
657
+
"markdownDescription": "Denies the register_listener command without any pre-configured scope."
658
+
},
659
+
{
660
"description": "Denies the remove_data_store command without any pre-configured scope.",
661
"type": "string",
662
"const": "core:app:deny-remove-data-store",
663
"markdownDescription": "Denies the remove_data_store command without any pre-configured scope."
664
+
},
665
+
{
666
+
"description": "Denies the remove_listener command without any pre-configured scope.",
667
+
"type": "string",
668
+
"const": "core:app:deny-remove-listener",
669
+
"markdownDescription": "Denies the remove_listener command without any pre-configured scope."
670
},
671
{
672
"description": "Denies the set_app_theme command without any pre-configured scope.",
···
1851
"markdownDescription": "Enables the set_focus command without any pre-configured scope."
1852
},
1853
{
1854
+
"description": "Enables the set_focusable command without any pre-configured scope.",
1855
+
"type": "string",
1856
+
"const": "core:window:allow-set-focusable",
1857
+
"markdownDescription": "Enables the set_focusable command without any pre-configured scope."
1858
+
},
1859
+
{
1860
"description": "Enables the set_fullscreen command without any pre-configured scope.",
1861
"type": "string",
1862
"const": "core:window:allow-set-fullscreen",
···
1927
"type": "string",
1928
"const": "core:window:allow-set-shadow",
1929
"markdownDescription": "Enables the set_shadow command without any pre-configured scope."
1930
+
},
1931
+
{
1932
+
"description": "Enables the set_simple_fullscreen command without any pre-configured scope.",
1933
+
"type": "string",
1934
+
"const": "core:window:allow-set-simple-fullscreen",
1935
+
"markdownDescription": "Enables the set_simple_fullscreen command without any pre-configured scope."
1936
},
1937
{
1938
"description": "Enables the set_size command without any pre-configured scope.",
···
2307
"markdownDescription": "Denies the set_focus command without any pre-configured scope."
2308
},
2309
{
2310
+
"description": "Denies the set_focusable command without any pre-configured scope.",
2311
+
"type": "string",
2312
+
"const": "core:window:deny-set-focusable",
2313
+
"markdownDescription": "Denies the set_focusable command without any pre-configured scope."
2314
+
},
2315
+
{
2316
"description": "Denies the set_fullscreen command without any pre-configured scope.",
2317
"type": "string",
2318
"const": "core:window:deny-set-fullscreen",
···
2383
"type": "string",
2384
"const": "core:window:deny-set-shadow",
2385
"markdownDescription": "Denies the set_shadow command without any pre-configured scope."
2386
+
},
2387
+
{
2388
+
"description": "Denies the set_simple_fullscreen command without any pre-configured scope.",
2389
+
"type": "string",
2390
+
"const": "core:window:deny-set-simple-fullscreen",
2391
+
"markdownDescription": "Denies the set_simple_fullscreen command without any pre-configured scope."
2392
},
2393
{
2394
"description": "Denies the set_size command without any pre-configured scope.",
+17
-9
src-tauri/src/frontend_calls/change_final_path.rs
+17
-9
src-tauri/src/frontend_calls/change_final_path.rs
···
1
use std::fs;
2
3
-
#[tauri::command]
4
-
pub fn change_final_path(new_path: &str) {
5
-
let config_path = dirs::config_dir()
6
-
.unwrap()
7
-
.join("PhazeDev/VRChatPhotoManager/.photos_path");
8
9
-
fs::write(&config_path, new_path.as_bytes()).unwrap();
10
11
match fs::metadata(&new_path) {
12
-
Ok(_) => {}
13
Err(_) => {
14
-
fs::create_dir(&new_path).unwrap();
15
}
16
-
};
17
}
···
1
use std::fs;
2
3
+
use tauri::{Emitter, State, Window};
4
5
+
use crate::util::cache::Cache;
6
7
+
#[tauri::command]
8
+
pub fn change_final_path(new_path: &str, window: Window, cache: State<Cache>) -> bool {
9
match fs::metadata(&new_path) {
10
+
Ok(_) => {
11
+
let config_path = dirs::config_dir()
12
+
.unwrap()
13
+
.join("PhazeDev/VRChatPhotoManager/.photos_path");
14
+
15
+
fs::write(&config_path, new_path.as_bytes()).unwrap();
16
+
cache.insert("photo-path".into(), new_path.to_owned());
17
+
18
+
true
19
+
}
20
Err(_) => {
21
+
window.emit("vrcpm-error", "Error Changing Path: Path does not exist.").unwrap();
22
+
false
23
}
24
+
}
25
}
+3
-1
src-tauri/src/frontend_calls/load_photos.rs
+3
-1
src-tauri/src/frontend_calls/load_photos.rs
···
16
let base_dir = cache.get("photo-path".into()).unwrap();
17
18
thread::spawn(move || {
19
-
20
let mut photos: Vec<path::PathBuf> = Vec::new();
21
let mut size: usize = 0;
22
23
for folder in fs::read_dir(&base_dir).unwrap() {
24
let f = folder.unwrap();
25
26
if f.metadata().unwrap().is_dir() {
27
for photo in fs::read_dir(f.path()).unwrap() {
···
16
let base_dir = cache.get("photo-path".into()).unwrap();
17
18
thread::spawn(move || {
19
let mut photos: Vec<path::PathBuf> = Vec::new();
20
let mut size: usize = 0;
21
22
+
let re = Regex::new(r"^[0-9]{4}-[0-9]{2}$").unwrap();
23
+
24
for folder in fs::read_dir(&base_dir).unwrap() {
25
let f = folder.unwrap();
26
+
if !re.is_match(f.file_name().to_str().unwrap()){ continue; }
27
28
if f.metadata().unwrap().is_dir() {
29
for photo in fs::read_dir(f.path()).unwrap() {
+1
src-tauri/src/main.rs
+1
src-tauri/src/main.rs
+7
-1
src-tauri/src/util/get_photo_path.rs
+7
-1
src-tauri/src/util/get_photo_path.rs