import { onCleanup, onMount, Show } from "solid-js"; import { bytesToFormatted } from "../utils"; import { invoke } from '@tauri-apps/api/core'; import anime from "animejs"; import { ViewState } from "./Managers/ViewManager"; let SettingsMenu = () => { let sliderBar: HTMLElement; let settingsContainer: HTMLElement; let currentButton = 0; let lastClickedButton = -1; let finalPathConfirm: HTMLElement; let finalPathInput: HTMLElement; let finalPathData: string; let finalPathPreviousData: string; let closeWithKey = ( e: KeyboardEvent ) => { if(e.key === 'Escape'){ window.ViewManager.ChangeState(ViewState.PHOTO_LIST); anime({ targets: '.settings', opacity: 0, translateX: '500px', easing: 'easeInOutQuad', duration: 250, complete: () => { anime.set('.settings', { display: 'none' }); } }) } } onMount(async () => { if(await invoke('get_config_value_string', { key: 'transparent' }) === "true"){ invoke('set_config_value_string', { key: 'transparent', value: 'true' }); anime({ targets: document.body, background: 'rgba(0, 0, 0, 0.5)', easing: 'linear', duration: 100 }); anime({ targets: '.settings', background: 'rgba(0, 0, 0, 0.5)', easing: 'linear', duration: 100 }); } else{ invoke('set_config_value_string', { key: 'transparent', value: 'false' }); anime({ targets: document.body, background: 'rgba(0, 0, 0, 1)', easing: 'linear', duration: 100 }); anime({ targets: '.settings', background: 'rgba(0, 0, 0, 0)', easing: 'linear', duration: 100 }); } let sliderMouseDown = false; let mouseStartX = 0; let width = window.innerWidth; let buttons = [ 370, 680 ]; let sliderPos = width / 2 - buttons[currentButton]; let sliderScale = width / (buttons[1] - buttons[0]); let render = () => { requestAnimationFrame(render); if(!sliderMouseDown){ sliderPos = sliderPos + (width / 2 - buttons[currentButton] - sliderPos) * 0.25; anime.set(sliderBar, { translateX: sliderPos }); settingsContainer.style.left = (sliderPos - (width / 2 - buttons[0])) * sliderScale + 'px'; } } render(); anime.set(sliderBar, { translateX: sliderPos }); sliderBar.addEventListener('touchstart', ( e: TouchEvent ) => { sliderMouseDown = true; mouseStartX = e.touches[0].clientX; }) window.addEventListener('touchmove', ( e: TouchEvent ) => { if(sliderMouseDown){ anime.set(sliderBar, { translateX: sliderPos - (mouseStartX - e.touches[0].clientX) }); settingsContainer.style.left = (sliderPos - (mouseStartX - e.touches[0].clientX) - (width / 2 - buttons[0])) * sliderScale + 'px'; } }) window.addEventListener('keyup', closeWithKey); window.addEventListener('touchend', ( e: TouchEvent ) => { if(sliderMouseDown){ sliderPos = sliderPos - (mouseStartX - e.touches[0].clientX); anime.set(sliderBar, { translateX: sliderPos - (mouseStartX - e.touches[0].clientX) }); sliderMouseDown = false; if(Math.abs(mouseStartX - e.touches[0].clientX) > 50){ let shortestDistance = 0; let selectedButton = -1; buttons.forEach(( pos, indx ) => { let dis = Math.abs(sliderPos - (width / 2 - pos)); if(selectedButton === -1){ shortestDistance = dis; selectedButton = indx; } else if(shortestDistance > dis){ shortestDistance = dis; selectedButton = indx; } }) currentButton = selectedButton; } else if(lastClickedButton != -1){ currentButton = lastClickedButton; lastClickedButton = -1 } } }) sliderBar.addEventListener('mousedown', ( e: MouseEvent ) => { sliderMouseDown = true; mouseStartX = e.clientX; }); window.addEventListener('mousemove', ( e: MouseEvent ) => { if(sliderMouseDown){ anime.set(sliderBar, { translateX: sliderPos - (mouseStartX - e.clientX) }); settingsContainer.style.left = sliderPos - (mouseStartX - e.clientX) + 'px'; settingsContainer.style.left = (sliderPos - (mouseStartX - e.clientX) - (width / 2 - buttons[0])) * sliderScale + 'px'; } }) window.addEventListener('mouseup', ( e: MouseEvent ) => { if(sliderMouseDown){ sliderPos = sliderPos - (mouseStartX - e.clientX); anime.set(sliderBar, { translateX: sliderPos - (mouseStartX - e.clientX) }); sliderMouseDown = false; if(Math.abs(mouseStartX - e.clientX) > 50){ let shortestDistance = 0; let selectedButton = -1; buttons.forEach(( pos, indx ) => { let dis = Math.abs(sliderPos - (width / 2 - pos)); if(selectedButton === -1){ shortestDistance = dis; selectedButton = indx; } else if(shortestDistance > dis){ shortestDistance = dis; selectedButton = indx; } }) currentButton = selectedButton; } else if(lastClickedButton != -1){ currentButton = lastClickedButton; lastClickedButton = -1 } } }) window.addEventListener('resize', () => { width = window.innerWidth; sliderPos = width / 2 - buttons[currentButton]; sliderScale = width / (buttons[1] - buttons[0]); anime.set(sliderBar, { translateX: sliderPos }); }) sliderBar.addEventListener('wheel', ( e: WheelEvent ) => { if(e.deltaY > 0){ if(buttons[currentButton + 1]) currentButton++; } else{ if(buttons[currentButton - 1]) currentButton--; } }) }) onCleanup(() => { window.removeEventListener('keyup', closeWithKey); }) return (
settingsContainer = el}>

Storage Settings

{ window.PhotoManager.PhotoCount() } Photos ({ bytesToFormatted(window.PhotoManager.PhotoSize(), 0) })

{ el.checked = await invoke('get_config_value_string', { key: 'start-in-bg' }) === "true" ? true : false; }} onChange={( el ) => { if(el.target.checked){ invoke('set_config_value_string', { key: 'start-in-bg', value: 'true' }); } else{ invoke('set_config_value_string', { key: 'start-in-bg', value: 'false' }); } }} /> Start in background
{ el.checked = await invoke('get_config_value_string', { key: 'start-with-win' }) === "true" ? true : false; }} onChange={( el ) => { if(el.target.checked){ invoke('set_config_value_string', { key: 'start-with-win', value: 'true' }); invoke("start_with_win", { start: true }); } else{ invoke('set_config_value_string', { key: 'start-with-win', value: 'false' }); invoke("start_with_win", { start: false }); } }} /> Start with windows
{ el.checked = await invoke('get_config_value_string', { key: 'transparent' }) === "true" ? true : false; }} onChange={( el ) => { if(el.target.checked){ invoke('set_config_value_string', { key: 'transparent', value: 'true' }); anime({ targets: document.body, background: 'rgba(0, 0, 0, 0.5)', easing: 'linear', duration: 100 }); anime({ targets: '.settings', background: 'rgba(0, 0, 0, 0.5)', easing: 'linear', duration: 100 }); } else{ invoke('set_config_value_string', { key: 'transparent', value: 'false' }); anime({ targets: document.body, background: 'rgba(0, 0, 0, 1)', easing: 'linear', duration: 100 }); anime({ targets: '.settings', background: 'rgba(0, 0, 0, 0)', easing: 'linear', duration: 100 }); } }} /> Window Transparency

