diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..ba18a20 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,33 @@ +{ + "name": "Debian", + "image": "mcr.microsoft.com/devcontainers/base:debian", + + "features": { + "ghcr.io/devcontainers/features/github-cli:1": {}, + "ghcr.io/devcontainers/features/rust:1": {}, + "ghcr.io/devcontainers/features/node:1": {}, + "ghcr.io/guiyomh/features/just:0": {} + }, + + "forwardPorts": [3000], + "customizations": { + "vscode": { + "extensions": [ + "GitHub.copilot", + "GitHub.copilot-chat", + "github.vscode-github-actions", + + "rust-lang.rust-analyzer", + "tamasfe.even-better-toml", + "serayuzgur.crates", + "vadimcn.vscode-lldb", + + "esbenp.prettier-vscode", + "dbaeumer.vscode-eslint", + "svelte.svelte-vscode" + ] + } + }, + "remoteUser": "vscode", + "postCreateCommand": "sudo apt-get update && sudo apt-get install -y emscripten && rustup target add wasm32-unknown-emscripten" +} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b04772c..1fb6e84 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,12 +15,6 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 - - name: Add release date to index.html - run: | - VERSION="$(date +'%Y-%m-%d %H:%M')" - sed --in-place "s//$VERSION/g" index.html - shell: bash - - name: Setup emscripten SDK uses: mymindstorm/setup-emsdk@v14 with: @@ -29,6 +23,11 @@ jobs: - name: Verify emscripten run: emcc -v + - name: Setup just + uses: extractions/setup-just@v2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Setup Stable Rust with emscripten target run: rustup target add wasm32-unknown-emscripten @@ -41,16 +40,33 @@ jobs: - name: Cargo fmt run: cargo fmt --all --check - - name: Cargo test - run: cargo test + - name: Test + run: just test + + - name: Build site + run: just b + + - name: Install Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + cache-dependency-path: app/package-lock.json + + - name: Install dependencies + run: npm ci + working-directory: app + + - name: build + run: | + npm run build + working-directory: app - - name: Build site into the public/ dir - run: ./script/build.sh - uses: actions/upload-pages-artifact@v3 if: ${{ github.event_name != 'pull_request' }} with: - path: './public' + path: './app/dist' retention-days: '1' deploy: diff --git a/.gitignore b/.gitignore index 93b6069..4d0b68a 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,5 @@ **/*.pdb # Static -**/public/ +**/public/pa.wasm +**/public/pa.js diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..caf2064 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,11 @@ +{ + "recommendations": [ + "rust-lang.rust-analyzer", + "tamasfe.even-better-toml", + "serayuzgur.crates", + "vadimcn.vscode-lldb", + "esbenp.prettier-vscode", + "dbaeumer.vscode-eslint", + "svelte.svelte-vscode" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..f7dde78 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "rust-analyzer.check.command": "clippy", + "rust-analyzer.checkOnSave": true, + "svelte.enable-ts-plugin": true, + "[svelte]": { + "editor.defaultFormatter": "svelte.svelte-vscode" + } +} diff --git a/Cargo.toml b/Cargo.toml index c25b0a4..6d8ab1b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,10 +1,13 @@ [workspace] -members = [ - "dist", - "power" -] +members = ["dist", "power"] resolver = "2" [profile.release] # Tell `rustc` to optimize for small code size. opt-level = "s" + +[workspace.package] +authors = [ + "Rik Huijzer ", + "Jose Storopoli ", +] diff --git a/README.md b/README.md index 36ea546..d31468c 100644 --- a/README.md +++ b/README.md @@ -4,12 +4,14 @@ Statistical power analyses in the browser via R's nmath library and WebAssembly. ## License -The favicon is obtained from Flaticon (https://www.flaticon.com/free-icon/statistics_4064965). +The favicon is obtained from [Flaticon](https://www.flaticon.com/free-icon/statistics_4064965). ## Developer notes Going via emscripten because we link a C library. -A big thanks to https://github.com/rustwasm/team/issues/291#issuecomment-644946504 for writing down how to build a C library to WebAssembly via Rust. +A big thanks to [`rustwasm/team`](https://github.com/rustwasm/team/issues/291#issuecomment-644946504) +for writing down how to build a C library to WebAssembly via Rust. -For development, checkout the scripts in the [`scripts` folder](https://github.com/poweranalyses-org/poweranalyses/tree/main/script). +For development, checkout the scripts in the [`justfile`](https://github.com/poweranalyses-org/poweranalyses/tree/main/justfile). +If you are lazy just run `just`. diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/app/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/app/README.md b/app/README.md new file mode 100644 index 0000000..3d1dc58 --- /dev/null +++ b/app/README.md @@ -0,0 +1,6 @@ +# Svelte + Vite + +This is the App's reactive frontend. + +The app is built with [Svelte](https://svelte.dev) and Vite. +We are using Svelte 5 for reactivity and leaning heavily in the new Runes API. diff --git a/app/index.html b/app/index.html new file mode 100644 index 0000000..86687c1 --- /dev/null +++ b/app/index.html @@ -0,0 +1,25 @@ + + + + + PowerAnalyses.org + + + + + + + + + +
+ + + + diff --git a/app/jsconfig.json b/app/jsconfig.json new file mode 100644 index 0000000..85821c2 --- /dev/null +++ b/app/jsconfig.json @@ -0,0 +1,44 @@ +{ + "compilerOptions": { + "moduleResolution": "bundler", + "target": "ESNext", + "module": "ESNext", + /** + * svelte-preprocess cannot figure out whether you have + * a value or a type, so tell TypeScript to enforce using + * `import type` instead of `import` for Types. + */ + "verbatimModuleSyntax": true, + "isolatedModules": true, + "resolveJsonModule": true, + /** + * To have warnings / errors of the Svelte compiler at the + * correct position, enable source maps by default. + */ + "sourceMap": true, + "esModuleInterop": true, + "skipLibCheck": true, + /** + * Typecheck JS in `.svelte` and `.js` files by default. + * Disable this if you'd like to use dynamic types. + */ + "checkJs": true, + "plugins": [ + { + "name": "typescript-svelte-plugin", + // the following options can be set additionally; they are optional; their default values are listed here + "enabled": true, // enables this plugin + "assumeIsSvelteProject": false // if true, skip detection and always assume it's a Svelte project + } + ] + }, + /** + * Use global.d.ts instead of compilerOptions.types + * to avoid limiting type declarations. + */ + "include": [ + "src/**/*.d.ts", + "src/**/*.js", + "src/**/*.svelte" + ] +} diff --git a/app/package-lock.json b/app/package-lock.json new file mode 100644 index 0000000..3589b82 --- /dev/null +++ b/app/package-lock.json @@ -0,0 +1,1048 @@ +{ + "name": "app", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "app", + "version": "0.1.0", + "devDependencies": { + "@sveltejs/vite-plugin-svelte": "^3.0.2", + "svelte": "^5.0.0-next.70", + "vite": "^5.1.4" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", + "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", + "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz", + "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz", + "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz", + "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", + "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz", + "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz", + "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz", + "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz", + "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz", + "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz", + "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz", + "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz", + "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz", + "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz", + "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz", + "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", + "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz", + "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", + "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz", + "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz", + "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz", + "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz", + "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.24", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.24.tgz", + "integrity": "sha512-+VaWXDa6+l6MhflBvVXjIEAzb59nQ2JUK3bwRp2zRpPtU+8TFRy9Gg/5oIcNlkEL5PGlBFGfemUVvIgLnTzq7Q==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.12.0.tgz", + "integrity": "sha512-+ac02NL/2TCKRrJu2wffk1kZ+RyqxVUlbjSagNgPm94frxtr+XDL12E5Ll1enWskLrtrZ2r8L3wED1orIibV/w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.12.0.tgz", + "integrity": "sha512-OBqcX2BMe6nvjQ0Nyp7cC90cnumt8PXmO7Dp3gfAju/6YwG0Tj74z1vKrfRz7qAv23nBcYM8BCbhrsWqO7PzQQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.12.0.tgz", + "integrity": "sha512-X64tZd8dRE/QTrBIEs63kaOBG0b5GVEd3ccoLtyf6IdXtHdh8h+I56C2yC3PtC9Ucnv0CpNFJLqKFVgCYe0lOQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.12.0.tgz", + "integrity": "sha512-cc71KUZoVbUJmGP2cOuiZ9HSOP14AzBAThn3OU+9LcA1+IUqswJyR1cAJj3Mg55HbjZP6OLAIscbQsQLrpgTOg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.12.0.tgz", + "integrity": "sha512-a6w/Y3hyyO6GlpKL2xJ4IOh/7d+APaqLYdMf86xnczU3nurFTaVN9s9jOXQg97BE4nYm/7Ga51rjec5nfRdrvA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.12.0.tgz", + "integrity": "sha512-0fZBq27b+D7Ar5CQMofVN8sggOVhEtzFUwOwPppQt0k+VR+7UHMZZY4y+64WJ06XOhBTKXtQB/Sv0NwQMXyNAA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.12.0.tgz", + "integrity": "sha512-eTvzUS3hhhlgeAv6bfigekzWZjaEX9xP9HhxB0Dvrdbkk5w/b+1Sxct2ZuDxNJKzsRStSq1EaEkVSEe7A7ipgQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.12.0.tgz", + "integrity": "sha512-ix+qAB9qmrCRiaO71VFfY8rkiAZJL8zQRXveS27HS+pKdjwUfEhqo2+YF2oI+H/22Xsiski+qqwIBxVewLK7sw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.12.0.tgz", + "integrity": "sha512-TenQhZVOtw/3qKOPa7d+QgkeM6xY0LtwzR8OplmyL5LrgTWIXpTQg2Q2ycBf8jm+SFW2Wt/DTn1gf7nFp3ssVA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.12.0.tgz", + "integrity": "sha512-LfFdRhNnW0zdMvdCb5FNuWlls2WbbSridJvxOvYWgSBOYZtgBfW9UGNJG//rwMqTX1xQE9BAodvMH9tAusKDUw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.12.0.tgz", + "integrity": "sha512-JPDxovheWNp6d7AHCgsUlkuCKvtu3RB55iNEkaQcf0ttsDU/JZF+iQnYcQJSk/7PtT4mjjVG8N1kpwnI9SLYaw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.12.0.tgz", + "integrity": "sha512-fjtuvMWRGJn1oZacG8IPnzIV6GF2/XG+h71FKn76OYFqySXInJtseAqdprVTDTyqPxQOG9Exak5/E9Z3+EJ8ZA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.12.0.tgz", + "integrity": "sha512-ZYmr5mS2wd4Dew/JjT0Fqi2NPB/ZhZ2VvPp7SmvPZb4Y1CG/LRcS6tcRo2cYU7zLK5A7cdbhWnnWmUjoI4qapg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sveltejs/vite-plugin-svelte": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-3.0.2.tgz", + "integrity": "sha512-MpmF/cju2HqUls50WyTHQBZUV3ovV/Uk8k66AN2gwHogNAG8wnW8xtZDhzNBsFJJuvmq1qnzA5kE7YfMJNFv2Q==", + "dev": true, + "dependencies": { + "@sveltejs/vite-plugin-svelte-inspector": "^2.0.0", + "debug": "^4.3.4", + "deepmerge": "^4.3.1", + "kleur": "^4.1.5", + "magic-string": "^0.30.5", + "svelte-hmr": "^0.15.3", + "vitefu": "^0.2.5" + }, + "engines": { + "node": "^18.0.0 || >=20" + }, + "peerDependencies": { + "svelte": "^4.0.0 || ^5.0.0-next.0", + "vite": "^5.0.0" + } + }, + "node_modules/@sveltejs/vite-plugin-svelte-inspector": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-2.0.0.tgz", + "integrity": "sha512-gjr9ZFg1BSlIpfZ4PRewigrvYmHWbDrq2uvvPB1AmTWKuM+dI1JXQSUu2pIrYLb/QncyiIGkFDFKTwJ0XqQZZg==", + "dev": true, + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.0.0 || >=20" + }, + "peerDependencies": { + "@sveltejs/vite-plugin-svelte": "^3.0.0", + "svelte": "^4.0.0 || ^5.0.0-next.0", + "vite": "^5.0.0" + } + }, + "node_modules/@sveltejs/vite-plugin-svelte/node_modules/svelte-hmr": { + "version": "0.15.3", + "resolved": "https://registry.npmjs.org/svelte-hmr/-/svelte-hmr-0.15.3.tgz", + "integrity": "sha512-41snaPswvSf8TJUhlkoJBekRrABDXDMdpNpT2tfHIv4JuhgvHqLMhEPGtaQn0BmbNSTkuz2Ed20DF2eHw0SmBQ==", + "dev": true, + "engines": { + "node": "^12.20 || ^14.13.1 || >= 16" + }, + "peerDependencies": { + "svelte": "^3.19.0 || ^4.0.0" + } + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + }, + "node_modules/acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-typescript": { + "version": "1.4.13", + "resolved": "https://registry.npmjs.org/acorn-typescript/-/acorn-typescript-1.4.13.tgz", + "integrity": "sha512-xsc9Xv0xlVfwp2o7sQ+GCQ1PgbkdcpWdTzrwXxO3xDMTAywVS3oXVOcOHuRjAPkS4P9b+yc/qNF15460v+jp4Q==", + "dev": true, + "peerDependencies": { + "acorn": ">=8.9.0" + } + }, + "node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/axobject-query": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.0.0.tgz", + "integrity": "sha512-+60uv1hiVFhHZeO+Lz0RYzsVHy5Wr1ayX0mwda9KPDVLNJgZ1T9Ny7VmFbLDzxsH0D87I86vgj3gFrjTJUYznw==", + "dev": true, + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/esbuild": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", + "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.19.12", + "@esbuild/android-arm": "0.19.12", + "@esbuild/android-arm64": "0.19.12", + "@esbuild/android-x64": "0.19.12", + "@esbuild/darwin-arm64": "0.19.12", + "@esbuild/darwin-x64": "0.19.12", + "@esbuild/freebsd-arm64": "0.19.12", + "@esbuild/freebsd-x64": "0.19.12", + "@esbuild/linux-arm": "0.19.12", + "@esbuild/linux-arm64": "0.19.12", + "@esbuild/linux-ia32": "0.19.12", + "@esbuild/linux-loong64": "0.19.12", + "@esbuild/linux-mips64el": "0.19.12", + "@esbuild/linux-ppc64": "0.19.12", + "@esbuild/linux-riscv64": "0.19.12", + "@esbuild/linux-s390x": "0.19.12", + "@esbuild/linux-x64": "0.19.12", + "@esbuild/netbsd-x64": "0.19.12", + "@esbuild/openbsd-x64": "0.19.12", + "@esbuild/sunos-x64": "0.19.12", + "@esbuild/win32-arm64": "0.19.12", + "@esbuild/win32-ia32": "0.19.12", + "@esbuild/win32-x64": "0.19.12" + } + }, + "node_modules/esm-env": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.0.0.tgz", + "integrity": "sha512-Cf6VksWPsTuW01vU9Mk/3vRue91Zevka5SjyNf3nEpokFRuqt/KjUQoGAwq9qMmhpLTHmXzSIrFRw8zxWzmFBA==", + "dev": true + }, + "node_modules/esrap": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/esrap/-/esrap-1.2.1.tgz", + "integrity": "sha512-dhkcOLfN/aDdMFI1iwPEcy/XqAZzGNfgfEJjZozy2tia6u0dQoZyXzkRshHTckuNsM+c0CYQndY+uRFe3N+AIQ==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15", + "@types/estree": "^1.0.1" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/is-reference": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.2.tgz", + "integrity": "sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==", + "dev": true, + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/locate-character": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", + "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", + "dev": true + }, + "node_modules/magic-string": { + "version": "0.30.7", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.7.tgz", + "integrity": "sha512-8vBuFF/I/+OSLRmdf2wwFCJCz+nSn0m6DPvGH1fS/KiQoSaR+sETbov0eIk9KhEKy8CYqIkIAnbohxT/4H0kuA==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/postcss": { + "version": "8.4.35", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz", + "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rollup": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.12.0.tgz", + "integrity": "sha512-wz66wn4t1OHIJw3+XU7mJJQV/2NAfw5OAk6G6Hoo3zcvz/XOfQ52Vgi+AN4Uxoxi0KBBwk2g8zPrTDA4btSB/Q==", + "dev": true, + "dependencies": { + "@types/estree": "1.0.5" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.12.0", + "@rollup/rollup-android-arm64": "4.12.0", + "@rollup/rollup-darwin-arm64": "4.12.0", + "@rollup/rollup-darwin-x64": "4.12.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.12.0", + "@rollup/rollup-linux-arm64-gnu": "4.12.0", + "@rollup/rollup-linux-arm64-musl": "4.12.0", + "@rollup/rollup-linux-riscv64-gnu": "4.12.0", + "@rollup/rollup-linux-x64-gnu": "4.12.0", + "@rollup/rollup-linux-x64-musl": "4.12.0", + "@rollup/rollup-win32-arm64-msvc": "4.12.0", + "@rollup/rollup-win32-ia32-msvc": "4.12.0", + "@rollup/rollup-win32-x64-msvc": "4.12.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/svelte": { + "version": "5.0.0-next.70", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.0.0-next.70.tgz", + "integrity": "sha512-o24bDlhjz4MLdc9Lq1SVdLarv9MDgYFTOmzFcAqytRcG0AMOw+LGMoTb8gkFTACOu4exbvL18RS8Z8xgzIrDoA==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.1", + "@jridgewell/sourcemap-codec": "^1.4.15", + "@types/estree": "^1.0.5", + "acorn": "^8.11.3", + "acorn-typescript": "^1.4.13", + "aria-query": "^5.3.0", + "axobject-query": "^4.0.0", + "esm-env": "^1.0.0", + "esrap": "^1.2.1", + "is-reference": "^3.0.2", + "locate-character": "^3.0.0", + "magic-string": "^0.30.5", + "zimmerframe": "^1.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/vite": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.4.tgz", + "integrity": "sha512-n+MPqzq+d9nMVTKyewqw6kSt+R3CkvF9QAKY8obiQn8g1fwTscKxyfaYnC632HtBXAQGc1Yjomphwn1dtwGAHg==", + "dev": true, + "dependencies": { + "esbuild": "^0.19.3", + "postcss": "^8.4.35", + "rollup": "^4.2.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vitefu": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-0.2.5.tgz", + "integrity": "sha512-SgHtMLoqaeeGnd2evZ849ZbACbnwQCIwRH57t18FxcXoZop0uQu0uzlIhJBlF/eWVzuce0sHeqPcDo+evVcg8Q==", + "dev": true, + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "node_modules/zimmerframe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.2.tgz", + "integrity": "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==", + "dev": true + } + } +} diff --git a/app/package.json b/app/package.json new file mode 100644 index 0000000..9a71580 --- /dev/null +++ b/app/package.json @@ -0,0 +1,20 @@ +{ + "name": "app", + "contributors": [ + "Jose Storopoli ", + "Rik Huijzer " + ], + "private": true, + "version": "0.1.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "devDependencies": { + "@sveltejs/vite-plugin-svelte": "^3.0.2", + "svelte": "^5.0.0-next.70", + "vite": "^5.1.4" + } +} diff --git a/favicon.png b/app/public/favicon.png similarity index 100% rename from favicon.png rename to app/public/favicon.png diff --git a/app/src/App.svelte b/app/src/App.svelte new file mode 100644 index 0000000..2dd8b65 --- /dev/null +++ b/app/src/App.svelte @@ -0,0 +1,118 @@ + + + + +
+
+ favicon + PowerAnalyses.org Beta +
+ + +
+
+ diff --git a/app/src/assets/.gitignore b/app/src/assets/.gitignore new file mode 100644 index 0000000..2d9e9fb --- /dev/null +++ b/app/src/assets/.gitignore @@ -0,0 +1,2 @@ +pa.wasm +pa.js diff --git a/app/src/assets/favicon.png b/app/src/assets/favicon.png new file mode 100644 index 0000000..82d2d22 Binary files /dev/null and b/app/src/assets/favicon.png differ diff --git a/style.css b/app/src/assets/style.css similarity index 100% rename from style.css rename to app/src/assets/style.css diff --git a/app/src/lib/Footer.svelte b/app/src/lib/Footer.svelte new file mode 100644 index 0000000..f68f336 --- /dev/null +++ b/app/src/lib/Footer.svelte @@ -0,0 +1,27 @@ + + +
+
+
+
+
Why this web site?

+ Statistical power analyses play a key role in science. + However, the code for these tools isn't always public. + This project shares the code along with the tool, so that people can verify the code and improve it. +
+
+
+
+ diff --git a/app/src/lib/Input.svelte b/app/src/lib/Input.svelte new file mode 100644 index 0000000..cb577a1 --- /dev/null +++ b/app/src/lib/Input.svelte @@ -0,0 +1,26 @@ + + +{#if family === "t"} + + + + +{:else if family === "f"} + + + + +{:else if family === "chi"} + + + + +{/if} + diff --git a/app/src/lib/Options.svelte b/app/src/lib/Options.svelte new file mode 100644 index 0000000..25f60a5 --- /dev/null +++ b/app/src/lib/Options.svelte @@ -0,0 +1,91 @@ + + + + +
+ + +
diff --git a/app/src/lib/Output.svelte b/app/src/lib/Output.svelte new file mode 100644 index 0000000..a0cfe79 --- /dev/null +++ b/app/src/lib/Output.svelte @@ -0,0 +1,135 @@ + + +
+ + + + + + + + + + + + + + + + + + + +
Sample size: + +
α err prob: + +
Power (1-β err prob): + +
Effect size: + +
+
+ {errorMessage} +
+
+ + +
+
+ diff --git a/app/src/lib/inputs/ChiTemplate.svelte b/app/src/lib/inputs/ChiTemplate.svelte new file mode 100644 index 0000000..764c4d9 --- /dev/null +++ b/app/src/lib/inputs/ChiTemplate.svelte @@ -0,0 +1,16 @@ + + +{#if test === "goodnessOfFitChisqTest"} + + Df: + + + + +{/if} + diff --git a/app/src/lib/inputs/FTemplate.svelte b/app/src/lib/inputs/FTemplate.svelte new file mode 100644 index 0000000..e52c0fb --- /dev/null +++ b/app/src/lib/inputs/FTemplate.svelte @@ -0,0 +1,189 @@ + + +{#if test === "ANCOVA"} + + Numerator df: + + + + + + Number of groups: + + + + + Number of covariates: + + + + +{:else if test === "oneWayANOVA"} + + Number of groups: + + + + + +{:else if test === "twoWayANOVA"} + + Numerator df: + + + + + + Number of groups: + + + + + +{:else if test === "betweenRepeatedANOVA"} + + Number of groups: + + + + + + Number of measurement: + + + + + + Corr among rep measures: + + + + + +{:else if test === "withinRepeatedANOVA"} + + Number of groups: + + + + + + Number of measurement: + + + + + + Corr among rep measures: + + + + + + Nonsphericity correction ε: + + + + + +{:else if test === "withinBetweenRepeatedANOVA"} + + Number of groups: + + + + + + Number of measurement: + + + + + + Corr among rep measures: + + + + + + Nonsphericity correction ε: + + + + + +{:else if test === "deviationFromZeroMultipleRegression"} + + Number of predictors: + + + + + +{:else if test === "increaseMultipleRegression"} + + Number of tested predictors: + + + + + + Total number of predictors: + + + + + +{/if} + diff --git a/app/src/lib/inputs/InputTemplate.svelte b/app/src/lib/inputs/InputTemplate.svelte new file mode 100644 index 0000000..6e43591 --- /dev/null +++ b/app/src/lib/inputs/InputTemplate.svelte @@ -0,0 +1,5 @@ + + + + +
diff --git a/app/src/lib/inputs/TTemplate.svelte b/app/src/lib/inputs/TTemplate.svelte new file mode 100644 index 0000000..9848755 --- /dev/null +++ b/app/src/lib/inputs/TTemplate.svelte @@ -0,0 +1,46 @@ + + +{#if test === "oneSampleTTest"} + + Tail(s): + + + + + +{:else if test === "independentSamplesTTest"} + + Tail(s): + + + + + + Allocation ratio N2/N1 + + + + + +{/if} + diff --git a/app/src/main.js b/app/src/main.js new file mode 100644 index 0000000..ffd92e7 --- /dev/null +++ b/app/src/main.js @@ -0,0 +1,8 @@ +import { mount } from "svelte"; +import App from "./App.svelte"; + +const app = mount(App, { + target: document.getElementById("app"), +}); + +export default app; diff --git a/app/src/vite-env.d.ts b/app/src/vite-env.d.ts new file mode 100644 index 0000000..4078e74 --- /dev/null +++ b/app/src/vite-env.d.ts @@ -0,0 +1,2 @@ +/// +/// diff --git a/app/svelte.config.js b/app/svelte.config.js new file mode 100644 index 0000000..5fc5071 --- /dev/null +++ b/app/svelte.config.js @@ -0,0 +1,8 @@ +import { vitePreprocess } from "@sveltejs/vite-plugin-svelte"; + +export default { + preprocess: vitePreprocess(), + compilerOptions: { + runes: true, + }, +}; diff --git a/app/vite.config.js b/app/vite.config.js new file mode 100644 index 0000000..d701969 --- /dev/null +++ b/app/vite.config.js @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import { svelte } from '@sveltejs/vite-plugin-svelte' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [svelte()], +}) diff --git a/dist/Cargo.toml b/dist/Cargo.toml index f536f79..25d0fb8 100644 --- a/dist/Cargo.toml +++ b/dist/Cargo.toml @@ -1,7 +1,10 @@ [package] name = "dist" version = "0.1.0" -authors = ["Rik Huijzer "] +authors = [ + "Rik Huijzer ", + "Jose Storopoli ", +] edition = "2021" [build-dependencies] diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..d72ee57 --- /dev/null +++ b/flake.lock @@ -0,0 +1,130 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1709126324, + "narHash": "sha256-q6EQdSeUZOG26WelxqkmR7kArjgWCdw5sfJVHPH/7j8=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "d465f4819400de7c8d874d50b982301f28a84605", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "flake-utils_2": { + "inputs": { + "systems": "systems_2" + }, + "locked": { + "lastModified": 1705309234, + "narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1709101946, + "narHash": "sha256-TsySgcWm/GlbYdL3AEva49ceeI2BdPQ7muwfYNr1fwo=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "d53c2037394da6fe98decca417fc8fda64bf2443", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1706487304, + "narHash": "sha256-LE8lVX28MV2jWJsidW13D2qrHU/RUUONendL2Q/WlJg=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "90f456026d284c22b3e3497be980b2e47d0b28ac", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs", + "rust-overlay": "rust-overlay" + } + }, + "rust-overlay": { + "inputs": { + "flake-utils": "flake-utils_2", + "nixpkgs": "nixpkgs_2" + }, + "locked": { + "lastModified": 1709086241, + "narHash": "sha256-3QHK5zu/5XOa+ghBeKzvt+/BLdEPjw/xDNLcpDfbkmg=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "5d56056fb905ff550ee61b6ebb6674d494f57a9e", + "type": "github" + }, + "original": { + "owner": "oxalica", + "repo": "rust-overlay", + "type": "github" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "systems_2": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..b9cd042 --- /dev/null +++ b/flake.nix @@ -0,0 +1,41 @@ +{ + description = "A basic flake with a shell"; + inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + inputs.flake-utils.url = "github:numtide/flake-utils"; + inputs.rust-overlay.url = "github:oxalica/rust-overlay"; + + outputs = { nixpkgs, flake-utils, rust-overlay, ... }: + flake-utils.lib.eachDefaultSystem (system: + let + overlays = [ (import rust-overlay) ]; + pkgs = import nixpkgs { + inherit system overlays; + }; + rust = pkgs.rust-bin.stable.latest.default.override + { + targets = [ "wasm32-unknown-emscripten" ]; + }; + in + { + devShells.default = pkgs.mkShell { + EM_CACHE = "/tmp/emscripten_cache"; + packages = with pkgs; [ + bashInteractive + + # rust + emscripten + rust + + # node + nodejs + + # svelte + nodePackages.svelte-language-server + ]; + shellHook = '' + export CC="${pkgs.emscripten}/bin/emcc" + ''; + }; + }); +} + diff --git a/frontend.js b/frontend.js deleted file mode 100644 index 673a3ae..0000000 --- a/frontend.js +++ /dev/null @@ -1,374 +0,0 @@ -function removeAllSelectOptions(selector) { - while (selector.options.length > 0) { - selector.remove(0); - } -} - -function addSelectOption(selector, text, enabled, value) { - let option = new Option(text, value); - option.disabled = !enabled; - selector.add(option, undefined); -} - -function getElementById(id) { - const elem = document.getElementById(id); - if (elem == null) { - throw new Error("Expected to find element with id: " + id); - } - return elem; -} - -function readString(id) { - const elem = getElementById(id); - return elem.value; -} - -// Update the "Statistical test" options based on the "Test family" setting. -function familyChanged() { - const family = readString("family"); - const testSelector = document.getElementById("test"); - removeAllSelectOptions(testSelector); - if (family == "exact") { - addSelectOption(testSelector, "Correlation: Bivariate normal model", true, 1); - addSelectOption(testSelector, "Linear multiple regression: Random model", false, 2); - addSelectOption(testSelector, "Proportion: Difference from constant (binomial test, one sample case)", false, 3); - addSelectOption(testSelector, "Proportions: Inequality, two dependent groups (McNemar)", false, 4); - addSelectOption(testSelector, "Proportions: Inequality, two independent groups (Fisher's exact test)", false, 5); - addSelectOption(testSelector, "Proportions: Inequality, two independent groups (unconditional)", false, 6); - addSelectOption(testSelector, "Proportions: Inequality (offset), two independent groups (unconditional)", false, 7); - addSelectOption(testSelector, "Proportions: Sign test (binomial test)", false, 8); - } else if (family == "f") { - addSelectOption(testSelector, "ANCOVA: Fixed effects, main effects, and interactions", true, 'ANCOVA'); - addSelectOption(testSelector, "ANOVA: Fixed effects, omnibus, one-way", true, 'oneWayANOVA'); - addSelectOption(testSelector, "ANOVA: Fixed effects, special, main effects, and interactions", true , 'twoWayANOVA'); - addSelectOption(testSelector, "ANOVA: Repeated measures, between factors",true, 'betweenRepeatedANOVA'); - addSelectOption(testSelector, "ANOVA: Repeated measures, within factors", true, 'withinRepeatedANOVA'); - addSelectOption(testSelector, "ANOVA: Repeated measures, within-between interaction", true, 'withinBetweenRepeatedANOVA'); - addSelectOption(testSelector, "Hotellings T²: One group mean vector", false, 7); - addSelectOption(testSelector, "Hotellings T²: Two group mean vector", false, 8); - addSelectOption(testSelector, "MANOVA: Global effects", false, 9); - addSelectOption(testSelector, "MANOVA: Special effects and interactions", false, 10); - addSelectOption(testSelector, "MANOVA: Repeated measures, between factors", false, 11); - addSelectOption(testSelector, "MANOVA: Repeated measures, within factors", false, 12); - addSelectOption(testSelector, "MANOVA: Repeated measures, within-between interaction", false, 13); - addSelectOption(testSelector, "Linear multiple regression: Fixed model, R² deviation from zero", true, 'deviationFromZeroMultipleRegression'); - addSelectOption(testSelector, "Linear multiple regression: Fixed model, R² increase", true, 'increaseMultipleRegression'); - addSelectOption(testSelector, "Variance: Test of equality (two sample case)", false, 16); - addSelectOption(testSelector, "Generic F test", false, 17); - } else if (family == "t") { - addSelectOption(testSelector, "Correlation: Point biseral model", false, 1); - addSelectOption(testSelector, "Linear bivariate regression: One group, size of slope", false, 2); - addSelectOption(testSelector, "Linear bivariate regression: Two groups, difference between intercepts", false, 3); - addSelectOption(testSelector, "Linear bivariate regression: Two groups, difference between slopes", false, 4); - addSelectOption(testSelector, "Linear multiple regression: Fixed model, single regression coefficient", false, 5); - addSelectOption(testSelector, "Means: Difference between two dependent means (matched pairs)", false, 'dependentSamplesTTest'); - addSelectOption(testSelector, "Means: Difference between two independent means (two groups)", true, 'independentSamplesTTest'); - addSelectOption(testSelector, "Means: Difference from constant (one sample case)", true, 'oneSampleTTest'); - addSelectOption(testSelector, "Means: Wilcoxon signed-rank test (matched pairs)", false, 9); - addSelectOption(testSelector, "Means: Wilcoxon signed-rank test (one sample case)", false, 10); - addSelectOption(testSelector, "Means: Wilcoxon-Mann-Whitney test (two groups)", false, 11); - addSelectOption(testSelector, "Generic t test", false, 12); - } else if (family == "chi") { - addSelectOption(testSelector, "Goodness-of-fit tests: Contingency tables", true, 1); - addSelectOption(testSelector, "Variance: Difference from constant (one sample case)", false, 2); - addSelectOption(testSelector, "Generic χ² test", false, 3); - } else if (family == "z") { - addSelectOption(testSelector, "Correlation: Tetrachoric model", false, 1); - addSelectOption(testSelector, "Correlations: Two dependent Pearson r's (common index)", true, 2); - addSelectOption(testSelector, "Correlations: Two dependent Pearson r's (no common index)", true, 3); - addSelectOption(testSelector, "Correlations: Two independent Pearson r's", true, 4); - addSelectOption(testSelector, "Logistic regression", true, 5); - addSelectOption(testSelector, "Poisson regression", false, 6); - addSelectOption(testSelector, "Proportions: Difference between two independent proportions", false, 7); - addSelectOption(testSelector, "Generic z test", false, 8); - } - updateNumberOutputAreas(); - return; -} - -function removeAllTableRows(table) { - while (table.rows.length > 0) { - table.deleteRow(0); - } -} - -/** Add an option to a table. */ -function addTableOption(table, description, element) { - var row = table.insertRow(table.rows.length); - var left = row.insertCell(0); - // Using innerHTML over textContent to allow formatting such as italic. - left.innerHTML = description.concat(":"); - var right = row.insertCell(1); - right.innerHTML = element; - return null; -} - -/** Return an input element for floats with element `id`. */ -function floatInputElement(id, defaultValue, step) { - return ``; -} - -function floatOutputElement(id, value) { - return `${value}`; -} - -function disableOutputElement(id) { - const elem = getElementById(id); - elem.disabled = true; - return null; -} - -function enableOutputElement(id) { - const elem = getElementById(id); - elem.disabled = false; - return null; -} - -function getInputTable() { - return document.getElementById("input"); -} - -/** Update the input and output area based on the "Type of power analysis" setting. */ -function updateNumberOutputAreas() { - const inputTable = getInputTable(); - removeAllTableRows(inputTable); - const family = readString("family"); - const test = readString("test"); - if (family == "exact") { - } else if (family == "f") { - if (test == "deviationFromZeroMultipleRegression") { - addTableOption(inputTable, "Number of predictors", ""); - } else if (test == "increaseMultipleRegression") { - addTableOption(inputTable, "Number of tested predictors", ""); - addTableOption(inputTable, "Total number of predictors", ""); - } else if (test == "oneWayANOVA") { - addTableOption(inputTable, "Number of groups", ""); - } else if (test == "twoWayANOVA") { - addTableOption(inputTable, "Numerator df", ""); - addTableOption(inputTable, "Number of groups", ""); - } else if (test == "ANCOVA") { - addTableOption(inputTable, "Numerator df", ""); - addTableOption(inputTable, "Number of groups", ""); - addTableOption(inputTable, "Number of covariates", ""); - } else if (test == "betweenRepeatedANOVA") { - addTableOption(inputTable, "Number of groups", ""); - addTableOption(inputTable, "Number of measurement", ""); - addTableOption(inputTable, "Corr among rep measures", ""); - } else if (test == "withinRepeatedANOVA" || test == "withinBetweenRepeatedANOVA") { - addTableOption(inputTable, "Number of groups", ""); - addTableOption(inputTable, "Number of measurement", ""); - addTableOption(inputTable, "Corr among rep measures", ""); - // FIXME: the lower bound of epsilon corresponds to 1 / (number of measurements - 1) - addTableOption(inputTable, "Nonsphericity correction ε", ""); - } - } else if (family == "t") { - addTableOption(inputTable, "Tail(s)", ""); - } else if (family == "chi") { - addTableOption(inputTable, "Df", ""); - } else if (family == "z") { - } - - enableOutputElement("n"); - enableOutputElement("alpha"); - enableOutputElement("power"); - enableOutputElement("es"); - - const analysis = readString("analysis"); - if (analysis == "n") { - disableOutputElement("n"); - } else if (analysis == "alpha") { - disableOutputElement("alpha"); - } else if (analysis == "power") { - disableOutputElement("power"); - } else if (analysis == "es") { - disableOutputElement("es"); - } - - updateOutput(); - - return null; -} - -function readFloat(id) { - const elem = getElementById(id); - return parseFloat(elem.value); -} - -const highlightBorder = [ - { border: '1px var(--favicon-red) solid' } -]; - -const highlightTiming = { - duration: 400, - iterations: 1, -} - -function setFloat(id, value) { - const elem = getElementById(id); - elem.animate(highlightBorder, highlightTiming); - elem.value = value; - return null; -} - -function readInt(id) { - const elem = getElementById(id); - return parseInt(elem.value); -} - -function tail() { - return readInt("tail"); -} - -function alpha() { - return readFloat("alpha"); -} - -function power() { - return readFloat("power"); -} - -function es() { - return readFloat("es"); -} - -function n() { - return readFloat("n"); -} - -function restrictFloat(id) { - const elem = getElementById(id); - const value = elem.value; - if (elem.max < elem.value) { - elem.value = elem.max; - } -} - -/** Enforce that input numbers are within the HTML specified values. */ -function restrictInput() { - restrictFloat("alpha"); -} - -function setError(text) { - const elem = getElementById("error"); - elem.innerText = text; - return null; -} - -function handleError(value) { - if (value == -111 || value == -111.0) { - setError("Unable to find a solution for given input."); - } - return null; -} - -function setOutput(id, out) { - handleError(out); - setFloat(id, out); - return null; -} - -function frontEndState() { - const inputTable = getInputTable(); - const inputElements = inputTable.querySelectorAll("input, select"); - - // Ignoring family because the back end will infer it from the test. - const analysis = readString("analysis"); - const test = readString("test"); - - const state = { - analysis: analysis, - test: test, - n: n(), - alpha: alpha(), - power: power(), - es: es() - }; - - for (let i = 0; i < inputElements.length; i++) { - const elem = inputElements[i]; - state[elem.id] = elem.value; - } - - return state; -} - -function writeToPtr(ptr, text) { - const buffer = Module.HEAPU8.buffer; - const view = new Uint8Array(buffer, ptr, 1024); - const encoder = new TextEncoder(); - const with_stop = text + ""; - view.set(encoder.encode(with_stop)); -} - -function readFromPtr(ptr) { - const buffer = Module.HEAPU8.buffer; - const view = new Uint8Array(buffer, ptr, 1024); - const length = view.findIndex(byte => byte === 0); - const decoder = new TextDecoder(); - - return decoder.decode(new Uint8Array(buffer, ptr, length)); -} - -/** Update the output area by calculating the numbers via WebAssembly. */ -function updateOutput() { - setError(""); - restrictInput(); - - const family = readString("family"); - const analysis = readString("analysis"); - const test = readString("test"); - - const state = frontEndState(); - const json = JSON.stringify(state); - console.log(`Sending the following json to the back end: ${json}`); - - const ptr = Module._alloc(); - writeToPtr(ptr, json); - Module._calculatePower(ptr); - const returned = readFromPtr(ptr); - Module._dealloc(ptr); - console.log(`Received the following json from the back end: ${returned}`); - const result = JSON.parse(returned); - const id = Object.keys(result)[0]; - setOutput(id, result[id]); - - return null; -} - -/** Reset the numbers in the output area. */ -function resetOutput() { - setFloat("n", 50); - setFloat("alpha", 0.05); - setFloat("power", 0.95); - setFloat("es", 0.5); - updateOutput(); -} - -function webAssemblySupport() { - try { - if (typeof WebAssembly === "object" && typeof WebAssembly.instantiate === "function") { - const module = new WebAssembly.Module(Uint8Array.of(0x0, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00)); - if (module instanceof WebAssembly.Module) { - return new WebAssembly.Instance(module) instanceof WebAssembly.Instance; - } - } - } catch (e) { - } - return false; -} - -if (!webAssemblySupport()) { - document.body.innerHTML = ` -
-
- This site only works with WebAssembly. Enable WebAssembly in your browser to continue. -
- `; -} else { - Module['onRuntimeInitialized'] = function() { - console.log("Loading of the poweranalyses.wasm library succeeded."); - familyChanged(); - updateNumberOutputAreas(); - updateOutput(); - } -} diff --git a/index.html b/index.html deleted file mode 100644 index 2b828de..0000000 --- a/index.html +++ /dev/null @@ -1,129 +0,0 @@ - - - - - PowerAnalyses.org - - - - - - - - - -
-
- - PowerAnalyses.org Beta -
- - - -
-
- - - - - -
Tail: - -
-
- - - - - - - - - - - - - - - - - - - -
Sample size: - -
α err prob: - -
Power (1-β err prob): - -
Effect size: - -
-
-
-
- - -
-
-
-
-
-
-
-
-
Why this web site?

- Statistical power analyses play a key role in science. - However, the code for these tools isn't always public. - This project shares the code along with the tool, so that people can verify the code and improve it. -
-
-
-
- -
- - - \ No newline at end of file diff --git a/justfile b/justfile new file mode 100644 index 0000000..a7eba8e --- /dev/null +++ b/justfile @@ -0,0 +1,23 @@ +default: build serve + +alias s := serve +alias b := build + +serve: + @echo "Serving..." + @cd app && npm run dev -- --open + +build: + @echo "Building..." + @cargo build --target wasm32-unknown-emscripten --release + @cp --verbose target/wasm32-unknown-emscripten/release/pa.wasm app/public/pa.wasm + @cp --verbose target/wasm32-unknown-emscripten/release/pa.js app/public/pa.js + +test: + @echo "Testing..." + @cargo test + +deploy: + @echo "Deploying..." + @cd app && npm run build + diff --git a/power/Cargo.toml b/power/Cargo.toml index e3cb0ca..76eee9a 100644 --- a/power/Cargo.toml +++ b/power/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "pa" version = "0.1.0" -authors = ["Rik Huijzer "] +authors = ["Rik Huijzer ", "Jose Storopoli "] edition = "2021" [dependencies] diff --git a/power/src/power.rs b/power/src/power.rs index 7521bd5..be3eaa9 100644 --- a/power/src/power.rs +++ b/power/src/power.rs @@ -29,7 +29,7 @@ pub enum TestKind { /// Multiple regression: increase of R^2. IncreaseMultipleRegression { /// Total number of predictors (#A + #B). - rho: i64, + p: i64, /// Number of tested predictors (#B). q: i64, }, @@ -151,9 +151,9 @@ impl TestKind { Ok(TestKind::DeviationFromZeroMultipleRegression { n_predictors }) } "increaseMultipleRegression" => { - let rho = parse_i64(data, "rho").unwrap(); + let p = parse_i64(data, "p").unwrap(); let q = parse_i64(data, "q").unwrap(); - Ok(TestKind::IncreaseMultipleRegression { rho, q }) + Ok(TestKind::IncreaseMultipleRegression { p, q }) } "ANCOVA" => { let k = parse_i64(data, "k").unwrap(); @@ -225,9 +225,9 @@ impl TestKind { TestKind::GoodnessOfFitChisqTest { df } => { Box::new(NoncentralChisq::new(*df as f64, es.powi(2) * n)) } - TestKind::IncreaseMultipleRegression { rho, q } => Box::new(NoncentralF::new( + TestKind::IncreaseMultipleRegression { p, q } => Box::new(NoncentralF::new( *q as f64, - n - (*rho as f64) - 1.0, + n - (*p as f64) - 1.0, es.powi(2) * n, )), TestKind::ANCOVA { k, q, p } => Box::new(NoncentralF::new( diff --git a/power/src/tests.rs b/power/src/tests.rs index c1e8c66..aac25a1 100644 --- a/power/src/tests.rs +++ b/power/src/tests.rs @@ -116,17 +116,17 @@ fn deviation_from_zero_multiple_regression() { #[test] fn increase_multiple_regression() { - let rho = "5"; + let p = "5"; let q = "2"; let f_squared = ES.sqrt(); let join = with_rest("increaseMultipleRegression"); - let extra = json!({"rho": rho, "q": q, "es": f_squared, "analysis": "alpha"}); + let extra = json!({"p": p, "q": q, "es": f_squared, "analysis": "alpha"}); test_interface(&join(&extra), 0.006); - let extra = json!({"rho": rho, "q": q, "es": f_squared, "analysis": "power"}); + let extra = json!({"p": p, "q": q, "es": f_squared, "analysis": "power"}); test_interface(&join(&extra), 0.994); - let extra = json!({"rho": rho, "q": q, "es": f_squared, "analysis": "es"}); + let extra = json!({"p": p, "q": q, "es": f_squared, "analysis": "es"}); test_interface(&join(&extra), 0.575); // take the sqrt of G*Power. - let extra = json!({"rho": rho, "q": q, "es": f_squared, "analysis": "n"}); + let extra = json!({"p": p, "q": q, "es": f_squared, "analysis": "n"}); test_interface(&join(&extra), 35.0); } diff --git a/script/README.md b/script/README.md deleted file mode 100644 index c72b9bd..0000000 --- a/script/README.md +++ /dev/null @@ -1,23 +0,0 @@ -# `script/` - -This folder contains some helper scripts that are useful during development. - -- `build.sh`: Compile the Rust code to WebAssembly and copy the result to the public directory. -- `entr-build.sh`: Build the project when a file changes. -- `entr-frontend.sh`: Move the front end files when a file changes. -- `public.sh`: Copy the files to the public directory. -- `serve.sh`: Serve the website locally with live reload. - -For running the tests when a file changes, use `cargo watch`: - -```sh -$ cargo install cargo-watch - -$ cargo watch -x test -``` - -To then run only one test called `some_test`, use: - -```sh -$ cargo watch -x 'test some_test' -``` diff --git a/script/build.sh b/script/build.sh deleted file mode 100755 index fae7f8e..0000000 --- a/script/build.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env bash - -# -# Compile the Rust code to WebAssembly and copy the result to the public directory. -# - -set -e - -BASEDIR=$(dirname $(dirname $(readlink -f "$0"))) -cd "$BASEDIR" - -# The || true prevents the script from failing if the grep returns no results. -EMSCRIPTEN_GREP="$(rustup target list --installed | { grep wasm32-unknown-emscripten || true; })" -if [[ "$EMSCRIPTEN_GREP" == "" ]]; then - echo "Expected the wasm32-unknown-emscripten target to be installed." - echo "Run 'rustup target add wasm32-unknown-emscripten' to fix this error." - echo "" - echo "You might also need to install the emscripten compiler (emcc) via your package manager." - echo "For Nix, set the EM_CACHE environment variable to something like ~/.cache/emscripten." -fi - -cargo build --target wasm32-unknown-emscripten --release - -./script/public.sh diff --git a/script/entr-build.sh b/script/entr-build.sh deleted file mode 100755 index c9d3bc9..0000000 --- a/script/entr-build.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env bash - -# -# Build the project when a file changes. -# - -set -e - -BASEDIR=$(dirname $(dirname $(readlink -f "$0"))) -cd "$BASEDIR" - -find dist power | entr -s ./script/build.sh diff --git a/script/entr-frontend.sh b/script/entr-frontend.sh deleted file mode 100755 index 0e621ae..0000000 --- a/script/entr-frontend.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env bash - -# -# Move the front end files when a file changes. -# - -set -e - -BASEDIR=$(dirname $(dirname $(readlink -f "$0"))) - -find index.html style.css frontend.js | entr -s "bash $BASEDIR/script/public.sh" diff --git a/script/public.sh b/script/public.sh deleted file mode 100755 index c974606..0000000 --- a/script/public.sh +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env bash - -# -# Copy the files to the public directory. -# - -set -e - -BASEDIR=$(dirname $(dirname $(readlink -f "$0"))) -cd "$BASEDIR" - -mkdir -p public -if [ -f public/index.html ]; then - chmod 666 public/index.html -fi -if [ -f public/style.css ]; then - chmod 666 public/style.css -fi -if [ -f public/frontend.js ]; then - chmod 666 public/frontend.js -fi -if [ -f public/pa.js ]; then - chmod 666 public/pa.js -fi -if [ -f public/pa.wasm ]; then - chmod 666 public/pa.wasm -fi - -cp --verbose index.html public -cp --verbose style.css public -cp --verbose favicon.png public -cp --verbose frontend.js public -cp --verbose target/wasm32-unknown-emscripten/release/pa.js public -cp --verbose target/wasm32-unknown-emscripten/release/pa.wasm public - -# To avoid accidentally editing the files in public manually. -chmod 444 public/index.html -chmod 444 public/style.css -chmod 444 public/frontend.js -chmod 444 public/pa.js -chmod 444 public/pa.wasm diff --git a/script/serve.sh b/script/serve.sh deleted file mode 100755 index 6b9f514..0000000 --- a/script/serve.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env bash - -# -# Serve the website locally with live reload. -# - -set -e - -BASEDIR=$(dirname $(dirname $(readlink -f "$0"))) - -cd "$BASEDIR/public" - -# Using https://github.com/tapio/live-server because it loads fast and supports live reload. -# Install with npm install -g live-server. -# Or via Nix, install nodePackages_latest.live-server. -live-server --no-browser