diff --git a/packages/gpa-calculator/.gitattributes b/packages/gpa-calculator/.gitattributes new file mode 100644 index 0000000..0f64c77 --- /dev/null +++ b/packages/gpa-calculator/.gitattributes @@ -0,0 +1,3 @@ +/.yarn/releases/** binary +/.yarn/plugins/** binary +*.lockb diff=lockb diff --git a/packages/gpa-calculator/.github/CODEOWNERS b/packages/gpa-calculator/.github/CODEOWNERS new file mode 100644 index 0000000..7926874 --- /dev/null +++ b/packages/gpa-calculator/.github/CODEOWNERS @@ -0,0 +1,15 @@ +# Each line is a file pattern followed by one or more owners. + +# These owners will be the default owners for everything in +# the repo. Unless a later match takes precedence, +# the global owners will be requested for +# review when someone opens a pull request. +* @lishaduck @ParkerH27 + +/src/ @lishaduck @ParkerH27 +*.js @ParkerH27 +*.ts @ParkerH27 @lishaduck +/docs/ @lishaduck +/.github/ @lishaduck +/public/ @ParkerH27 @lishaduck +/.all-contributorsrc @lishaduck diff --git a/packages/gpa-calculator/.github/dependabot.yml b/packages/gpa-calculator/.github/dependabot.yml new file mode 100644 index 0000000..395977e --- /dev/null +++ b/packages/gpa-calculator/.github/dependabot.yml @@ -0,0 +1,13 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file + +version: 2 +updates: + - package-ecosystem: "npm" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "monthly" + assignees: + - lishaduck diff --git a/packages/gpa-calculator/.github/workflows/main.yml b/packages/gpa-calculator/.github/workflows/main.yml new file mode 100644 index 0000000..e85a1fb --- /dev/null +++ b/packages/gpa-calculator/.github/workflows/main.yml @@ -0,0 +1,84 @@ +--- +name: Deploy to GitHub Pages + +env: + CI: true + +on: + # Runs on pushes to the default branch + push: + branches: + - main + + # Runs on PRs targeting the default branch + pull_request: + types: [opened, synchronize, reopened, closed] + branches: + - main + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +jobs: + build_job: + name: Build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3.5.0 + with: + submodules: true + + - name: Install bun + uses: oven-sh/setup-bun@v1 + with: + bun-version: 1.0.29 + + - name: Cache + uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4.0.0 + id: cache + with: + path: | + .turbo + node_modules + ~/.bun/install/cache + key: ${{ runner.os }}-${{ runner.arch }}-node_modules-${{ hashFiles('**/bun.lockb', '**/turbo.json') }} + + - name: Install dependencies + if: steps.cache.outputs.cache-hit != 'true' + run: bun --bun install --frozen-lockfile + env: + SHARP_IGNORE_GLOBAL_LIBVIPS: 1 + + - name: Lint & Build + run: bun --bun run turbo build + + - name: Redirect 404 to Index for SPA + run: cp dist/index.html dist/404.html + + - name: Setup Pages + uses: actions/configure-pages@v3.0.5 + + - name: Upload Artifact + uses: actions/upload-pages-artifact@v1.0.8 + with: + path: "./dist/" + + deploy_job: + name: Deploy + needs: [build_job] + if: github.event_name == 'push' + runs-on: ubuntu-latest + timeout-minutes: 4 + permissions: + pages: write + id-token: write + contents: read + concurrency: + group: "pages" + cancel-in-progress: true + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - id: deployment + uses: actions/deploy-pages@v2.0.0 diff --git a/packages/gpa-calculator/.gitignore b/packages/gpa-calculator/.gitignore new file mode 100644 index 0000000..d06586b --- /dev/null +++ b/packages/gpa-calculator/.gitignore @@ -0,0 +1,138 @@ +# Misc +dev-dist +build +types +.turbo + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* +vite.config.ts.timestamp-*.mjs + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp +.cache + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions diff --git a/packages/gpa-calculator/.prettierignore b/packages/gpa-calculator/.prettierignore new file mode 100644 index 0000000..c221644 --- /dev/null +++ b/packages/gpa-calculator/.prettierignore @@ -0,0 +1,7 @@ +/node_modules/ +/dist/ +/build/ +/artifacts/ +/coverage/ +.git/ +turbo.json diff --git a/packages/gpa-calculator/.prettierrc b/packages/gpa-calculator/.prettierrc new file mode 100644 index 0000000..66d06d8 --- /dev/null +++ b/packages/gpa-calculator/.prettierrc @@ -0,0 +1,23 @@ +{ + "trailingComma": "all", + "singleQuote": false, + "semi": true, + "tabWidth": 2, + "useTabs": false, + "endOfLine": "lf", + "overrides": [ + { + "files": [".prettierrc"], + "options": { + "parser": "json" + } + }, + { + "files": ["tsconfig.json"], + "options": { + "trailingComma": "none" + } + } + ], + "experimentalTernaries": true +} diff --git a/packages/gpa-calculator/.replit b/packages/gpa-calculator/.replit new file mode 100644 index 0000000..45832f7 --- /dev/null +++ b/packages/gpa-calculator/.replit @@ -0,0 +1,41 @@ +run = "npm run start" +entrypoint = "main.js" + +hidden = [".config"] + +[nix] +channel = "stable-22_11" + +[env] +# PATH = "/home/runner/$REPL_SLUG/.config/npm/node_global/bin:/home/runner/$REPL_SLUG/node_modules/.bin" +# XDG_CONFIG_HOME = "/home/runner/.config" +# npm_config_prefix = "/home/runner/$REPL_SLUG/.config/npm/node_global" + +[gitHubImport] +requiredFiles = [ + ".replit", + "replit.nix", + # ".config" +] + +[packager] +language = "nodejs" + [packager.features] + packageSearch = true + guessImports = true + enabledForHosting = false + +[languages] + [languages.javascript] + pattern = "**/{*.js,*.jsx,*.ts,*.tsx}" + [languages.javascript.languageServer] + start = "typescript-language-server --stdio" + +[deployment] +build = ["sh", "-c", "npm run build"] +run = ["sh", "-c", "npm run start"] +type = "static" + +# This file is for the Replit IDE to specify which file to run and what language. +# It is unnecessary for any use-case that does not include the Replit IDE +# IDE: Replit.com diff --git a/packages/gpa-calculator/.vscode/extensions.json b/packages/gpa-calculator/.vscode/extensions.json new file mode 100644 index 0000000..edd2d00 --- /dev/null +++ b/packages/gpa-calculator/.vscode/extensions.json @@ -0,0 +1,10 @@ +{ + "recommendations": [ + "christian-kohler.npm-intellisense", + "ms-edgedevtools.vscode-edge-devtools", + "pwabuilder.pwa-studio", + "esbenp.prettier-vscode", + "davidanson.vscode-markdownlint", + "swellaby.node-pack" + ] +} diff --git a/packages/gpa-calculator/.vscode/launch.json b/packages/gpa-calculator/.vscode/launch.json new file mode 100644 index 0000000..2ae70e6 --- /dev/null +++ b/packages/gpa-calculator/.vscode/launch.json @@ -0,0 +1,27 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "msedge", + "request": "launch", + "name": "Run PWA", + "webRoot": "${workspaceFolder}/", + "runtimeArgs": ["--app=http://localhost:3000"], + "sourceMapPathOverrides": { + "../../src": "${workspaceFolder}/src", + "../../src/*": "${workspaceFolder}/src/*" + }, + "preLaunchTask": "npm run dev-task", + "postDebugTask": "postdebugKill" + }, + { + "name": "Launch Microsoft Edge and open the Edge DevTools", + "request": "launch", + "type": "vscode-edge-devtools.debug", + "url": "" // Provide your project's url to finish configuring + } + ] +} diff --git a/packages/gpa-calculator/.vscode/settings.json b/packages/gpa-calculator/.vscode/settings.json new file mode 100644 index 0000000..cbab92d --- /dev/null +++ b/packages/gpa-calculator/.vscode/settings.json @@ -0,0 +1,16 @@ +{ + "files.trimTrailingWhitespace": true, + "markdownlint.config": { + "MD028": false, + "MD025": { + "front_matter_title": "" + } + }, + "explorer.fileNesting.patterns": { + "package.json": ".prettierrc, .prettierignore, bun.lockb, eslint.config.js, turbo.json", + "README*": "*.${capture}", + ".replit": "replit.*" + }, + "cSpell.enableFiletypes": ["!typescript"], + "prettier.prettierPath": "./node_modules/prettier/index.cjs" +} diff --git a/packages/gpa-calculator/.vscode/tasks.json b/packages/gpa-calculator/.vscode/tasks.json new file mode 100644 index 0000000..eaeae18 --- /dev/null +++ b/packages/gpa-calculator/.vscode/tasks.json @@ -0,0 +1,36 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "npm run dev-task", + "type": "npm", + "script": "dev-task", + "isBackground": true, + "problemMatcher": { + "owner": "custom", + "pattern": { + "regexp": "^$" + }, + "background": { + "activeOnStart": true, + "beginsPattern": ".*", + "endsPattern": "ready in .+" + } + } + }, + { + "label": "postdebugKill", + "command": "echo ${input:terminate}", + "type": "shell", + "problemMatcher": [] + } + ], + "inputs": [ + { + "id": "terminate", + "type": "command", + "command": "workbench.action.tasks.terminate", + "args": "npm run dev-task" + } + ] +} diff --git a/packages/gpa-calculator/README.md b/packages/gpa-calculator/README.md new file mode 100644 index 0000000..b2e2590 --- /dev/null +++ b/packages/gpa-calculator/README.md @@ -0,0 +1,19 @@ +# Gpa Calculator + +A PWA that calculates your GPA! + +## Created by + +- Parker Hasenkamp ([**@ParkerH27**](https://github.com/ParkerH27)) +- Eli D. ([**@lishaduck**](https://github.com/lishaduck)) + +## Links + +- Github Repository: + + +- Github Hosted Site: + + +- Replit Project: + diff --git a/packages/gpa-calculator/bun.lockb b/packages/gpa-calculator/bun.lockb new file mode 100755 index 0000000..7e0acc5 Binary files /dev/null and b/packages/gpa-calculator/bun.lockb differ diff --git a/packages/gpa-calculator/eslint.config.js b/packages/gpa-calculator/eslint.config.js new file mode 100644 index 0000000..b030651 --- /dev/null +++ b/packages/gpa-calculator/eslint.config.js @@ -0,0 +1,64 @@ +import { sheriff } from "eslint-config-sheriff"; +import { defineFlatConfig } from "eslint-define-config"; + +/** + * @type import("@sherifforg/types").SheriffSettings + */ +const sheriffOptions = { + react: false, + lodash: false, + next: false, + playwright: false, + jest: false, + vitest: false, + // astro: false, // THIS DOESN'T DO ANYTHING! + pathsOveriddes: { + tsconfigLocation: [ + "./tsconfig.json", + "./tsconfig.sw.json", + "./tsconfig.eslint.json", + ], + }, +}; + +/** + * @type import("eslint-define-config").FlatESLintConfig[] + */ +const sheriffRules = sheriff(sheriffOptions); + +export default defineFlatConfig([ + ...sheriffRules, + { + files: ["**/*.ts"], + rules: { + "@typescript-eslint/naming-convention": "off", + "prefer-destructuring": "off", + "@typescript-eslint/prefer-destructuring": "warn", + "unicorn/prefer-query-selector": "warn", + "func-style": ["error", "declaration", { allowArrowFunctions: true }], + "no-plusplus": ["error", { allowForLoopAfterthoughts: true }], + "operator-assignment": ["warn", "always"], + "@typescript-eslint/no-non-null-assertion": "off", + "no-console": "warn", + "no-negated-condition": "off", + "unicorn/no-negated-condition": "error", + "@typescript-eslint/ban-ts-comment": [ + "error", + { "ts-expect-error": true, "ts-check": false }, + ], + "import/no-unresolved": [2, { ignore: ["^virtual:"] }], + "@typescript-eslint/prefer-function-type": "warn", + "@typescript-eslint/explicit-function-return-type": "warn", + "@typescript-eslint/promise-function-async": "warn", + "@typescript-eslint/strict-boolean-expressions": "warn", + "@typescript-eslint/return-await": [2, "always"], + // This rule doesn't support enforcing implicit return for multiline returns. + "arrow-return-style/arrow-return-style": "off", + "arrow-body-style": 0, + "@typescript-eslint/restrict-template-expressions": [ + 2, + { allowNumber: true }, + ], + }, + }, +]); diff --git a/packages/gpa-calculator/index.html b/packages/gpa-calculator/index.html new file mode 100644 index 0000000..53536b8 --- /dev/null +++ b/packages/gpa-calculator/index.html @@ -0,0 +1,118 @@ + + + + + + + GPA Calculator + + + + + +
+ + × + + +
+