VRChat Photo Path: invoke('get_user_photos_path').then(( path: any ) => { el.innerHTML = ''; el.appendChild( finalPathInput = el} onInput={( el ) => { finalPathConfirm.style.display = 'inline-block'; finalPathData = el.target.innerHTML; }} contenteditable>{path} as Node); finalPathPreviousData = path; }) }> Loading... finalPathConfirm = el}> { finalPathPreviousData = finalPathData; finalPathConfirm.style.display = 'none'; await invoke('change_final_path', { newPath: finalPathData }); await invoke('relaunch'); anime({ targets: '.settings', opacity: 0, translateX: '500px', easing: 'easeInOutQuad', duration: 250, complete: () => { anime.set('.settings', { display: 'none' }); } }) window.location.reload(); }}> Save { finalPathData = finalPathPreviousData; finalPathInput.innerHTML = finalPathPreviousData; finalPathConfirm.style.display = 'none'; }}> Cancel

VRCPM Version: invoke('get_version').then((ver: any) => el.innerHTML = ver)}>Loading...


To change the directory VRChat outputs photos to, you can change the "picture_output_folder" key in the invoke('open_url', { url: 'https://docs.vrchat.com/docs/configuration-file#camera-and-screenshot-settings' })}>config.json file
Alternitavely, you can use VRCX to edit the config file.


VRChat Photo Manager supports photos with extra metadata provided by VRCX.

Account Settings

You aren't logged in. To enable cloud sync and sharing features you need to login to your PhazeID.

{ window.AccountManager.login(); }}>Login
}>
sliderBar = el}>
lastClickedButton = 0}>Program Settings
lastClickedButton = 1}>Account Settings
) } export default SettingsMenu;