├── src ├── vite-env.d.ts ├── assets │ ├── icon.png │ └── icon.svg ├── main.tsx ├── components │ ├── frameComponents │ │ ├── MonitorFrame.tsx │ │ ├── WindowFrame.tsx │ │ └── IntegratedFrame.tsx │ ├── configComponents │ │ ├── autoSummon │ │ │ ├── SwitchConfElm.tsx │ │ │ ├── AutoSummonConfElm.tsx │ │ │ ├── ShortcutConfElm.tsx │ │ │ ├── SizeConfElm.tsx │ │ │ └── ThresholdConfElm.tsx │ │ ├── DragTermMarginConfElm.tsx │ │ ├── WinWinMapSummonConfElm.tsx │ │ ├── CursorConfigElm.tsx │ │ ├── SummonPosConfElm.tsx │ │ └── ConfigElm.tsx │ ├── listComponents │ │ ├── ListComponent.tsx │ │ └── FrameRowComponent.tsx │ └── AppMain.tsx ├── winwin-type.ts ├── util.ts ├── App.tsx ├── App.css ├── app-store.ts ├── drag-util.ts ├── style.css ├── fetch-objects.ts └── hooks │ ├── frame-hook.ts │ ├── app-hook.ts │ └── config-hook.ts ├── src-tauri ├── build.rs ├── .gitignore ├── src │ ├── lib.rs │ ├── winapi │ │ ├── mod.rs │ │ ├── mouse.rs │ │ ├── monitors.rs │ │ ├── winprocess.rs │ │ ├── virtual_desktop.rs │ │ ├── structs.rs │ │ ├── winevents.rs │ │ └── winwins.rs │ ├── log.rs │ ├── backend.rs │ ├── util.rs │ └── main.rs ├── icons │ ├── icon.ico │ ├── icon.png │ ├── 32x32.png │ ├── icon.icns │ ├── 128x128.png │ ├── StoreLogo.png │ ├── 128x128@2x.png │ ├── Square30x30Logo.png │ ├── Square44x44Logo.png │ ├── Square71x71Logo.png │ ├── Square89x89Logo.png │ ├── Square107x107Logo.png │ ├── Square142x142Logo.png │ ├── Square150x150Logo.png │ ├── Square284x284Logo.png │ └── Square310x310Logo.png ├── examples │ ├── move_cursor.rs │ ├── get_monitors.rs │ ├── get_windows.rs │ ├── move_windows.rs │ └── get_events.rs ├── Cargo.toml └── tauri.conf.json ├── .github ├── winwinmap1.png ├── winwinmap_demo.gif ├── winwinmap2_configopen.png └── workflows │ └── build.yaml ├── .vscode └── extensions.json ├── tsconfig.node.json ├── .gitignore ├── index.html ├── tsconfig.json ├── package.json ├── LICENSE.md ├── vite.config.ts ├── public ├── vite.svg └── tauri.svg ├── README.md └── yarn.lock /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src-tauri/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | tauri_build::build() 3 | } 4 | -------------------------------------------------------------------------------- /src-tauri/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ -------------------------------------------------------------------------------- /src-tauri/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod backend; 2 | pub mod log; 3 | pub mod util; 4 | pub mod winapi; 5 | -------------------------------------------------------------------------------- /src/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anotherhollow1125/win-win-map/HEAD/src/assets/icon.png -------------------------------------------------------------------------------- /.github/winwinmap1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anotherhollow1125/win-win-map/HEAD/.github/winwinmap1.png -------------------------------------------------------------------------------- /src-tauri/icons/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anotherhollow1125/win-win-map/HEAD/src-tauri/icons/icon.ico -------------------------------------------------------------------------------- /src-tauri/icons/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anotherhollow1125/win-win-map/HEAD/src-tauri/icons/icon.png -------------------------------------------------------------------------------- /.github/winwinmap_demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anotherhollow1125/win-win-map/HEAD/.github/winwinmap_demo.gif -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["tauri-apps.tauri-vscode", "rust-lang.rust-analyzer"] 3 | } 4 | -------------------------------------------------------------------------------- /src-tauri/icons/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anotherhollow1125/win-win-map/HEAD/src-tauri/icons/32x32.png -------------------------------------------------------------------------------- /src-tauri/icons/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anotherhollow1125/win-win-map/HEAD/src-tauri/icons/icon.icns -------------------------------------------------------------------------------- /src-tauri/icons/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anotherhollow1125/win-win-map/HEAD/src-tauri/icons/128x128.png -------------------------------------------------------------------------------- /src-tauri/icons/StoreLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anotherhollow1125/win-win-map/HEAD/src-tauri/icons/StoreLogo.png -------------------------------------------------------------------------------- /src-tauri/icons/128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anotherhollow1125/win-win-map/HEAD/src-tauri/icons/128x128@2x.png -------------------------------------------------------------------------------- /.github/winwinmap2_configopen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anotherhollow1125/win-win-map/HEAD/.github/winwinmap2_configopen.png -------------------------------------------------------------------------------- /src-tauri/icons/Square30x30Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anotherhollow1125/win-win-map/HEAD/src-tauri/icons/Square30x30Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square44x44Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anotherhollow1125/win-win-map/HEAD/src-tauri/icons/Square44x44Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square71x71Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anotherhollow1125/win-win-map/HEAD/src-tauri/icons/Square71x71Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square89x89Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anotherhollow1125/win-win-map/HEAD/src-tauri/icons/Square89x89Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square107x107Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anotherhollow1125/win-win-map/HEAD/src-tauri/icons/Square107x107Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square142x142Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anotherhollow1125/win-win-map/HEAD/src-tauri/icons/Square142x142Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square150x150Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anotherhollow1125/win-win-map/HEAD/src-tauri/icons/Square150x150Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square284x284Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anotherhollow1125/win-win-map/HEAD/src-tauri/icons/Square284x284Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square310x310Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anotherhollow1125/win-win-map/HEAD/src-tauri/icons/Square310x310Logo.png -------------------------------------------------------------------------------- /src-tauri/examples/move_cursor.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use win_win_map::winapi::mouse::set_cursor_pos; 3 | 4 | fn main() -> Result<()> { 5 | set_cursor_pos(10, 10)?; 6 | 7 | Ok(()) 8 | } 9 | -------------------------------------------------------------------------------- /src-tauri/examples/get_monitors.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use win_win_map::winapi::Canvas; 3 | 4 | fn main() -> Result<()> { 5 | let canvas = Canvas::new()?; 6 | 7 | println!("{:?}", canvas); 8 | 9 | Ok(()) 10 | } 11 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "ESNext", 5 | "moduleResolution": "Node", 6 | "allowSyntheticDefaultImports": true 7 | }, 8 | "include": ["vite.config.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /src-tauri/src/winapi/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod monitors; 2 | pub mod mouse; 3 | pub mod structs; 4 | pub mod virtual_desktop; 5 | pub mod winevents; 6 | pub mod winprocess; 7 | pub mod winwins; 8 | 9 | pub use structs::Canvas; 10 | pub use structs::WinInfo; 11 | -------------------------------------------------------------------------------- /src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import App from "./App"; 4 | import "./style.css"; 5 | 6 | ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( 7 | 8 | 9 | 10 | ); 11 | -------------------------------------------------------------------------------- /src/components/frameComponents/MonitorFrame.tsx: -------------------------------------------------------------------------------- 1 | export interface MonitorFrameProps { 2 | width: number; 3 | height: number; 4 | left: number; 5 | top: number; 6 | } 7 | 8 | export const MonitorFrame = (props: MonitorFrameProps) => { 9 | return
; 10 | }; 11 | 12 | export default MonitorFrame; 13 | -------------------------------------------------------------------------------- /src-tauri/src/winapi/mouse.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{anyhow, Result}; 2 | use windows::Win32::UI::WindowsAndMessaging::SetCursorPos; 3 | 4 | pub fn set_cursor_pos(x: i32, y: i32) -> Result<()> { 5 | let result = unsafe { SetCursorPos(x, y) }; 6 | if result.as_bool() { 7 | Ok(()) 8 | } else { 9 | Err(anyhow!("SetCursorPos failed")) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Tauri + React + TS 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/winwin-type.ts: -------------------------------------------------------------------------------- 1 | export interface WinInfo { 2 | hwnd: number; 3 | title: string; 4 | exe_name?: string; 5 | left: number; 6 | top: number; 7 | width: number; 8 | height: number; 9 | is_foreground: boolean; 10 | } 11 | 12 | export interface Rect { 13 | left: number; 14 | top: number; 15 | right: number; 16 | bottom: number; 17 | } 18 | 19 | export interface Canvas { 20 | min_x: number; 21 | min_y: number; 22 | max_x: number; 23 | max_y: number; 24 | monitors: Rect[]; 25 | } 26 | -------------------------------------------------------------------------------- /src/util.ts: -------------------------------------------------------------------------------- 1 | export const constructShortcut = ( 2 | e: React.KeyboardEvent 3 | ): string => { 4 | const shortcut_list = []; 5 | if (e.altKey) { 6 | shortcut_list.push("Alt"); 7 | } 8 | if (e.ctrlKey) { 9 | shortcut_list.push("Control"); 10 | } 11 | if (e.shiftKey) { 12 | shortcut_list.push("Shift"); 13 | } 14 | /* 15 | if (e.metaKey) { 16 | shortcut_list.push("Meta"); 17 | } 18 | */ 19 | 20 | if (shortcut_list.indexOf(e.key) == -1) { 21 | shortcut_list.push(e.key); 22 | } 23 | 24 | const shortcut = shortcut_list.join("+"); 25 | 26 | return shortcut; 27 | }; 28 | -------------------------------------------------------------------------------- /src/components/frameComponents/WindowFrame.tsx: -------------------------------------------------------------------------------- 1 | export interface WindowFrameProps { 2 | width: number; 3 | height: number; 4 | left: number; 5 | top: number; 6 | is_target: boolean; 7 | is_active: boolean; 8 | onClick: (e: React.MouseEvent) => void; 9 | } 10 | 11 | const WindowFrame = (props: WindowFrameProps) => { 12 | return ( 13 |
20 | ); 21 | }; 22 | 23 | export default WindowFrame; 24 | -------------------------------------------------------------------------------- /src/assets/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 6 | "allowJs": false, 7 | "skipLibCheck": true, 8 | "esModuleInterop": false, 9 | "allowSyntheticDefaultImports": true, 10 | "strict": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "module": "ESNext", 13 | "moduleResolution": "Node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx", 18 | "baseUrl": ".", 19 | "paths": { 20 | "@/*": ["src/*"] 21 | } 22 | }, 23 | "include": ["src/**/*", "hooks/config_hook.ts"], 24 | "references": [{ "path": "./tsconfig.node.json" }] 25 | } 26 | -------------------------------------------------------------------------------- /src/components/configComponents/autoSummon/SwitchConfElm.tsx: -------------------------------------------------------------------------------- 1 | import { Config, ConfigMethods } from "@/hooks/config-hook"; 2 | import Switch from "@mui/material/Switch"; 3 | 4 | export interface AutoSummonSwitchElmProps { 5 | config: Config; 6 | configMethods: ConfigMethods; 7 | } 8 | 9 | const AutoSummonSwitchElm = ({ 10 | config, 11 | configMethods, 12 | }: AutoSummonSwitchElmProps) => { 13 | const handleChange = (event: React.ChangeEvent) => { 14 | configMethods.setAutoSummonEnabled(event.target.checked); 15 | }; 16 | 17 | return ( 18 | <> 19 | {"Auto Summon: "} 20 | 21 | 22 | ); 23 | }; 24 | 25 | export default AutoSummonSwitchElm; 26 | -------------------------------------------------------------------------------- /src/components/configComponents/DragTermMarginConfElm.tsx: -------------------------------------------------------------------------------- 1 | import TextField from "@mui/material/TextField"; 2 | import { Config, ConfigMethods } from "@/hooks/config-hook"; 3 | 4 | interface DragTermMarginConfElmProps { 5 | config: Config; 6 | configMethods: ConfigMethods; 7 | } 8 | 9 | const DragTermMarginConfElm = ({ 10 | config, 11 | configMethods, 12 | }: DragTermMarginConfElmProps) => { 13 | return ( 14 | { 19 | configMethods.setDragTermMargin(Number(e.target.value)); 20 | }} 21 | inputProps={{ 22 | step: 1, 23 | type: "number", 24 | }} 25 | /> 26 | ); 27 | }; 28 | 29 | export default DragTermMarginConfElm; 30 | -------------------------------------------------------------------------------- /src-tauri/examples/get_windows.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use win_win_map::util::Com; 3 | use win_win_map::winapi::virtual_desktop::VirtualDesktopManager; 4 | use win_win_map::winapi::WinInfo; 5 | 6 | fn main() -> Result<()> { 7 | let _com = Com::new()?; 8 | let vdm = VirtualDesktopManager::new()?; 9 | let wininfo_vec = WinInfo::get_windows_info()?; 10 | 11 | for wininfo in wininfo_vec { 12 | let this_disp = vdm.is_window_on_current_desktop(wininfo.hwnd)?; 13 | let sign = if this_disp { " " } else { "~" }; 14 | println!("{}{:?}", sign, wininfo); 15 | } 16 | 17 | println!("===================="); 18 | 19 | let this_display_windows = WinInfo::get_windows_info_filtered()?; 20 | 21 | for wininfo in this_display_windows { 22 | println!(" {:?}", wininfo); 23 | } 24 | 25 | WinInfo::drop_vdmth()?; 26 | 27 | Ok(()) 28 | } 29 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import "./App.css"; 2 | import AppMain from "@/components/AppMain"; 3 | import { useMemo } from "react"; 4 | import { createTheme, ThemeProvider } from "@mui/material/styles"; 5 | import useMediaQuery from "@mui/material/useMediaQuery"; 6 | import { Provider } from "react-redux"; 7 | import store from "@/app-store"; 8 | 9 | const App = () => { 10 | // https://amateur-engineer.com/react-mui-dark-mode/ 11 | const prefersDarkMode = useMediaQuery("(prefers-color-scheme: dark)"); 12 | const theme = useMemo( 13 | () => 14 | createTheme({ 15 | palette: { 16 | mode: prefersDarkMode ? "dark" : "light", 17 | }, 18 | }), 19 | [prefersDarkMode] 20 | ); 21 | 22 | return ( 23 | 24 | 25 | 26 | 27 | 28 | ); 29 | }; 30 | 31 | export default App; 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "win-win-map", 3 | "private": true, 4 | "version": "0.0.4", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc && vite build", 9 | "preview": "vite preview", 10 | "tauri": "tauri" 11 | }, 12 | "dependencies": { 13 | "@emotion/react": "^11.10.5", 14 | "@emotion/styled": "^11.10.5", 15 | "@mui/icons-material": "^5.11.0", 16 | "@mui/material": "^5.11.8", 17 | "@reduxjs/toolkit": "^1.9.3", 18 | "@tauri-apps/api": "^1.2.0", 19 | "react": "^18.2.0", 20 | "react-dom": "^18.2.0", 21 | "react-draggable": "^4.4.5", 22 | "react-redux": "^8.0.5", 23 | "redux": "^4.2.1" 24 | }, 25 | "devDependencies": { 26 | "@tauri-apps/cli": "^1.2.2", 27 | "@types/node": "^18.7.10", 28 | "@types/react": "^18.0.15", 29 | "@types/react-dom": "^18.0.6", 30 | "@vitejs/plugin-react": "^3.0.0", 31 | "typescript": "^4.6.4", 32 | "vite": "^4.0.0" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src-tauri/src/log.rs: -------------------------------------------------------------------------------- 1 | use env_logger::fmt::Color; 2 | use log::Level::*; 3 | use std::io::Write; 4 | 5 | pub fn env_logger_init() { 6 | env_logger::builder() 7 | .format(|buf, record| { 8 | let mut level_style = buf.style(); 9 | let color = match record.level() { 10 | Error => Color::Red, 11 | Warn => Color::Yellow, 12 | Info => Color::Green, 13 | Debug => Color::Blue, 14 | Trace => Color::Magenta, 15 | }; 16 | level_style.set_color(color).set_bold(true); 17 | 18 | let ts = buf.timestamp(); 19 | writeln!( 20 | buf, 21 | "[{} {} {} @{}:{}] {}", 22 | ts, 23 | level_style.value(record.level()), 24 | record.target(), 25 | record.file().unwrap_or("*"), 26 | record.line().unwrap_or(0), 27 | record.args(), 28 | ) 29 | }) 30 | .init(); 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright 2023 (c) namnium (anotherhollow1125) https://www.namnium.work/ 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import react from "@vitejs/plugin-react"; 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | resolve: { 8 | alias: [{ find: "@", replacement: "/src" }], 9 | }, 10 | 11 | // Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build` 12 | // prevent vite from obscuring rust errors 13 | clearScreen: false, 14 | // tauri expects a fixed port, fail if that port is not available 15 | server: { 16 | port: 1420, 17 | strictPort: true, 18 | }, 19 | // to make use of `TAURI_DEBUG` and other env variables 20 | // https://tauri.studio/v1/api/config#buildconfig.beforedevcommand 21 | envPrefix: ["VITE_", "TAURI_"], 22 | build: { 23 | // Tauri supports es2021 24 | target: process.env.TAURI_PLATFORM == "windows" ? "chrome105" : "safari13", 25 | // don't minify for debug builds 26 | minify: !process.env.TAURI_DEBUG ? "esbuild" : false, 27 | // produce sourcemaps for debug builds 28 | sourcemap: !!process.env.TAURI_DEBUG, 29 | }, 30 | }); 31 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | .mapZone { 2 | top: 0; 3 | z-index: 1; 4 | padding: 8px; 5 | background-color: #f6f6f6; 6 | } 7 | 8 | .object { 9 | background-color: transparent; 10 | box-sizing: border-box; 11 | } 12 | 13 | .object.child { 14 | position: absolute; 15 | } 16 | 17 | .object.base { 18 | position: relative; 19 | border: 1px solid red; 20 | } 21 | 22 | .disabled { 23 | opacity: 0.5; 24 | } 25 | 26 | .object.monitor { 27 | border: 1px solid blue; 28 | background-color: rgba(0, 0, 255, 0.1); 29 | } 30 | 31 | .object.window { 32 | border: 1px solid black; 33 | /* border-top: 5px solid black; */ 34 | } 35 | 36 | .object.window.active { 37 | background-color: rgba(0, 0, 0, 0.1); 38 | } 39 | 40 | .object.window.target { 41 | border-top: 5px solid black; 42 | } 43 | 44 | @media (prefers-color-scheme: dark) { 45 | .mapZone { 46 | background-color: #2f2f2f; 47 | } 48 | .object.window { 49 | border-color: white; 50 | } 51 | .object.window.active { 52 | background-color: rgba(255, 255, 255, 0.1); 53 | } 54 | .object.window.target { 55 | border-top-color: white; 56 | } 57 | } -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | tags: 5 | - "v*" 6 | workflow_dispatch: 7 | 8 | jobs: 9 | release: 10 | strategy: 11 | fail-fast: false 12 | matrix: 13 | platform: [windows-latest] 14 | runs-on: ${{ matrix.platform }} 15 | steps: 16 | - name: Checkout repository 17 | uses: actions/checkout@v2 18 | 19 | - name: Node.js setup 20 | uses: actions/setup-node@v1 21 | with: 22 | node-version: 18 23 | 24 | - name: Rust setup 25 | uses: actions-rs/toolchain@v1 26 | with: 27 | toolchain: stable 28 | 29 | - name: Install app dependencies and build web 30 | run: yarn && yarn build 31 | 32 | - name: Build the app 33 | uses: tauri-apps/tauri-action@v0.3 34 | 35 | env: 36 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 37 | with: 38 | tagName: v__VERSION__ # tauri-action replaces \_\_VERSION\_\_ with the app version 39 | releaseName: "v__VERSION__" 40 | releaseBody: "See the assets to download this version and install." 41 | releaseDraft: true 42 | prerelease: false -------------------------------------------------------------------------------- /src-tauri/examples/move_windows.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use dialoguer::{console::Term, theme::ColorfulTheme, Select}; 3 | use win_win_map::util::Com; 4 | use win_win_map::winapi::virtual_desktop::VirtualDesktopManager; 5 | use win_win_map::winapi::WinInfo; 6 | 7 | fn main() -> Result<()> { 8 | let _com = Com::new()?; 9 | let vdm = VirtualDesktopManager::new()?; 10 | let wininfo_vec = WinInfo::get_windows_info()? 11 | .into_iter() 12 | .filter(|wi| vdm.is_window_on_current_desktop(wi.hwnd).unwrap_or(false)) 13 | .collect::>(); 14 | 15 | let selection = Select::with_theme(&ColorfulTheme::default()) 16 | .with_prompt("Select a window") 17 | .items(&wininfo_vec) 18 | .default(0) 19 | .interact_on_opt(&Term::stderr()) 20 | .unwrap(); 21 | 22 | match selection { 23 | Some(i) => { 24 | let wi = &wininfo_vec[i]; 25 | println!("Move and Resize {} to (20, 20, 200, 100)", wi); 26 | wi.move_and_resize(20, 20, 200, 100)?; 27 | // wi.set_foreground()?; 28 | } 29 | None => { 30 | println!("No window selected"); 31 | } 32 | } 33 | 34 | Ok(()) 35 | } 36 | -------------------------------------------------------------------------------- /public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/configComponents/autoSummon/AutoSummonConfElm.tsx: -------------------------------------------------------------------------------- 1 | import SwitchConfElm from "@/components/configComponents/autoSummon/SwitchConfElm"; 2 | import ThresholdConfElm from "@/components/configComponents/autoSummon/ThresholdConfElm"; 3 | import SizeConfElm from "@/components/configComponents/autoSummon/SizeConfElm"; 4 | import AutoSummonShortcutConfElm from "@/components/configComponents/autoSummon/ShortcutConfElm"; 5 | import Grid from "@mui/material/Grid"; 6 | import { Config, ConfigMethods } from "@/hooks/config-hook"; 7 | 8 | export interface AutoSummonConfElmProps { 9 | config: Config; 10 | configMethods: ConfigMethods; 11 | } 12 | 13 | const AutoSummonConfElm = ({ 14 | config, 15 | configMethods, 16 | }: AutoSummonConfElmProps) => { 17 | return ( 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 33 | 34 | 35 | ); 36 | }; 37 | 38 | export default AutoSummonConfElm; 39 | -------------------------------------------------------------------------------- /src/components/configComponents/WinWinMapSummonConfElm.tsx: -------------------------------------------------------------------------------- 1 | import TextField from "@mui/material/TextField"; 2 | import { Config, ConfigMethods } from "@/hooks/config-hook"; 3 | import { IconButton } from "@mui/material"; 4 | import ClearIcon from "@mui/icons-material/Clear"; 5 | import { constructShortcut } from "@/util"; 6 | 7 | interface ConfigElmProps { 8 | config: Config; 9 | configMethods: ConfigMethods; 10 | } 11 | 12 | const WinWinMapSummonConfElm = ({ config, configMethods }: ConfigElmProps) => { 13 | const handleShortcut = async (e: React.KeyboardEvent) => { 14 | const shortcut = constructShortcut(e); 15 | configMethods.setWinWinMapSummonShortcut(shortcut); 16 | }; 17 | 18 | const resetShortcut = () => { 19 | configMethods.setWinWinMapSummonShortcut(""); 20 | }; 21 | 22 | return ( 23 | <> 24 | 36 | 37 | 38 | ), 39 | }} 40 | /> 41 | 42 | ); 43 | }; 44 | 45 | export default WinWinMapSummonConfElm; 46 | -------------------------------------------------------------------------------- /src/app-store.ts: -------------------------------------------------------------------------------- 1 | import { configureStore, createSlice, PayloadAction } from "@reduxjs/toolkit"; 2 | 3 | export type DragStateEnum = "dragging" | "idling"; 4 | 5 | export const dragStateSlice = createSlice({ 6 | name: "dragState", 7 | initialState: { 8 | state: "idling" as DragStateEnum, 9 | handle: (st: DragStateEnum) => {}, 10 | }, 11 | reducers: { 12 | setDragging: (state) => { 13 | state.state = "dragging"; 14 | state.handle(state.state); 15 | }, 16 | setIdling: (state) => { 17 | state.state = "idling"; 18 | state.handle(state.state); 19 | }, 20 | setHandle: (state, handle: PayloadAction<(st: DragStateEnum) => void>) => { 21 | state.handle = handle.payload; 22 | }, 23 | }, 24 | }); 25 | 26 | // TODO: app-hookの内容をこちらに一部移動する 27 | // 正直したほうがいいかしないべきか迷ってる 28 | 29 | export const { setDragging, setIdling, setHandle } = dragStateSlice.actions; 30 | // export default dragStateSlice.reducer; 31 | 32 | const store = configureStore({ 33 | reducer: { 34 | dragState: dragStateSlice.reducer, 35 | }, 36 | middleware: (getDefaultMiddleware) => 37 | getDefaultMiddleware({ 38 | serializableCheck: { 39 | ignoredActions: ["dragState/setHandle"], 40 | // ignoredActionPaths: ["payload"], 41 | ignoredPaths: ["dragState.handle"], 42 | }, 43 | }), 44 | }); 45 | 46 | export default store; 47 | export type DragState = ReturnType; 48 | export type AppDispatch = typeof store.dispatch; 49 | -------------------------------------------------------------------------------- /src/components/configComponents/autoSummon/ShortcutConfElm.tsx: -------------------------------------------------------------------------------- 1 | import TextField from "@mui/material/TextField"; 2 | import { Config, ConfigMethods } from "@/hooks/config-hook"; 3 | import { IconButton } from "@mui/material"; 4 | import ClearIcon from "@mui/icons-material/Clear"; 5 | import { constructShortcut } from "@/util"; 6 | 7 | interface AutoSummonShortcutConfElmProps { 8 | config: Config; 9 | configMethods: ConfigMethods; 10 | } 11 | 12 | const AutoSummonShortcutConfElm = ({ 13 | config, 14 | configMethods, 15 | }: AutoSummonShortcutConfElmProps) => { 16 | const handleShortcut = async (e: React.KeyboardEvent) => { 17 | const shortcut = constructShortcut(e); 18 | configMethods.setAutoSummonShortcut(shortcut); 19 | }; 20 | 21 | const resetShortcut = () => { 22 | configMethods.setAutoSummonShortcut(""); 23 | }; 24 | 25 | return ( 26 | <> 27 | 39 | 40 | 41 | ), 42 | }} 43 | /> 44 | 45 | ); 46 | }; 47 | 48 | export default AutoSummonShortcutConfElm; 49 | -------------------------------------------------------------------------------- /src/components/configComponents/CursorConfigElm.tsx: -------------------------------------------------------------------------------- 1 | import TextField from "@mui/material/TextField"; 2 | import { Config, ConfigMethods } from "@/hooks/config-hook"; 3 | import { IconButton } from "@mui/material"; 4 | import ClearIcon from "@mui/icons-material/Clear"; 5 | import { constructShortcut } from "@/util"; 6 | // import MouseIcon from "@mui/icons-material/Mouse"; 7 | 8 | interface CursorConfigElmProps { 9 | config: Config; 10 | configMethods: ConfigMethods; 11 | } 12 | 13 | const CursorConfigElm = ({ config, configMethods }: CursorConfigElmProps) => { 14 | const handleShortcut = async (e: React.KeyboardEvent) => { 15 | const shortcut = constructShortcut(e); 16 | configMethods.setSummonMouseCursorShortcut(shortcut); 17 | }; 18 | 19 | const resetShortcut = () => { 20 | configMethods.setSummonMouseCursorShortcut(""); 21 | }; 22 | 23 | return ( 24 | <> 25 | 37 | 38 | 39 | ), 40 | }} 41 | /> 42 | 43 | ); 44 | }; 45 | 46 | export default CursorConfigElm; 47 | -------------------------------------------------------------------------------- /src-tauri/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "win-win-map" 3 | version = "0.0.4" 4 | description = "A Tauri App" 5 | authors = ["you"] 6 | license = "" 7 | repository = "" 8 | edition = "2021" 9 | rust-version = "1.57" 10 | 11 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 12 | 13 | [build-dependencies] 14 | tauri-build = { version = "1.2", features = [] } 15 | 16 | [dependencies] 17 | serde_json = "1.0" 18 | serde = { version = "1.0", features = ["derive"] } 19 | tauri = { version = "1.2", features = ["app-all", "fs-all", "global-shortcut-all", "path-all", "process-exit", "window-set-size"] } 20 | anyhow = "1.0.68" 21 | once_cell = "1.17.0" 22 | tokio = { version = "1.25.0", features = ["full"] } 23 | crossbeam = "0.8.2" 24 | env_logger = "0.10.0" 25 | log = "0.4.17" 26 | dotenv = "0.15.0" 27 | 28 | [dev-dependencies] 29 | ctrlc = "3.2.4" 30 | dialoguer = "0.10.3" 31 | 32 | [dependencies.windows] 33 | varsion = "0.44.0" 34 | features = [ 35 | "Win32_System_Com", 36 | "Win32_UI_WindowsAndMessaging", 37 | "Win32_Foundation", 38 | "Win32_UI_Accessibility", 39 | "Win32_System_Threading", 40 | "Win32_System_Console", 41 | "Win32_Graphics_Gdi", 42 | "Win32_UI_Shell", 43 | # "interface", 44 | ] 45 | 46 | [features] 47 | # by default Tauri runs in production mode 48 | # when `tauri dev` runs it is executed with `cargo run --no-default-features` if `devPath` is an URL 49 | default = ["custom-protocol"] 50 | # this feature is used used for production builds where `devPath` points to the filesystem 51 | # DO NOT remove this 52 | custom-protocol = ["tauri/custom-protocol"] 53 | -------------------------------------------------------------------------------- /src/components/configComponents/SummonPosConfElm.tsx: -------------------------------------------------------------------------------- 1 | import TextField from "@mui/material/TextField"; 2 | import { Config, ConfigMethods } from "@/hooks/config-hook"; 3 | import Grid from "@mui/material/Grid"; 4 | 5 | export interface SummonPosConfElmProps { 6 | config: Config; 7 | configMethods: ConfigMethods; 8 | } 9 | 10 | const SummonPosConfElm = ({ config, configMethods }: SummonPosConfElmProps) => { 11 | return ( 12 | 13 | 14 | {"Summon Pos: "} 15 | 16 | 17 | { 22 | configMethods.setSummonPoint({ 23 | x: Number(e.target.value), 24 | y: config.summon_point?.y ?? 0, 25 | }); 26 | }} 27 | inputProps={{ 28 | step: 1, 29 | type: "number", 30 | }} 31 | /> 32 | 33 | 34 | { 39 | configMethods.setSummonPoint({ 40 | x: config.summon_point?.x ?? 0, 41 | y: Number(e.target.value), 42 | }); 43 | }} 44 | inputProps={{ 45 | step: 1, 46 | type: "number", 47 | }} 48 | /> 49 | 50 | 51 | ); 52 | }; 53 | 54 | export default SummonPosConfElm; 55 | -------------------------------------------------------------------------------- /src/components/configComponents/autoSummon/SizeConfElm.tsx: -------------------------------------------------------------------------------- 1 | import Grid from "@mui/material/Grid"; 2 | import { Config, ConfigMethods } from "@/hooks/config-hook"; 3 | import TextField from "@mui/material/TextField"; 4 | 5 | interface AutoSummonSizeConfElmProps { 6 | config: Config; 7 | configMethods: ConfigMethods; 8 | } 9 | 10 | const AutoSummonSizeConfElm = ({ 11 | config, 12 | configMethods, 13 | }: AutoSummonSizeConfElmProps) => { 14 | return ( 15 | 16 | 17 | { 22 | configMethods.setAutoSummonSize({ 23 | width: Number(e.target.value), 24 | height: config.auto_summon_size?.height ?? 0, 25 | }); 26 | }} 27 | inputProps={{ 28 | step: 1, 29 | type: "number", 30 | }} 31 | /> 32 | 33 | 34 | { 39 | configMethods.setAutoSummonSize({ 40 | width: config.auto_summon_size?.width ?? 0, 41 | height: Number(e.target.value), 42 | }); 43 | }} 44 | inputProps={{ 45 | step: 1, 46 | type: "number", 47 | }} 48 | /> 49 | 50 | 51 | ); 52 | }; 53 | 54 | export default AutoSummonSizeConfElm; 55 | -------------------------------------------------------------------------------- /src/components/listComponents/ListComponent.tsx: -------------------------------------------------------------------------------- 1 | import List from "@mui/material/List"; 2 | import ListItem from "@mui/material/ListItem"; 3 | import { WinInfo } from "@/winwin-type"; 4 | import FrameRowComponent from "@/components/listComponents/FrameRowComponent"; 5 | import { Config, ConfigMethods } from "@/hooks/config-hook"; 6 | import { WindowAttr } from "@/hooks/frame-hook"; 7 | 8 | interface ListComponentProps { 9 | windows: WindowAttr[]; 10 | config: Config; 11 | configMethods: ConfigMethods; 12 | target_hwnd: number | undefined; 13 | setTarget: (w: WindowAttr) => void; 14 | accessable_windows: WinInfo[]; 15 | } 16 | 17 | const ListComponent = ({ 18 | windows, 19 | config, 20 | configMethods, 21 | target_hwnd: target, 22 | setTarget, 23 | accessable_windows, 24 | }: ListComponentProps) => { 25 | windows.sort((a, b) => { 26 | if (a.original.hwnd === target) { 27 | return -1; 28 | } else if (b.original.hwnd === target) { 29 | return 1; 30 | } 31 | 32 | if (a.is_visible && !b.is_visible) { 33 | return -1; 34 | } else if (!a.is_visible && b.is_visible) { 35 | return 1; 36 | } 37 | 38 | return 0; 39 | }); 40 | 41 | return ( 42 | 43 | {windows.map((window) => ( 44 | 45 | 53 | 54 | ))} 55 | 56 | ); 57 | }; 58 | 59 | export default ListComponent; 60 | -------------------------------------------------------------------------------- /src-tauri/src/backend.rs: -------------------------------------------------------------------------------- 1 | use crate::winapi::winevents::{init_event_notifier, UpdateEvents}; 2 | use anyhow::{anyhow, Result}; 3 | use std::thread; 4 | use tauri::{App, Manager, Wry}; 5 | use tokio::sync::mpsc::Receiver; 6 | use tokio::task; 7 | 8 | pub fn prepare() -> Result<(Receiver, thread::JoinHandle>)> { 9 | let Some((rx, th)) = init_event_notifier()? else { 10 | return Err(anyhow!("init_event_notifier already initialized")); 11 | }; 12 | 13 | Ok((rx, th)) 14 | } 15 | 16 | #[derive(serde::Serialize, Clone)] 17 | struct UpdateEventsPayload { 18 | kind: String, 19 | hwnd: u64, 20 | } 21 | 22 | pub fn tauri_setup(app: &mut App, mut rx: Receiver) -> task::JoinHandle<()> { 23 | let main_window = app.get_window("main").unwrap(); 24 | 25 | let mw = main_window.clone(); 26 | let notification_thread = tokio::spawn(async move { 27 | while let Some(event) = rx.recv().await { 28 | let payload = match event { 29 | UpdateEvents::Active(hwnd) => UpdateEventsPayload { 30 | kind: "active".to_string(), 31 | hwnd: hwnd.0 as _, 32 | }, 33 | UpdateEvents::Move(hwnd) => UpdateEventsPayload { 34 | kind: "move".to_string(), 35 | hwnd: hwnd.0 as _, 36 | }, 37 | UpdateEvents::Destroy(hwnd) => UpdateEventsPayload { 38 | kind: "destroy".to_string(), 39 | hwnd: hwnd.0 as _, 40 | }, 41 | }; 42 | if let Err(e) = mw.emit("update", payload) { 43 | log::error!("{:?}", e); 44 | } 45 | } 46 | }); 47 | 48 | notification_thread 49 | } 50 | -------------------------------------------------------------------------------- /src-tauri/src/winapi/monitors.rs: -------------------------------------------------------------------------------- 1 | use crate::winapi::structs::Canvas; 2 | use anyhow::Result; 3 | use windows::Win32::{ 4 | Foundation::{BOOL, LPARAM, RECT}, 5 | Graphics::Gdi::{EnumDisplayMonitors, HDC, HMONITOR}, 6 | }; 7 | 8 | unsafe extern "system" fn monitor_enum_proc( 9 | _p0: HMONITOR, 10 | _p1: HDC, 11 | rect: *mut RECT, 12 | v: LPARAM, 13 | ) -> BOOL { 14 | let rect_vec = &mut *(v.0 as *mut Vec); 15 | rect_vec.push(*rect); 16 | 17 | BOOL(1) 18 | } 19 | 20 | pub fn get_monitors_rect() -> Result> { 21 | let mut rect_vec = Vec::new(); 22 | unsafe { 23 | EnumDisplayMonitors( 24 | None, 25 | None, 26 | Some(monitor_enum_proc), 27 | LPARAM(&mut rect_vec as *mut _ as _), 28 | ); 29 | } 30 | Ok(rect_vec) 31 | } 32 | 33 | pub(crate) fn get_canvas() -> Result { 34 | let monitors = get_monitors_rect()?; 35 | // キモチワルク見えるかもしれないけど繰り返し回数は最小 36 | // それに可読性は何も問題ない 37 | let mut min_x = i32::MAX; 38 | let mut min_y = i32::MAX; 39 | let mut max_x = i32::MIN; 40 | let mut max_y = i32::MIN; 41 | for monitor in &monitors { 42 | min_x = min_x.min(monitor.left); 43 | min_y = min_y.min(monitor.top); 44 | max_x = max_x.max(monitor.right); 45 | max_y = max_y.max(monitor.bottom); 46 | } 47 | 48 | Ok(Canvas { 49 | min_x, 50 | min_y, 51 | max_x, 52 | max_y, 53 | monitors, 54 | }) 55 | } 56 | 57 | #[cfg(test)] 58 | mod tests { 59 | use super::*; 60 | 61 | #[test] 62 | fn test_get_monitors_rect() { 63 | let _monitors = get_monitors_rect().unwrap(); 64 | } 65 | 66 | #[test] 67 | fn test_canvas() { 68 | let _canvas = Canvas::new().unwrap(); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src-tauri/src/winapi/winprocess.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{anyhow, Result}; 2 | use windows::{ 3 | core::PWSTR, 4 | Win32::{ 5 | Foundation::{CloseHandle, GetLastError, HANDLE, HWND}, 6 | System::Threading::{ 7 | OpenProcess, QueryFullProcessImageNameW, PROCESS_NAME_FORMAT, 8 | PROCESS_QUERY_LIMITED_INFORMATION, 9 | }, 10 | UI::WindowsAndMessaging::GetWindowThreadProcessId, 11 | }, 12 | }; 13 | 14 | struct ProcessHandle { 15 | inner: HANDLE, 16 | } 17 | 18 | impl ProcessHandle { 19 | fn new(pid: u32) -> Result { 20 | let handle = unsafe { OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, pid) }?; 21 | 22 | Ok(Self { inner: handle }) 23 | } 24 | 25 | fn get_exe_name(&self) -> Result { 26 | let mut v = vec![0; 2048]; 27 | let buffer = PWSTR::from_raw(v.as_mut_ptr()); 28 | let mut size = v.len() as u32; 29 | let res = unsafe { 30 | QueryFullProcessImageNameW( 31 | self.inner, 32 | PROCESS_NAME_FORMAT(0), 33 | buffer, 34 | (&mut size) as *mut _, 35 | ) 36 | } 37 | .as_bool(); 38 | 39 | if !res { 40 | return Err(anyhow!( 41 | "Failed to QueryFullProcessImageNameW: {:?}", 42 | unsafe { GetLastError() } 43 | )); 44 | } 45 | 46 | Ok(unsafe { buffer.to_string() }?) 47 | } 48 | } 49 | 50 | impl Drop for ProcessHandle { 51 | fn drop(&mut self) { 52 | unsafe { CloseHandle(self.inner) }; 53 | } 54 | } 55 | 56 | pub fn get_exe_name(hwnd: HWND) -> Result { 57 | let mut process_id: u32 = 0; 58 | 59 | let _ = unsafe { GetWindowThreadProcessId(hwnd, Some((&mut process_id) as *mut _)) }; 60 | let handle = ProcessHandle::new(process_id)?; 61 | handle.get_exe_name() 62 | } 63 | -------------------------------------------------------------------------------- /src-tauri/src/winapi/virtual_desktop.rs: -------------------------------------------------------------------------------- 1 | use crate::util::Com; 2 | use crate::winapi::WinInfo; 3 | use anyhow::Result; 4 | use crossbeam::channel::{unbounded, Receiver, Sender}; 5 | use std::thread::{self, JoinHandle}; 6 | use windows::Win32::{ 7 | Foundation::HWND, 8 | System::Com::{CoCreateInstance, CLSCTX_ALL}, 9 | UI::Shell::{IVirtualDesktopManager, VirtualDesktopManager as VirtualDesktopManager_ID}, 10 | }; 11 | 12 | pub struct VirtualDesktopManager(IVirtualDesktopManager); 13 | 14 | impl VirtualDesktopManager { 15 | pub fn new() -> Result { 16 | let virtual_desktop_manager = 17 | unsafe { CoCreateInstance(&VirtualDesktopManager_ID, None, CLSCTX_ALL)? }; 18 | Ok(Self(virtual_desktop_manager)) 19 | } 20 | 21 | pub fn is_window_on_current_desktop(&self, hwnd: HWND) -> Result { 22 | let is_on_current_desktop = unsafe { self.0.IsWindowOnCurrentVirtualDesktop(hwnd)? }; 23 | Ok(is_on_current_desktop.as_bool()) 24 | } 25 | 26 | pub fn filter_thread() -> (JoinHandle<()>, Sender>, Receiver>) { 27 | let (input_sender, input_receiver) = unbounded::>(); 28 | let (output_sender, output_receiver) = unbounded(); 29 | 30 | // 最近考えが改まって来たのだけど、スレッドはunwrapでパニックしてほしい... 31 | let th = thread::spawn(move || { 32 | let _com = Com::new().unwrap(); 33 | let vdm = VirtualDesktopManager::new().unwrap(); 34 | 35 | while let Ok(wininfo_vec) = input_receiver.recv() { 36 | let this_disp_vec = wininfo_vec 37 | .into_iter() 38 | .filter(|wi| vdm.is_window_on_current_desktop(wi.hwnd).unwrap_or(false)) 39 | .collect::>(); 40 | 41 | output_sender.send(this_disp_vec).unwrap(); 42 | } 43 | }); 44 | 45 | (th, input_sender, output_receiver) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/drag-util.ts: -------------------------------------------------------------------------------- 1 | import { CanvasInfo } from "@/hooks/frame-hook"; 2 | import { Config } from "@/hooks/config-hook"; 3 | import { WinInfo } from "@/winwin-type"; 4 | import { ForceSummonWindow } from "@/hooks/app-hook"; 5 | 6 | const rawPos2Pos = ( 7 | raw_pos: { x: number; y: number }, 8 | canvasInfo: CanvasInfo 9 | ) => { 10 | if (canvasInfo.canvas === undefined || canvasInfo.scale === undefined) { 11 | return raw_pos; 12 | } 13 | 14 | const x = Math.round(raw_pos.x * canvasInfo.scale + canvasInfo.canvas.min_x); 15 | const y = Math.round(raw_pos.y * canvasInfo.scale + canvasInfo.canvas.min_y); 16 | 17 | return { x, y }; 18 | }; 19 | 20 | const checkTermInMonitor = ( 21 | pos: { x: number; y: number }, 22 | canvasInfo: CanvasInfo, 23 | config: Config 24 | ): boolean => { 25 | const { x, y } = pos; 26 | 27 | if (canvasInfo.canvas === undefined) { 28 | return false; 29 | } 30 | 31 | for (const monitor of canvasInfo.canvas.monitors) { 32 | const left = monitor.left + config.dragTermMargin; 33 | const top = monitor.top + config.dragTermMargin; 34 | const right = monitor.right - config.dragTermMargin; 35 | const bottom = monitor.bottom - config.dragTermMargin; 36 | 37 | if (left <= x && top <= y && x <= right && y <= bottom) { 38 | return true; 39 | } 40 | } 41 | 42 | return false; 43 | }; 44 | 45 | export const tryDragMove = async ( 46 | window: WinInfo, 47 | raw_pos: { x: number; y: number }, 48 | canvasInfo: CanvasInfo, 49 | config: Config 50 | // littleShrink: boolean = false // 移動時に少し小さくすればいい戦法はあまり意味なかったです 51 | ): Promise => { 52 | const pos = rawPos2Pos(raw_pos, canvasInfo); 53 | 54 | if (!checkTermInMonitor(pos, canvasInfo, config)) { 55 | return false; 56 | } 57 | 58 | await ForceSummonWindow(window.hwnd, pos, { 59 | width: window.width, 60 | height: window.height, 61 | }); 62 | 63 | return true; 64 | }; 65 | -------------------------------------------------------------------------------- /src-tauri/examples/get_events.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{anyhow, Result}; 2 | use std::collections::HashMap; 3 | use win_win_map::winapi::{ 4 | winevents::{close_chan, init_event_notifier, UpdateEvents}, 5 | WinInfo, 6 | }; 7 | // use std::process::exit; 8 | 9 | #[tokio::main] 10 | async fn main() -> Result<()> { 11 | let mut windows = WinInfo::get_windows_info()? 12 | .into_iter() 13 | .map(|win| (win.hwnd.0, win)) 14 | .collect::>(); 15 | 16 | let Ok(Some((mut receiver, th))) = init_event_notifier() else { 17 | return Err(anyhow!("init_event_notifier failed")); 18 | }; 19 | 20 | ctrlc::set_handler(move || { 21 | let _ = close_chan(); 22 | println!("Ctrl-C pressed, exiting..."); 23 | }) 24 | .expect("Error setting Ctrl-C handler"); 25 | 26 | while let Some(event) = receiver.recv().await { 27 | match event { 28 | UpdateEvents::Active(hwnd) => { 29 | let win = match windows.get(&hwnd.0) { 30 | Some(win) => win, 31 | None => { 32 | let Ok(Some(wininfo)) = WinInfo::from_hwnd(hwnd.0) else { continue }; 33 | windows.insert(hwnd.0, wininfo); 34 | windows.get(&hwnd.0).unwrap() 35 | } 36 | }; 37 | println!("Active: {}", win.title); 38 | } 39 | UpdateEvents::Move(hwnd) => { 40 | let Some(win) = windows.get(&hwnd.0) else { continue }; 41 | println!("Move: {}", win.title); 42 | } 43 | UpdateEvents::Destroy(hwnd) => { 44 | let Some(win) = windows.get(&hwnd.0) else { continue }; 45 | println!("Destroy: {}", win.title); 46 | } 47 | } 48 | } 49 | 50 | th.join().map_err(|_| anyhow!("thread join failed"))??; 51 | 52 | Ok(()) 53 | } 54 | -------------------------------------------------------------------------------- /src/components/AppMain.tsx: -------------------------------------------------------------------------------- 1 | import IntegratedFrame from "@/components/frameComponents/IntegratedFrame"; 2 | import useAppState from "@/hooks/app-hook"; 3 | import ConfigElm from "@/components/configComponents/ConfigElm"; 4 | import ListComponent from "@/components/listComponents/ListComponent"; 5 | import { useMediaQuery } from "@mui/material"; 6 | import { Box } from "@mui/system"; 7 | 8 | const AppMain = () => { 9 | const [ 10 | framesInfo, 11 | config, 12 | configMethods, 13 | updateFrames, 14 | tagetForceRefresh, 15 | target, 16 | setTarget, 17 | canvasInfo, 18 | ] = useAppState(); 19 | const isMobile = useMediaQuery("(max-width: 600px)"); 20 | const isPC = !isMobile; 21 | 22 | return ( 23 | <> 24 | 30 | {config !== undefined && config.showMap && isPC && framesInfo ? ( 31 | 34 | tagetForceRefresh(target?.original.hwnd, framesInfo.windows) 35 | } 36 | target={target} 37 | canvasInfo={canvasInfo} 38 | config={config} 39 | setTarget={setTarget} 40 | /> 41 | ) : ( 42 | <> 43 | )} 44 | {config ? ( 45 | 46 | ) : ( 47 | <>Loading... 48 | )} 49 | 50 | {isPC && framesInfo && config ? ( 51 | w.original)} 58 | /> 59 | ) : ( 60 | <> 61 | )} 62 | 63 | ); 64 | }; 65 | 66 | export default AppMain; 67 | -------------------------------------------------------------------------------- /src-tauri/tauri.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "build": { 3 | "beforeDevCommand": "yarn dev", 4 | "beforeBuildCommand": "yarn build", 5 | "devPath": "http://localhost:1420", 6 | "distDir": "../dist" 7 | }, 8 | "package": { 9 | "productName": "win-win-map", 10 | "version": "0.0.4" 11 | }, 12 | "tauri": { 13 | "allowlist": { 14 | "window": { 15 | "setSize": true 16 | }, 17 | "app": { 18 | "all": true 19 | }, 20 | "fs": { 21 | "all": true, 22 | "scope": ["$APPCONFIG", "$APPCONFIG/*"] 23 | }, 24 | "path": { 25 | "all": true 26 | }, 27 | "globalShortcut": { 28 | "all": true 29 | }, 30 | "process": { 31 | "exit": true 32 | } 33 | }, 34 | "bundle": { 35 | "active": true, 36 | "category": "DeveloperTool", 37 | "copyright": "", 38 | "deb": { 39 | "depends": [] 40 | }, 41 | "externalBin": [], 42 | "icon": [ 43 | "icons/32x32.png", 44 | "icons/128x128.png", 45 | "icons/128x128@2x.png", 46 | "icons/icon.icns", 47 | "icons/icon.ico" 48 | ], 49 | "identifier": "win-win-map.namnium.work", 50 | "longDescription": "", 51 | "macOS": { 52 | "entitlements": null, 53 | "exceptionDomain": "", 54 | "frameworks": [], 55 | "providerShortName": null, 56 | "signingIdentity": null 57 | }, 58 | "resources": [], 59 | "shortDescription": "", 60 | "targets": "all", 61 | "windows": { 62 | "certificateThumbprint": null, 63 | "digestAlgorithm": "sha256", 64 | "timestampUrl": "" 65 | } 66 | }, 67 | "security": { 68 | "csp": null 69 | }, 70 | "updater": { 71 | "active": false 72 | }, 73 | "windows": [ 74 | { 75 | "label": "main", 76 | "fullscreen": false, 77 | "height": 600, 78 | "resizable": true, 79 | "title": "win-win-map", 80 | "width": 800 81 | } 82 | ] 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | } 4 | 5 | :root { 6 | font-family: Inter, Avenir, Helvetica, Arial, sans-serif; 7 | font-size: 16px; 8 | line-height: 24px; 9 | font-weight: 400; 10 | 11 | color: #0f0f0f; 12 | background-color: #f6f6f6; 13 | 14 | font-synthesis: none; 15 | text-rendering: optimizeLegibility; 16 | overflow-x: hidden; 17 | -webkit-font-smoothing: antialiased; 18 | -moz-osx-font-smoothing: grayscale; 19 | -webkit-text-size-adjust: 100%; 20 | } 21 | 22 | .container { 23 | margin: 0; 24 | padding-top: 10vh; 25 | display: flex; 26 | flex-direction: column; 27 | justify-content: center; 28 | text-align: center; 29 | } 30 | 31 | .logo { 32 | height: 6em; 33 | padding: 1.5em; 34 | will-change: filter; 35 | transition: 0.75s; 36 | } 37 | 38 | .logo.tauri:hover { 39 | filter: drop-shadow(0 0 2em #24c8db); 40 | } 41 | 42 | .row { 43 | display: flex; 44 | justify-content: center; 45 | } 46 | 47 | a { 48 | font-weight: 500; 49 | color: #646cff; 50 | text-decoration: inherit; 51 | } 52 | 53 | a:hover { 54 | color: #535bf2; 55 | } 56 | 57 | h1 { 58 | text-align: center; 59 | } 60 | 61 | input, 62 | button { 63 | border-radius: 8px; 64 | border: 1px solid transparent; 65 | padding: 0.6em 1.2em; 66 | font-size: 1em; 67 | font-weight: 500; 68 | font-family: inherit; 69 | color: #0f0f0f; 70 | background-color: #ffffff; 71 | transition: border-color 0.25s; 72 | box-shadow: 0 2px 2px rgba(0, 0, 0, 0.2); 73 | } 74 | 75 | button { 76 | cursor: pointer; 77 | } 78 | 79 | button:hover { 80 | border-color: #396cd8; 81 | } 82 | 83 | input, 84 | button { 85 | outline: none; 86 | } 87 | 88 | #greet-input { 89 | margin-right: 5px; 90 | } 91 | 92 | @media (prefers-color-scheme: dark) { 93 | :root { 94 | color: #f6f6f6; 95 | background-color: #2f2f2f; 96 | } 97 | 98 | a:hover { 99 | color: #24c8db; 100 | } 101 | 102 | input, 103 | button { 104 | color: #ffffff; 105 | background-color: #0f0f0f98; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /public/tauri.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/fetch-objects.ts: -------------------------------------------------------------------------------- 1 | import { invoke } from "@tauri-apps/api/tauri"; 2 | import { WinInfo, Canvas } from "@/winwin-type"; 3 | import { FramesInfo } from "@/hooks/frame-hook"; 4 | 5 | // ハードコードでごめん... 6 | const margin = 8; 7 | 8 | export const calcScale = ( 9 | width: number, 10 | height: number, 11 | appWidth: number, 12 | appHeight: number 13 | ): { scale: number; w_is_larger: boolean } => { 14 | const x_scale = width / (appWidth - margin * 2); 15 | const y_scale = height / (appHeight - margin * 2); 16 | const w_is_larger = width > height; 17 | const scale = width > height ? x_scale : y_scale; 18 | return { scale, w_is_larger }; 19 | }; 20 | 21 | type fetchObjectsRes = [framesInfo: FramesInfo, canvas: Canvas, scale: number]; 22 | 23 | export const fetchObjects = async (): Promise => { 24 | const canvas = await invoke("get_canvas"); 25 | const { scale } = calcScale( 26 | canvas.max_x - canvas.min_x, 27 | canvas.max_y - canvas.min_y, 28 | document.body.clientWidth, 29 | document.body.clientHeight 30 | ); 31 | 32 | const monitors = [ 33 | ...canvas.monitors.map((rect) => { 34 | const real_width = rect.right - rect.left; 35 | const real_height = rect.bottom - rect.top; 36 | const zIndex = -(real_width * real_height); 37 | return { 38 | left: (rect.left - canvas.min_x) / scale, 39 | top: (rect.top - canvas.min_y) / scale, 40 | width: real_width / scale, 41 | height: real_height / scale, 42 | zIndex, 43 | }; 44 | }), 45 | ]; 46 | 47 | const thread_windows_hwnds = await ( 48 | await invoke("get_thread_windows") 49 | ).map((w) => w.hwnd); 50 | // console.log("thread_windows_raw", thread_windows_raw); 51 | 52 | const windows_raw = await invoke("get_windows"); 53 | // console.log("windows_raw", windows_raw); 54 | 55 | const windows = windows_raw.map((w) => { 56 | const zIndex = -(w.width * w.height); 57 | // const zIndex = 0; 58 | return { 59 | left: (w.left - canvas.min_x) / scale, 60 | top: (w.top - canvas.min_y) / scale, 61 | width: w.width / scale, 62 | height: w.height / scale, 63 | is_relative: thread_windows_hwnds.includes(w.hwnd), 64 | original: w, 65 | is_visible: true, 66 | zIndex, 67 | }; 68 | }); 69 | 70 | return [ 71 | { 72 | width: (canvas.max_x - canvas.min_x) / scale, 73 | height: (canvas.max_y - canvas.min_y) / scale, 74 | monitors: monitors, 75 | windows: [...windows], 76 | }, 77 | canvas, 78 | scale, 79 | ]; 80 | }; 81 | 82 | export default fetchObjects; 83 | -------------------------------------------------------------------------------- /src-tauri/src/util.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{anyhow, Result}; 2 | use std::sync::Mutex; 3 | use tokio::sync::mpsc::{channel, Receiver, Sender}; 4 | use windows::Win32::System::Com::{CoInitialize, CoUninitialize}; 5 | 6 | pub(crate) struct Chan 7 | where 8 | Item: std::fmt::Debug + Sync + Send + 'static, 9 | { 10 | sender: Mutex>>, 11 | receiver: Mutex>>, 12 | } 13 | 14 | impl Chan 15 | where 16 | Item: std::fmt::Debug + Sync + Send + 'static, 17 | { 18 | pub fn new() -> Self { 19 | let (sender, receiver) = channel(256); 20 | Chan { 21 | sender: Mutex::new(Some(sender)), 22 | receiver: Mutex::new(Some(receiver)), 23 | } 24 | } 25 | 26 | pub fn take_receiver(&self) -> Result>> { 27 | let Ok(mut receiver) = self.receiver.lock() else { 28 | return Err(anyhow!("receiver lock failed")); 29 | }; 30 | let receiver = receiver.take(); 31 | Ok(receiver) 32 | } 33 | 34 | /* 35 | pub fn drop_sender(&self) -> Result<()> { 36 | let Ok(mut sender) = self.sender.lock() else { 37 | return Err(anyhow!("sender lock failed")); 38 | }; 39 | sender.take(); 40 | Ok(()) 41 | } 42 | */ 43 | 44 | pub fn send(&self, item: Item) -> Result<()> { 45 | let Ok(sender) = self.sender.lock() else { 46 | return Err(anyhow!("sender lock failed")); 47 | }; 48 | let Some(sender) = sender.as_ref() else { 49 | return Err(anyhow!("sender already closed")); 50 | }; 51 | sender.blocking_send(item)?; 52 | Ok(()) 53 | } 54 | 55 | // Dropトレイトで表現したかったけど思いついてません... 56 | pub fn close_and_reset(&self) -> Result<()> { 57 | let Ok(mut sender) = self.sender.lock() else { 58 | return Err(anyhow!("sender lock failed")); 59 | }; 60 | let Ok(mut receiver) = self.receiver.lock() else { 61 | return Err(anyhow!("receiver lock failed")); 62 | }; 63 | let (new_sender, new_receiver) = channel(256); 64 | *sender = Some(new_sender); 65 | *receiver = Some(new_receiver); 66 | Ok(()) 67 | } 68 | } 69 | 70 | pub struct Com; 71 | 72 | impl Com { 73 | pub fn new() -> Result { 74 | unsafe { 75 | println!("CoInitialize"); 76 | CoInitialize(None)?; 77 | } 78 | 79 | Ok(Com) 80 | } 81 | } 82 | 83 | impl Drop for Com { 84 | fn drop(&mut self) { 85 | use std::io::{stdout, Write}; 86 | unsafe { 87 | println!("CoUninitialize"); 88 | stdout().flush().unwrap(); 89 | CoUninitialize(); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src-tauri/src/winapi/structs.rs: -------------------------------------------------------------------------------- 1 | use crate::winapi::monitors::get_canvas; 2 | use anyhow::Result; 3 | use serde::ser::{Serialize, SerializeStruct, Serializer}; 4 | use windows::Win32::Foundation::{HWND, RECT}; 5 | 6 | #[derive(Debug, Clone)] 7 | pub struct WinInfo { 8 | pub hwnd: HWND, 9 | pub title: String, 10 | pub exe_name: Option, 11 | pub left: i32, 12 | pub top: i32, 13 | pub width: i32, 14 | pub height: i32, 15 | } 16 | 17 | impl Serialize for WinInfo { 18 | fn serialize(&self, serializer: S) -> Result 19 | where 20 | S: Serializer, 21 | { 22 | let is_foreground = self.is_foreground(); 23 | let mut state = serializer.serialize_struct("WinInfo", 6)?; 24 | state.serialize_field("hwnd", &self.hwnd.0)?; 25 | state.serialize_field("title", &self.title)?; 26 | if let Some(exe_name) = self.exe_name.as_ref() { 27 | state.serialize_field("exe_name", exe_name)?; 28 | } 29 | state.serialize_field("left", &self.left)?; 30 | state.serialize_field("top", &self.top)?; 31 | state.serialize_field("width", &self.width)?; 32 | state.serialize_field("height", &self.height)?; 33 | state.serialize_field("is_foreground", &is_foreground)?; 34 | state.end() 35 | } 36 | } 37 | 38 | #[derive(Debug)] 39 | pub struct Canvas { 40 | pub min_x: i32, 41 | pub min_y: i32, 42 | pub max_x: i32, 43 | pub max_y: i32, 44 | pub monitors: Vec, 45 | } 46 | 47 | impl Canvas { 48 | pub fn new() -> Result { 49 | get_canvas() 50 | } 51 | 52 | pub fn width(&self) -> i32 { 53 | self.max_x - self.min_x 54 | } 55 | 56 | pub fn height(&self) -> i32 { 57 | self.max_y - self.min_y 58 | } 59 | } 60 | 61 | #[derive(serde::Serialize)] 62 | struct JSRect { 63 | left: i32, 64 | top: i32, 65 | right: i32, 66 | bottom: i32, 67 | } 68 | 69 | impl Serialize for Canvas { 70 | fn serialize(&self, serializer: S) -> Result 71 | where 72 | S: Serializer, 73 | { 74 | let mut state = serializer.serialize_struct("Canvas", 5)?; 75 | state.serialize_field("min_x", &self.min_x)?; 76 | state.serialize_field("min_y", &self.min_y)?; 77 | state.serialize_field("max_x", &self.max_x)?; 78 | state.serialize_field("max_y", &self.max_y)?; 79 | 80 | let monitors = self 81 | .monitors 82 | .iter() 83 | .map(|rect| JSRect { 84 | left: rect.left, 85 | top: rect.top, 86 | right: rect.right, 87 | bottom: rect.bottom, 88 | }) 89 | .collect::>(); 90 | 91 | state.serialize_field("monitors", &monitors)?; 92 | 93 | state.end() 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/components/configComponents/autoSummon/ThresholdConfElm.tsx: -------------------------------------------------------------------------------- 1 | import Grid from "@mui/material/Grid"; 2 | import TextField from "@mui/material/TextField"; 3 | import { Config, ConfigMethods } from "@/hooks/config-hook"; 4 | 5 | export interface AutoSummonThresholdConfElmProps { 6 | config: Config; 7 | configMethods: ConfigMethods; 8 | } 9 | 10 | interface TLRB { 11 | top: number; 12 | left: number; 13 | right: number; 14 | bottom: number; 15 | } 16 | 17 | const confWrap = (config: Config): TLRB => { 18 | return { 19 | top: config.auto_summon_threshold?.top ?? 0, 20 | left: config.auto_summon_threshold?.left ?? 0, 21 | right: config.auto_summon_threshold?.right ?? 0, 22 | bottom: config.auto_summon_threshold?.bottom ?? 0, 23 | }; 24 | }; 25 | 26 | const AutoSummonThresholdConfElm = ({ 27 | config, 28 | configMethods, 29 | }: AutoSummonThresholdConfElmProps) => { 30 | return ( 31 | 32 | 33 | { 38 | configMethods.setAutoSummonThreshold({ 39 | ...confWrap(config), 40 | left: Number(e.target.value), 41 | }); 42 | }} 43 | inputProps={{ 44 | step: 1, 45 | type: "number", 46 | }} 47 | /> 48 | 49 | 50 | { 55 | configMethods.setAutoSummonThreshold({ 56 | ...confWrap(config), 57 | top: Number(e.target.value), 58 | }); 59 | }} 60 | inputProps={{ 61 | step: 1, 62 | type: "number", 63 | }} 64 | /> 65 | 66 | 67 | { 72 | configMethods.setAutoSummonThreshold({ 73 | ...confWrap(config), 74 | right: Number(e.target.value), 75 | }); 76 | }} 77 | inputProps={{ 78 | step: 1, 79 | type: "number", 80 | }} 81 | /> 82 | 83 | 84 | { 89 | configMethods.setAutoSummonThreshold({ 90 | ...confWrap(config), 91 | bottom: Number(e.target.value), 92 | }); 93 | }} 94 | inputProps={{ 95 | step: 1, 96 | type: "number", 97 | }} 98 | /> 99 | 100 | 101 | ); 102 | }; 103 | 104 | export default AutoSummonThresholdConfElm; 105 | -------------------------------------------------------------------------------- /src-tauri/src/winapi/winevents.rs: -------------------------------------------------------------------------------- 1 | use crate::util::Chan; 2 | use anyhow::{anyhow, Result}; 3 | use once_cell::sync::Lazy; 4 | use std::{sync::Mutex, thread::JoinHandle}; 5 | use tokio::sync::mpsc::Receiver; 6 | use windows::Win32::{ 7 | Foundation::HWND, 8 | System::Threading::GetCurrentThreadId, 9 | UI::Accessibility::{SetWinEventHook, UnhookWinEvent, HWINEVENTHOOK}, 10 | UI::WindowsAndMessaging::{ 11 | DispatchMessageW, GetMessageW, PostThreadMessageW, 12 | TranslateMessage, /* EVENT_OBJECT_CREATE, */ 13 | EVENT_OBJECT_DESTROY, EVENT_SYSTEM_FOREGROUND, EVENT_SYSTEM_MOVESIZEEND, MSG, OBJID_WINDOW, 14 | WINEVENT_OUTOFCONTEXT, WINEVENT_SKIPOWNPROCESS, WM_QUIT, 15 | }, 16 | }; 17 | 18 | #[derive(Debug)] 19 | pub enum UpdateEvents { 20 | Active(HWND), 21 | Move(HWND), 22 | // Create(HWND), 23 | Destroy(HWND), 24 | } 25 | 26 | static UPDATEEVENTS_CHAN: Lazy> = Lazy::new(|| Chan::new()); 27 | static WINTHID: Mutex> = Mutex::new(None); // for windows thread id 28 | 29 | unsafe extern "system" fn event_hook_proc( 30 | _h_win_event_hook: HWINEVENTHOOK, 31 | event: u32, 32 | hwnd: HWND, 33 | id_object: i32, 34 | _id_child: i32, 35 | _dw_event_thread: u32, 36 | _dwms_event_time: u32, 37 | ) { 38 | let ue = match event { 39 | EVENT_SYSTEM_FOREGROUND => UpdateEvents::Active(hwnd), 40 | EVENT_SYSTEM_MOVESIZEEND => UpdateEvents::Move(hwnd), 41 | EVENT_OBJECT_DESTROY if id_object == OBJID_WINDOW.0 => UpdateEvents::Destroy(hwnd), 42 | _ => return, 43 | }; 44 | let _ = UPDATEEVENTS_CHAN.send(ue); 45 | } 46 | 47 | pub fn close_chan() -> Result<()> { 48 | let Ok(mut id_l) = WINTHID.lock() else { 49 | return Err(anyhow!("Failed to lock WINTHID")); 50 | }; 51 | let Some(id) = id_l.take() else { 52 | return Err(anyhow!("Failed to get WINTHID")); 53 | }; 54 | unsafe { 55 | PostThreadMessageW(id, WM_QUIT, None, None); 56 | } 57 | UPDATEEVENTS_CHAN.close_and_reset() 58 | } 59 | 60 | pub fn init_event_notifier() -> Result, JoinHandle>)>> { 61 | let Some(recv) = UPDATEEVENTS_CHAN.take_receiver()? else { 62 | return Ok(None); 63 | }; 64 | 65 | let th = std::thread::spawn(|| unsafe { 66 | let Ok(mut id_l) = WINTHID.lock() else { 67 | return Err(anyhow!("Failed to lock WINTHID")); 68 | }; 69 | *id_l = Some(GetCurrentThreadId()); 70 | drop(id_l); 71 | 72 | let e = SetWinEventHook( 73 | EVENT_SYSTEM_FOREGROUND, 74 | EVENT_OBJECT_DESTROY, 75 | None, 76 | Some(event_hook_proc), 77 | 0, 78 | 0, 79 | WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS, 80 | ); 81 | 82 | if e.is_invalid() { 83 | return Err(anyhow!("SetWinEventHook failed")); 84 | } 85 | 86 | let mut msg = MSG::default(); 87 | while GetMessageW(&mut msg, None, 0, 0).as_bool() { 88 | TranslateMessage(&msg); 89 | DispatchMessageW(&msg); 90 | } 91 | let true = UnhookWinEvent(e).as_bool() else { 92 | return Err(anyhow!("UnhookWinEvent failed")); 93 | }; 94 | 95 | Ok(()) 96 | }); 97 | 98 | Ok(Some((recv, th))) 99 | } 100 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # win-win-map 2 | 3 | ![winwinmap demo](.github/winwinmap_demo.gif) 4 | 5 | ※ 見た目はバージョンにより異なる可能性があります。 6 | 7 | [![Release](https://github.com/anotherhollow1125/win-win-map/actions/workflows/build.yaml/badge.svg)](https://github.com/anotherhollow1125/win-win-map/actions/workflows/build.yaml) 8 | 9 | Windows' Windows Map、略してwin-win-mapは、マウスカーソル及びウィンドウを特定座標に移動させるWindows用ユーティリティアプリです! 10 | 11 | - マウスカーソルを設定座標に召喚 12 | - ウィンドウを設定座標に召喚 13 | - マップ上のウィンドウをドラッグすることによる移動 14 | 15 | 等が主な機能になります。 16 | 17 | リリースページ: https://github.com/anotherhollow1125/win-win-map/releases 18 | 19 | ダウンロード: https://github.com/anotherhollow1125/win-win-map/releases/download/v0.0.4/win-win-map_0.0.4_x64_en-US.msi 20 | 21 | ## アプリのモチベーション 22 | 23 | 本アプリを作った製作者は、同じPCにも関わらず離れた位置にモニターがある作業環境で生活していました。 24 | 25 | 作業場所を変えた際にも同じウィンドウで作業を続けるために本アプリを制作いたしました。 26 | 27 | よって制作者にしか需要がなさそうなアプリですが、使い方を記します。 28 | 29 | ## 使い方 30 | 31 | ![winwinmap screenshot 1](.github/winwinmap1.png) 32 | 33 | - ① マップゾーン: ウィンドウの位置関係の把握及びウィンドウのドラッグによる移動が可能です。 34 | - 上に太いタイトルバー状の線がある: ドラッグ可能なウィンドウ 35 | - 薄く色がついたウィンドウ: アクティブウィンドウ 36 | - 上記2つは異なることもあります。 37 | - ② 設定ゾーン: 召喚座標、ショートカットなどの各種設定を行います(後述)。 38 | - ③ リストゾーン: ウィンドウの一覧です。 39 | - 一番左のボタン: ウィンドウ召喚 40 | - 真ん中の目ボタン: ウィンドウ名称によるウィンドウ無視有効化無効化 41 | - 無視されたウィンドウはマップ上には表示されなくなります。 42 | - 一番右の目ボタン: 実行ファイル名によるウィンドウ無視有効化無効化 43 | 44 | 本アプリはメディアクエリを活用することで小さい状態ではマップとリストが非表示になるようになっております。 45 | 46 | ## 設定項目 47 | 48 | ### Config 折りたたみ時項目 49 | 50 | | 項目名 | 説明 | 51 | | :--------------------- | :---------------------------------------------------------------------------------------- | 52 | | CONFIG | 詳細設定を展開します。 | 53 | | マウスアイコンのチップ | 後述するCursor Summon Shortcutの現在値を確認できます。 | 54 | | 四角が連なったチップ | 後述するAuto Summonのクリックによる切り替え及びAS Toggle Shortcutの現在値を確認できます。 | 55 | | Map | クリックでマップの表示/非表示を切り替えられます。 | 56 | | minify/expand | クリックで本アプリを小さくしたり、大きくしたりできます。 | 57 | 58 | ![winwinmap screenshot 2](.github/winwinmap2_configopen.png) 59 | 60 | ### Config 展開時項目 61 | 62 | | 項目名 | 説明 | 63 | | :--------------------- | :----------------------------------------------------------------------------------------------------------- | 64 | | Summon Pos | マウスカーソル及びウィンドウの召喚座標です。 | 65 | | Cursor Summon Shortcut ※1 | マウスカーソル召喚のショートカットを設定します。 | 66 | | Auto Summon | 後述するしきい値の外に存在するウィンドウがアクティブになった際に、自動召喚する機能のオンオフを操作できます。 | 67 | | Left Top Right Bottom | このしきい値の外にあるウィンドウがAuto Summonの対象になります。 | 68 | | Width Height | 召喚されるウィンドウのサイズを指定します。 | 69 | | AS Toggle Shortcut ※1 | Auto Summonのオンオフを切り替えるショートカットを設定します。 | 70 | | WinWinMap Summon Shortcut ※1 | 本アプリを召喚するためのショートカットです。(Auto Summonの対象ではないためこの設定があります。) | 71 | | Drag Term Margin | ※2 | 72 | 73 | ※1: 各種ショートカットの設定については、テキストフィールドにフォーカスした状態で設定したいショートカットキーをそのまま押下することで設定します。 74 | 75 | ※2: ウィンドウを移動させる際、座標がモニターのギリギリの場所であると表示がおかしくなってしまう事象が確認されているため、モニターの端から一定のマージンを取った場所にしかウィンドウを移動できない作りになっています。そのための設定項目です。 76 | 77 | 本アプリの挙動及び設定項目は制作者がほしいと考えたものが中心のため、もっと良い案や改善するべき点があればissueをいただけると幸いです。 -------------------------------------------------------------------------------- /src/hooks/frame-hook.ts: -------------------------------------------------------------------------------- 1 | import { useState, useRef } from "react"; 2 | import fetchObjects from "@/fetch-objects"; 3 | import { WinInfo } from "@/winwin-type"; 4 | 5 | import { Canvas } from "@/winwin-type"; 6 | import { useDispatch } from "react-redux"; 7 | import { DragStateEnum, setHandle } from "@/app-store"; 8 | 9 | export interface MonitorAttr { 10 | left: number; 11 | top: number; 12 | width: number; 13 | height: number; 14 | zIndex: number; 15 | } 16 | 17 | export interface WindowAttr { 18 | left: number; 19 | top: number; 20 | width: number; 21 | height: number; 22 | is_relative: boolean; 23 | original: WinInfo; // YAGNI原則的には良くないけど表示用にオリジナルの情報をすべて残したいというモチベーション 24 | is_visible: boolean; 25 | zIndex: number; 26 | } 27 | 28 | export interface FramesInfo { 29 | width: number; 30 | height: number; 31 | monitors: MonitorAttr[]; 32 | windows: WindowAttr[]; 33 | } 34 | 35 | export interface CanvasInfo { 36 | canvas: Canvas | undefined; 37 | scale: number | undefined; 38 | area: number | undefined; 39 | } 40 | 41 | type useFrameRes = [ 42 | frames: FramesInfo | undefined, 43 | updateFrames: () => Promise, 44 | targetForceRefresh: ( 45 | targettingHwnd: number | undefined, 46 | windows: WindowAttr[] 47 | ) => void, 48 | target: WindowAttr | undefined, 49 | setTarget: (target: WindowAttr) => void, 50 | canvasInfo: CanvasInfo 51 | ]; 52 | 53 | const useFrame = (): useFrameRes => { 54 | const [frames, setFrames] = useState(undefined); 55 | const [target, setTargetInner] = useState(undefined); 56 | const [canvas, setCanvas] = useState(undefined); 57 | const [scale, setScale] = useState(undefined); 58 | const dispatch = useDispatch(); 59 | const dragState = useRef("idling"); 60 | const dragStateChangeHandle = (st: DragStateEnum) => { 61 | dragState.current = st; 62 | }; 63 | dispatch(setHandle(dragStateChangeHandle)); 64 | 65 | const setTarget = (w: WindowAttr | undefined) => { 66 | if (w === undefined) { 67 | setTargetInner(undefined); 68 | return; 69 | } 70 | 71 | if (w.is_relative) { 72 | setTargetInner(undefined); 73 | return; 74 | } 75 | 76 | setTargetInner({ ...w }); 77 | }; 78 | 79 | const updateFrames = async () => { 80 | if (dragState.current !== "idling") { 81 | console.log("cannot update"); 82 | return; 83 | } 84 | 85 | const [frames, cnvs, scl] = await fetchObjects(); 86 | 87 | const newTarget = frames.windows.find((w) => w.original.is_foreground); 88 | const preTarget = frames.windows.find( 89 | (w) => target !== undefined && w.original.hwnd == target.original.hwnd 90 | ); 91 | setTarget(newTarget); 92 | 93 | setFrames(frames); 94 | setCanvas(cnvs); 95 | setScale(scl); 96 | }; 97 | 98 | const targetForceRefresh = async ( 99 | targettingHwnd: number | undefined, 100 | windows: WindowAttr[] 101 | ) => { 102 | const [frames, cnvs, scl] = await fetchObjects(); 103 | 104 | const newTarget = frames.windows.find( 105 | (w) => targettingHwnd !== undefined && w.original.hwnd == targettingHwnd 106 | ); 107 | setTarget(newTarget); 108 | 109 | setFrames(frames); 110 | setCanvas(cnvs); 111 | setScale(scl); 112 | }; 113 | 114 | const area = canvas 115 | ? (canvas.max_x - canvas.min_x) * (canvas.max_y - canvas.min_y) 116 | : undefined; 117 | 118 | return [ 119 | frames, 120 | updateFrames, 121 | targetForceRefresh, 122 | target, 123 | setTarget, 124 | { canvas, scale, area }, 125 | ]; 126 | }; 127 | 128 | export default useFrame; 129 | -------------------------------------------------------------------------------- /src-tauri/src/main.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr( 2 | all(not(debug_assertions), target_os = "windows"), 3 | windows_subsystem = "windows" 4 | )] 5 | 6 | use anyhow::Result; 7 | use tauri::WindowEvent; 8 | use win_win_map::backend; 9 | use win_win_map::log::env_logger_init; 10 | use win_win_map::winapi::{mouse, winevents, Canvas, WinInfo}; 11 | 12 | // Learn more about Tauri commands at https://tauri.app/v1/guides/features/command 13 | 14 | #[tauri::command] 15 | fn get_canvas() -> Result { 16 | Canvas::new().map_err(|e| e.to_string()) 17 | } 18 | 19 | #[tauri::command] 20 | fn get_windows() -> Result, String> { 21 | WinInfo::get_windows_info_filtered().map_err(|e| e.to_string()) 22 | } 23 | 24 | #[tauri::command] 25 | fn get_thread_windows() -> Result, String> { 26 | WinInfo::get_thread_windows_info().map_err(|e| e.to_string()) 27 | } 28 | 29 | #[tauri::command] 30 | fn get_window(hwnd: u64) -> Result { 31 | let win_info = WinInfo::from_hwnd(hwnd as _) 32 | .map_err(|e| e.to_string())? 33 | .ok_or_else(|| "Failed to get WinInfo from hwnd".to_owned())?; 34 | Ok(win_info) 35 | } 36 | 37 | #[tauri::command] 38 | fn set_cursor_pos(x: i32, y: i32) -> Result<(), String> { 39 | mouse::set_cursor_pos(x, y).map_err(|e| e.to_string()) 40 | } 41 | 42 | #[tauri::command] 43 | fn set_window_pos_and_size( 44 | hwnd: u64, 45 | x: i32, 46 | y: i32, 47 | width: i32, 48 | height: i32, 49 | ) -> Result<(), String> { 50 | let win_info = WinInfo::from_hwnd(hwnd as _) 51 | .map_err(|e| e.to_string())? 52 | .ok_or_else(|| "Failed to get WinInfo from hwnd".to_owned())?; 53 | win_info 54 | .move_and_resize(x, y, width, height) 55 | .map_err(|e| e.to_string()) 56 | } 57 | 58 | #[tauri::command] 59 | fn set_foreground(hwnd: u64) -> Result<(), String> { 60 | let win_info = WinInfo::from_hwnd(hwnd as _) 61 | .map_err(|e| e.to_string())? 62 | .ok_or_else(|| "Failed to get WinInfo from hwnd".to_owned())?; 63 | win_info.set_foreground().map_err(|e| e.to_string()) 64 | } 65 | 66 | #[tauri::command] 67 | fn set_thread_window_pos_and_size(x: i32, y: i32, width: i32, height: i32) -> Result<(), String> { 68 | let win_info = WinInfo::get_thread_windows_info() 69 | .map_err(|e| e.to_string())? 70 | .into_iter() 71 | .next() 72 | .ok_or_else(|| "Failed to get WinInfo from hwnd".to_owned())?; 73 | 74 | win_info 75 | .move_and_resize(x, y, width, height) 76 | .map_err(|e| e.to_string()) 77 | } 78 | 79 | #[tokio::main] 80 | async fn main() -> Result<()> { 81 | dotenv::dotenv().ok(); 82 | tauri::async_runtime::set(tokio::runtime::Handle::current()); 83 | env_logger_init(); 84 | 85 | let (rx, th) = backend::prepare()?; 86 | 87 | tauri::Builder::default() 88 | .invoke_handler(tauri::generate_handler![ 89 | get_canvas, 90 | get_windows, 91 | get_thread_windows, 92 | get_window, 93 | set_cursor_pos, 94 | set_window_pos_and_size, 95 | set_foreground, 96 | set_thread_window_pos_and_size, 97 | ]) 98 | .setup(|app| { 99 | let _notification_thread = backend::tauri_setup(app, rx); 100 | 101 | Ok(()) 102 | }) 103 | .on_window_event(|event| match event.event() { 104 | WindowEvent::CloseRequested { .. } => { 105 | let close_chan_res = winevents::close_chan(); 106 | let drop_vdmth_res = WinInfo::drop_vdmth(); 107 | println!( 108 | "CloseRequested, exiting...: {:?} {:?}", 109 | close_chan_res, drop_vdmth_res 110 | ); 111 | } 112 | _ => {} 113 | }) 114 | .run(tauri::generate_context!())?; 115 | 116 | match th.join() { 117 | Ok(Ok(())) => (), 118 | Ok(Err(e)) => log::error!("join th: {}", e), 119 | Err(e) => log::error!("join th: {:?}", e), 120 | } 121 | 122 | Ok(()) 123 | } 124 | -------------------------------------------------------------------------------- /src/components/listComponents/FrameRowComponent.tsx: -------------------------------------------------------------------------------- 1 | import { WinInfo } from "@/winwin-type"; 2 | import { Grid } from "@mui/material"; 3 | import { ManualSummonWindow } from "@/hooks/app-hook"; 4 | import IconButton from "@mui/material/IconButton"; 5 | import MoveUpIcon from "@mui/icons-material/MoveUp"; 6 | import { Config, ConfigMethods } from "@/hooks/config-hook"; 7 | import { WindowAttr } from "@/hooks/frame-hook"; 8 | import { invoke } from "@tauri-apps/api"; 9 | import Tooltip from "@mui/material/Tooltip"; 10 | import Box from "@mui/material/Box"; 11 | import VisibilityIcon from "@mui/icons-material/Visibility"; 12 | import VisibilityOffIcon from "@mui/icons-material/VisibilityOff"; 13 | 14 | interface FrameRowComponentProps { 15 | window: WindowAttr; 16 | config: Config; 17 | configMethods: ConfigMethods; 18 | target: number | undefined; 19 | setTarget: (w: WindowAttr) => void; 20 | accessable_windows: WinInfo[]; 21 | } 22 | 23 | const FrameRowComponent = ({ 24 | window, 25 | config, 26 | configMethods, 27 | target, 28 | setTarget, 29 | accessable_windows, 30 | }: FrameRowComponentProps) => { 31 | const full_exe_name = window.original.exe_name ?? "-"; 32 | const exe_name = full_exe_name.replace(/^.*?[\/\\]?([^\/\\]*?)$/, "$1"); 33 | 34 | const title_eye = !config.nameFilter.has(window.original.title) ? ( 35 | { 37 | configMethods.addFilteredName(window.original.title); 38 | }} 39 | > 40 | 41 | 42 | ) : ( 43 | { 45 | configMethods.removeFilteredName(window.original.title); 46 | }} 47 | > 48 | 49 | 50 | ); 51 | 52 | const exe_name_eye = !config.exeNameFilter.has(full_exe_name) ? ( 53 | { 55 | configMethods.addFilteredExeName(full_exe_name); 56 | }} 57 | > 58 | 59 | 60 | ) : ( 61 | { 63 | configMethods.removeFilteredExeName(full_exe_name); 64 | }} 65 | > 66 | 67 | 68 | ); 69 | 70 | const enabledItem = ( 71 | 72 | 73 | { 75 | ManualSummonWindow( 76 | window.original.hwnd, 77 | config, 78 | accessable_windows 79 | ); 80 | }} 81 | > 82 | 83 | 84 | 85 | { 89 | if (window.original.hwnd === target) { 90 | return; 91 | } 92 | setTarget(window); 93 | }} 94 | onDoubleClick={() => { 95 | if (target === undefined) { 96 | return; 97 | } 98 | invoke("set_foreground", { 99 | hwnd: target, 100 | }); 101 | }} 102 | > 103 | {window.original.title} {` (${window.original.hwnd})`} 104 | 105 | 106 | {title_eye} 107 | 108 | 109 | 110 | {exe_name} 111 | 112 | 113 | 114 | {exe_name_eye} 115 | 116 | 117 | ); 118 | 119 | const disabledItem = ( 120 | 121 | 122 | 123 | {window.original.title} {` (${window.original.hwnd})`} 124 | 125 | 126 | {title_eye} 127 | 128 | 129 | 130 | {exe_name} 131 | 132 | 133 | 134 | {exe_name_eye} 135 | 136 | 137 | ); 138 | 139 | return window.is_visible ? enabledItem : disabledItem; 140 | }; 141 | 142 | export default FrameRowComponent; 143 | -------------------------------------------------------------------------------- /src/components/frameComponents/IntegratedFrame.tsx: -------------------------------------------------------------------------------- 1 | import WindowFrame from "@/components/frameComponents/WindowFrame"; 2 | import MonitorFrame from "@/components/frameComponents/MonitorFrame"; 3 | import { FramesInfo, CanvasInfo } from "@/hooks/frame-hook"; 4 | import Draggable from "react-draggable"; 5 | import { useRef, useState, useEffect } from "react"; 6 | import { tryDragMove } from "@/drag-util"; 7 | import { Config } from "@/hooks/config-hook"; 8 | import { WindowAttr } from "@/hooks/frame-hook"; 9 | import { useSelector, useDispatch } from "react-redux"; 10 | import { setDragging, setIdling, DragState } from "@/app-store"; 11 | 12 | export interface IntegratedFrameProps { 13 | framesInfo: FramesInfo; 14 | forceUpdate: () => void; 15 | target: WindowAttr | undefined; 16 | canvasInfo: CanvasInfo; 17 | config: Config; 18 | setTarget: (w: WindowAttr) => void; 19 | } 20 | 21 | const FrameManager = ({ 22 | framesInfo: { width, height, monitors, windows }, 23 | forceUpdate, 24 | target, 25 | canvasInfo, 26 | config, 27 | setTarget, 28 | }: IntegratedFrameProps) => { 29 | const targetNodeRef = useRef(null); 30 | const [targetPos, setTargetPos] = useState({ x: 0, y: 0 }); 31 | const dragState = useSelector((state: DragState) => state.dragState.state); 32 | const dispatch = useDispatch(); 33 | 34 | useEffect(() => { 35 | if (target) { 36 | const newPos = { x: target.left, y: target.top }; 37 | setTargetPos(newPos); 38 | } 39 | }, [target]); 40 | 41 | const targetFrame = 42 | target && target.is_visible ? ( 43 | { 49 | // dragState.current = "dragging"; 50 | dispatch(setDragging()); 51 | }} 52 | onStop={(_e, data) => { 53 | const pos = { x: data.x, y: data.y }; 54 | tryDragMove(target.original, pos, canvasInfo, config).then((res) => { 55 | // 動機ズレ防止 56 | setTimeout(() => { 57 | forceUpdate(); 58 | // dragState.current = "idling"; 59 | dispatch(setIdling()); 60 | }, 100); 61 | }); 62 | }} 63 | onDrag={(_e, data) => { 64 | const { x, y } = data; 65 | setTargetPos({ x, y }); 66 | const pos = { x: data.x, y: data.y }; 67 | tryDragMove(target.original, pos, canvasInfo, config); 68 | }} 69 | > 70 |
71 | {}} 78 | /> 79 |
80 |
81 | ) : ( 82 | <> 83 | ); 84 | 85 | return ( 86 | <> 87 |
forceUpdate()} 96 | > 97 | <> 98 | {monitors.map((monitor, index) => { 99 | return ; 100 | })} 101 | {windows.flatMap((window, index) => { 102 | const is_target = 103 | target?.original.hwnd === window.original.hwnd ?? false; 104 | return window.is_visible && !is_target 105 | ? [ 106 | { 112 | e.stopPropagation(); 113 | if (window.original.hwnd === target?.original.hwnd) { 114 | return; 115 | } 116 | setTarget(window); 117 | }} 118 | />, 119 | ] 120 | : []; 121 | })} 122 | 123 | {targetFrame} 124 |
125 | 126 | ); 127 | }; 128 | 129 | export default FrameManager; 130 | -------------------------------------------------------------------------------- /src/components/configComponents/ConfigElm.tsx: -------------------------------------------------------------------------------- 1 | import Grid from "@mui/material/Grid"; 2 | import { Config, ConfigMethods } from "@/hooks/config-hook"; 3 | import CursorConfigElm from "@/components/configComponents/CursorConfigElm"; 4 | import SummonPosConfElm from "@/components/configComponents/SummonPosConfElm"; 5 | import AutoSummonConfElm from "@/components/configComponents/autoSummon/AutoSummonConfElm"; 6 | import WinWinMapSummonConfElm from "@/components/configComponents/WinWinMapSummonConfElm"; 7 | import DragTermMarginConfElm from "@/components/configComponents/DragTermMarginConfElm"; 8 | import SettingsIcon from "@mui/icons-material/Settings"; 9 | import ArrowDropUpIcon from "@mui/icons-material/ArrowDropUp"; 10 | import ArrowDropDownIcon from "@mui/icons-material/ArrowDropDown"; 11 | import MouseIcon from "@mui/icons-material/Mouse"; 12 | import { useState } from "react"; 13 | import { Box } from "@mui/system"; 14 | import Chip from "@mui/material/Chip"; 15 | import Button from "@mui/material/Button"; 16 | import AutoAwesomeMotionIcon from "@mui/icons-material/AutoAwesomeMotion"; 17 | import { useMediaQuery } from "@mui/material"; 18 | import CloseFullscreenIcon from "@mui/icons-material/CloseFullscreen"; 19 | import OpenInFullIcon from "@mui/icons-material/OpenInFull"; 20 | import MapIcon from "@mui/icons-material/Map"; 21 | import { appWindow, LogicalSize } from "@tauri-apps/api/window"; 22 | import { getVersion } from "@tauri-apps/api/app"; 23 | import { useEffect } from "react"; 24 | 25 | interface ConfigElmProps { 26 | config: Config; 27 | configMethods: ConfigMethods; 28 | } 29 | 30 | const ConfigElm = ({ config, configMethods }: ConfigElmProps) => { 31 | const isMobile = useMediaQuery("(max-width: 600px)"); 32 | const [version, setVersion] = useState(""); 33 | 34 | useEffect(() => { 35 | getVersion().then((v) => { 36 | setVersion(v); 37 | }); 38 | }, []); 39 | 40 | const configButtonElm = ( 41 | 52 | ); 53 | 54 | const mapChipElm = ( 55 | } 57 | label={"Map"} 58 | variant={config.showMap ? "filled" : "outlined"} 59 | onClick={() => { 60 | configMethods.toggleShowMap(); 61 | }} 62 | sx={{ 63 | marginRight: 1, 64 | }} 65 | /> 66 | ); 67 | 68 | const sizeChangeChipElm = isMobile ? ( 69 | } 71 | label={"expand"} 72 | onClick={() => { 73 | appWindow.setSize(new LogicalSize(800, 600)); 74 | }} 75 | /> 76 | ) : ( 77 | } 79 | label={"minify"} 80 | onClick={() => { 81 | appWindow.setSize(new LogicalSize(600, 100)); 82 | }} 83 | /> 84 | ); 85 | 86 | const closedElm = ( 87 | 95 | {configButtonElm} 96 | } 98 | label={config.summon_mouse_cursor_shortcut} 99 | sx={{ 100 | marginRight: 1, 101 | }} 102 | /> 103 | } 105 | label={config.auto_summon_shortcut} 106 | variant={config.auto_summon_enabled ? "filled" : "outlined"} 107 | onClick={() => { 108 | configMethods.setAutoSummonEnabled(!config.auto_summon_enabled); 109 | }} 110 | sx={{ 111 | marginRight: 1, 112 | }} 113 | /> 114 | {mapChipElm} 115 | {sizeChangeChipElm} 116 | 117 | ); 118 | 119 | const openedElm = ( 120 | 129 | 130 | 136 | {configButtonElm} 137 | {mapChipElm} 138 | {sizeChangeChipElm} 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | {`Ver. ${version}`} 158 | 159 | 160 | ); 161 | 162 | return config.opened ? openedElm : closedElm; 163 | }; 164 | 165 | export default ConfigElm; 166 | -------------------------------------------------------------------------------- /src/hooks/app-hook.ts: -------------------------------------------------------------------------------- 1 | import { useState, useEffect, useRef } from "react"; 2 | import { Event, listen } from "@tauri-apps/api/event"; 3 | import useConfig from "@/hooks/config-hook"; 4 | import { Config, ConfigMethods } from "@/hooks/config-hook"; 5 | import { invoke } from "@tauri-apps/api/tauri"; 6 | import useFrames from "@/hooks/frame-hook"; 7 | import { FramesInfo, CanvasInfo } from "@/hooks/frame-hook"; 8 | import { WindowAttr } from "@/hooks/frame-hook"; 9 | import { WinInfo } from "@/winwin-type"; 10 | import { DragStateEnum } from "@/app-store"; 11 | 12 | type useAppStateRes = [ 13 | frames: FramesInfo | undefined, 14 | config: Config | undefined, 15 | configMethods: ConfigMethods, 16 | updateFrames: () => Promise, 17 | targetForceRefresh: ( 18 | targettingHwnd: number | undefined, 19 | windows: WindowAttr[] 20 | ) => void, 21 | target: WindowAttr | undefined, 22 | setTarget: (w: WindowAttr) => void, 23 | canvasInfo: CanvasInfo 24 | ]; 25 | 26 | interface Payload { 27 | kind: "active" | "move" | "destroy"; 28 | hwnd: number; 29 | } 30 | 31 | const isAllowedAccess = (hwnd: number, accessable_windows: WinInfo[]) => { 32 | return accessable_windows.map((w) => w.hwnd).includes(hwnd); 33 | }; 34 | 35 | const summonWindow = async ( 36 | hwnd: number, 37 | accessable_windows: WinInfo[], 38 | pos: { x: number; y: number }, 39 | wh: { width: number; height: number } 40 | ) => { 41 | if (!isAllowedAccess(hwnd, accessable_windows)) { 42 | return; 43 | } 44 | 45 | setTimeout(async () => { 46 | await invoke("set_window_pos_and_size", { 47 | hwnd: hwnd, 48 | x: pos.x, 49 | y: pos.y, 50 | width: wh.width, 51 | height: wh.height, 52 | }); 53 | }, 100); 54 | }; 55 | 56 | export const ForceSummonWindow = async ( 57 | hwnd: number, 58 | pos: { x: number; y: number }, 59 | wh: { width: number; height: number } 60 | ) => { 61 | setTimeout(async () => { 62 | await invoke("set_window_pos_and_size", { 63 | hwnd: hwnd, 64 | x: pos.x, 65 | y: pos.y, 66 | width: wh.width, 67 | height: wh.height, 68 | }); 69 | }, 100); 70 | }; 71 | 72 | const inThreshold = (cnfg: Config, winInfo: WinInfo): boolean => { 73 | if (cnfg.auto_summon_threshold === undefined) { 74 | return false; 75 | } 76 | 77 | return ( 78 | cnfg.auto_summon_threshold.left <= winInfo.left && 79 | cnfg.auto_summon_threshold.top <= winInfo.top && 80 | cnfg.auto_summon_threshold.right >= winInfo.left && 81 | cnfg.auto_summon_threshold.bottom >= winInfo.top 82 | ); 83 | }; 84 | 85 | const summonSize = (cnfg: Config) => { 86 | if (cnfg.auto_summon_size === undefined) { 87 | return { 88 | width: 100, 89 | height: 100, 90 | }; 91 | } 92 | 93 | return { 94 | width: cnfg.auto_summon_size.width > 0 ? cnfg.auto_summon_size.width : 100, 95 | height: 96 | cnfg.auto_summon_size.height > 0 ? cnfg.auto_summon_size.height : 100, 97 | }; 98 | }; 99 | 100 | const tryAutoSummon = async ( 101 | e: Event, 102 | cnfg: Config, 103 | accessable_windows: WinInfo[] 104 | ) => { 105 | let winInfo; 106 | try { 107 | winInfo = await invoke("get_window", { 108 | hwnd: e.payload.hwnd, 109 | }); 110 | } catch { 111 | return; 112 | } 113 | 114 | if (!isAllowedAccess(winInfo.hwnd, accessable_windows)) { 115 | return; 116 | } 117 | 118 | if ( 119 | cnfg === undefined || 120 | cnfg.summon_point === undefined || 121 | cnfg.auto_summon_threshold === undefined || 122 | cnfg.auto_summon_size === undefined 123 | ) { 124 | return; 125 | } 126 | 127 | const autoSummonCond = 128 | cnfg !== undefined && 129 | cnfg.auto_summon_enabled && 130 | e.payload.kind === "active" && 131 | cnfg.summon_point !== undefined && 132 | cnfg.auto_summon_threshold !== undefined && 133 | !inThreshold(cnfg, winInfo); 134 | 135 | if (!autoSummonCond) { 136 | return; 137 | } 138 | 139 | await summonWindow( 140 | e.payload.hwnd, 141 | accessable_windows, 142 | cnfg.summon_point, 143 | summonSize(cnfg) 144 | ); 145 | }; 146 | 147 | export const ManualSummonWindow = ( 148 | hwnd: number, 149 | cnfg: Config, 150 | accessable_windows: WinInfo[] 151 | ) => { 152 | if ( 153 | cnfg === undefined || 154 | cnfg.summon_point === undefined || 155 | cnfg.auto_summon_threshold === undefined || 156 | cnfg.auto_summon_size === undefined 157 | ) { 158 | return; 159 | } 160 | 161 | summonWindow(hwnd, accessable_windows, cnfg.summon_point, summonSize(cnfg)); 162 | 163 | setTimeout(async () => { 164 | await invoke("set_foreground", { 165 | hwnd, 166 | }); 167 | }, 100); 168 | }; 169 | 170 | const useAppState = (): useAppStateRes => { 171 | const [ 172 | frames, 173 | updateFrames, 174 | targetForceRefresh, 175 | target, 176 | setTarget, 177 | canvasInfo, 178 | ] = useFrames(); 179 | const [appWH, setAppWH] = useState([undefined]); // resize イベント用のダミー 180 | const [config, configMethods] = useConfig(); 181 | const unlisten = useRef<(() => void) | undefined>(undefined); 182 | 183 | const setListenFunc = async (cnfg: Config) => { 184 | /* 185 | if (unlisten.current !== undefined) { 186 | unlisten.current?.(); 187 | } 188 | */ 189 | const accessable_windows = 190 | frames?.windows 191 | .flatMap((w) => (w.is_visible ? [w] : [])) 192 | .map((w) => w.original) ?? []; 193 | 194 | const unlstn = await listen("update", async (e) => { 195 | await tryAutoSummon(e, cnfg, accessable_windows); 196 | await updateFrames(); 197 | }); 198 | // init時二重呼び出しに対処するため、await後に解除 199 | if (unlisten.current !== undefined) { 200 | unlisten.current?.(); 201 | } 202 | unlisten.current = unlstn; 203 | }; 204 | 205 | useEffect(() => { 206 | (async () => { 207 | await updateFrames(); 208 | if (config !== undefined) { 209 | await setListenFunc(config); 210 | } 211 | })(); 212 | window.addEventListener("resize", () => { 213 | setAppWH([undefined]); 214 | }); 215 | }, []); 216 | 217 | useEffect(() => { 218 | updateFrames(); 219 | }, [appWH]); 220 | 221 | useEffect(() => { 222 | (async () => { 223 | if (config !== undefined) { 224 | // console.log("setListenFunc"); 225 | await setListenFunc(config); 226 | } 227 | })(); 228 | }, [config]); 229 | 230 | frames?.windows.forEach((w) => { 231 | const name_incld = config?.nameFilter.has(w.original.title) ?? false; 232 | const exe_name_incld = 233 | config?.exeNameFilter.has(w.original.exe_name ?? "-") ?? false; 234 | w.is_visible = !(name_incld || exe_name_incld); 235 | }); 236 | 237 | if (target) { 238 | const name_incld = config?.nameFilter.has(target.original.title) ?? false; 239 | const exe_name_incld = 240 | config?.exeNameFilter.has(target.original.exe_name ?? "-") ?? false; 241 | target.is_visible = !(name_incld || exe_name_incld); 242 | } 243 | 244 | return [ 245 | frames, 246 | config, 247 | configMethods, 248 | updateFrames, 249 | targetForceRefresh, 250 | target, 251 | setTarget, 252 | canvasInfo, 253 | ]; 254 | }; 255 | 256 | export default useAppState; 257 | -------------------------------------------------------------------------------- /src-tauri/src/winapi/winwins.rs: -------------------------------------------------------------------------------- 1 | use crate::winapi::structs::WinInfo; 2 | use crate::winapi::virtual_desktop::VirtualDesktopManager as VDM; 3 | use crate::winapi::winprocess::get_exe_name; 4 | use anyhow::{anyhow, Result}; 5 | use crossbeam::channel::{Receiver, Sender}; 6 | use once_cell::sync::Lazy; 7 | use std::fmt; 8 | use std::sync::Mutex; 9 | use std::thread::JoinHandle as stdJoinHandle; 10 | use windows::{ 11 | core::HSTRING, 12 | Win32::{ 13 | Foundation::{GetLastError, BOOL, HWND, LPARAM}, 14 | System::Threading::GetCurrentThreadId, 15 | UI::WindowsAndMessaging::{ 16 | EnumThreadWindows, EnumWindows, GetForegroundWindow, GetWindowInfo, GetWindowTextW, 17 | MoveWindow, 18 | SetForegroundWindow, /*SetWindowPos, HWND_TOPMOST, SWP_NOMOVE, SWP_NOSIZE, */ 19 | WS_MINIMIZE, WS_VISIBLE, 20 | }, 21 | }, 22 | }; 23 | 24 | impl fmt::Display for WinInfo { 25 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 26 | let arrow = if self.is_foreground() { ">" } else { " " }; 27 | write!(f, "{} {:?}", arrow, self,) 28 | } 29 | } 30 | 31 | fn judge_active_window(hwnd: HWND) -> Result> { 32 | let window_info = unsafe { 33 | let mut window_info = std::mem::zeroed(); 34 | let true = GetWindowInfo(hwnd, &mut window_info).as_bool() else { 35 | return Err(anyhow!("Failed to GetWindowInfo: {:?}", GetLastError())); 36 | }; 37 | window_info 38 | }; 39 | 40 | if window_info.dwStyle & WS_VISIBLE.0 == 0 { 41 | return Ok(None); 42 | } 43 | if window_info.dwStyle & WS_MINIMIZE.0 != 0 { 44 | return Ok(None); 45 | } 46 | 47 | let width = window_info.rcWindow.right - window_info.rcWindow.left; 48 | let height = window_info.rcWindow.bottom - window_info.rcWindow.top; 49 | if width == 0 || height == 0 { 50 | return Ok(None); 51 | } 52 | 53 | let mut window_title = vec![0; 2048]; 54 | let l = unsafe { GetWindowTextW(hwnd, &mut window_title) } as usize; 55 | let Ok(window_title) = HSTRING::from_wide(&window_title[..l]) else { 56 | return Ok(None); 57 | }; 58 | let title = window_title.to_string_lossy(); 59 | 60 | if title.len() == 0 { 61 | return Ok(None); 62 | } 63 | 64 | let exe_name = match get_exe_name(hwnd) { 65 | Ok(exe_name) => Some(exe_name), 66 | Err(e) => { 67 | eprintln!("Warn: failed to get_exe_name: {}", e); 68 | None 69 | } 70 | }; 71 | 72 | Ok(Some(WinInfo { 73 | hwnd, 74 | title, 75 | exe_name, 76 | left: window_info.rcWindow.left, 77 | top: window_info.rcWindow.top, 78 | width, 79 | height, 80 | })) 81 | } 82 | 83 | unsafe extern "system" fn enum_windows_proc(hwnd: HWND, lparam: LPARAM) -> BOOL { 84 | let wininfo_vec = &mut *(lparam.0 as *mut Vec); 85 | 86 | let window_info = match judge_active_window(hwnd) { 87 | Ok(Some(window_info)) => window_info, 88 | Err(e) => { 89 | eprintln!("Warn: failed to judge_active_window: {}", e); 90 | return BOOL(1); 91 | } 92 | _ => return BOOL(1), 93 | }; 94 | 95 | wininfo_vec.push(window_info); 96 | 97 | BOOL(1) 98 | } 99 | 100 | static VDMTH: Lazy< 101 | Mutex< 102 | Option<( 103 | stdJoinHandle<()>, 104 | Sender>, 105 | Receiver>, 106 | )>, 107 | >, 108 | > = Lazy::new(|| Mutex::new(Some(VDM::filter_thread()))); 109 | 110 | impl WinInfo { 111 | pub fn from_hwnd(hwnd: isize) -> Result> { 112 | judge_active_window(HWND(hwnd)) 113 | } 114 | 115 | pub fn move_to(&self, x: i32, y: i32) -> Result<()> { 116 | unsafe { 117 | let true = MoveWindow(self.hwnd, x, y, self.width, self.height, true).as_bool() else { 118 | return Err(anyhow!("Failed to MoveWindow: {:?}", GetLastError())); 119 | }; 120 | } 121 | Ok(()) 122 | } 123 | 124 | pub fn resize(&self, width: i32, height: i32) -> Result<()> { 125 | unsafe { 126 | let true = MoveWindow(self.hwnd, self.left, self.top, width, height, true).as_bool() else { 127 | return Err(anyhow!("Failed to MoveWindow: {:?}", GetLastError())); 128 | }; 129 | } 130 | Ok(()) 131 | } 132 | 133 | pub fn move_and_resize(&self, x: i32, y: i32, width: i32, height: i32) -> Result<()> { 134 | unsafe { 135 | let true = MoveWindow(self.hwnd, x, y, width, height, true).as_bool() else { 136 | return Err(anyhow!("Failed to MoveWindow: {:?}", GetLastError())); 137 | }; 138 | } 139 | Ok(()) 140 | } 141 | 142 | // この実装だと、アクティブにするのではなく、常に最前面に表示されてしまう 143 | /* 144 | pub fn set_foreground(&self) -> Result<()> { 145 | unsafe { 146 | let res = SetWindowPos(self.hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); 147 | if !res.as_bool() { 148 | return Err(anyhow!("Failed to SetWindowPos: {:?}", GetLastError())); 149 | } 150 | } 151 | Ok(()) 152 | } 153 | */ 154 | 155 | pub fn set_foreground(&self) -> Result<()> { 156 | unsafe { 157 | let res = SetForegroundWindow(self.hwnd); 158 | if !res.as_bool() { 159 | return Err(anyhow!( 160 | "Failed to SetForegroundWindow: {:?}", 161 | GetLastError() 162 | )); 163 | } 164 | } 165 | Ok(()) 166 | } 167 | 168 | pub fn is_foreground(&self) -> bool { 169 | let foreground_hwnd = unsafe { GetForegroundWindow() }; 170 | self.hwnd == foreground_hwnd 171 | } 172 | 173 | pub fn get_windows_info() -> Result> { 174 | let mut wininfo_vec = Vec::new(); 175 | let res = unsafe { 176 | EnumWindows( 177 | Some(enum_windows_proc), 178 | LPARAM(&mut wininfo_vec as *mut _ as _), 179 | ) 180 | .as_bool() 181 | }; 182 | if !res { 183 | return Err(anyhow!("Failed to EnumWindows: {:?}", unsafe { 184 | GetLastError() 185 | })); 186 | }; 187 | Ok(wininfo_vec) 188 | } 189 | 190 | pub fn get_windows_info_filtered() -> Result> { 191 | let Ok(vdmth_lock) = VDMTH.lock() else { 192 | return Err(anyhow!("Failed to lock VDMTH")); 193 | }; 194 | let Some(vdmth) = &*vdmth_lock else { 195 | return Err(anyhow!("VDMTH is not initialized")); 196 | }; 197 | let all_wins = Self::get_windows_info()?; 198 | 199 | vdmth.1.send(all_wins)?; 200 | let res = vdmth.2.recv()?; 201 | 202 | Ok(res) 203 | } 204 | 205 | pub fn get_thread_windows_info() -> Result> { 206 | let mut wininfo_vec = Vec::new(); 207 | let res = unsafe { 208 | EnumThreadWindows( 209 | GetCurrentThreadId(), 210 | Some(enum_windows_proc), 211 | LPARAM(&mut wininfo_vec as *mut _ as _), 212 | ) 213 | .as_bool() 214 | }; 215 | if !res { 216 | return Err(anyhow!("Failed to EnumThreadWindows: {:?}", unsafe { 217 | GetLastError() 218 | })); 219 | } 220 | Ok(wininfo_vec) 221 | } 222 | 223 | pub fn drop_vdmth() -> Result<()> { 224 | let Ok(mut vdmth_lock) = VDMTH.lock() else { 225 | return Ok(()); 226 | }; 227 | let Some((th, tx, rx)) = vdmth_lock.take() else { 228 | return Ok(()); 229 | }; 230 | 231 | drop(tx); 232 | drop(rx); 233 | 234 | th.join() 235 | .map_err(|e| anyhow!("Failed to join VDMTH: {:?}", e))?; 236 | 237 | Ok(()) 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /src/hooks/config-hook.ts: -------------------------------------------------------------------------------- 1 | import { useState, useEffect, useRef } from "react"; 2 | import { 3 | readTextFile, 4 | writeTextFile, 5 | exists, 6 | createDir, 7 | BaseDirectory, 8 | } from "@tauri-apps/api/fs"; 9 | import { register, unregisterAll } from "@tauri-apps/api/globalShortcut"; 10 | import { invoke } from "@tauri-apps/api/tauri"; 11 | 12 | export interface ThresholdRect { 13 | left: number; 14 | top: number; 15 | right: number; 16 | bottom: number; 17 | } 18 | 19 | export interface WH { 20 | width: number; 21 | height: number; 22 | } 23 | 24 | export interface Config { 25 | summon_mouse_cursor_shortcut: string | undefined; 26 | summon_point: { x: number; y: number } | undefined; 27 | auto_summon_enabled: boolean; 28 | auto_summon_threshold: ThresholdRect | undefined; 29 | auto_summon_size: WH | undefined; 30 | auto_summon_shortcut: string | undefined; 31 | winwinmap_summon_shortcut: string | undefined; 32 | showMap: boolean; 33 | opened: boolean; 34 | nameFilter: Set; 35 | exeNameFilter: Set; 36 | dragTermMargin: number; 37 | } 38 | 39 | interface ConfigFile extends Omit { 40 | nameFilter: string[]; 41 | exeNameFilter: string[]; 42 | } 43 | 44 | const ConfigFile2Config = (cnfgFile: ConfigFile): Config => { 45 | return { 46 | ...cnfgFile, 47 | nameFilter: new Set(cnfgFile.nameFilter), 48 | exeNameFilter: new Set(cnfgFile.exeNameFilter), 49 | }; 50 | }; 51 | 52 | const Config2ConfigFile = (cnfg: Config): ConfigFile => { 53 | return { 54 | ...cnfg, 55 | nameFilter: Array.from(cnfg.nameFilter), 56 | exeNameFilter: Array.from(cnfg.exeNameFilter), 57 | }; 58 | }; 59 | 60 | const DefaultConfig = () => { 61 | return { 62 | summon_mouse_cursor_shortcut: "", 63 | summon_point: { x: 0, y: 0 }, 64 | auto_summon_enabled: false, 65 | auto_summon_threshold: { 66 | left: 0, 67 | top: 0, 68 | right: 0, 69 | bottom: 0, 70 | }, 71 | auto_summon_size: { 72 | width: 0, 73 | height: 0, 74 | }, 75 | auto_summon_shortcut: "", 76 | winwinmap_summon_shortcut: "", 77 | showMap: true, 78 | opened: false, 79 | nameFilter: new Set(), 80 | exeNameFilter: new Set(), 81 | dragTermMargin: 10, 82 | } as Config; 83 | }; 84 | 85 | export interface ConfigMethods { 86 | setSummonMouseCursorShortcut: (shortcut: string) => void; 87 | setSummonPoint: (point: { x: number; y: number }) => void; 88 | setAutoSummonEnabled: (enabled: boolean) => void; 89 | setAutoSummonThreshold: (threshold: ThresholdRect) => void; 90 | setAutoSummonSize: (size: WH) => void; 91 | setAutoSummonShortcut: (shortcut: string) => void; 92 | setWinWinMapSummonShortcut: (shortcut: string) => void; 93 | toggleShowMap: () => void; 94 | toggleOpened: () => void; 95 | addFilteredName: (name: string) => void; 96 | removeFilteredName: (name: string) => void; 97 | addFilteredExeName: (name: string) => void; 98 | removeFilteredExeName: (name: string) => void; 99 | setDragTermMargin: (margin: number) => void; 100 | } 101 | 102 | type useConfigRes = [Config | undefined, ConfigMethods]; 103 | 104 | const useConfig = (): useConfigRes => { 105 | const [config, setConfig] = useState(undefined); 106 | const initializeAsyncFn = useRef<(() => Promise) | undefined>( 107 | undefined 108 | ); 109 | 110 | const setSummonMouseCursorShortcut = (shortcut: string) => { 111 | const c = config ?? DefaultConfig(); 112 | c.summon_mouse_cursor_shortcut = shortcut; 113 | setConfig({ ...c }); 114 | }; 115 | 116 | const setSummonPoint = (point: { x: number; y: number }) => { 117 | const c = config ?? DefaultConfig(); 118 | c.summon_point = point; 119 | setConfig({ ...c }); 120 | }; 121 | 122 | const setAutoSummonEnabled = (enabled: boolean) => { 123 | const c = config ?? DefaultConfig(); 124 | c.auto_summon_enabled = enabled; 125 | setConfig({ ...c }); 126 | }; 127 | 128 | const setAutoSummonThreshold = (threshold: ThresholdRect) => { 129 | const c = config ?? DefaultConfig(); 130 | c.auto_summon_threshold = threshold; 131 | setConfig({ ...c }); 132 | }; 133 | 134 | const setAutoSummonSize = (size: WH) => { 135 | const c = config ?? DefaultConfig(); 136 | c.auto_summon_size = size; 137 | setConfig({ ...c }); 138 | }; 139 | 140 | const setAutoSummonShortcut = (shortcut: string) => { 141 | const c = config ?? DefaultConfig(); 142 | c.auto_summon_shortcut = shortcut; 143 | setConfig({ ...c }); 144 | }; 145 | 146 | const setWinWinMapSummonShortcut = (shortcut: string) => { 147 | const c = config ?? DefaultConfig(); 148 | c.winwinmap_summon_shortcut = shortcut; 149 | setConfig({ ...c }); 150 | }; 151 | 152 | const toggleShowMap = () => { 153 | const c = config ?? DefaultConfig(); 154 | c.showMap = !c.showMap; 155 | setConfig({ ...c }); 156 | }; 157 | 158 | const toggleOpened = () => { 159 | const c = config ?? DefaultConfig(); 160 | c.opened = !c.opened; 161 | setConfig({ ...c }); 162 | }; 163 | 164 | const addFilteredName = (name: string) => { 165 | const c = config ?? DefaultConfig(); 166 | c.nameFilter.add(name); 167 | setConfig({ ...c }); 168 | }; 169 | 170 | const addFilteredExeName = (name: string) => { 171 | const c = config ?? DefaultConfig(); 172 | c.exeNameFilter.add(name); 173 | setConfig({ ...c }); 174 | }; 175 | 176 | const removeFilteredName = (name: string) => { 177 | const c = config ?? DefaultConfig(); 178 | c.nameFilter.delete(name); 179 | setConfig({ ...c }); 180 | }; 181 | 182 | const removeFilteredExeName = (name: string) => { 183 | const c = config ?? DefaultConfig(); 184 | c.exeNameFilter.delete(name); 185 | setConfig({ ...c }); 186 | }; 187 | 188 | const setDragTermMargin = (margin: number) => { 189 | const c = config ?? DefaultConfig(); 190 | c.dragTermMargin = margin; 191 | setConfig({ ...c }); 192 | }; 193 | 194 | useEffect(() => { 195 | if (initializeAsyncFn.current !== undefined) { 196 | return; 197 | } 198 | 199 | initializeAsyncFn.current = async () => { 200 | try { 201 | const profileBookStr = await readTextFile("config.json", { 202 | dir: BaseDirectory.AppConfig, 203 | }); 204 | const configFile = JSON.parse(profileBookStr) as ConfigFile; 205 | const config = { ...DefaultConfig(), ...ConfigFile2Config(configFile) }; 206 | setConfig(config); 207 | } catch (error) { 208 | console.warn(error); 209 | setConfig({ 210 | ...DefaultConfig(), 211 | }); 212 | } 213 | 214 | console.log("Config initialized"); 215 | }; 216 | initializeAsyncFn.current(); 217 | }, []); 218 | 219 | useEffect(() => { 220 | if (config === undefined) { 221 | return; 222 | } 223 | 224 | (async () => { 225 | // file save 226 | const ext = await exists("", { dir: BaseDirectory.AppConfig }); 227 | if (!ext) { 228 | await createDir("", { dir: BaseDirectory.AppConfig }); 229 | } 230 | 231 | const configFile = Config2ConfigFile(config); 232 | await writeTextFile("config.json", JSON.stringify(configFile), { 233 | dir: BaseDirectory.AppConfig, 234 | }); 235 | 236 | // register shortcut 237 | await unregisterAll(); 238 | 239 | // Cursor Summon Shortcut 240 | if ( 241 | config.summon_mouse_cursor_shortcut !== undefined && 242 | config.summon_mouse_cursor_shortcut !== "" 243 | ) { 244 | await register(config.summon_mouse_cursor_shortcut, async () => { 245 | if (config.summon_point === undefined) { 246 | return; 247 | } 248 | await invoke("set_cursor_pos", config.summon_point); 249 | }); 250 | } 251 | 252 | // Auto Summon Toggle Shortcut 253 | if ( 254 | config.auto_summon_shortcut !== undefined && 255 | config.auto_summon_shortcut !== "" 256 | ) { 257 | await register(config.auto_summon_shortcut, async () => { 258 | if (config.auto_summon_enabled === undefined) { 259 | return; 260 | } 261 | setAutoSummonEnabled(!config.auto_summon_enabled); 262 | }); 263 | } 264 | 265 | // WinWinMap Summon Shortcut 266 | if ( 267 | config.winwinmap_summon_shortcut !== undefined && 268 | config.winwinmap_summon_shortcut !== "" && 269 | config.summon_point !== undefined && 270 | config.auto_summon_size !== undefined 271 | ) { 272 | const x = config.summon_point.x; 273 | const y = config.summon_point.y; 274 | const wh = config.auto_summon_size; 275 | await register(config.winwinmap_summon_shortcut, async () => { 276 | await invoke("set_thread_window_pos_and_size", { 277 | x: x, 278 | y: y, 279 | width: wh.width, 280 | height: wh.height, 281 | }); 282 | }); 283 | } 284 | })(); 285 | }, [config]); 286 | 287 | return [ 288 | config, 289 | { 290 | setSummonMouseCursorShortcut, 291 | setSummonPoint, 292 | setAutoSummonEnabled, 293 | setAutoSummonThreshold, 294 | setAutoSummonSize, 295 | setAutoSummonShortcut, 296 | setWinWinMapSummonShortcut, 297 | toggleShowMap, 298 | toggleOpened, 299 | addFilteredName, 300 | removeFilteredName, 301 | addFilteredExeName, 302 | removeFilteredExeName, 303 | setDragTermMargin, 304 | }, 305 | ]; 306 | }; 307 | 308 | export default useConfig; 309 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@ampproject/remapping@^2.1.0": 6 | version "2.2.0" 7 | resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.0.tgz#56c133824780de3174aed5ab6834f3026790154d" 8 | integrity sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w== 9 | dependencies: 10 | "@jridgewell/gen-mapping" "^0.1.0" 11 | "@jridgewell/trace-mapping" "^0.3.9" 12 | 13 | "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.18.6": 14 | version "7.18.6" 15 | resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.18.6.tgz#3b25d38c89600baa2dcc219edfa88a74eb2c427a" 16 | integrity sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q== 17 | dependencies: 18 | "@babel/highlight" "^7.18.6" 19 | 20 | "@babel/compat-data@^7.20.5": 21 | version "7.20.14" 22 | resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.20.14.tgz#4106fc8b755f3e3ee0a0a7c27dde5de1d2b2baf8" 23 | integrity sha512-0YpKHD6ImkWMEINCyDAD0HLLUH/lPCefG8ld9it8DJB2wnApraKuhgYTvTY1z7UFIfBTGy5LwncZ+5HWWGbhFw== 24 | 25 | "@babel/core@^7.20.7": 26 | version "7.20.12" 27 | resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.20.12.tgz#7930db57443c6714ad216953d1356dac0eb8496d" 28 | integrity sha512-XsMfHovsUYHFMdrIHkZphTN/2Hzzi78R08NuHfDBehym2VsPDL6Zn/JAD/JQdnRvbSsbQc4mVaU1m6JgtTEElg== 29 | dependencies: 30 | "@ampproject/remapping" "^2.1.0" 31 | "@babel/code-frame" "^7.18.6" 32 | "@babel/generator" "^7.20.7" 33 | "@babel/helper-compilation-targets" "^7.20.7" 34 | "@babel/helper-module-transforms" "^7.20.11" 35 | "@babel/helpers" "^7.20.7" 36 | "@babel/parser" "^7.20.7" 37 | "@babel/template" "^7.20.7" 38 | "@babel/traverse" "^7.20.12" 39 | "@babel/types" "^7.20.7" 40 | convert-source-map "^1.7.0" 41 | debug "^4.1.0" 42 | gensync "^1.0.0-beta.2" 43 | json5 "^2.2.2" 44 | semver "^6.3.0" 45 | 46 | "@babel/generator@^7.20.7": 47 | version "7.20.14" 48 | resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.20.14.tgz#9fa772c9f86a46c6ac9b321039400712b96f64ce" 49 | integrity sha512-AEmuXHdcD3A52HHXxaTmYlb8q/xMEhoRP67B3T4Oq7lbmSoqroMZzjnGj3+i1io3pdnF8iBYVu4Ilj+c4hBxYg== 50 | dependencies: 51 | "@babel/types" "^7.20.7" 52 | "@jridgewell/gen-mapping" "^0.3.2" 53 | jsesc "^2.5.1" 54 | 55 | "@babel/helper-compilation-targets@^7.20.7": 56 | version "7.20.7" 57 | resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.7.tgz#a6cd33e93629f5eb473b021aac05df62c4cd09bb" 58 | integrity sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ== 59 | dependencies: 60 | "@babel/compat-data" "^7.20.5" 61 | "@babel/helper-validator-option" "^7.18.6" 62 | browserslist "^4.21.3" 63 | lru-cache "^5.1.1" 64 | semver "^6.3.0" 65 | 66 | "@babel/helper-environment-visitor@^7.18.9": 67 | version "7.18.9" 68 | resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz#0c0cee9b35d2ca190478756865bb3528422f51be" 69 | integrity sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg== 70 | 71 | "@babel/helper-function-name@^7.19.0": 72 | version "7.19.0" 73 | resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz#941574ed5390682e872e52d3f38ce9d1bef4648c" 74 | integrity sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w== 75 | dependencies: 76 | "@babel/template" "^7.18.10" 77 | "@babel/types" "^7.19.0" 78 | 79 | "@babel/helper-hoist-variables@^7.18.6": 80 | version "7.18.6" 81 | resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz#d4d2c8fb4baeaa5c68b99cc8245c56554f926678" 82 | integrity sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q== 83 | dependencies: 84 | "@babel/types" "^7.18.6" 85 | 86 | "@babel/helper-module-imports@^7.16.7", "@babel/helper-module-imports@^7.18.6": 87 | version "7.18.6" 88 | resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz#1e3ebdbbd08aad1437b428c50204db13c5a3ca6e" 89 | integrity sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA== 90 | dependencies: 91 | "@babel/types" "^7.18.6" 92 | 93 | "@babel/helper-module-transforms@^7.20.11": 94 | version "7.20.11" 95 | resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.20.11.tgz#df4c7af713c557938c50ea3ad0117a7944b2f1b0" 96 | integrity sha512-uRy78kN4psmji1s2QtbtcCSaj/LILFDp0f/ymhpQH5QY3nljUZCaNWz9X1dEj/8MBdBEFECs7yRhKn8i7NjZgg== 97 | dependencies: 98 | "@babel/helper-environment-visitor" "^7.18.9" 99 | "@babel/helper-module-imports" "^7.18.6" 100 | "@babel/helper-simple-access" "^7.20.2" 101 | "@babel/helper-split-export-declaration" "^7.18.6" 102 | "@babel/helper-validator-identifier" "^7.19.1" 103 | "@babel/template" "^7.20.7" 104 | "@babel/traverse" "^7.20.10" 105 | "@babel/types" "^7.20.7" 106 | 107 | "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.19.0": 108 | version "7.20.2" 109 | resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz#d1b9000752b18d0877cff85a5c376ce5c3121629" 110 | integrity sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ== 111 | 112 | "@babel/helper-simple-access@^7.20.2": 113 | version "7.20.2" 114 | resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz#0ab452687fe0c2cfb1e2b9e0015de07fc2d62dd9" 115 | integrity sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA== 116 | dependencies: 117 | "@babel/types" "^7.20.2" 118 | 119 | "@babel/helper-split-export-declaration@^7.18.6": 120 | version "7.18.6" 121 | resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz#7367949bc75b20c6d5a5d4a97bba2824ae8ef075" 122 | integrity sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA== 123 | dependencies: 124 | "@babel/types" "^7.18.6" 125 | 126 | "@babel/helper-string-parser@^7.19.4": 127 | version "7.19.4" 128 | resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz#38d3acb654b4701a9b77fb0615a96f775c3a9e63" 129 | integrity sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw== 130 | 131 | "@babel/helper-validator-identifier@^7.18.6", "@babel/helper-validator-identifier@^7.19.1": 132 | version "7.19.1" 133 | resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2" 134 | integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w== 135 | 136 | "@babel/helper-validator-option@^7.18.6": 137 | version "7.18.6" 138 | resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz#bf0d2b5a509b1f336099e4ff36e1a63aa5db4db8" 139 | integrity sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw== 140 | 141 | "@babel/helpers@^7.20.7": 142 | version "7.20.13" 143 | resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.20.13.tgz#e3cb731fb70dc5337134cadc24cbbad31cc87ad2" 144 | integrity sha512-nzJ0DWCL3gB5RCXbUO3KIMMsBY2Eqbx8mBpKGE/02PgyRQFcPQLbkQ1vyy596mZLaP+dAfD+R4ckASzNVmW3jg== 145 | dependencies: 146 | "@babel/template" "^7.20.7" 147 | "@babel/traverse" "^7.20.13" 148 | "@babel/types" "^7.20.7" 149 | 150 | "@babel/highlight@^7.18.6": 151 | version "7.18.6" 152 | resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf" 153 | integrity sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g== 154 | dependencies: 155 | "@babel/helper-validator-identifier" "^7.18.6" 156 | chalk "^2.0.0" 157 | js-tokens "^4.0.0" 158 | 159 | "@babel/parser@^7.20.13", "@babel/parser@^7.20.7": 160 | version "7.20.13" 161 | resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.20.13.tgz#ddf1eb5a813588d2fb1692b70c6fce75b945c088" 162 | integrity sha512-gFDLKMfpiXCsjt4za2JA9oTMn70CeseCehb11kRZgvd7+F67Hih3OHOK24cRrWECJ/ljfPGac6ygXAs/C8kIvw== 163 | 164 | "@babel/plugin-syntax-jsx@^7.17.12": 165 | version "7.18.6" 166 | resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz#a8feef63b010150abd97f1649ec296e849943ca0" 167 | integrity sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q== 168 | dependencies: 169 | "@babel/helper-plugin-utils" "^7.18.6" 170 | 171 | "@babel/plugin-transform-react-jsx-self@^7.18.6": 172 | version "7.18.6" 173 | resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.18.6.tgz#3849401bab7ae8ffa1e3e5687c94a753fc75bda7" 174 | integrity sha512-A0LQGx4+4Jv7u/tWzoJF7alZwnBDQd6cGLh9P+Ttk4dpiL+J5p7NSNv/9tlEFFJDq3kjxOavWmbm6t0Gk+A3Ig== 175 | dependencies: 176 | "@babel/helper-plugin-utils" "^7.18.6" 177 | 178 | "@babel/plugin-transform-react-jsx-source@^7.19.6": 179 | version "7.19.6" 180 | resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.19.6.tgz#88578ae8331e5887e8ce28e4c9dc83fb29da0b86" 181 | integrity sha512-RpAi004QyMNisst/pvSanoRdJ4q+jMCWyk9zdw/CyLB9j8RXEahodR6l2GyttDRyEVWZtbN+TpLiHJ3t34LbsQ== 182 | dependencies: 183 | "@babel/helper-plugin-utils" "^7.19.0" 184 | 185 | "@babel/runtime@^7.12.1", "@babel/runtime@^7.9.2": 186 | version "7.21.0" 187 | resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.21.0.tgz#5b55c9d394e5fcf304909a8b00c07dc217b56673" 188 | integrity sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw== 189 | dependencies: 190 | regenerator-runtime "^0.13.11" 191 | 192 | "@babel/runtime@^7.12.5", "@babel/runtime@^7.18.3", "@babel/runtime@^7.20.6", "@babel/runtime@^7.20.7", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7": 193 | version "7.20.13" 194 | resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.20.13.tgz#7055ab8a7cff2b8f6058bf6ae45ff84ad2aded4b" 195 | integrity sha512-gt3PKXs0DBoL9xCvOIIZ2NEqAGZqHjAnmVbfQtB620V0uReIQutpel14KcneZuer7UioY8ALKZ7iocavvzTNFA== 196 | dependencies: 197 | regenerator-runtime "^0.13.11" 198 | 199 | "@babel/template@^7.18.10", "@babel/template@^7.20.7": 200 | version "7.20.7" 201 | resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.20.7.tgz#a15090c2839a83b02aa996c0b4994005841fd5a8" 202 | integrity sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw== 203 | dependencies: 204 | "@babel/code-frame" "^7.18.6" 205 | "@babel/parser" "^7.20.7" 206 | "@babel/types" "^7.20.7" 207 | 208 | "@babel/traverse@^7.20.10", "@babel/traverse@^7.20.12", "@babel/traverse@^7.20.13": 209 | version "7.20.13" 210 | resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.20.13.tgz#817c1ba13d11accca89478bd5481b2d168d07473" 211 | integrity sha512-kMJXfF0T6DIS9E8cgdLCSAL+cuCK+YEZHWiLK0SXpTo8YRj5lpJu3CDNKiIBCne4m9hhTIqUg6SYTAI39tAiVQ== 212 | dependencies: 213 | "@babel/code-frame" "^7.18.6" 214 | "@babel/generator" "^7.20.7" 215 | "@babel/helper-environment-visitor" "^7.18.9" 216 | "@babel/helper-function-name" "^7.19.0" 217 | "@babel/helper-hoist-variables" "^7.18.6" 218 | "@babel/helper-split-export-declaration" "^7.18.6" 219 | "@babel/parser" "^7.20.13" 220 | "@babel/types" "^7.20.7" 221 | debug "^4.1.0" 222 | globals "^11.1.0" 223 | 224 | "@babel/types@^7.18.6", "@babel/types@^7.19.0", "@babel/types@^7.20.2", "@babel/types@^7.20.7": 225 | version "7.20.7" 226 | resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.20.7.tgz#54ec75e252318423fc07fb644dc6a58a64c09b7f" 227 | integrity sha512-69OnhBxSSgK0OzTJai4kyPDiKTIe3j+ctaHdIGVbRahTLAT7L3R9oeXHC2aVSuGYt3cVnoAMDmOCgJ2yaiLMvg== 228 | dependencies: 229 | "@babel/helper-string-parser" "^7.19.4" 230 | "@babel/helper-validator-identifier" "^7.19.1" 231 | to-fast-properties "^2.0.0" 232 | 233 | "@emotion/babel-plugin@^11.10.5": 234 | version "11.10.5" 235 | resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.10.5.tgz#65fa6e1790ddc9e23cc22658a4c5dea423c55c3c" 236 | integrity sha512-xE7/hyLHJac7D2Ve9dKroBBZqBT7WuPQmWcq7HSGb84sUuP4mlOWoB8dvVfD9yk5DHkU1m6RW7xSoDtnQHNQeA== 237 | dependencies: 238 | "@babel/helper-module-imports" "^7.16.7" 239 | "@babel/plugin-syntax-jsx" "^7.17.12" 240 | "@babel/runtime" "^7.18.3" 241 | "@emotion/hash" "^0.9.0" 242 | "@emotion/memoize" "^0.8.0" 243 | "@emotion/serialize" "^1.1.1" 244 | babel-plugin-macros "^3.1.0" 245 | convert-source-map "^1.5.0" 246 | escape-string-regexp "^4.0.0" 247 | find-root "^1.1.0" 248 | source-map "^0.5.7" 249 | stylis "4.1.3" 250 | 251 | "@emotion/cache@^11.10.5": 252 | version "11.10.5" 253 | resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.10.5.tgz#c142da9351f94e47527ed458f7bbbbe40bb13c12" 254 | integrity sha512-dGYHWyzTdmK+f2+EnIGBpkz1lKc4Zbj2KHd4cX3Wi8/OWr5pKslNjc3yABKH4adRGCvSX4VDC0i04mrrq0aiRA== 255 | dependencies: 256 | "@emotion/memoize" "^0.8.0" 257 | "@emotion/sheet" "^1.2.1" 258 | "@emotion/utils" "^1.2.0" 259 | "@emotion/weak-memoize" "^0.3.0" 260 | stylis "4.1.3" 261 | 262 | "@emotion/hash@^0.9.0": 263 | version "0.9.0" 264 | resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.9.0.tgz#c5153d50401ee3c027a57a177bc269b16d889cb7" 265 | integrity sha512-14FtKiHhy2QoPIzdTcvh//8OyBlknNs2nXRwIhG904opCby3l+9Xaf/wuPvICBF0rc1ZCNBd3nKe9cd2mecVkQ== 266 | 267 | "@emotion/is-prop-valid@^1.2.0": 268 | version "1.2.0" 269 | resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-1.2.0.tgz#7f2d35c97891669f7e276eb71c83376a5dc44c83" 270 | integrity sha512-3aDpDprjM0AwaxGE09bOPkNxHpBd+kA6jty3RnaEXdweX1DF1U3VQpPYb0g1IStAuK7SVQ1cy+bNBBKp4W3Fjg== 271 | dependencies: 272 | "@emotion/memoize" "^0.8.0" 273 | 274 | "@emotion/memoize@^0.8.0": 275 | version "0.8.0" 276 | resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.8.0.tgz#f580f9beb67176fa57aae70b08ed510e1b18980f" 277 | integrity sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA== 278 | 279 | "@emotion/react@^11.10.5": 280 | version "11.10.5" 281 | resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.10.5.tgz#95fff612a5de1efa9c0d535384d3cfa115fe175d" 282 | integrity sha512-TZs6235tCJ/7iF6/rvTaOH4oxQg2gMAcdHemjwLKIjKz4rRuYe1HJ2TQJKnAcRAfOUDdU8XoDadCe1rl72iv8A== 283 | dependencies: 284 | "@babel/runtime" "^7.18.3" 285 | "@emotion/babel-plugin" "^11.10.5" 286 | "@emotion/cache" "^11.10.5" 287 | "@emotion/serialize" "^1.1.1" 288 | "@emotion/use-insertion-effect-with-fallbacks" "^1.0.0" 289 | "@emotion/utils" "^1.2.0" 290 | "@emotion/weak-memoize" "^0.3.0" 291 | hoist-non-react-statics "^3.3.1" 292 | 293 | "@emotion/serialize@^1.1.1": 294 | version "1.1.1" 295 | resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.1.1.tgz#0595701b1902feded8a96d293b26be3f5c1a5cf0" 296 | integrity sha512-Zl/0LFggN7+L1liljxXdsVSVlg6E/Z/olVWpfxUTxOAmi8NU7YoeWeLfi1RmnB2TATHoaWwIBRoL+FvAJiTUQA== 297 | dependencies: 298 | "@emotion/hash" "^0.9.0" 299 | "@emotion/memoize" "^0.8.0" 300 | "@emotion/unitless" "^0.8.0" 301 | "@emotion/utils" "^1.2.0" 302 | csstype "^3.0.2" 303 | 304 | "@emotion/sheet@^1.2.1": 305 | version "1.2.1" 306 | resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.2.1.tgz#0767e0305230e894897cadb6c8df2c51e61a6c2c" 307 | integrity sha512-zxRBwl93sHMsOj4zs+OslQKg/uhF38MB+OMKoCrVuS0nyTkqnau+BM3WGEoOptg9Oz45T/aIGs1qbVAsEFo3nA== 308 | 309 | "@emotion/styled@^11.10.5": 310 | version "11.10.5" 311 | resolved "https://registry.yarnpkg.com/@emotion/styled/-/styled-11.10.5.tgz#1fe7bf941b0909802cb826457e362444e7e96a79" 312 | integrity sha512-8EP6dD7dMkdku2foLoruPCNkRevzdcBaY6q0l0OsbyJK+x8D9HWjX27ARiSIKNF634hY9Zdoedh8bJCiva8yZw== 313 | dependencies: 314 | "@babel/runtime" "^7.18.3" 315 | "@emotion/babel-plugin" "^11.10.5" 316 | "@emotion/is-prop-valid" "^1.2.0" 317 | "@emotion/serialize" "^1.1.1" 318 | "@emotion/use-insertion-effect-with-fallbacks" "^1.0.0" 319 | "@emotion/utils" "^1.2.0" 320 | 321 | "@emotion/unitless@^0.8.0": 322 | version "0.8.0" 323 | resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.8.0.tgz#a4a36e9cbdc6903737cd20d38033241e1b8833db" 324 | integrity sha512-VINS5vEYAscRl2ZUDiT3uMPlrFQupiKgHz5AA4bCH1miKBg4qtwkim1qPmJj/4WG6TreYMY111rEFsjupcOKHw== 325 | 326 | "@emotion/use-insertion-effect-with-fallbacks@^1.0.0": 327 | version "1.0.0" 328 | resolved "https://registry.yarnpkg.com/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.0.tgz#ffadaec35dbb7885bd54de3fa267ab2f860294df" 329 | integrity sha512-1eEgUGmkaljiBnRMTdksDV1W4kUnmwgp7X9G8B++9GYwl1lUdqSndSriIrTJ0N7LQaoauY9JJ2yhiOYK5+NI4A== 330 | 331 | "@emotion/utils@^1.2.0": 332 | version "1.2.0" 333 | resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.2.0.tgz#9716eaccbc6b5ded2ea5a90d65562609aab0f561" 334 | integrity sha512-sn3WH53Kzpw8oQ5mgMmIzzyAaH2ZqFEbozVVBSYp538E06OSE6ytOp7pRAjNQR+Q/orwqdQYJSe2m3hCOeznkw== 335 | 336 | "@emotion/weak-memoize@^0.3.0": 337 | version "0.3.0" 338 | resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.3.0.tgz#ea89004119dc42db2e1dba0f97d553f7372f6fcb" 339 | integrity sha512-AHPmaAx+RYfZz0eYu6Gviiagpmiyw98ySSlQvCUhVGDRtDFe4DBS0x1bSjdF3gqUDYOczB+yYvBTtEylYSdRhg== 340 | 341 | "@esbuild/android-arm64@0.16.17": 342 | version "0.16.17" 343 | resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.16.17.tgz#cf91e86df127aa3d141744edafcba0abdc577d23" 344 | integrity sha512-MIGl6p5sc3RDTLLkYL1MyL8BMRN4tLMRCn+yRJJmEDvYZ2M7tmAf80hx1kbNEUX2KJ50RRtxZ4JHLvCfuB6kBg== 345 | 346 | "@esbuild/android-arm@0.16.17": 347 | version "0.16.17" 348 | resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.16.17.tgz#025b6246d3f68b7bbaa97069144fb5fb70f2fff2" 349 | integrity sha512-N9x1CMXVhtWEAMS7pNNONyA14f71VPQN9Cnavj1XQh6T7bskqiLLrSca4O0Vr8Wdcga943eThxnVp3JLnBMYtw== 350 | 351 | "@esbuild/android-x64@0.16.17": 352 | version "0.16.17" 353 | resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.16.17.tgz#c820e0fef982f99a85c4b8bfdd582835f04cd96e" 354 | integrity sha512-a3kTv3m0Ghh4z1DaFEuEDfz3OLONKuFvI4Xqczqx4BqLyuFaFkuaG4j2MtA6fuWEFeC5x9IvqnX7drmRq/fyAQ== 355 | 356 | "@esbuild/darwin-arm64@0.16.17": 357 | version "0.16.17" 358 | resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.16.17.tgz#edef4487af6b21afabba7be5132c26d22379b220" 359 | integrity sha512-/2agbUEfmxWHi9ARTX6OQ/KgXnOWfsNlTeLcoV7HSuSTv63E4DqtAc+2XqGw1KHxKMHGZgbVCZge7HXWX9Vn+w== 360 | 361 | "@esbuild/darwin-x64@0.16.17": 362 | version "0.16.17" 363 | resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.16.17.tgz#42829168730071c41ef0d028d8319eea0e2904b4" 364 | integrity sha512-2By45OBHulkd9Svy5IOCZt376Aa2oOkiE9QWUK9fe6Tb+WDr8hXL3dpqi+DeLiMed8tVXspzsTAvd0jUl96wmg== 365 | 366 | "@esbuild/freebsd-arm64@0.16.17": 367 | version "0.16.17" 368 | resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.16.17.tgz#1f4af488bfc7e9ced04207034d398e793b570a27" 369 | integrity sha512-mt+cxZe1tVx489VTb4mBAOo2aKSnJ33L9fr25JXpqQqzbUIw/yzIzi+NHwAXK2qYV1lEFp4OoVeThGjUbmWmdw== 370 | 371 | "@esbuild/freebsd-x64@0.16.17": 372 | version "0.16.17" 373 | resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.16.17.tgz#636306f19e9bc981e06aa1d777302dad8fddaf72" 374 | integrity sha512-8ScTdNJl5idAKjH8zGAsN7RuWcyHG3BAvMNpKOBaqqR7EbUhhVHOqXRdL7oZvz8WNHL2pr5+eIT5c65kA6NHug== 375 | 376 | "@esbuild/linux-arm64@0.16.17": 377 | version "0.16.17" 378 | resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.16.17.tgz#a003f7ff237c501e095d4f3a09e58fc7b25a4aca" 379 | integrity sha512-7S8gJnSlqKGVJunnMCrXHU9Q8Q/tQIxk/xL8BqAP64wchPCTzuM6W3Ra8cIa1HIflAvDnNOt2jaL17vaW+1V0g== 380 | 381 | "@esbuild/linux-arm@0.16.17": 382 | version "0.16.17" 383 | resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.16.17.tgz#b591e6a59d9c4fe0eeadd4874b157ab78cf5f196" 384 | integrity sha512-iihzrWbD4gIT7j3caMzKb/RsFFHCwqqbrbH9SqUSRrdXkXaygSZCZg1FybsZz57Ju7N/SHEgPyaR0LZ8Zbe9gQ== 385 | 386 | "@esbuild/linux-ia32@0.16.17": 387 | version "0.16.17" 388 | resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.16.17.tgz#24333a11027ef46a18f57019450a5188918e2a54" 389 | integrity sha512-kiX69+wcPAdgl3Lonh1VI7MBr16nktEvOfViszBSxygRQqSpzv7BffMKRPMFwzeJGPxcio0pdD3kYQGpqQ2SSg== 390 | 391 | "@esbuild/linux-loong64@0.16.17": 392 | version "0.16.17" 393 | resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.16.17.tgz#d5ad459d41ed42bbd4d005256b31882ec52227d8" 394 | integrity sha512-dTzNnQwembNDhd654cA4QhbS9uDdXC3TKqMJjgOWsC0yNCbpzfWoXdZvp0mY7HU6nzk5E0zpRGGx3qoQg8T2DQ== 395 | 396 | "@esbuild/linux-mips64el@0.16.17": 397 | version "0.16.17" 398 | resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.16.17.tgz#4e5967a665c38360b0a8205594377d4dcf9c3726" 399 | integrity sha512-ezbDkp2nDl0PfIUn0CsQ30kxfcLTlcx4Foz2kYv8qdC6ia2oX5Q3E/8m6lq84Dj/6b0FrkgD582fJMIfHhJfSw== 400 | 401 | "@esbuild/linux-ppc64@0.16.17": 402 | version "0.16.17" 403 | resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.16.17.tgz#206443a02eb568f9fdf0b438fbd47d26e735afc8" 404 | integrity sha512-dzS678gYD1lJsW73zrFhDApLVdM3cUF2MvAa1D8K8KtcSKdLBPP4zZSLy6LFZ0jYqQdQ29bjAHJDgz0rVbLB3g== 405 | 406 | "@esbuild/linux-riscv64@0.16.17": 407 | version "0.16.17" 408 | resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.16.17.tgz#c351e433d009bf256e798ad048152c8d76da2fc9" 409 | integrity sha512-ylNlVsxuFjZK8DQtNUwiMskh6nT0vI7kYl/4fZgV1llP5d6+HIeL/vmmm3jpuoo8+NuXjQVZxmKuhDApK0/cKw== 410 | 411 | "@esbuild/linux-s390x@0.16.17": 412 | version "0.16.17" 413 | resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.16.17.tgz#661f271e5d59615b84b6801d1c2123ad13d9bd87" 414 | integrity sha512-gzy7nUTO4UA4oZ2wAMXPNBGTzZFP7mss3aKR2hH+/4UUkCOyqmjXiKpzGrY2TlEUhbbejzXVKKGazYcQTZWA/w== 415 | 416 | "@esbuild/linux-x64@0.16.17": 417 | version "0.16.17" 418 | resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.16.17.tgz#e4ba18e8b149a89c982351443a377c723762b85f" 419 | integrity sha512-mdPjPxfnmoqhgpiEArqi4egmBAMYvaObgn4poorpUaqmvzzbvqbowRllQ+ZgzGVMGKaPkqUmPDOOFQRUFDmeUw== 420 | 421 | "@esbuild/netbsd-x64@0.16.17": 422 | version "0.16.17" 423 | resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.16.17.tgz#7d4f4041e30c5c07dd24ffa295c73f06038ec775" 424 | integrity sha512-/PzmzD/zyAeTUsduZa32bn0ORug+Jd1EGGAUJvqfeixoEISYpGnAezN6lnJoskauoai0Jrs+XSyvDhppCPoKOA== 425 | 426 | "@esbuild/openbsd-x64@0.16.17": 427 | version "0.16.17" 428 | resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.16.17.tgz#970fa7f8470681f3e6b1db0cc421a4af8060ec35" 429 | integrity sha512-2yaWJhvxGEz2RiftSk0UObqJa/b+rIAjnODJgv2GbGGpRwAfpgzyrg1WLK8rqA24mfZa9GvpjLcBBg8JHkoodg== 430 | 431 | "@esbuild/sunos-x64@0.16.17": 432 | version "0.16.17" 433 | resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.16.17.tgz#abc60e7c4abf8b89fb7a4fe69a1484132238022c" 434 | integrity sha512-xtVUiev38tN0R3g8VhRfN7Zl42YCJvyBhRKw1RJjwE1d2emWTVToPLNEQj/5Qxc6lVFATDiy6LjVHYhIPrLxzw== 435 | 436 | "@esbuild/win32-arm64@0.16.17": 437 | version "0.16.17" 438 | resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.16.17.tgz#7b0ff9e8c3265537a7a7b1fd9a24e7bd39fcd87a" 439 | integrity sha512-ga8+JqBDHY4b6fQAmOgtJJue36scANy4l/rL97W+0wYmijhxKetzZdKOJI7olaBaMhWt8Pac2McJdZLxXWUEQw== 440 | 441 | "@esbuild/win32-ia32@0.16.17": 442 | version "0.16.17" 443 | resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.16.17.tgz#e90fe5267d71a7b7567afdc403dfd198c292eb09" 444 | integrity sha512-WnsKaf46uSSF/sZhwnqE4L/F89AYNMiD4YtEcYekBt9Q7nj0DiId2XH2Ng2PHM54qi5oPrQ8luuzGszqi/veig== 445 | 446 | "@esbuild/win32-x64@0.16.17": 447 | version "0.16.17" 448 | resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.16.17.tgz#c5a1a4bfe1b57f0c3e61b29883525c6da3e5c091" 449 | integrity sha512-y+EHuSchhL7FjHgvQL/0fnnFmO4T1bhvWANX6gcnqTjtnKWbTvUMCpGnv2+t+31d7RzyEAYAd4u2fnIhHL6N/Q== 450 | 451 | "@jridgewell/gen-mapping@^0.1.0": 452 | version "0.1.1" 453 | resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz#e5d2e450306a9491e3bd77e323e38d7aff315996" 454 | integrity sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w== 455 | dependencies: 456 | "@jridgewell/set-array" "^1.0.0" 457 | "@jridgewell/sourcemap-codec" "^1.4.10" 458 | 459 | "@jridgewell/gen-mapping@^0.3.2": 460 | version "0.3.2" 461 | resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9" 462 | integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A== 463 | dependencies: 464 | "@jridgewell/set-array" "^1.0.1" 465 | "@jridgewell/sourcemap-codec" "^1.4.10" 466 | "@jridgewell/trace-mapping" "^0.3.9" 467 | 468 | "@jridgewell/resolve-uri@3.1.0": 469 | version "3.1.0" 470 | resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" 471 | integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== 472 | 473 | "@jridgewell/set-array@^1.0.0", "@jridgewell/set-array@^1.0.1": 474 | version "1.1.2" 475 | resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" 476 | integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== 477 | 478 | "@jridgewell/sourcemap-codec@1.4.14", "@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.13": 479 | version "1.4.14" 480 | resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" 481 | integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== 482 | 483 | "@jridgewell/trace-mapping@^0.3.9": 484 | version "0.3.17" 485 | resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz#793041277af9073b0951a7fe0f0d8c4c98c36985" 486 | integrity sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g== 487 | dependencies: 488 | "@jridgewell/resolve-uri" "3.1.0" 489 | "@jridgewell/sourcemap-codec" "1.4.14" 490 | 491 | "@mui/base@5.0.0-alpha.117": 492 | version "5.0.0-alpha.117" 493 | resolved "https://registry.yarnpkg.com/@mui/base/-/base-5.0.0-alpha.117.tgz#d0873a046af7b5c85f5559a0d715de74501838ed" 494 | integrity sha512-3GlRSZdSrvDQ4k03dSV2rM+97JbNWimFOqGsE7n7Mi8WuBSYCgnPe56bQp3E5cShOrTn11dGH8FRCmVMcCEXqQ== 495 | dependencies: 496 | "@babel/runtime" "^7.20.7" 497 | "@emotion/is-prop-valid" "^1.2.0" 498 | "@mui/types" "^7.2.3" 499 | "@mui/utils" "^5.11.7" 500 | "@popperjs/core" "^2.11.6" 501 | clsx "^1.2.1" 502 | prop-types "^15.8.1" 503 | react-is "^18.2.0" 504 | 505 | "@mui/core-downloads-tracker@^5.11.8": 506 | version "5.11.8" 507 | resolved "https://registry.yarnpkg.com/@mui/core-downloads-tracker/-/core-downloads-tracker-5.11.8.tgz#e1ba16686276b75f501ebe4fd5b60ce600704e3c" 508 | integrity sha512-n/uJRIwZAaJaROaOA4VzycxDo27cusnrRzfycnAkAP5gBndwOJQ1CXjd1Y7hJe5eorj/ukixC7IZD+qCClMCMg== 509 | 510 | "@mui/icons-material@^5.11.0": 511 | version "5.11.0" 512 | resolved "https://registry.yarnpkg.com/@mui/icons-material/-/icons-material-5.11.0.tgz#9ea6949278b2266d2683866069cd43009eaf6464" 513 | integrity sha512-I2LaOKqO8a0xcLGtIozC9xoXjZAto5G5gh0FYUMAlbsIHNHIjn4Xrw9rvjY20vZonyiGrZNMAlAXYkY6JvhF6A== 514 | dependencies: 515 | "@babel/runtime" "^7.20.6" 516 | 517 | "@mui/material@^5.11.8": 518 | version "5.11.8" 519 | resolved "https://registry.yarnpkg.com/@mui/material/-/material-5.11.8.tgz#6ee1b46209932dd9e86430db083bf8260d79b902" 520 | integrity sha512-MpIVmtj9VJBhPHvPWoMkfCPpmVGXT4q43PtCJsdKIdc7W9/nG3Kpqw2oWyw+UxG5xG7eLhmfRFGPKvj4/WopEQ== 521 | dependencies: 522 | "@babel/runtime" "^7.20.7" 523 | "@mui/base" "5.0.0-alpha.117" 524 | "@mui/core-downloads-tracker" "^5.11.8" 525 | "@mui/system" "^5.11.8" 526 | "@mui/types" "^7.2.3" 527 | "@mui/utils" "^5.11.7" 528 | "@types/react-transition-group" "^4.4.5" 529 | clsx "^1.2.1" 530 | csstype "^3.1.1" 531 | prop-types "^15.8.1" 532 | react-is "^18.2.0" 533 | react-transition-group "^4.4.5" 534 | 535 | "@mui/private-theming@^5.11.7": 536 | version "5.11.7" 537 | resolved "https://registry.yarnpkg.com/@mui/private-theming/-/private-theming-5.11.7.tgz#e92b87d6ea68ae5a23d0f0d9d248361b889a98db" 538 | integrity sha512-XzRTSZdc8bhuUdjablTNv3kFkZ/XIMlKkOqqJCU0G8W3tWGXpau2DXkafPd1ddjPhF9zF3qLKNGgKCChYItjgA== 539 | dependencies: 540 | "@babel/runtime" "^7.20.7" 541 | "@mui/utils" "^5.11.7" 542 | prop-types "^15.8.1" 543 | 544 | "@mui/styled-engine@^5.11.8": 545 | version "5.11.8" 546 | resolved "https://registry.yarnpkg.com/@mui/styled-engine/-/styled-engine-5.11.8.tgz#4b0c8c7985b1629f83fdb54dc0883fd07a5d28f0" 547 | integrity sha512-iSpZp9AoeictsDi5xAQ4PGXu7mKtQyzMl7ZaWpHIGMFpsNnfY3NQNg+wkj/gpsAZ+Zg+IIyD+t+ig71Kr9fa0w== 548 | dependencies: 549 | "@babel/runtime" "^7.20.7" 550 | "@emotion/cache" "^11.10.5" 551 | csstype "^3.1.1" 552 | prop-types "^15.8.1" 553 | 554 | "@mui/system@^5.11.8": 555 | version "5.11.8" 556 | resolved "https://registry.yarnpkg.com/@mui/system/-/system-5.11.8.tgz#3d9375a5af9f4a644165103fd64d42e4b58370cb" 557 | integrity sha512-zhroUcxAw2x/dISBJKhGbD70DOYCwMFRo7o/LUYTiUfQkfmLhRfEf1bopWgY9nYstn7QOxOq9fA3aR3pHrUTbw== 558 | dependencies: 559 | "@babel/runtime" "^7.20.7" 560 | "@mui/private-theming" "^5.11.7" 561 | "@mui/styled-engine" "^5.11.8" 562 | "@mui/types" "^7.2.3" 563 | "@mui/utils" "^5.11.7" 564 | clsx "^1.2.1" 565 | csstype "^3.1.1" 566 | prop-types "^15.8.1" 567 | 568 | "@mui/types@^7.2.3": 569 | version "7.2.3" 570 | resolved "https://registry.yarnpkg.com/@mui/types/-/types-7.2.3.tgz#06faae1c0e2f3a31c86af6f28b3a4a42143670b9" 571 | integrity sha512-tZ+CQggbe9Ol7e/Fs5RcKwg/woU+o8DCtOnccX6KmbBc7YrfqMYEYuaIcXHuhpT880QwNkZZ3wQwvtlDFA2yOw== 572 | 573 | "@mui/utils@^5.11.7": 574 | version "5.11.7" 575 | resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-5.11.7.tgz#a343a5d375b4140c875bf4c96825c1a148994800" 576 | integrity sha512-8uyNDeVHZA804Ego20Erv8TpxlbqTe/EbhTI2H1UYr4/RiIbBprat8W4Qqr2UQIsC/b3DLz+0RQ6R/E5BxEcLA== 577 | dependencies: 578 | "@babel/runtime" "^7.20.7" 579 | "@types/prop-types" "^15.7.5" 580 | "@types/react-is" "^16.7.1 || ^17.0.0" 581 | prop-types "^15.8.1" 582 | react-is "^18.2.0" 583 | 584 | "@popperjs/core@^2.11.6": 585 | version "2.11.6" 586 | resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.6.tgz#cee20bd55e68a1720bdab363ecf0c821ded4cd45" 587 | integrity sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw== 588 | 589 | "@reduxjs/toolkit@^1.9.3": 590 | version "1.9.3" 591 | resolved "https://registry.yarnpkg.com/@reduxjs/toolkit/-/toolkit-1.9.3.tgz#27e1a33072b5a312e4f7fa19247fec160bbb2df9" 592 | integrity sha512-GU2TNBQVofL09VGmuSioNPQIu6Ml0YLf4EJhgj0AvBadRlCGzUWet8372LjvO4fqKZF2vH1xU0htAa7BrK9pZg== 593 | dependencies: 594 | immer "^9.0.16" 595 | redux "^4.2.0" 596 | redux-thunk "^2.4.2" 597 | reselect "^4.1.7" 598 | 599 | "@tauri-apps/api@^1.2.0": 600 | version "1.2.0" 601 | resolved "https://registry.yarnpkg.com/@tauri-apps/api/-/api-1.2.0.tgz#1f196b3e012971227f41b98214c846430a4eb477" 602 | integrity sha512-lsI54KI6HGf7VImuf/T9pnoejfgkNoXveP14pVV7XarrQ46rOejIVJLFqHI9sRReJMGdh2YuCoI3cc/yCWCsrw== 603 | 604 | "@tauri-apps/cli-darwin-arm64@1.2.3": 605 | version "1.2.3" 606 | resolved "https://registry.yarnpkg.com/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-1.2.3.tgz#dae9142e683c00199f4d7e088f22b564b08b9cac" 607 | integrity sha512-phJN3fN8FtZZwqXg08bcxfq1+X1JSDglLvRxOxB7VWPq+O5SuB8uLyssjJsu+PIhyZZnIhTGdjhzLSFhSXfLsw== 608 | 609 | "@tauri-apps/cli-darwin-x64@1.2.3": 610 | version "1.2.3" 611 | resolved "https://registry.yarnpkg.com/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-1.2.3.tgz#c6f84a11a1a7800e3e8e22c8fa5b95d0b3d1f802" 612 | integrity sha512-jFZ/y6z8z6v4yliIbXKBXA7BJgtZVMsITmEXSuD6s5+eCOpDhQxbRkr6CA+FFfr+/r96rWSDSgDenDQuSvPAKw== 613 | 614 | "@tauri-apps/cli-linux-arm-gnueabihf@1.2.3": 615 | version "1.2.3" 616 | resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-1.2.3.tgz#ecccec4c255ab32903fb36e1c746ed7b4eff0d1d" 617 | integrity sha512-C7h5vqAwXzY0kRGSU00Fj8PudiDWFCiQqqUNI1N+fhCILrzWZB9TPBwdx33ZfXKt/U4+emdIoo/N34v3TiAOmQ== 618 | 619 | "@tauri-apps/cli-linux-arm64-gnu@1.2.3": 620 | version "1.2.3" 621 | resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-1.2.3.tgz#c3915de83a8fbe6f406eaa0b524a17c091a9a2cd" 622 | integrity sha512-buf1c8sdkuUzVDkGPQpyUdAIIdn5r0UgXU6+H5fGPq/Xzt5K69JzXaeo6fHsZEZghbV0hOK+taKV4J0m30UUMQ== 623 | 624 | "@tauri-apps/cli-linux-arm64-musl@1.2.3": 625 | version "1.2.3" 626 | resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.2.3.tgz#40f9f7cf0b4088964661fd412eff7310cb4ac605" 627 | integrity sha512-x88wPS9W5xAyk392vc4uNHcKBBvCp0wf4H9JFMF9OBwB7vfd59LbQCFcPSu8f0BI7bPrOsyHqspWHuFL8ojQEA== 628 | 629 | "@tauri-apps/cli-linux-x64-gnu@1.2.3": 630 | version "1.2.3" 631 | resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-1.2.3.tgz#0b3e4c1fda6205dbe872f4b69506669476f60591" 632 | integrity sha512-ZMz1jxEVe0B4/7NJnlPHmwmSIuwiD6ViXKs8F+OWWz2Y4jn5TGxWKFg7DLx5OwQTRvEIZxxT7lXHi5CuTNAxKg== 633 | 634 | "@tauri-apps/cli-linux-x64-musl@1.2.3": 635 | version "1.2.3" 636 | resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-1.2.3.tgz#edcf8f53da50337a2e763d4fda750ef56124036c" 637 | integrity sha512-B/az59EjJhdbZDzawEVox0LQu2ZHCZlk8rJf85AMIktIUoAZPFbwyiUv7/zjzA/sY6Nb58OSJgaPL2/IBy7E0A== 638 | 639 | "@tauri-apps/cli-win32-ia32-msvc@1.2.3": 640 | version "1.2.3" 641 | resolved "https://registry.yarnpkg.com/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-1.2.3.tgz#0592d3e4eee4685674579ba897eef1469c6f1cfe" 642 | integrity sha512-ypdO1OdC5ugNJAKO2m3sb1nsd+0TSvMS9Tr5qN/ZSMvtSduaNwrcZ3D7G/iOIanrqu/Nl8t3LYlgPZGBKlw7Ng== 643 | 644 | "@tauri-apps/cli-win32-x64-msvc@1.2.3": 645 | version "1.2.3" 646 | resolved "https://registry.yarnpkg.com/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-1.2.3.tgz#89f0cc36e11e56564161602cd6add155cc7b0dfb" 647 | integrity sha512-CsbHQ+XhnV/2csOBBDVfH16cdK00gNyNYUW68isedmqcn8j+s0e9cQ1xXIqi+Hue3awp8g3ImYN5KPepf3UExw== 648 | 649 | "@tauri-apps/cli@^1.2.2": 650 | version "1.2.3" 651 | resolved "https://registry.yarnpkg.com/@tauri-apps/cli/-/cli-1.2.3.tgz#957f8a3a370f306e9e1ea5a891cb30aed91af64e" 652 | integrity sha512-erxtXuPhMEGJPBtnhPILD4AjuT81GZsraqpFvXAmEJZ2p8P6t7MVBifCL8LznRknznM3jn90D3M8RNBP3wcXTw== 653 | optionalDependencies: 654 | "@tauri-apps/cli-darwin-arm64" "1.2.3" 655 | "@tauri-apps/cli-darwin-x64" "1.2.3" 656 | "@tauri-apps/cli-linux-arm-gnueabihf" "1.2.3" 657 | "@tauri-apps/cli-linux-arm64-gnu" "1.2.3" 658 | "@tauri-apps/cli-linux-arm64-musl" "1.2.3" 659 | "@tauri-apps/cli-linux-x64-gnu" "1.2.3" 660 | "@tauri-apps/cli-linux-x64-musl" "1.2.3" 661 | "@tauri-apps/cli-win32-ia32-msvc" "1.2.3" 662 | "@tauri-apps/cli-win32-x64-msvc" "1.2.3" 663 | 664 | "@types/hoist-non-react-statics@^3.3.1": 665 | version "3.3.1" 666 | resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f" 667 | integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA== 668 | dependencies: 669 | "@types/react" "*" 670 | hoist-non-react-statics "^3.3.0" 671 | 672 | "@types/node@^18.7.10": 673 | version "18.11.18" 674 | resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.18.tgz#8dfb97f0da23c2293e554c5a50d61ef134d7697f" 675 | integrity sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA== 676 | 677 | "@types/parse-json@^4.0.0": 678 | version "4.0.0" 679 | resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" 680 | integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== 681 | 682 | "@types/prop-types@*", "@types/prop-types@^15.7.5": 683 | version "15.7.5" 684 | resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf" 685 | integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w== 686 | 687 | "@types/react-dom@^18.0.6": 688 | version "18.0.10" 689 | resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.0.10.tgz#3b66dec56aa0f16a6cc26da9e9ca96c35c0b4352" 690 | integrity sha512-E42GW/JA4Qv15wQdqJq8DL4JhNpB3prJgjgapN3qJT9K2zO5IIAQh4VXvCEDupoqAwnz0cY4RlXeC/ajX5SFHg== 691 | dependencies: 692 | "@types/react" "*" 693 | 694 | "@types/react-is@^16.7.1 || ^17.0.0": 695 | version "17.0.3" 696 | resolved "https://registry.yarnpkg.com/@types/react-is/-/react-is-17.0.3.tgz#2d855ba575f2fc8d17ef9861f084acc4b90a137a" 697 | integrity sha512-aBTIWg1emtu95bLTLx0cpkxwGW3ueZv71nE2YFBpL8k/z5czEW8yYpOo8Dp+UUAFAtKwNaOsh/ioSeQnWlZcfw== 698 | dependencies: 699 | "@types/react" "*" 700 | 701 | "@types/react-transition-group@^4.4.5": 702 | version "4.4.5" 703 | resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.5.tgz#aae20dcf773c5aa275d5b9f7cdbca638abc5e416" 704 | integrity sha512-juKD/eiSM3/xZYzjuzH6ZwpP+/lejltmiS3QEzV/vmb/Q8+HfDmxu+Baga8UEMGBqV88Nbg4l2hY/K2DkyaLLA== 705 | dependencies: 706 | "@types/react" "*" 707 | 708 | "@types/react@*", "@types/react@^18.0.15": 709 | version "18.0.27" 710 | resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.27.tgz#d9425abe187a00f8a5ec182b010d4fd9da703b71" 711 | integrity sha512-3vtRKHgVxu3Jp9t718R9BuzoD4NcQ8YJ5XRzsSKxNDiDonD2MXIT1TmSkenxuCycZJoQT5d2vE8LwWJxBC1gmA== 712 | dependencies: 713 | "@types/prop-types" "*" 714 | "@types/scheduler" "*" 715 | csstype "^3.0.2" 716 | 717 | "@types/scheduler@*": 718 | version "0.16.2" 719 | resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39" 720 | integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew== 721 | 722 | "@types/use-sync-external-store@^0.0.3": 723 | version "0.0.3" 724 | resolved "https://registry.yarnpkg.com/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz#b6725d5f4af24ace33b36fafd295136e75509f43" 725 | integrity sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA== 726 | 727 | "@vitejs/plugin-react@^3.0.0": 728 | version "3.0.1" 729 | resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-3.0.1.tgz#ad21fb81377970dd4021a31cd95a03eb6f5c4c48" 730 | integrity sha512-mx+QvYwIbbpOIJw+hypjnW1lAbKDHtWK5ibkF/V1/oMBu8HU/chb+SnqJDAsLq1+7rGqjktCEomMTM5KShzUKQ== 731 | dependencies: 732 | "@babel/core" "^7.20.7" 733 | "@babel/plugin-transform-react-jsx-self" "^7.18.6" 734 | "@babel/plugin-transform-react-jsx-source" "^7.19.6" 735 | magic-string "^0.27.0" 736 | react-refresh "^0.14.0" 737 | 738 | ansi-styles@^3.2.1: 739 | version "3.2.1" 740 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" 741 | integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== 742 | dependencies: 743 | color-convert "^1.9.0" 744 | 745 | babel-plugin-macros@^3.1.0: 746 | version "3.1.0" 747 | resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz#9ef6dc74deb934b4db344dc973ee851d148c50c1" 748 | integrity sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg== 749 | dependencies: 750 | "@babel/runtime" "^7.12.5" 751 | cosmiconfig "^7.0.0" 752 | resolve "^1.19.0" 753 | 754 | browserslist@^4.21.3: 755 | version "4.21.5" 756 | resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.5.tgz#75c5dae60063ee641f977e00edd3cfb2fb7af6a7" 757 | integrity sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w== 758 | dependencies: 759 | caniuse-lite "^1.0.30001449" 760 | electron-to-chromium "^1.4.284" 761 | node-releases "^2.0.8" 762 | update-browserslist-db "^1.0.10" 763 | 764 | callsites@^3.0.0: 765 | version "3.1.0" 766 | resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" 767 | integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== 768 | 769 | caniuse-lite@^1.0.30001449: 770 | version "1.0.30001450" 771 | resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001450.tgz#022225b91200589196b814b51b1bbe45144cf74f" 772 | integrity sha512-qMBmvmQmFXaSxexkjjfMvD5rnDL0+m+dUMZKoDYsGG8iZN29RuYh9eRoMvKsT6uMAWlyUUGDEQGJJYjzCIO9ew== 773 | 774 | chalk@^2.0.0: 775 | version "2.4.2" 776 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" 777 | integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== 778 | dependencies: 779 | ansi-styles "^3.2.1" 780 | escape-string-regexp "^1.0.5" 781 | supports-color "^5.3.0" 782 | 783 | clsx@^1.1.1, clsx@^1.2.1: 784 | version "1.2.1" 785 | resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12" 786 | integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg== 787 | 788 | color-convert@^1.9.0: 789 | version "1.9.3" 790 | resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" 791 | integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== 792 | dependencies: 793 | color-name "1.1.3" 794 | 795 | color-name@1.1.3: 796 | version "1.1.3" 797 | resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" 798 | integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== 799 | 800 | convert-source-map@^1.5.0, convert-source-map@^1.7.0: 801 | version "1.9.0" 802 | resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" 803 | integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== 804 | 805 | cosmiconfig@^7.0.0: 806 | version "7.1.0" 807 | resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.1.0.tgz#1443b9afa596b670082ea46cbd8f6a62b84635f6" 808 | integrity sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA== 809 | dependencies: 810 | "@types/parse-json" "^4.0.0" 811 | import-fresh "^3.2.1" 812 | parse-json "^5.0.0" 813 | path-type "^4.0.0" 814 | yaml "^1.10.0" 815 | 816 | csstype@^3.0.2, csstype@^3.1.1: 817 | version "3.1.1" 818 | resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.1.tgz#841b532c45c758ee546a11d5bd7b7b473c8c30b9" 819 | integrity sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw== 820 | 821 | debug@^4.1.0: 822 | version "4.3.4" 823 | resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" 824 | integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== 825 | dependencies: 826 | ms "2.1.2" 827 | 828 | dom-helpers@^5.0.1: 829 | version "5.2.1" 830 | resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902" 831 | integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA== 832 | dependencies: 833 | "@babel/runtime" "^7.8.7" 834 | csstype "^3.0.2" 835 | 836 | electron-to-chromium@^1.4.284: 837 | version "1.4.284" 838 | resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz#61046d1e4cab3a25238f6bf7413795270f125592" 839 | integrity sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA== 840 | 841 | error-ex@^1.3.1: 842 | version "1.3.2" 843 | resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" 844 | integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== 845 | dependencies: 846 | is-arrayish "^0.2.1" 847 | 848 | esbuild@^0.16.3: 849 | version "0.16.17" 850 | resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.16.17.tgz#fc2c3914c57ee750635fee71b89f615f25065259" 851 | integrity sha512-G8LEkV0XzDMNwXKgM0Jwu3nY3lSTwSGY6XbxM9cr9+s0T/qSV1q1JVPBGzm3dcjhCic9+emZDmMffkwgPeOeLg== 852 | optionalDependencies: 853 | "@esbuild/android-arm" "0.16.17" 854 | "@esbuild/android-arm64" "0.16.17" 855 | "@esbuild/android-x64" "0.16.17" 856 | "@esbuild/darwin-arm64" "0.16.17" 857 | "@esbuild/darwin-x64" "0.16.17" 858 | "@esbuild/freebsd-arm64" "0.16.17" 859 | "@esbuild/freebsd-x64" "0.16.17" 860 | "@esbuild/linux-arm" "0.16.17" 861 | "@esbuild/linux-arm64" "0.16.17" 862 | "@esbuild/linux-ia32" "0.16.17" 863 | "@esbuild/linux-loong64" "0.16.17" 864 | "@esbuild/linux-mips64el" "0.16.17" 865 | "@esbuild/linux-ppc64" "0.16.17" 866 | "@esbuild/linux-riscv64" "0.16.17" 867 | "@esbuild/linux-s390x" "0.16.17" 868 | "@esbuild/linux-x64" "0.16.17" 869 | "@esbuild/netbsd-x64" "0.16.17" 870 | "@esbuild/openbsd-x64" "0.16.17" 871 | "@esbuild/sunos-x64" "0.16.17" 872 | "@esbuild/win32-arm64" "0.16.17" 873 | "@esbuild/win32-ia32" "0.16.17" 874 | "@esbuild/win32-x64" "0.16.17" 875 | 876 | escalade@^3.1.1: 877 | version "3.1.1" 878 | resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" 879 | integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== 880 | 881 | escape-string-regexp@^1.0.5: 882 | version "1.0.5" 883 | resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" 884 | integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== 885 | 886 | escape-string-regexp@^4.0.0: 887 | version "4.0.0" 888 | resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" 889 | integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== 890 | 891 | find-root@^1.1.0: 892 | version "1.1.0" 893 | resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4" 894 | integrity sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng== 895 | 896 | fsevents@~2.3.2: 897 | version "2.3.2" 898 | resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" 899 | integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== 900 | 901 | function-bind@^1.1.1: 902 | version "1.1.1" 903 | resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" 904 | integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== 905 | 906 | gensync@^1.0.0-beta.2: 907 | version "1.0.0-beta.2" 908 | resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" 909 | integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== 910 | 911 | globals@^11.1.0: 912 | version "11.12.0" 913 | resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" 914 | integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== 915 | 916 | has-flag@^3.0.0: 917 | version "3.0.0" 918 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" 919 | integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== 920 | 921 | has@^1.0.3: 922 | version "1.0.3" 923 | resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" 924 | integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== 925 | dependencies: 926 | function-bind "^1.1.1" 927 | 928 | hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.1, hoist-non-react-statics@^3.3.2: 929 | version "3.3.2" 930 | resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" 931 | integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== 932 | dependencies: 933 | react-is "^16.7.0" 934 | 935 | immer@^9.0.16: 936 | version "9.0.19" 937 | resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.19.tgz#67fb97310555690b5f9cd8380d38fc0aabb6b38b" 938 | integrity sha512-eY+Y0qcsB4TZKwgQzLaE/lqYMlKhv5J9dyd2RhhtGhNo2njPXDqU9XPfcNfa3MIDsdtZt5KlkIsirlo4dHsWdQ== 939 | 940 | import-fresh@^3.2.1: 941 | version "3.3.0" 942 | resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" 943 | integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== 944 | dependencies: 945 | parent-module "^1.0.0" 946 | resolve-from "^4.0.0" 947 | 948 | is-arrayish@^0.2.1: 949 | version "0.2.1" 950 | resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" 951 | integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== 952 | 953 | is-core-module@^2.9.0: 954 | version "2.11.0" 955 | resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.11.0.tgz#ad4cb3e3863e814523c96f3f58d26cc570ff0144" 956 | integrity sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw== 957 | dependencies: 958 | has "^1.0.3" 959 | 960 | "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: 961 | version "4.0.0" 962 | resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" 963 | integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== 964 | 965 | jsesc@^2.5.1: 966 | version "2.5.2" 967 | resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" 968 | integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== 969 | 970 | json-parse-even-better-errors@^2.3.0: 971 | version "2.3.1" 972 | resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" 973 | integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== 974 | 975 | json5@^2.2.2: 976 | version "2.2.3" 977 | resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" 978 | integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== 979 | 980 | lines-and-columns@^1.1.6: 981 | version "1.2.4" 982 | resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" 983 | integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== 984 | 985 | loose-envify@^1.1.0, loose-envify@^1.4.0: 986 | version "1.4.0" 987 | resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" 988 | integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== 989 | dependencies: 990 | js-tokens "^3.0.0 || ^4.0.0" 991 | 992 | lru-cache@^5.1.1: 993 | version "5.1.1" 994 | resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" 995 | integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== 996 | dependencies: 997 | yallist "^3.0.2" 998 | 999 | magic-string@^0.27.0: 1000 | version "0.27.0" 1001 | resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.27.0.tgz#e4a3413b4bab6d98d2becffd48b4a257effdbbf3" 1002 | integrity sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA== 1003 | dependencies: 1004 | "@jridgewell/sourcemap-codec" "^1.4.13" 1005 | 1006 | ms@2.1.2: 1007 | version "2.1.2" 1008 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" 1009 | integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== 1010 | 1011 | nanoid@^3.3.4: 1012 | version "3.3.4" 1013 | resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab" 1014 | integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw== 1015 | 1016 | node-releases@^2.0.8: 1017 | version "2.0.9" 1018 | resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.9.tgz#fe66405285382b0c4ac6bcfbfbe7e8a510650b4d" 1019 | integrity sha512-2xfmOrRkGogbTK9R6Leda0DGiXeY3p2NJpy4+gNCffdUvV6mdEJnaDEic1i3Ec2djAo8jWYoJMR5PB0MSMpxUA== 1020 | 1021 | object-assign@^4.1.1: 1022 | version "4.1.1" 1023 | resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" 1024 | integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== 1025 | 1026 | parent-module@^1.0.0: 1027 | version "1.0.1" 1028 | resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" 1029 | integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== 1030 | dependencies: 1031 | callsites "^3.0.0" 1032 | 1033 | parse-json@^5.0.0: 1034 | version "5.2.0" 1035 | resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" 1036 | integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== 1037 | dependencies: 1038 | "@babel/code-frame" "^7.0.0" 1039 | error-ex "^1.3.1" 1040 | json-parse-even-better-errors "^2.3.0" 1041 | lines-and-columns "^1.1.6" 1042 | 1043 | path-parse@^1.0.7: 1044 | version "1.0.7" 1045 | resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" 1046 | integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== 1047 | 1048 | path-type@^4.0.0: 1049 | version "4.0.0" 1050 | resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" 1051 | integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== 1052 | 1053 | picocolors@^1.0.0: 1054 | version "1.0.0" 1055 | resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" 1056 | integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== 1057 | 1058 | postcss@^8.4.20: 1059 | version "8.4.21" 1060 | resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.21.tgz#c639b719a57efc3187b13a1d765675485f4134f4" 1061 | integrity sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg== 1062 | dependencies: 1063 | nanoid "^3.3.4" 1064 | picocolors "^1.0.0" 1065 | source-map-js "^1.0.2" 1066 | 1067 | prop-types@^15.6.2, prop-types@^15.8.1: 1068 | version "15.8.1" 1069 | resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" 1070 | integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== 1071 | dependencies: 1072 | loose-envify "^1.4.0" 1073 | object-assign "^4.1.1" 1074 | react-is "^16.13.1" 1075 | 1076 | react-dom@^18.2.0: 1077 | version "18.2.0" 1078 | resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d" 1079 | integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g== 1080 | dependencies: 1081 | loose-envify "^1.1.0" 1082 | scheduler "^0.23.0" 1083 | 1084 | react-draggable@^4.4.5: 1085 | version "4.4.5" 1086 | resolved "https://registry.yarnpkg.com/react-draggable/-/react-draggable-4.4.5.tgz#9e37fe7ce1a4cf843030f521a0a4cc41886d7e7c" 1087 | integrity sha512-OMHzJdyJbYTZo4uQE393fHcqqPYsEtkjfMgvCHr6rejT+Ezn4OZbNyGH50vv+SunC1RMvwOTSWkEODQLzw1M9g== 1088 | dependencies: 1089 | clsx "^1.1.1" 1090 | prop-types "^15.8.1" 1091 | 1092 | react-is@^16.13.1, react-is@^16.7.0: 1093 | version "16.13.1" 1094 | resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" 1095 | integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== 1096 | 1097 | react-is@^18.0.0, react-is@^18.2.0: 1098 | version "18.2.0" 1099 | resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" 1100 | integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== 1101 | 1102 | react-redux@^8.0.5: 1103 | version "8.0.5" 1104 | resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-8.0.5.tgz#e5fb8331993a019b8aaf2e167a93d10af469c7bd" 1105 | integrity sha512-Q2f6fCKxPFpkXt1qNRZdEDLlScsDWyrgSj0mliK59qU6W5gvBiKkdMEG2lJzhd1rCctf0hb6EtePPLZ2e0m1uw== 1106 | dependencies: 1107 | "@babel/runtime" "^7.12.1" 1108 | "@types/hoist-non-react-statics" "^3.3.1" 1109 | "@types/use-sync-external-store" "^0.0.3" 1110 | hoist-non-react-statics "^3.3.2" 1111 | react-is "^18.0.0" 1112 | use-sync-external-store "^1.0.0" 1113 | 1114 | react-refresh@^0.14.0: 1115 | version "0.14.0" 1116 | resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.0.tgz#4e02825378a5f227079554d4284889354e5f553e" 1117 | integrity sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ== 1118 | 1119 | react-transition-group@^4.4.5: 1120 | version "4.4.5" 1121 | resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1" 1122 | integrity sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g== 1123 | dependencies: 1124 | "@babel/runtime" "^7.5.5" 1125 | dom-helpers "^5.0.1" 1126 | loose-envify "^1.4.0" 1127 | prop-types "^15.6.2" 1128 | 1129 | react@^18.2.0: 1130 | version "18.2.0" 1131 | resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5" 1132 | integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ== 1133 | dependencies: 1134 | loose-envify "^1.1.0" 1135 | 1136 | redux-thunk@^2.4.2: 1137 | version "2.4.2" 1138 | resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.4.2.tgz#b9d05d11994b99f7a91ea223e8b04cf0afa5ef3b" 1139 | integrity sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q== 1140 | 1141 | redux@^4.2.0, redux@^4.2.1: 1142 | version "4.2.1" 1143 | resolved "https://registry.yarnpkg.com/redux/-/redux-4.2.1.tgz#c08f4306826c49b5e9dc901dee0452ea8fce6197" 1144 | integrity sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w== 1145 | dependencies: 1146 | "@babel/runtime" "^7.9.2" 1147 | 1148 | regenerator-runtime@^0.13.11: 1149 | version "0.13.11" 1150 | resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" 1151 | integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== 1152 | 1153 | reselect@^4.1.7: 1154 | version "4.1.7" 1155 | resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.1.7.tgz#56480d9ff3d3188970ee2b76527bd94a95567a42" 1156 | integrity sha512-Zu1xbUt3/OPwsXL46hvOOoQrap2azE7ZQbokq61BQfiXvhewsKDwhMeZjTX9sX0nvw1t/U5Audyn1I9P/m9z0A== 1157 | 1158 | resolve-from@^4.0.0: 1159 | version "4.0.0" 1160 | resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" 1161 | integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== 1162 | 1163 | resolve@^1.19.0, resolve@^1.22.1: 1164 | version "1.22.1" 1165 | resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177" 1166 | integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== 1167 | dependencies: 1168 | is-core-module "^2.9.0" 1169 | path-parse "^1.0.7" 1170 | supports-preserve-symlinks-flag "^1.0.0" 1171 | 1172 | rollup@^3.7.0: 1173 | version "3.12.1" 1174 | resolved "https://registry.yarnpkg.com/rollup/-/rollup-3.12.1.tgz#2975b97713e4af98c15e7024b88292d7fddb3853" 1175 | integrity sha512-t9elERrz2i4UU9z7AwISj3CQcXP39cWxgRWLdf4Tm6aKm1eYrqHIgjzXBgb67GNY1sZckTFFi0oMozh3/S++Ig== 1176 | optionalDependencies: 1177 | fsevents "~2.3.2" 1178 | 1179 | scheduler@^0.23.0: 1180 | version "0.23.0" 1181 | resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.0.tgz#ba8041afc3d30eb206a487b6b384002e4e61fdfe" 1182 | integrity sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw== 1183 | dependencies: 1184 | loose-envify "^1.1.0" 1185 | 1186 | semver@^6.3.0: 1187 | version "6.3.0" 1188 | resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" 1189 | integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== 1190 | 1191 | source-map-js@^1.0.2: 1192 | version "1.0.2" 1193 | resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" 1194 | integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== 1195 | 1196 | source-map@^0.5.7: 1197 | version "0.5.7" 1198 | resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" 1199 | integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ== 1200 | 1201 | stylis@4.1.3: 1202 | version "4.1.3" 1203 | resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.1.3.tgz#fd2fbe79f5fed17c55269e16ed8da14c84d069f7" 1204 | integrity sha512-GP6WDNWf+o403jrEp9c5jibKavrtLW+/qYGhFxFrG8maXhwTBI7gLLhiBb0o7uFccWN+EOS9aMO6cGHWAO07OA== 1205 | 1206 | supports-color@^5.3.0: 1207 | version "5.5.0" 1208 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" 1209 | integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== 1210 | dependencies: 1211 | has-flag "^3.0.0" 1212 | 1213 | supports-preserve-symlinks-flag@^1.0.0: 1214 | version "1.0.0" 1215 | resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" 1216 | integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== 1217 | 1218 | to-fast-properties@^2.0.0: 1219 | version "2.0.0" 1220 | resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" 1221 | integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== 1222 | 1223 | typescript@^4.6.4: 1224 | version "4.9.5" 1225 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a" 1226 | integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== 1227 | 1228 | update-browserslist-db@^1.0.10: 1229 | version "1.0.10" 1230 | resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz#0f54b876545726f17d00cd9a2561e6dade943ff3" 1231 | integrity sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ== 1232 | dependencies: 1233 | escalade "^3.1.1" 1234 | picocolors "^1.0.0" 1235 | 1236 | use-sync-external-store@^1.0.0: 1237 | version "1.2.0" 1238 | resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a" 1239 | integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA== 1240 | 1241 | vite@^4.0.0: 1242 | version "4.0.4" 1243 | resolved "https://registry.yarnpkg.com/vite/-/vite-4.0.4.tgz#4612ce0b47bbb233a887a54a4ae0c6e240a0da31" 1244 | integrity sha512-xevPU7M8FU0i/80DMR+YhgrzR5KS2ORy1B4xcX/cXLsvnUWvfHuqMmVU6N0YiJ4JWGRJJsLCgjEzKjG9/GKoSw== 1245 | dependencies: 1246 | esbuild "^0.16.3" 1247 | postcss "^8.4.20" 1248 | resolve "^1.22.1" 1249 | rollup "^3.7.0" 1250 | optionalDependencies: 1251 | fsevents "~2.3.2" 1252 | 1253 | yallist@^3.0.2: 1254 | version "3.1.1" 1255 | resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" 1256 | integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== 1257 | 1258 | yaml@^1.10.0: 1259 | version "1.10.2" 1260 | resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" 1261 | integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== 1262 | --------------------------------------------------------------------------------