These actions refresh the page.

+ + +
+
+ +
+
+ + +
+ Middle School +
+
+ Grade Level + +
+
+
+ +
+
+
+
How to use!
+

+ Input number of classes you are taking and press "Submit", then input + your grades under "Grades". +

+

+ Your class data will then be saved to your device. This is so if you + refresh, your data is saved! +

+ +
+ + +
+
+ +
+
Grades:
+
+
+

No Data

+
+

+
+
+ + diff --git a/packages/gpa-calculator/netlify.toml b/packages/gpa-calculator/netlify.toml new file mode 100644 index 0000000..a4799f6 --- /dev/null +++ b/packages/gpa-calculator/netlify.toml @@ -0,0 +1,39 @@ +collections = [] # only needed for CMS +media_folder = "" # unneeded, but required by the schema + +# Settings in the [build] context are global and are applied to +# all contexts unless otherwise overridden by more specific contexts. +[build] +# Directory to change to before starting a build. +# This is where we will look for package.json/.nvmrc/etc. +# If not set, defaults to the root directory. +# base = "." + +# Directory that contains the deploy-ready HTML files and +# assets generated by the build. This is relative to the base +# directory if one has been set, or the root directory if +# a base has not been set. This sample publishes the directory +# located at the absolute path "root/project/build-output" + +# publish = "build-output/" + +# Default build command. +command = "npm run build" + +[backend] + +[[redirects]] +from = "/*" +to = "/GPA_Calculator" +status = 301 + +[[redirects]] +from = "/index.html" +to = "/GPA_Calculator" +status = 301 +force = true + +[[redirects]] +from = "/GPA_Calculator" +to = "/index.html" +status = 200 diff --git a/packages/gpa-calculator/package.json b/packages/gpa-calculator/package.json new file mode 100644 index 0000000..da36144 --- /dev/null +++ b/packages/gpa-calculator/package.json @@ -0,0 +1,71 @@ +{ + "name": "gpa-calculator", + "private": true, + "version": "1.1.0", + "description": "A GPA calculator for built for students in the Pattonville School District.", + "repository": { + "type": "git", + "url": "git+https://github.com/PSDTools/GPA_Calculator.git" + }, + "markdown": "github", + "author": { + "name": "Parker Hasenkamp", + "email": "58083460+PetalCat@users.noreply.github.com" + }, + "readme": "README.md", + "contributors": [ + { + "name": "Eli", + "email": "88557639+lishaduck@users.noreply.github.com" + } + ], + "type": "module", + "exports": { + "import": [ + "./src/script.ts" + ] + }, + "dependencies": { + "idb-keyval": "^6.2.1", + "unstorage": "^1.10.2", + "workbox-build": "^7.1.0", + "workbox-core": "^7.1.0", + "workbox-precaching": "^7.1.0", + "zod": "^3.23.8" + }, + "devDependencies": { + "@eslint-types/typescript-eslint": "^7.5.0", + "@eslint-types/unicorn": "^52.0.0", + "@sherifforg/types": "^3.1.1", + "@types/bun": "^1.1.2", + "@types/eslint": "^8.56.10", + "@types/html-minifier-terser": "^7.0.2", + "@vite-pwa/assets-generator": "^0.2.4", + "browserslist": "^4.23.0", + "browserslist-to-esbuild": "^2.1.1", + "eslint": "^8.57.0", + "eslint-config-sheriff": "^18.5.0", + "eslint-define-config": "^2.1.0", + "html-minifier-terser": "^7.2.0", + "lightningcss": "^1.25.0", + "prettier": "3.2.5", + "turbo": "^1.13.3", + "typescript": "^5.4.5", + "vite": "^5.2.11", + "vite-plugin-pwa": "^0.20.0" + }, + "scripts": { + "dev": "vite --open", + "bundle": "vite build", + "typecheck:src": "tsc", + "typecheck:sw": "tsc -p tsconfig.sw.json", + "typecheck:extra": "tsc -p tsconfig.eslint.json", + "format": "prettier . --write", + "format:check": "prettier . --check", + "lint": "eslint ." + }, + "packageManager": "bun@1.0.13", + "browserslist": [ + "defaults and fully supports es6-module" + ] +} diff --git a/packages/gpa-calculator/public/bground.png b/packages/gpa-calculator/public/bground.png new file mode 100644 index 0000000..4982d66 Binary files /dev/null and b/packages/gpa-calculator/public/bground.png differ diff --git a/packages/gpa-calculator/public/logo.svg b/packages/gpa-calculator/public/logo.svg new file mode 100644 index 0000000..39885e0 --- /dev/null +++ b/packages/gpa-calculator/public/logo.svg @@ -0,0 +1,475 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/gpa-calculator/replit.nix b/packages/gpa-calculator/replit.nix new file mode 100644 index 0000000..78a0fb0 --- /dev/null +++ b/packages/gpa-calculator/replit.nix @@ -0,0 +1,6 @@ +{ pkgs }: { + deps = [ + pkgs.nodejs-18_x + pkgs.nodePackages.typescript-language-server + ]; +} diff --git a/packages/gpa-calculator/src/data/data-types.ts b/packages/gpa-calculator/src/data/data-types.ts new file mode 100644 index 0000000..45d320f --- /dev/null +++ b/packages/gpa-calculator/src/data/data-types.ts @@ -0,0 +1,37 @@ +import { z } from "zod"; + +type Course = z.infer; + +/** + * Represent a course. + */ +const courseSchema = z.object({ + letterGrade: z.number().readonly(), + classText: z.string().readonly(), + classNum: z.number().readonly(), + classType: z.string().readonly(), +}); + +/** + * Create a new course. + */ +const newCourse = z + .function() + .args( + courseSchema.partial({ + letterGrade: true, + classText: true, + classType: true, + }), + ) + .returns(courseSchema) + .implement( + ({ classNum, classText = "", letterGrade = 5, classType = "1" }) => ({ + letterGrade, + classText, + classNum, + classType, + }), + ); + +export { courseSchema, newCourse, type Course }; diff --git a/packages/gpa-calculator/src/data/utils.ts b/packages/gpa-calculator/src/data/utils.ts new file mode 100644 index 0000000..2b5e0a8 --- /dev/null +++ b/packages/gpa-calculator/src/data/utils.ts @@ -0,0 +1,15 @@ +// TODO(lishaduck): convert this to be named runtimeQuerySelector +function getElementByIdTyped( + id: string, + type: new () => T, +): T | undefined { + const element = document.querySelector(`#${id}`); + + if (element instanceof type) { + return element; + } + + return undefined; +} + +export { getElementByIdTyped }; diff --git a/packages/gpa-calculator/src/script.ts b/packages/gpa-calculator/src/script.ts new file mode 100644 index 0000000..57f8d4d --- /dev/null +++ b/packages/gpa-calculator/src/script.ts @@ -0,0 +1,389 @@ +/** + * The main script. + */ + +import "./styles/global.css"; + +import { registerSW } from "virtual:pwa-register"; +import { newCourse, type Course } from "./data/data-types.js"; +import { + clearAll, + clearData, + getData, + getGrade, + setData, + setGrade, +} from "./scripts/storage.js"; +import { getElementByIdTyped as getElementById } from "./data/utils.js"; + +declare global { + interface Window { + hsmsSwap: () => Promise; + classAmount: () => Promise; + loadgpa: () => Promise; + clearData: () => Promise; + clearAll: () => Promise; + toggleNav: (open: boolean) => void; + startApp: () => Promise; + darkMode: () => Promise; + } +} + +let courses: Course[] = []; +let classAmountNum = 0; + +let tempLGID = ""; +let tempCTID = ""; +let tempCTYID = ""; +let tempElementId = ""; +let tempElementIdNext = ""; + +const high = "High School"; +const middle = "Middle School"; +const hsmsInput = getElementById("hsmsInput", HTMLInputElement)!; +const gradeLvl = getElementById("gradeLvl", HTMLElement)!; + +window.clearData = clearData; +window.clearAll = clearAll; + +/** + * Toggle the navigation. + * If it's going to open: + * - Set the width of the side navigation to 250px and the left margin of the page content to 250px and add a black background color to the body. + * - Otherwise, set the width of the side navigation to 0 and the left margin of the page content to 0, and the background color of the body to white. + * + * @param open - If you want to open the sidebar (defaults to close). + */ +function toggleNav(open: boolean): void { + const classlist = getElementById("mySidenav", HTMLDivElement)?.classList; + const w100 = "w-full"; + const w0 = "w-0"; + + classlist?.add(open ? w100 : w0); + classlist?.remove(open ? w0 : w100); +} +window.toggleNav = toggleNav; + +function getTypeIds(): NodeListOf { + return document.querySelectorAll('[id^="typeId"]'); +} + +/** + * Swaps the High School and the Middle School. + */ +async function hsmsSwap(): Promise { + const { checked } = hsmsInput; + + if (checked) { + gradeLvl.innerHTML = high; + + await setGrade(String(checked)); + getElementById("numOfClasses", HTMLInputElement)!.value = String( + courses.length, + ); + + for (const element of getTypeIds()) { + element.innerHTML = `
+ +
`; + } + } else { + gradeLvl.innerHTML = middle; + await setGrade(String(checked)); + + for (const element of getTypeIds()) { + element.innerHTML = ""; + } + } +} +window.hsmsSwap = hsmsSwap; + +/** + * Create a `Course`. + */ +function createCourse(num: number): void { + tempElementId = `temp${num}`; + tempElementIdNext = `temp${num + 1}`; + + // creates html elements in the courses class + getElementById(tempElementId, HTMLDivElement)!.innerHTML = `
+
+ + +
+ +
+
+ + + +
+
+
`; +} + +/** + * Remove "Saved!" text. + */ +function saveRemove(): void { + const element = getElementById("saved", HTMLElement); + + if (element !== undefined) { + element.innerHTML = ""; + } +} + +/** + * Saves values to the array. + */ +async function loadgpa(): Promise { + // set vars + let pregpa = 0; + let courseLen = courses.length; + + tempLGID = ""; + tempCTID = ""; + tempCTYID = ""; + + // save classes to array + for (const [itr, course] of courses.entries()) { + tempLGID = `cl${itr + 1}`; + tempCTID = `cl${itr + 1}txt`; + tempCTYID = `cltyp${itr + 1}`; + + course.letterGrade = Number( + getElementById(tempLGID, HTMLSelectElement)!.value, + ); + + course.classText = getElementById(tempCTID, HTMLInputElement)!.value; + } + + // remove N/A from addition + for (const course of courses) { + if (course.letterGrade === 5) { + courseLen -= 1; + } else { + // adds to pregpa + if (course.classType === "2" && hsmsInput.checked) { + if (course.letterGrade === 0) { + pregpa += course.letterGrade; + } else { + pregpa += 1 + course.letterGrade; + } + } else { + pregpa += course.letterGrade; + } + } + } + + // Divide. + const gpa = pregpa / courseLen; + // Round. + const roundedgpa = Math.round(gpa * 100) / 100; + + getElementById("gpa", HTMLHeadingElement)!.innerHTML = + `Your GPA is a: ${roundedgpa}`; + + // shows save text + getElementById("saved", HTMLParagraphElement)!.innerHTML = "Saved!"; + setTimeout(saveRemove, 1000); + + // save storage + await setData(courses); + for (const [itr] of courses.entries()) { + getElementById(`slide${itr + 1}`, HTMLInputElement)!.value = getElementById( + `cl${itr + 1}`, + HTMLSelectElement, + )!.value; + } +} +window.loadgpa = loadgpa; + +async function classAmount(): Promise { + courses = []; // if storage don't exist, create the array + + // get textbox with number of classes + classAmountNum = Math.abs( + parseInt(getElementById("numOfClasses", HTMLInputElement)!.value), + ); + + if ( + classAmountNum === 0 || + Number.isNaN(classAmountNum) || + classAmountNum > 256 + ) { + classAmountNum = 7; // stops NaN/0/null on numOfClasses textbox + } + + // creates classes for number of iterations + for (let itr = 0; itr < classAmountNum; itr++) { + courses.push(newCourse({ classNum: itr + 1 })); + createCourse(itr + 1); + } + if (!hsmsInput.checked) { + // loops through all course classes and removes the honors dropdowns + for (const element of getTypeIds()) { + element.innerHTML = ""; + } + } + + // calculates and saves the gpa + await loadgpa(); + // sets the gpa text to "" + getElementById("gpa", HTMLHeadingElement)!.innerHTML = ""; +} +window.classAmount = classAmount; + +/** + * Populates course object data. + */ +function createStorageCourse(classNum: number): void { + const num = classNum; + + tempElementId = `temp${num}`; + tempElementIdNext = `temp${num + 1}`; +} + +/** + * Not to be confused with {@link getStorage}. + */ +function fromStorage(arraystorage: Course[]): void { + courses = arraystorage; + // creates courses from array data after it is pulled from storage + + for (const course of courses) { + createCourse(course.classNum); + createStorageCourse(course.classNum); + } + for (const [itr] of courses.entries()) { + tempLGID = `cl${itr + 1}`; + tempCTID = `cl${itr + 1}txt`; + tempCTYID = `cltyp${itr + 1}`; + + for (const course of courses) { + createCourse(course.classNum); + createStorageCourse(course.classNum); + } + + for (const [itr2, course] of courses.entries()) { + tempLGID = `cl${itr2 + 1}`; + tempCTID = `cl${itr2 + 1}txt`; + tempCTYID = `cltyp${itr2 + 1}`; + + getElementById(tempLGID, HTMLSelectElement)!.value = String( + course.letterGrade, + ); + getElementById(tempCTID, HTMLInputElement)!.value = course.classText; + getElementById(tempCTYID, HTMLSelectElement)!.value = course.classType; + } + + if (!hsmsInput.checked) { + for (const element of getTypeIds()) { + element.innerHTML = ""; + } + } + } +} + +/** + * Called on page load. + * + * Pulls data from storage. + */ +async function getStorage(): Promise { + const gradestorage = await getGrade(); + const arraystorage = await getData(); + + if (gradestorage === "true") { + hsmsInput.checked = true; + gradeLvl.innerHTML = high; + } else if (gradestorage === "false") { + hsmsInput.checked = false; + gradeLvl.innerHTML = middle; + } + if (arraystorage === null) { + // if storage doesn't exist + await classAmount(); + } else { + // if storage does exist + fromStorage(arraystorage); + await loadgpa(); + } + if (gradestorage === "false") { + await hsmsSwap(); + } +} + +async function updateSw(): Promise { + await registerSW({ + onRegisteredSW(swUrl, r) { + const intervalMS = 60 * 60 * 1000; + + r && + // eslint-disable-next-line @typescript-eslint/no-misused-promises + setInterval(async (): Promise => { + if (r.installing !== null) { + return; + } + + if (Object.hasOwn(navigator, "connection") && !navigator.onLine) { + return; + } + + const resp = await fetch(swUrl, { + cache: "no-store", + headers: { + cache: "no-store", + "cache-control": "no-cache", + }, + }); + + if (resp.status === 200) { + await r.update(); + } + }, intervalMS); + }, + })(true); +} + +async function startApp(): Promise { + await updateSw(); + await getStorage(); +} + +window.startApp = startApp; diff --git a/packages/gpa-calculator/src/scripts/storage.ts b/packages/gpa-calculator/src/scripts/storage.ts new file mode 100644 index 0000000..7ddef62 --- /dev/null +++ b/packages/gpa-calculator/src/scripts/storage.ts @@ -0,0 +1,44 @@ +import { createStorage, type Storage } from "unstorage"; +import indexedDbDriver from "unstorage/drivers/indexedb"; +import type { Course } from "../data/data-types"; + +const storage: Storage = createStorage({ + driver: indexedDbDriver({ base: "app:" }), +}); + +const arraystorage = "arraystorage"; +const gradestorage = "gradestorage"; + +/** Clears class storage data. */ +async function clearData(): Promise { + await storage.removeItem(arraystorage); + location.reload(); +} + +/** Clears all website storage data. */ +async function clearAll(): Promise { + await storage.clear(); + location.reload(); +} + +/** Sets the storage data. */ +async function setData(value: Course[]): Promise { + await storage.setItem(arraystorage, value); +} + +/** Gets the storage data. */ +async function getData(): Promise { + return await storage.getItem(arraystorage); +} + +/** Sets the grade. */ +async function setGrade(value: string): Promise { + await storage.setItem(gradestorage, value); +} + +/** Gets the grade. */ +async function getGrade(): Promise { + return await storage.getItem(gradestorage); +} + +export { clearAll, clearData, getData, getGrade, setData, setGrade }; diff --git a/packages/gpa-calculator/src/styles/fonts/SIL Open Font License.txt b/packages/gpa-calculator/src/styles/fonts/SIL Open Font License.txt new file mode 100644 index 0000000..75122d6 --- /dev/null +++ b/packages/gpa-calculator/src/styles/fonts/SIL Open Font License.txt @@ -0,0 +1,44 @@ +Copyright (c) October 14, 2013 Jamie Wilson (jamiewilson.io), with Reserved Font Name Norwester. + + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others. + +The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the copyright statement(s). + +"Original Version" refers to the collection of Font Software components as distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, or substituting -- in part or in whole -- any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment. + +"Author" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission. + +5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. \ No newline at end of file diff --git a/packages/gpa-calculator/src/styles/fonts/norwester.otf b/packages/gpa-calculator/src/styles/fonts/norwester.otf new file mode 100644 index 0000000..715ce86 Binary files /dev/null and b/packages/gpa-calculator/src/styles/fonts/norwester.otf differ diff --git a/packages/gpa-calculator/src/styles/global.css b/packages/gpa-calculator/src/styles/global.css new file mode 100644 index 0000000..aab4fe3 --- /dev/null +++ b/packages/gpa-calculator/src/styles/global.css @@ -0,0 +1,269 @@ +html, +body { + height: 100%; +} + +@font-face { + font-family: Norwester; + src: + local("Norwester") format("opentype"), + url("/src/styles/fonts/norwester.otf") format("opentype"); +} + +body { + font-family: Norwester, Verdana, Geneva, Tahoma, sans-serif; +} + +button { + background-color: #00d5ff; + color: black; +} + +.row { + display: flex; +} + +.bg { + background-image: url("/bground.png"); +} +.blacktxt { + color: white; + background-color: rgba(0, 0, 0, 0); +} + +/* The switch - the box around the slider */ +.oldswitch { + position: relative; + display: inline-block; + width: 60px; + height: 34px; +} + +/* Hide default HTML checkbox */ +.oldswitch input { + opacity: 0; + width: 0; + height: 0; +} + +/* The slider */ +.switch { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: #ccc; + -webkit-transition: 0.4s; + -moz-transition: 0.4s; + -o-transition: 0.4s; + transition: 0.4s; +} + +.switch:before { + position: absolute; + content: ""; + height: 26px; + width: 26px; + left: 4px; + bottom: 4px; + background-color: white; + -webkit-transition: 0.4s; + -moz-transition: 0.4s; + -o-transition: 0.4s; + transition: 0.4s; +} + +input:checked + .switch { + background-color: #00d5ff; +} + +input:focus + .switch { + box-shadow: 0 0 1px #00d5ff; +} + +input:checked + .switch:before { + -webkit-transform: translateX(26px); + -moz-transform: translateX(26px); + -o-transform: translateX(26px); + -ms-transform: translateX(26px); + transform: translateX(26px); +} + +/* Rounded sliders */ +.switch.round { + border-radius: 34px; +} + +.switch.round:before { + border-radius: 50%; +} + +/* The Overlay (background) */ +.overlay { + /* Height & width depends on how you want to reveal the overlay (see JS below) */ + height: 100%; + position: fixed; /* Stay in place */ + z-index: 1; /* Sit on top */ + left: 0; + top: 0; + background-color: rgb(0, 0, 0); /* Black fallback color */ + background-color: rgba(0, 0, 0, 0.9); /* Black w/opacity */ + overflow-x: hidden; /* Disable horizontal scroll */ + transition: 0.5s; /* 0.5 second transition effect to slide in or slide down the overlay (height or width, depending on reveal) */ +} + +/* Position the content inside the overlay */ +.overlay-content { + position: relative; + top: 25%; /* 25% from the top */ + width: 100%; /* 100% width */ + text-align: center; /* Centered text/links */ + margin-top: 30px; /* 30px top margin to avoid conflict with the close button on smaller screens */ +} + +/* The navigation links inside the overlay */ +.overlay a { + padding: 8px; + text-decoration: none; + font-size: 36px; + color: #818181; + display: block; /* Display block instead of inline */ + transition: 0.3s; /* Transition effects on hover (color) */ +} + +/* When you mouse over the navigation links, change their color */ +.overlay a:hover, +.overlay a:focus { + color: #f1f1f1; +} + +/* Position the close button (top right corner) */ +.overlay .closebtn { + position: absolute; + top: 20px; + right: 45px; + font-size: 60px; +} + +/* When the height of the screen is less than 450 pixels, change the font-size of the links and position the close button again, so they don't overlap */ +@media screen and (max-height: 450px) { + .overlay a { + font-size: 20px; + } + .overlay .closebtn { + font-size: 40px; + top: 15px; + right: 35px; + } +} + +::-webkit-input-placeholder { + /* WebKit, Blink, Edge */ + color: #ffffff; +} + +:-moz-placeholder { + /* Mozilla Firefox 4 to 18 */ + color: #ffffff; + opacity: 1; +} + +::-moz-placeholder { + /* Mozilla Firefox 19+ */ + color: #ffffff; + opacity: 1; +} + +:-ms-input-placeholder { + /* Internet Explorer 10-11 */ + color: #ffffff; +} + +::-ms-input-placeholder { + /* Microsoft Edge */ + color: #ffffff; +} + +::placeholder { + /* Most modern browsers support this now. */ + color: #ffffff; +} + +input[type="range"] { + margin: auto; + -webkit-appearance: none; + appearance: none; + position: relative; + overflow: hidden; + height: 25px; + width: 200px; + cursor: pointer; + border-radius: 0; /* iOS */ + border-radius: 13px; +} + +::-webkit-slider-runnable-track { + background: #000000; +} + +/* + 1. Set to 0 width and remove border for a slider without a thumb + 2. Shadow is negative the full width of the input and has a spread of the width of the input. + */ +::-webkit-slider-thumb { + -webkit-appearance: none; + width: 25px; /* 1 */ + height: 25px; + border-radius: 13px; + background: #00d5ff; + box-shadow: -500px 0 0 490px #00d5ff; /* 2 */ + border: 2px solid #ffffff; /* 1 */ +} + +::-moz-range-track { + height: 40px; + background: #000000; +} + +::-moz-range-thumb { + background: #000000; + height: 40px; + width: 20px; + border: 3px solid #000000; + border-radius: 0 !important; + box-shadow: -200px 0 0 200px #1bd6ff; + box-sizing: border-box; +} + +::-ms-fill-lower { + background: #1bd6ff; +} + +::-ms-thumb { + background: #000000; + border: 2px solid #000000; + height: 40px; + width: 20px; + box-sizing: border-box; +} + +::-ms-ticks-after { + display: none; +} + +::-ms-ticks-before { + display: none; +} + +::-ms-track { + background: #000000; + color: transparent; + height: 40px; + border: none; +} + +::-ms-tooltip { + display: none; +} diff --git a/packages/gpa-calculator/src/sw.ts b/packages/gpa-calculator/src/sw.ts new file mode 100644 index 0000000..b980d0c --- /dev/null +++ b/packages/gpa-calculator/src/sw.ts @@ -0,0 +1,8 @@ +import { cleanupOutdatedCaches, precacheAndRoute } from "workbox-precaching"; + +cleanupOutdatedCaches(); + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-expect-error: __WB_MANIFEST is a placeholder that workbox-precaching will inject at compile time. +// eslint-disable-next-line @typescript-eslint/no-unsafe-argument +precacheAndRoute(self.__WB_MANIFEST ?? []); diff --git a/packages/gpa-calculator/tailwind.config.ts b/packages/gpa-calculator/tailwind.config.ts new file mode 100644 index 0000000..31225b1 --- /dev/null +++ b/packages/gpa-calculator/tailwind.config.ts @@ -0,0 +1,4 @@ +/** + * I'm not used yet. + * I just fix intellisense. + */ diff --git a/packages/gpa-calculator/tsconfig.eslint.json b/packages/gpa-calculator/tsconfig.eslint.json new file mode 100644 index 0000000..8427310 --- /dev/null +++ b/packages/gpa-calculator/tsconfig.eslint.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "include": ["vite.config.ts", "eslint.config.js", "tailwind.config.ts"], + "compilerOptions": { + "tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.extra.json", + "allowJs": true, + "checkJs": true, + "types": ["@types/bun"] + } +} diff --git a/packages/gpa-calculator/tsconfig.json b/packages/gpa-calculator/tsconfig.json new file mode 100644 index 0000000..7e9f4d8 --- /dev/null +++ b/packages/gpa-calculator/tsconfig.json @@ -0,0 +1,40 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "compilerOptions": { + "target": "esnext", + "module": "esnext", + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "lib": ["esnext", "dom", "dom.iterable"], + "incremental": true, + "tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json", + "strict": true, + "moduleDetection": "force", + "noFallthroughCasesInSwitch": true, + "forceConsistentCasingInFileNames": true, + "noImplicitReturns": true, + "noImplicitOverride": true, + "exactOptionalPropertyTypes": true, + "noUncheckedIndexedAccess": true, + "noPropertyAccessFromIndexSignature": true, + "noUnusedLocals": true, + "allowUnusedLabels": false, + "allowUnreachableCode": false, + "noUnusedParameters": true, + "isolatedModules": true, + "esModuleInterop": true, + "resolveJsonModule": true, + "verbatimModuleSyntax": true, // this is required for Sheriff to perform correctly. + "noEmit": true, // you should pretty much never need this, most projects are transpiled and bundled with specific tools like Rollup nowadays. + "skipLibCheck": true, + "allowSyntheticDefaultImports": true, + "allowJs": false, + "checkJs": false, + "experimentalDecorators": false, + "useDefineForClassFields": true, + "paths": {}, + "types": ["vite-plugin-pwa/vanillajs", "vite/client"] + }, + "include": ["src/**/*.ts"], + "exclude": ["node_modules", "src/sw.ts"] +} diff --git a/packages/gpa-calculator/tsconfig.sw.json b/packages/gpa-calculator/tsconfig.sw.json new file mode 100644 index 0000000..846710d --- /dev/null +++ b/packages/gpa-calculator/tsconfig.sw.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "include": ["src/sw.ts"], + "exclude": ["node_modules"], + "compilerOptions": { + "lib": ["ESNext", "WebWorker"], + "tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.sw.json" + } +} diff --git a/packages/gpa-calculator/turbo.json b/packages/gpa-calculator/turbo.json new file mode 100644 index 0000000..bbf8e30 --- /dev/null +++ b/packages/gpa-calculator/turbo.json @@ -0,0 +1,115 @@ +{ + "$schema": "https://turbo.build/schema.json", + "globalDependencies": ["bun.lockb"], + "pipeline": { + "build": { + "dependsOn": [ + "bundle", + "typecheck", + "lint", + "format:check" + ] + }, + "bundle": { + "outputs": [ + "dist/**" + ], + "inputs": [ + "src/**", + "public/**", + "vite.config.ts", + "index.html" + ] + }, + "lint": { + "inputs": [ + "**/*.ts", + "eslint.config.js" + ] + }, + "typecheck": { + "dependsOn": [ + "typecheck:src", + "typecheck:sw", + "typecheck:extra" + ] + }, + "typecheck:src": { + "outputs": [ + "node_modules/.cache/tsbuildinfo.json" + ], + "inputs": [ + "src/**", + "!src/sw.ts", + "tsconfig.json" + ] + }, + "typecheck:sw": { + "outputs": [ + "node_modules/.cache/tsbuildinfo.sw.json" + ], + "inputs": [ + "src/sw.ts", + "tsconfig.json" + ] + }, + "typecheck:extra": { + "outputs": [ + "node_modules/.cache/tsbuildinfo.extra.json" + ], + "inputs": [ + "vite.config.ts" + ] + }, + "format": { + "outputs": [ + "node_modules/.cache/prettier/**" + ], + "inputs": [ + "$TURBO_DEFAULT$", + "**/*.html", + "**/*.ts", + "**/*.md", + "**/*.json", + "**/*.js", + "**/*.css", + ".prettierc", + "!**/dist/**" + ] + }, + "format:check": { + "outputs": [ + "node_modules/.cache/prettier/**" + ], + "inputs": [ + "$TURBO_DEFAULT$", + "**/*.html", + "**/*.ts", + "**/*.md", + "tsconfig*.json", + "turbo.json", + "**/*.js", + "**/*.css", + ".prettierc", + "!**/dist/**" + ] + }, + "dev": { + "cache": false, + "persistent": true, + "outputs": [ + "node_modules/.vite/**", + "dev-dist/**" + ], + "inputs": [ + "src/**", + "public/**", + "vite.config.ts", + "index.html" + ] + } + }, + "remoteCache": { + "enabled": false + } +} diff --git a/packages/gpa-calculator/vite.config.ts b/packages/gpa-calculator/vite.config.ts new file mode 100644 index 0000000..0791436 --- /dev/null +++ b/packages/gpa-calculator/vite.config.ts @@ -0,0 +1,98 @@ +import { resolve } from "node:path"; +import browserslist from "browserslist"; +import browserslistToEsbuild from "browserslist-to-esbuild"; +import { browserslistToTargets } from "lightningcss"; +import { defineConfig } from "vite"; +import { VitePWA } from "vite-plugin-pwa"; +import { minify } from "html-minifier-terser"; + +const browsersList = browserslist(); +const basename = "/GPA_Calculator/"; + +// https://vitejs.dev/config/ +export default defineConfig({ + base: basename, + build: { + sourcemap: true, + rollupOptions: { + input: { + // TODO(lishaduck): Once oven-sh/bun#2472 is resolved, use it. Pun not intended :) + main: resolve(import.meta.dirname, "index.html"), + }, + }, + target: browserslistToEsbuild(browsersList), + cssMinify: "lightningcss", + }, + css: { + transformer: "lightningcss", + lightningcss: { + targets: browserslistToTargets(browsersList), + }, + }, + plugins: [ + { + name: "html-minify", // Name of the plugin + transformIndexHtml: { + order: "post", + handler: async (html: string): Promise => + await minify(html, { + removeAttributeQuotes: true, + collapseWhitespace: true, + removeComments: true, + removeRedundantAttributes: true, + useShortDoctype: true, + removeEmptyAttributes: true, + collapseBooleanAttributes: true, + minifyURLs: true, + collapseInlineTagWhitespace: true, + decodeEntities: true, + noNewlinesBeforeTagClose: true, + removeStyleLinkTypeAttributes: true, + removeScriptTypeAttributes: true, + }), + }, + }, + VitePWA({ + strategies: "injectManifest", + injectManifest: { + swSrc: "src/sw.ts", + swDest: "dist/sw.js", + globDirectory: "dist", + globPatterns: ["**/*.{html,js,css,json,png}"], + }, + injectRegister: "script-defer", + registerType: "autoUpdate", + srcDir: "src", + filename: "sw.ts", + workbox: { + cleanupOutdatedCaches: true, + }, + manifest: { + id: basename, + scope: basename, + name: "Pattonville GPA Calculator", + display: "standalone", + start_url: basename, + short_name: "GPA Calculator", + theme_color: "#00843e", + description: "GPA Calculator for Pattonville", + dir: "ltr", + orientation: "any", + background_color: "#000000", + related_applications: [], + prefer_related_applications: false, + display_override: ["window-controls-overlay"], + screenshots: [], + // features: [], + categories: [], + shortcuts: [], + }, + pwaAssets: { + htmlPreset: "2023", + preset: "minimal-2023", + image: "public/logo.svg", + overrideManifestIcons: true, + }, + }), + ], +});