+1
-1
build-release.sh
+1
-1
build-release.sh
+10
-1
changelog
+10
-1
changelog
···
121
121
122
122
Hotfix 1:
123
123
- Fixed loading when an image file is corrupted
124
-
- Fixed update prompt when not connected to internet
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";
1
+
import { createSignal, onMount } from "solid-js";
2
2
3
3
import PhotoList from "./PhotoList";
4
4
import PhotoViewer from "./PhotoViewer";
5
5
import SettingsMenu from "./SettingsMenu";
6
-
import { utils } from "animejs";
6
+
import { animate, utils } from "animejs";
7
+
import { listen } from "@tauri-apps/api/event";
7
8
8
9
let App = () => {
10
+
let [ errorText, setErrorText ] = createSignal('');
11
+
9
12
onMount(() => {
10
13
utils.set('.settings',
11
14
{
···
13
16
opacity: 0,
14
17
translateX: '500px'
15
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
+
});
16
38
})
17
39
18
40
return (
···
23
45
<SettingsMenu />
24
46
25
47
<div class="copy-notif">Image Copied!</div>
48
+
<div class="error-notif">{ errorText() }</div>
26
49
</div>
27
50
);
28
51
}
+4
-5
src/Components/PhotoViewer.tsx
+4
-5
src/Components/PhotoViewer.tsx
···
154
154
let dstWidth;
155
155
let dstHeight;
156
156
157
-
let imgHeight = imageViewer.height;
158
-
let imgWidth = imageViewer.width;
157
+
let imgHeight = imageViewer.naturalHeight;
158
+
let imgWidth = imageViewer.naturalWidth;
159
159
160
160
if(
161
161
imgWidth / window.innerWidth <
···
178
178
utils.set(photoLayerManager, { translateY: '20px', opacity: 0, display: 'none' });
179
179
180
180
window.addEventListener('keyup', switchPhotoWithKey);
181
-
182
-
resizeImage();
183
-
184
181
window.addEventListener('resize', () => resizeImage());
185
182
186
183
let contextMenuOpen = false;
···
262
259
if(photo){
263
260
imageViewer.src = (window.OS === "windows" ? "http://photo.localhost/" : 'photo://localhost/') + window.PhotoViewerManager.CurrentPhoto()?.path.split('\\').join('/') + "?full";
264
261
imageViewer.crossOrigin = 'anonymous';
262
+
263
+
imageViewer.onload = () => { resizeImage(); }
265
264
266
265
animate(imageViewer, {
267
266
opacity: 1,
+17
-14
src/Components/SettingsMenu.tsx
+17
-14
src/Components/SettingsMenu.tsx
···
314
314
</span>
315
315
<span style={{ display: 'none' }} ref={( el ) => finalPathConfirm = el}>
316
316
<span class="path" style={{ color: 'green' }} onClick={async () => {
317
-
finalPathPreviousData = finalPathData;
318
-
finalPathConfirm.style.display = 'none';
317
+
let changed = await invoke('change_final_path', { newPath: finalPathData });
319
318
320
-
await invoke('change_final_path', { newPath: finalPathData });
321
-
window.location.reload();
319
+
if(changed){
320
+
finalPathPreviousData = finalPathData;
321
+
finalPathConfirm.style.display = 'none';
322
322
323
-
animate('.settings', {
324
-
opacity: 0,
325
-
translateX: '500px',
326
-
easing: 'easeInOutQuad',
327
-
duration: 250,
328
-
onComplete: () => {
329
-
utils.set('.settings', { display: 'none' });
330
-
}
331
-
})
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
+
})
332
334
333
-
window.location.reload();
335
+
window.location.reload();
336
+
}
334
337
}}>
335
338
Save
336
339
</span>
+17
src/styles.css
+17
src/styles.css
···
100
100
img{
101
101
max-width: 100%;
102
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;
103
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
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
520
},
521
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`",
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
523
"type": "string",
524
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`"
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
526
},
527
527
{
528
528
"description": "Enables the app_hide command without any pre-configured scope.",
···
567
567
"markdownDescription": "Enables the name command without any pre-configured scope."
568
568
},
569
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
+
{
570
576
"description": "Enables the remove_data_store command without any pre-configured scope.",
571
577
"type": "string",
572
578
"const": "core:app:allow-remove-data-store",
573
579
"markdownDescription": "Enables the remove_data_store command without any pre-configured scope."
574
580
},
575
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
+
{
576
588
"description": "Enables the set_app_theme command without any pre-configured scope.",
577
589
"type": "string",
578
590
"const": "core:app:allow-set-app-theme",
···
639
651
"markdownDescription": "Denies the name command without any pre-configured scope."
640
652
},
641
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
+
{
642
660
"description": "Denies the remove_data_store command without any pre-configured scope.",
643
661
"type": "string",
644
662
"const": "core:app:deny-remove-data-store",
645
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."
646
670
},
647
671
{
648
672
"description": "Denies the set_app_theme command without any pre-configured scope.",
···
1827
1851
"markdownDescription": "Enables the set_focus command without any pre-configured scope."
1828
1852
},
1829
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
+
{
1830
1860
"description": "Enables the set_fullscreen command without any pre-configured scope.",
1831
1861
"type": "string",
1832
1862
"const": "core:window:allow-set-fullscreen",
···
1897
1927
"type": "string",
1898
1928
"const": "core:window:allow-set-shadow",
1899
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."
1900
1936
},
1901
1937
{
1902
1938
"description": "Enables the set_size command without any pre-configured scope.",
···
2271
2307
"markdownDescription": "Denies the set_focus command without any pre-configured scope."
2272
2308
},
2273
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
+
{
2274
2316
"description": "Denies the set_fullscreen command without any pre-configured scope.",
2275
2317
"type": "string",
2276
2318
"const": "core:window:deny-set-fullscreen",
···
2341
2383
"type": "string",
2342
2384
"const": "core:window:deny-set-shadow",
2343
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."
2344
2392
},
2345
2393
{
2346
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
1
use std::fs;
2
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");
3
+
use tauri::{Emitter, State, Window};
8
4
9
-
fs::write(&config_path, new_path.as_bytes()).unwrap();
5
+
use crate::util::cache::Cache;
10
6
7
+
#[tauri::command]
8
+
pub fn change_final_path(new_path: &str, window: Window, cache: State<Cache>) -> bool {
11
9
match fs::metadata(&new_path) {
12
-
Ok(_) => {}
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
+
}
13
20
Err(_) => {
14
-
fs::create_dir(&new_path).unwrap();
21
+
window.emit("vrcpm-error", "Error Changing Path: Path does not exist.").unwrap();
22
+
false
15
23
}
16
-
};
24
+
}
17
25
}
+3
-1
src-tauri/src/frontend_calls/load_photos.rs
+3
-1
src-tauri/src/frontend_calls/load_photos.rs
···
16
16
let base_dir = cache.get("photo-path".into()).unwrap();
17
17
18
18
thread::spawn(move || {
19
-
20
19
let mut photos: Vec<path::PathBuf> = Vec::new();
21
20
let mut size: usize = 0;
22
21
22
+
let re = Regex::new(r"^[0-9]{4}-[0-9]{2}$").unwrap();
23
+
23
24
for folder in fs::read_dir(&base_dir).unwrap() {
24
25
let f = folder.unwrap();
26
+
if !re.is_match(f.file_name().to_str().unwrap()){ continue; }
25
27
26
28
if f.metadata().unwrap().is_dir() {
27
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
···
7
7
8
8
match fs::read_to_string(config_path) {
9
9
Ok(path) => {
10
-
path::PathBuf::from(path)
10
+
let p = path::PathBuf::from(path);
11
+
12
+
if fs::exists(&p).unwrap(){
13
+
p
14
+
} else{
15
+
dirs::picture_dir().unwrap().join("VRChat")
16
+
}
11
17
},
12
18
Err(_) => {
13
19
let p = dirs::picture_dir().unwrap().join("VRChat");