+24
-2
src-tauri/src/main.rs
+24
-2
src-tauri/src/main.rs
···
148
148
fs::remove_file(p).unwrap();
149
149
}
150
150
151
+
#[tauri::command]
152
+
fn change_final_path( new_path: &str ){
153
+
let config_path = dirs::picture_dir().unwrap().join(".vrchat_photos");
154
+
fs::write(&config_path, new_path.as_bytes()).unwrap();
155
+
}
156
+
151
157
fn main() {
152
158
std::env::set_var("WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS", "--ignore-gpu-blacklist");
159
+
tauri_plugin_deep_link::prepare("uk.phaz.vrcpm");
153
160
154
-
tauri_plugin_deep_link::prepare("uk.phaz.vrcpm");
161
+
// Double check the app has an install directory
162
+
let container_folder = dirs::home_dir().unwrap().join("AppData\\Roaming\\PhazeDev\\VRChatPhotoManager");
163
+
match fs::metadata(&container_folder){
164
+
Ok(meta) => {
165
+
if meta.is_file(){
166
+
panic!("Cannot launch app as the container path is a file not a directory");
167
+
}
168
+
},
169
+
Err(_) => {
170
+
fs::create_dir(&container_folder).unwrap();
171
+
}
172
+
}
173
+
174
+
// Do auto update stuff here (once im publishing builds)
155
175
156
176
// Setup the tray icon and menu buttons
157
177
let quit = CustomMenuItem::new("quit".to_string(), "Quit");
···
193
213
re1.is_match(path.to_str().unwrap()) ||
194
214
re2.is_match(path.to_str().unwrap())
195
215
{
216
+
thread::sleep(time::Duration::from_millis(1000));
196
217
sender.send((1, path.clone().strip_prefix(dirs::picture_dir().unwrap().join("VRChat")).unwrap().to_path_buf())).unwrap();
197
218
}
198
219
},
···
331
352
.invoke_handler(tauri::generate_handler![
332
353
start_user_auth, load_photos, close_splashscreen,
333
354
load_photo_meta, delete_photo, open_url,
334
-
find_world_by_id, start_with_win, get_user_photos_path
355
+
find_world_by_id, start_with_win, get_user_photos_path,
356
+
change_final_path
335
357
])
336
358
.run(tauri::generate_context!())
337
359
.expect("error while running tauri application");
+12
-4
src-tauri/src/worldscraper.rs
+12
-4
src-tauri/src/worldscraper.rs
···
14
14
favourites: u64,
15
15
tags: String,
16
16
from: String,
17
-
from_site: String
17
+
from_site: String,
18
+
found: bool
18
19
}
19
20
20
21
impl World{
···
22
23
println!("Fetching world data for {}", &world_id);
23
24
24
25
let mut world = World {
25
-
id: "".into(),
26
+
id: world_id.clone(),
26
27
name: "".into(),
27
28
author: "".into(),
28
29
author_id: "".into(),
···
33
34
favourites: 0,
34
35
tags: "".into(),
35
36
from: "https://vrclist.com/worlds/".into(),
36
-
from_site: "vrclist.com".into()
37
+
from_site: "vrclist.com".into(),
38
+
found: false
37
39
};
38
40
39
41
let client = reqwest::blocking::Client::new();
···
48
50
.text()
49
51
.unwrap();
50
52
53
+
if &fixed_id_req == "" {
54
+
return world;
55
+
}
56
+
57
+
world.found = true;
58
+
51
59
let fixed_id: serde_json::Value = serde_json::from_str(&fixed_id_req).unwrap();
52
60
world.from = format!("https://vrclist.com/worlds/{}", fixed_id["id"].to_string());
53
61
···
60
68
.json()
61
69
.unwrap();
62
70
63
-
world.id = world_id.clone();
64
71
world.name = world_data["name"].to_string();
65
72
world.author = world_data["authorName"].to_string();
66
73
world.author_id = world_data["authorId"].to_string();
···
101
108
s.serialize_field("tags", &self.tags)?;
102
109
s.serialize_field("from", &self.from)?;
103
110
s.serialize_field("fromSite", &self.from_site)?;
111
+
s.serialize_field("found", &self.found)?;
104
112
105
113
s.end()
106
114
}
+25
-17
src/Components/PhotoViewer.tsx
+25
-17
src/Components/PhotoViewer.tsx
···
24
24
favourites: number,
25
25
tags: any,
26
26
from: string,
27
-
fromSite: string
27
+
fromSite: string,
28
+
found: boolean
28
29
}
29
30
}
30
31
···
204
205
anime({ targets: '.prev-button', top: '75%', easing: 'easeInOutQuad', duration: 100 });
205
206
anime({ targets: '.next-button', top: '75%', easing: 'easeInOutQuad', duration: 100 });
206
207
}
207
-
208
+
208
209
isOpen = photo != null;
209
210
})
210
211
})
211
212
212
213
let loadWorldData = ( data: WorldCache ) => {
213
-
let tags: string[] = JSON.parse(data.worldData.tags.split('\\\\').join("").split('\\').join("").slice(1, -1));
214
-
215
214
worldInfoContainer.innerHTML = '';
216
215
worldInfoContainer.appendChild(
217
216
<div>
218
-
<div class="world-name">{ data.worldData.name } <i onClick={() => invoke('open_url', { url: 'https://vrchat.com/home/world/' + data.worldData.id })} style={{ "margin-left": '0px', "font-size": '12px', 'color': '#bbb', cursor: 'pointer' }} class="fa-solid fa-arrow-up-right-from-square"></i></div>
219
-
<div style={{ width: '75%', margin: 'auto' }}>{ data.worldData.desc }</div>
217
+
<Show when={ data.worldData.found == false && props.currentPhotoView().metadata }>
218
+
<div>
219
+
<div class="world-name">{ JSON.parse(props.currentPhotoView().metadata).world.name } <i onClick={() => invoke('open_url', { url: 'https://vrchat.com/home/world/' + data.worldData.id })} style={{ "margin-left": '0px', "font-size": '12px', 'color': '#bbb', cursor: 'pointer' }} class="fa-solid fa-arrow-up-right-from-square"></i></div>
220
+
<div style={{ width: '75%', margin: 'auto' }}>Could not fetch world information... Is the world private?</div>
221
+
</div>
222
+
</Show>
223
+
<Show when={ data.worldData.found == true }>
224
+
<div class="world-name">{ data.worldData.name } <i onClick={() => invoke('open_url', { url: 'https://vrchat.com/home/world/' + data.worldData.id })} style={{ "margin-left": '0px', "font-size": '12px', 'color': '#bbb', cursor: 'pointer' }} class="fa-solid fa-arrow-up-right-from-square"></i></div>
225
+
<div style={{ width: '75%', margin: 'auto' }}>{ data.worldData.desc }</div>
220
226
221
-
<br />
222
-
<div class="world-tags">
223
-
<For each={tags}>
224
-
{( tag ) =>
225
-
<div>{ tag.replace("author_tag_", "").replace("system_", "") }</div>
226
-
}
227
-
</For>
228
-
</div>
227
+
<br />
228
+
<div class="world-tags">
229
+
<For each={JSON.parse(data.worldData.tags.split('\\\\').join("").split('\\').join("").slice(1, -1))}>
230
+
{( tag ) =>
231
+
<div>{ tag.replace("author_tag_", "").replace("system_", "") }</div>
232
+
}
233
+
</For>
234
+
</div>
235
+
</Show>
229
236
</div> as Node
230
237
)
231
238
}
···
245
252
favourites: event.payload.favourites,
246
253
tags: event.payload.tags,
247
254
from: event.payload.from,
248
-
fromSite: event.payload.fromSite
255
+
fromSite: event.payload.fromSite,
256
+
found: event.payload.found
249
257
}
250
258
}
251
259
252
-
loadWorldData(worldData);
253
-
254
260
worldCache.push(worldData);
255
261
localStorage.setItem("worldCache", JSON.stringify(worldCache));
262
+
263
+
loadWorldData(worldData);
256
264
})
257
265
258
266
return (
+33
-2
src/Components/SettingsMenu.tsx
+33
-2
src/Components/SettingsMenu.tsx
···
13
13
let settingsContainer: HTMLElement;
14
14
let currentButton = 0;
15
15
let lastClickedButton = -1;
16
+
let finalPathConfirm: HTMLElement;
17
+
let finalPathInput: HTMLElement;
18
+
let finalPathData: string;
19
+
let finalPathPreviousData: string;
16
20
17
21
onMount(() => {
18
22
let sliderMouseDown = false;
···
192
196
</div>
193
197
194
198
<br />
195
-
<p>VRChat Photo Path: <span class="path" ref={( el ) => invoke('get_user_photos_path').then(( path: any ) => { el.innerHTML = path; console.log(path) })}>Loading...</span></p>
196
-
<p>Final Photo Path: <span class="path" ref={( el ) => invoke('get_user_photos_path').then(( path: any ) => { el.innerHTML = path; console.log(path) })}>Loading...</span></p>
199
+
<p>VRChat Photo Path: <span class="path" ref={( el ) => invoke('get_user_photos_path').then(( path: any ) => el.innerHTML = path)}>Loading...</span></p>
200
+
<p>
201
+
Final Photo Path:
202
+
<span class="path" ref={( el ) =>
203
+
invoke('get_user_photos_path').then(( path: any ) => {
204
+
el.innerHTML = '';
205
+
el.appendChild(<span style={{ outline: 'none' }} ref={( el ) => finalPathInput = el} onInput={( el ) => {
206
+
finalPathConfirm.style.display = 'inline-block';
207
+
finalPathData = el.target.innerHTML;
208
+
}} contenteditable>{path}</span> as Node);
209
+
210
+
finalPathPreviousData = path;
211
+
})
212
+
}>
213
+
Loading...
214
+
</span>
215
+
<span style={{ display: 'none' }} ref={( el ) => finalPathConfirm = el}>
216
+
<span class="path" style={{ color: 'green' }} onClick={() => {
217
+
finalPathPreviousData = finalPathData;
218
+
finalPathConfirm.style.display = 'none';
219
+
}}><i class="fa-solid fa-check"></i></span>
220
+
221
+
<span class="path" style={{ color: 'red' }} onClick={() => {
222
+
finalPathData = finalPathPreviousData;
223
+
finalPathInput.innerHTML = finalPathPreviousData;
224
+
finalPathConfirm.style.display = 'none';
225
+
}}><i class="fa-solid fa-xmark"></i></span>
226
+
</span>
227
+
</p>
197
228
</div>
198
229
<div class="settings-block">
199
230
<h1>Account Settings</h1>