diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..a8f2237 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,5 @@ +FROM nginx:latest + +COPY dist /usr/share/nginx/html +COPY nginx.conf /etc/nginx/nginx.conf + diff --git a/deploy.yaml b/deploy.yaml new file mode 100644 index 0000000..db66273 --- /dev/null +++ b/deploy.yaml @@ -0,0 +1,56 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: mathy +spec: + selector: + matchLabels: + name: mathy + template: + metadata: + labels: + name: mathy + spec: + containers: + - name: mathy + image: glmdev/mathy:latest + imagePullPolicy: Always + ports: + - containerPort: 80 +--- +apiVersion: v1 +kind: Service +metadata: + name: mathy-service +spec: + ports: + - name: http + port: 80 + protocol: TCP + targetPort: 80 + selector: + name: mathy +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: mathy-ingress + annotations: + kubernetes.io/ingress.class: "nginx" + nginx.ingress.kubernetes.io/ssl-redirect: "true" +spec: + tls: + - hosts: + - mathy.glmdev.tech + secretName: mathy-tls + rules: + - host: "mathy.glmdev.tech" + http: + paths: + - pathType: Prefix + path: "/" + backend: + service: + name: mathy-service + port: + number: 80 diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..889dff5 --- /dev/null +++ b/nginx.conf @@ -0,0 +1,15 @@ +worker_processes 4; + +events { worker_connections 1024; } + +http { + server { + listen 80; + root /usr/share/nginx/html; + include /etc/nginx/mime.types; + + location / { + try_files $uri $uri/ /index.html; + } + } +} diff --git a/package.json b/package.json index 11888fb..5e35781 100644 --- a/package.json +++ b/package.json @@ -8,23 +8,38 @@ "prepare": "pnpm run build", "lint": "eslint . --ext .ts", "lint:fix": "eslint --fix . --ext .ts", - "preview": "vite preview" + "preview": "vite preview", + "cd:build": "pnpm run build && docker image build . -t glmdev/mathy", + "cd:push": "docker push glmdev/mathy", + "cd:deploy": "kubectl apply -f deploy.yaml", + "cd:rollout": "kubectl rollout restart deployment/mathy", + "cd": "pnpm run cd:build && pnpm run cd:push && pnpm run cd:rollout" }, "dependencies": { + "@auth0/auth0-vue": "^1.0.0", + "@quasar/extras": "^1.13.5", + "@types/katex": "^0.14.0", + "@types/uuid": "^8.3.4", "@vuetify/vite-plugin": "1.0.0-alpha.10", + "dependency-graph": "^0.11.0", "install": "^0.13.0", + "katex": "^0.15.3", + "mathjs": "^10.4.3", + "quasar": "^2.6.6", + "uuid": "^8.3.2", "vue": "^3.2.25", "vue-router": "^4.0.14", - "vuetify": "3.0.0-beta.0", - "@auth0/auth0-vue": "^1.0.0" + "vuetify": "3.0.0-beta.0" }, "devDependencies": { "@braks/revue-draggable": "^0.4.2", + "@quasar/vite-plugin": "^1.0.9", "@typescript-eslint/eslint-plugin": "^5.18.0", "@typescript-eslint/parser": "^5.18.0", "@vitejs/plugin-vue": "^2.3.1", "@volar/vue-language-service": "^0.33.9", "eslint": "^8.13.0", + "sass": "1.32.0", "typescript": "^4.5.4", "vite": "^2.9.0", "vue-tsc": "^0.33.9" diff --git a/plugins/vuetify.ts b/plugins/vuetify.ts deleted file mode 100644 index ecfb4f6..0000000 --- a/plugins/vuetify.ts +++ /dev/null @@ -1,4 +0,0 @@ -import 'vuetify/styles' -import { createVuetify } from 'vuetify' - -export default createVuetify() diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1337bda..992a4ae 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3,14 +3,24 @@ lockfileVersion: 5.3 specifiers: '@auth0/auth0-vue': ^1.0.0 '@braks/revue-draggable': ^0.4.2 + '@quasar/extras': ^1.13.5 + '@quasar/vite-plugin': ^1.0.9 + '@types/katex': ^0.14.0 + '@types/uuid': ^8.3.4 '@typescript-eslint/eslint-plugin': ^5.18.0 '@typescript-eslint/parser': ^5.18.0 '@vitejs/plugin-vue': ^2.3.1 '@volar/vue-language-service': ^0.33.9 '@vuetify/vite-plugin': 1.0.0-alpha.10 + dependency-graph: ^0.11.0 eslint: ^8.13.0 install: ^0.13.0 + katex: ^0.15.3 + mathjs: ^10.4.3 + quasar: ^2.6.6 + sass: 1.32.0 typescript: ^4.5.4 + uuid: ^8.3.2 vite: ^2.9.0 vue: ^3.2.25 vue-router: ^4.0.14 @@ -19,21 +29,31 @@ specifiers: dependencies: '@auth0/auth0-vue': 1.0.0_vue-router@4.0.14 + '@quasar/extras': 1.13.5 + '@types/katex': 0.14.0 + '@types/uuid': 8.3.4 '@vuetify/vite-plugin': 1.0.0-alpha.10_vite@2.9.1+vuetify@3.0.0-beta.0 + dependency-graph: 0.11.0 install: 0.13.0 + katex: 0.15.3 + mathjs: 10.4.3 + quasar: 2.6.6 + uuid: 8.3.2 vue: 3.2.31 vue-router: 4.0.14_vue@3.2.31 vuetify: 3.0.0-beta.0_vue@3.2.31 devDependencies: '@braks/revue-draggable': 0.4.2_vue@3.2.31 + '@quasar/vite-plugin': 1.0.9_c0effa32a194597943674c123f9c6ed2 '@typescript-eslint/eslint-plugin': 5.18.0_0dd9be2ba5ed9805045f3fec8be848f5 '@typescript-eslint/parser': 5.18.0_eslint@8.13.0+typescript@4.6.3 '@vitejs/plugin-vue': 2.3.1_vite@2.9.1+vue@3.2.31 '@volar/vue-language-service': 0.33.9 eslint: 8.13.0 + sass: 1.32.0 typescript: 4.6.3 - vite: 2.9.1 + vite: 2.9.1_sass@1.32.0 vue-tsc: 0.33.9_typescript@4.6.3 packages: @@ -89,6 +109,13 @@ packages: engines: {node: '>=6.0.0'} hasBin: true + /@babel/runtime/7.17.9: + resolution: {integrity: sha512-lSiBBvodq29uShpWGNbgFdKYNiFDo5/HIYsaCEY9ff4sb10x9jizo2+pRrSyF4jKZCXqgzuqBOQKbUm90gQwJg==} + engines: {node: '>=6.9.0'} + dependencies: + regenerator-runtime: 0.13.9 + dev: false + /@babel/types/7.17.0: resolution: {integrity: sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==} engines: {node: '>=6.9.0'} @@ -184,6 +211,23 @@ packages: fastq: 1.13.0 dev: true + /@quasar/extras/1.13.5: + resolution: {integrity: sha512-H4X3jwqOJpDrn1Pof1mJIpoHdBrZQSR9ZpnQ7RCkJsJccpTyQzs8Q6ehVXfIZrco2kd3UPitRQqEiW7k45ckLg==} + dev: false + + /@quasar/vite-plugin/1.0.9_c0effa32a194597943674c123f9c6ed2: + resolution: {integrity: sha512-i3tOXLu9SvBG/u/VH/vlPpwzU3QHVtogoSl23Y3i9Clgo6XCtm9mHB0XnQvsLQgNfUTkAKJPKnGwAPQd9uBu1A==} + engines: {node: '>=12'} + peerDependencies: + quasar: ^2.0.0 + vite: ^2.0.0 + vue: ^3.0.0 + dependencies: + quasar: 2.6.6 + vite: 2.9.1_sass@1.32.0 + vue: 3.2.31 + dev: true + /@sindresorhus/is/0.14.0: resolution: {integrity: sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==} engines: {node: '>=6'} @@ -317,6 +361,10 @@ packages: resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==} dev: true + /@types/katex/0.14.0: + resolution: {integrity: sha512-+2FW2CcT0K3P+JMR8YG846bmDwplKUTsWgT2ENwdQ1UdVfRk3GQrh6Mi4sTopy30gI8Uau5CEqHTDZ6YvWIUPA==} + dev: false + /@types/minimatch/3.0.5: resolution: {integrity: sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==} dev: true @@ -337,6 +385,10 @@ packages: resolution: {integrity: sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==} dev: true + /@types/uuid/8.3.4: + resolution: {integrity: sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==} + dev: false + /@types/vfile-message/2.0.0: resolution: {integrity: sha512-GpTIuDpb9u4zIO165fUy9+fXcULdD8HFRNli04GehoMVbeNq7D6OBnqSmg3lxZnC+UvgUhEWKxdKiwYUkGltIw==} deprecated: This is a stub types definition. vfile-message provides its own type definitions, so you do not need this installed. @@ -485,7 +537,7 @@ packages: vite: ^2.5.10 vue: ^3.2.25 dependencies: - vite: 2.9.1 + vite: 2.9.1_sass@1.32.0 vue: 3.2.31 dev: true @@ -712,7 +764,7 @@ packages: dependencies: '@vuetify/loader-shared': 1.3.0_vuetify@3.0.0-beta.0 debug: 4.3.4 - vite: 2.9.1 + vite: 2.9.1_sass@1.32.0 vuetify: 3.0.0-beta.0_vue@3.2.31 transitivePeerDependencies: - supports-color @@ -825,6 +877,14 @@ packages: color-convert: 2.0.1 dev: true + /anymatch/3.1.2: + resolution: {integrity: sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==} + engines: {node: '>= 8'} + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + dev: true + /argparse/1.0.10: resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} dependencies: @@ -877,6 +937,11 @@ packages: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} dev: true + /binary-extensions/2.2.0: + resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} + engines: {node: '>=8'} + dev: true + /boxen/3.2.0: resolution: {integrity: sha512-cU4J/+NodM3IHdSL2yN8bqYqnmlBTidDR4RC7nJs61ZmtGz8VZzM3HLQX0zY5mrSmPtR3xWwsq2jOUQqFZN8+A==} engines: {node: '>=6'} @@ -1004,6 +1069,21 @@ packages: is-regex: 1.1.4 dev: true + /chokidar/3.5.3: + resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} + engines: {node: '>= 8.10.0'} + dependencies: + anymatch: 3.1.2 + braces: 3.0.2 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.2 + dev: true + /ci-info/2.0.0: resolution: {integrity: sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==} dev: true @@ -1053,10 +1133,19 @@ packages: resolution: {integrity: sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==} dev: true + /commander/8.3.0: + resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} + engines: {node: '>= 12'} + dev: false + /commondir/1.0.1: resolution: {integrity: sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=} dev: false + /complex.js/2.1.0: + resolution: {integrity: sha512-RdcrDz7YynXp/YXGwXIZ4MtmxXXniT5WmLFRX93cuXUX+0geWAqB8l1BoLXF+3BkzviVzHlpw27P9ow7MvlcmA==} + dev: false + /concat-map/0.0.1: resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=} dev: true @@ -1162,6 +1251,10 @@ packages: engines: {node: '>=0.10.0'} dev: true + /decimal.js/10.3.1: + resolution: {integrity: sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ==} + dev: false + /decompress-response/3.3.0: resolution: {integrity: sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=} engines: {node: '>=4'} @@ -1205,6 +1298,11 @@ packages: slash: 3.0.0 dev: true + /dependency-graph/0.11.0: + resolution: {integrity: sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==} + engines: {node: '>= 0.6.0'} + dev: false + /dir-glob/3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} @@ -1509,6 +1607,10 @@ packages: esbuild-windows-arm64: 0.14.34 dev: true + /escape-latex/1.2.0: + resolution: {integrity: sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw==} + dev: false + /escape-string-regexp/1.0.5: resolution: {integrity: sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=} engines: {node: '>=0.8.0'} @@ -1761,6 +1863,10 @@ packages: engines: {node: '>=0.4.x'} dev: true + /fraction.js/4.2.0: + resolution: {integrity: sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==} + dev: false + /fs.realpath/1.0.0: resolution: {integrity: sha1-FQStJSMVjKpA20onh8sBQRmU6k8=} dev: true @@ -2063,6 +2169,13 @@ packages: resolution: {integrity: sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=} dev: true + /is-binary-path/2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + dependencies: + binary-extensions: 2.2.0 + dev: true + /is-buffer/2.0.5: resolution: {integrity: sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==} engines: {node: '>=4'} @@ -2204,6 +2317,10 @@ packages: resolution: {integrity: sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=} dev: true + /javascript-natural-sort/0.7.1: + resolution: {integrity: sha1-+eIwPUUH9tdDVac2ZNFED7Wg71k=} + dev: false + /js-stringify/1.0.2: resolution: {integrity: sha1-Fzb939lyTyijaCrcYjCufk6Weds=} dev: true @@ -2262,6 +2379,13 @@ packages: promise: 7.3.1 dev: true + /katex/0.15.3: + resolution: {integrity: sha512-Al6V7RJsmjklT9QItyHWGaQCt+NYTle1bZwB1e9MR/tLoIT1MXaHy9UpfGSB7eaqDgjjqqRxQOaQGrALCrEyBQ==} + hasBin: true + dependencies: + commander: 8.3.0 + dev: false + /keyv/3.1.0: resolution: {integrity: sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==} dependencies: @@ -2399,6 +2523,22 @@ packages: engines: {node: '>=8'} dev: true + /mathjs/10.4.3: + resolution: {integrity: sha512-C50lWorCOplBec9Ik5fzhHuOx4G4+mtdz3r1G2I1/r8yj+CpYFXLXNqTdg59oKmIF1tKcIzpxlC4s2dGL7f3pg==} + engines: {node: '>= 12'} + hasBin: true + dependencies: + '@babel/runtime': 7.17.9 + complex.js: 2.1.0 + decimal.js: 10.3.1 + escape-latex: 1.2.0 + fraction.js: 4.2.0 + javascript-natural-sort: 0.7.1 + seedrandom: 3.0.5 + tiny-emitter: 2.1.0 + typed-function: 2.1.0 + dev: false + /meow/5.0.0: resolution: {integrity: sha512-CbTqYU17ABaLefO8vCU153ZZlprKYWDljcndKKDCFcYQITzWCXZAVk4QMFZPgvzrnUQ3uItnIE/LoUOwrT15Ig==} engines: {node: '>=6'} @@ -2506,6 +2646,11 @@ packages: validate-npm-package-license: 3.0.4 dev: true + /normalize-path/3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + dev: true + /normalize-url/4.5.1: resolution: {integrity: sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==} engines: {node: '>=8'} @@ -2856,6 +3001,11 @@ packages: engines: {node: '>=6'} dev: true + /quasar/2.6.6: + resolution: {integrity: sha512-rXoGZROOXVutQX14l/ys0te/NrgQ754krI0Vz52pBXdznyS+dTL6AgGpr3JMwffoxpxhQyedrL2jOAMpq+Jsgw==} + engines: {node: '>= 10.18.1', npm: '>= 6.13.4', yarn: '>= 1.21.1'} + dev: false + /queue-microtask/1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} dev: true @@ -2928,6 +3078,13 @@ packages: util-deprecate: 1.0.2 dev: true + /readdirp/3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + dependencies: + picomatch: 2.3.1 + dev: true + /redent/2.0.0: resolution: {integrity: sha1-wbIAe0LVfrE4kHmzyDM2OdXhzKo=} engines: {node: '>=4'} @@ -2944,6 +3101,10 @@ packages: strip-indent: 3.0.0 dev: true + /regenerator-runtime/0.13.9: + resolution: {integrity: sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==} + dev: false + /regexpp/3.2.0: resolution: {integrity: sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==} engines: {node: '>=8'} @@ -3047,6 +3208,18 @@ packages: suf-regex: 0.3.4 dev: true + /sass/1.32.0: + resolution: {integrity: sha512-fhyqEbMIycQA4blrz/C0pYhv2o4x2y6FYYAH0CshBw3DXh5D5wyERgxw0ptdau1orc/GhNrhF7DFN2etyOCEng==} + engines: {node: '>=8.9.0'} + hasBin: true + dependencies: + chokidar: 3.5.3 + dev: true + + /seedrandom/3.0.5: + resolution: {integrity: sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==} + dev: false + /semver-diff/2.1.0: resolution: {integrity: sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=} engines: {node: '>=0.10.0'} @@ -3293,6 +3466,10 @@ packages: resolution: {integrity: sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=} dev: true + /tiny-emitter/2.1.0: + resolution: {integrity: sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==} + dev: false + /to-fast-properties/2.0.0: resolution: {integrity: sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=} engines: {node: '>=4'} @@ -3388,6 +3565,11 @@ packages: engines: {node: '>=8'} dev: true + /typed-function/2.1.0: + resolution: {integrity: sha512-bctQIOqx2iVbWGDGPWwIm18QScpu2XRmkC19D8rQGFsjKSgteq/o1hTZvIG/wuDq8fanpBDrLkLq+aEN/6y5XQ==} + engines: {node: '>= 10'} + dev: false + /typedarray/0.0.6: resolution: {integrity: sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=} dev: true @@ -3547,6 +3729,11 @@ packages: resolution: {integrity: sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=} dev: true + /uuid/8.3.2: + resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + hasBin: true + dev: false + /v8-compile-cache/2.3.0: resolution: {integrity: sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==} dev: true @@ -3626,7 +3813,7 @@ packages: vfile-message: 2.0.4 dev: true - /vite/2.9.1: + /vite/2.9.1_sass@1.32.0: resolution: {integrity: sha512-vSlsSdOYGcYEJfkQ/NeLXgnRv5zZfpAsdztkIrs7AZHV8RCMZQkwjo4DS5BnrYTqoWqLoUe1Cah4aVO4oNNqCQ==} engines: {node: '>=12.2.0'} hasBin: true @@ -3646,6 +3833,7 @@ packages: postcss: 8.4.12 resolve: 1.22.0 rollup: 2.70.1 + sass: 1.32.0 optionalDependencies: fsevents: 2.3.2 dev: true diff --git a/src/App.vue b/src/App.vue index 06aa3e6..ef017f8 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,10 +1,38 @@ + + diff --git a/src/components/Statement.vue b/src/components/Statement.vue new file mode 100644 index 0000000..214275d --- /dev/null +++ b/src/components/Statement.vue @@ -0,0 +1,56 @@ + + + + + diff --git a/src/main.ts b/src/main.ts index 55d3e96..21ec924 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,11 +1,55 @@ +/* +-------------------------------------------------- + Vue Helpers +-------------------------------------------------- +*/ import { createApp } from 'vue' -import App from './App.vue' + +import router from './router' + +/* +-------------------------------------------------- + App UI +-------------------------------------------------- +*/ +import { Quasar } from 'quasar' + +// Import icon libraries +import '@quasar/extras/roboto-font-latin-ext/roboto-font-latin-ext.css' +import '@quasar/extras/material-icons/material-icons.css' +import '@quasar/extras/fontawesome-v6/fontawesome-v6.css' + +// A few examples for animations from Animate.css: +// import @quasar/extras/animate/fadeIn.css +// import @quasar/extras/animate/fadeOut.css + +// Import Quasar css +import 'quasar/src/css/index.sass' + +/* +-------------------------------------------------- + Plugins +-------------------------------------------------- +*/ import { DraggablePlugin } from '@braks/revue-draggable' import { createAuth0 } from '@auth0/auth0-vue' +import 'katex/dist/katex.min.css' +import 'katex/dist/contrib/auto-render.min' + +/* +-------------------------------------------------- + Components +-------------------------------------------------- +*/ +import App from './App.vue' const app = createApp(App) +app.use(Quasar, { + plugins: {}, // import Quasar plugins and add here +}) + app.use( createAuth0({ domain: 'dev-ge84r-eu.us.auth0.com', @@ -14,5 +58,7 @@ app.use( }), ) +app.use(router) + app.use(DraggablePlugin) app.mount('#app') diff --git a/src/pages/Editor.vue b/src/pages/Editor.vue new file mode 100644 index 0000000..d267c91 --- /dev/null +++ b/src/pages/Editor.vue @@ -0,0 +1,19 @@ + + + + + \ No newline at end of file diff --git a/src/components/Home.vue b/src/pages/Login.vue similarity index 100% rename from src/components/Home.vue rename to src/pages/Login.vue diff --git a/src/pages/Scratch.vue b/src/pages/Scratch.vue new file mode 100644 index 0000000..d2aa3fd --- /dev/null +++ b/src/pages/Scratch.vue @@ -0,0 +1,21 @@ + + + diff --git a/src/quasar-variables.sass b/src/quasar-variables.sass new file mode 100644 index 0000000..1994d16 --- /dev/null +++ b/src/quasar-variables.sass @@ -0,0 +1,10 @@ +$primary : #1976D2 +$secondary : #26A69A +$accent : #9C27B0 + +$dark : #1D1D1D + +$positive : #21BA45 +$negative : #C10015 +$info : #31CCEC +$warning : #F2C037 \ No newline at end of file diff --git a/src/router.ts b/src/router.ts new file mode 100644 index 0000000..e0a2ff8 --- /dev/null +++ b/src/router.ts @@ -0,0 +1,27 @@ +import { createRouter, createWebHistory } from 'vue-router' +import Home from './pages/Login.vue' + +const routes = [ + { + path: '/', + name: 'Home', + component: Home, + }, + { + path: '/scratch', + name: 'Scratch', + component: () => import(/* webpackChunkName: "scratch" */ './pages/Scratch.vue'), + }, + { + path: '/editor', + name: 'Editor', + component: () => import('./pages/Editor.vue'), + }, +] + +const router = createRouter({ + history: createWebHistory(), + routes: routes, +}) + +export default router diff --git a/src/support/page.ts b/src/support/page.ts new file mode 100644 index 0000000..3e77930 --- /dev/null +++ b/src/support/page.ts @@ -0,0 +1,112 @@ +import {MathStatement} from './parse' +import * as math from 'mathjs' +import {DepGraph} from 'dependency-graph' +import { v4 as uuidv4 } from 'uuid' +import {EvaluationResult, Maybe, StatementID, VariableName} from '../types' + +/** + * Wrapper for a page containing multiple interrelated mathematical statements. + */ +export class MathPage { + /** The statements on the page. */ + protected statements: Record = {} + + constructor( + /** Unique page ID. */ + public readonly id: string, + ) {} + + /** Get a statement by ID if it exists. */ + getStatement(id: StatementID): Maybe { + return this.statements[id] + } + + /** Add a statement to this page. */ + addStatement(statement: MathStatement): this { + this.statements[statement.id] = statement + return this + } + + /** Parse the math expression and add it to the page as a statement. */ + addRaw(statement: string): StatementID { + const stmt = new MathStatement(uuidv4() as StatementID, statement) + this.addStatement(stmt) + return stmt.id + } + + /** Get all symbols referenced by statements on this page. */ + symbols(): math.SymbolNode[] { + return Object.values(this.statements) + .map(x => x.symbols()) + .reduce((carry, current) => current.concat(carry), []) + } + + /** Get all symbols defined on this page. */ + defines(): math.SymbolNode[] { + return Object.values(this.statements) + .map(x => x.defines()) + .reduce((carry, current) => current.concat(carry), []) + } + + /** Get all symbols used on this page. */ + uses(): math.SymbolNode[] { + return Object.values(this.statements) + .map(x => x.uses()) + .reduce((carry, current) => current.concat(carry), []) + } + + /** Get a mapping of symbol names to the statements where they are defined. */ + definers(): Record { + const definers: Record = {} + + for ( const statement of Object.values(this.statements) ) { + for ( const symbol of statement.defines() ) { + definers[symbol.name as VariableName] = statement + } + } + + return definers + } + + /** Get the dependency graph of variable declarations between statements on this page. */ + dependencies(): DepGraph { + const graph = new DepGraph() + const defined: Record = this.definers() + + for ( const statement of Object.values(this.statements) ) { + graph.addNode(statement.id, statement) + } + + for ( const statement of Object.values(this.statements) ) { + for ( const symbol of statement.uses() ) { + const provider = defined[symbol.name as VariableName] + if ( !provider ) { + throw new Error('No provider for undefined symbol: ' + symbol.name) + } + + graph.addDependency(statement.id, provider.id) + } + } + + return graph + } + + /** Evaluate the current state of the page and get the result. */ + evaluate(): EvaluationResult { + const evaluations: Record = {} + const scope: Record = {} + const graph = this.dependencies() + + for ( const node of graph.overallOrder() ) { + const stmt = this.statements[node as StatementID] + evaluations[stmt.id] = stmt.parse() + .compile() + .evaluate(scope) + } + + return { + variables: scope, + statements: evaluations, + } + } +} diff --git a/src/support/parse.ts b/src/support/parse.ts new file mode 100644 index 0000000..6dfdae2 --- /dev/null +++ b/src/support/parse.ts @@ -0,0 +1,317 @@ +import * as math from 'mathjs' +import katex from 'katex' +import {HTMLString, LaTeXString, StatementID} from '../types' +import {v4 as uuidv4} from 'uuid' + +/** Base class for walks over MathNode trees. */ +export abstract class MathNodeWalk { + walk(node: math.MathNode): TReturn { + if ( math.isAccessorNode(node) ) { + return this.walkAccessorNode(node) + } else if ( math.isArrayNode(node) ) { + return this.walkArrayNode(node) + } else if ( math.isAssignmentNode(node) ) { + return this.walkAssignmentNode(node) + } else if ( math.isBlockNode(node) ) { + return this.walkBlockNode(node) + } else if ( math.isConditionalNode(node) ) { + return this.walkConditionalNode(node) + } else if ( math.isConstantNode(node) ) { + return this.walkConstantNode(node) + } else if ( math.isFunctionAssignmentNode(node) ) { + return this.walkFunctionAssignmentNode(node) + } else if ( math.isFunctionNode(node) ) { + return this.walkFunctionNode(node) + } else if ( math.isIndexNode(node) ) { + return this.walkIndexNode(node) + } else if ( math.isObjectNode(node) ) { + return this.walkObjectNode(node) + } else if ( math.isOperatorNode(node) ) { + return this.walkOperatorNode(node) + } else if ( math.isParenthesisNode(node) ) { + return this.walkParenthesisNode(node) + } else if ( math.isRangeNode(node) ) { + return this.walkRangeNode(node) + } else if ( (node as unknown as any).isRelationalNode ) { + return this.walkRelationalNode(node as unknown as any) + } else if ( math.isSymbolNode(node) ) { + return this.walkSymbolNode(node) + } + + throw new TypeError('Invalid MathNode: ' + node) + } + + abstract walkAccessorNode(node: math.AccessorNode): TReturn + + abstract walkArrayNode(node: math.ArrayNode): TReturn + + abstract walkAssignmentNode(node: math.AssignmentNode): TReturn + + abstract walkBlockNode(node: math.BlockNode): TReturn + + abstract walkConditionalNode(node: math.ConditionalNode): TReturn + + abstract walkConstantNode(node: math.ConstantNode): TReturn + + abstract walkFunctionAssignmentNode(node: math.FunctionAssignmentNode): TReturn + + abstract walkFunctionNode(node: math.FunctionNode): TReturn + + abstract walkIndexNode(node: math.IndexNode): TReturn + + abstract walkObjectNode(node: math.ObjectNode): TReturn + + abstract walkOperatorNode(node: math.OperatorNode): TReturn + + abstract walkParenthesisNode(node: math.ParenthesisNode): TReturn + + abstract walkRangeNode(node: math.RangeNode): TReturn + + abstract walkRelationalNode(node: math.RelationalNode): TReturn + + abstract walkSymbolNode(node: math.SymbolNode): TReturn +} + + +/** A walk that accumulates all different SymbolNode instances in a tree. */ +export class SymbolWalk extends MathNodeWalk { + walkAccessorNode(node: math.AccessorNode): math.SymbolNode[] { + return [ + ...this.walk(node.object), + ...this.walk(node.index), + ] + } + + walkArrayNode(node: math.ArrayNode): math.SymbolNode[] { + return node.items + .map(x => this.walk(x)) + .reduce((carry, current) => current.concat(carry), []) + } + + walkAssignmentNode(node: math.AssignmentNode): math.SymbolNode[] { + return [ + ...this.walk(node.object), + ...this.walk(node.value), + ...(node.index ? this.walk(node.index) : []), + ] + } + + walkBlockNode(node: math.BlockNode): math.SymbolNode[] { + return node.blocks + .map(x => x.node) + .map(x => this.walk(x)) + .reduce((carry, current) => current.concat(carry), []) + } + + walkConditionalNode(node: math.ConditionalNode): math.SymbolNode[] { + return [ + ...this.walk(node.condition), + ...this.walk(node.trueExpr), + ...this.walk(node.falseExpr), + ] + } + + walkConstantNode(): math.SymbolNode[] { + return [] + } + + walkFunctionAssignmentNode(node: math.FunctionAssignmentNode): math.SymbolNode[] { + return this.walk(node.expr) + } + + walkFunctionNode(node: math.FunctionNode): math.SymbolNode[] { + return [ + ...this.walk(node.fn), + ...node.args + .map(x => this.walk(x)) + .reduce((carry, current) => current.concat(carry), []), + ] + } + + walkIndexNode(node: math.IndexNode): math.SymbolNode[] { + return node.dimensions + .map(x => this.walk(x)) + .reduce((carry, current) => current.concat(carry), []) + } + + walkObjectNode(node: math.ObjectNode): math.SymbolNode[] { + return Object.values(node.properties) + .map(x => this.walk(x)) + .reduce((carry, current) => current.concat(carry), []) + } + + walkOperatorNode(node: math.OperatorNode): math.SymbolNode[] { + return node.args + .map(x => this.walk(x)) + .reduce((carry, current) => carry.concat(current), []) + } + + walkParenthesisNode(node: math.ParenthesisNode): math.SymbolNode[] { + return this.walk(node.content) + } + + walkRangeNode(node: math.RangeNode): math.SymbolNode[] { + return [ + ...this.walk(node.start), + ...this.walk(node.end), + ...(node.step ? this.walk(node.step) : []), + ] + } + + walkRelationalNode(node: math.RelationalNode): math.SymbolNode[] { + return node.params + .map(x => this.walk(x)) + .reduce((carry, current) => carry.concat(current), []) + } + + walkSymbolNode(node: math.SymbolNode): math.SymbolNode[] { + return [node] + } +} + + +/** A walk that accumulates all SymbolNode instances used on the RHS of expressions. */ +export class RValSymbolWalk extends SymbolWalk { + walkAssignmentNode(node: math.AssignmentNode): math.SymbolNode[] { + return this.walk(node.value) + } + + walkFunctionNode(node: math.FunctionNode): math.SymbolNode[] { + return [ + ...this.walk(node.fn), // FIXME should this be removed? Not sure if this is rval or lval + ...node.args + .map(x => this.walk(x)) + .reduce((carry, current) => current.concat(carry), []), + ] + } +} + + +/** A walk that accumulates SymbolNode instances used on the LHS of assignments. */ +export class LValSymbolWalk extends SymbolWalk { + walkAccessorNode(): math.SymbolNode[] { + return [] + } + + walkArrayNode(): math.SymbolNode[] { + return [] + } + + walkAssignmentNode(node: math.AssignmentNode): math.SymbolNode[] { + if ( math.isSymbolNode(node.object) ) { + return super.walkSymbolNode(node.object) + } + + return super.walkAccessorNode(node.object) + } + + walkBlockNode(node: math.BlockNode): math.SymbolNode[] { + return node.blocks + .map(x => this.walk(x.node)) + .reduce((carry, current) => current.concat(carry), []) + } + + walkConditionalNode(): math.SymbolNode[] { + return [] + } + + walkConstantNode(): math.SymbolNode[] { + return [] + } + + walkFunctionAssignmentNode(): math.SymbolNode[] { + return [] + } + + walkFunctionNode(): math.SymbolNode[] { + return [] + } + + walkIndexNode(): math.SymbolNode[] { + return [] + } + + walkObjectNode(): math.SymbolNode[] { + return [] + } + + walkOperatorNode(): math.SymbolNode[] { + return [] + } + + walkParenthesisNode(node: math.ParenthesisNode): math.SymbolNode[] { + return this.walk(node.content) + } + + walkRangeNode(): math.SymbolNode[] { + return [] + } + + walkRelationalNode(): math.SymbolNode[] { + return [] + } + + walkSymbolNode(): math.SymbolNode[] { + return [] + } +} + + +/** A single mathematical statement. */ +export class MathStatement { + static temp(raw: string): MathStatement { + return new MathStatement(uuidv4() as StatementID, raw) + } + + constructor( + /** Unique ID of this statement. */ + public readonly id: StatementID, + + /** The raw statement input by the user. */ + public readonly raw: string, + ) {} + + /** Parse the raw statement to an AST. */ + parse(): math.MathNode { + return math.parse(this.raw) + } + + /** Convert the statement to its equivalent LaTeX code. */ + toLaTeX(): LaTeXString { + return this.parse().toTex() as LaTeXString + } + + /** Render the statement as HTML string. */ + toHTMLString(): HTMLString { + return katex.renderToString(this.toLaTeX(), { + output: 'html', + }) as HTMLString + } + + /** Render the statement to a DOM element. */ + toDOM(node?: HTMLElement): HTMLSpanElement { + if ( !node ) { + node = document.createElement('span') + } + + katex.render(this.toLaTeX(), node, { + output: 'html', + }) + return node + } + + /** Get all symbols referenced in this statement. */ + symbols(): math.SymbolNode[] { + return (new SymbolWalk()).walk(this.parse()) + } + + /** Get all symbols defined on the LHS of this statement. */ + defines(): math.SymbolNode[] { + return (new LValSymbolWalk()).walk(this.parse()) + } + + /** Get all symbols used on the RHS of this statement. */ + uses(): math.SymbolNode[] { + return (new RValSymbolWalk()).walk(this.parse()) + } +} diff --git a/src/types.ts b/src/types.ts index 56babb4..c0b76d6 100644 --- a/src/types.ts +++ b/src/types.ts @@ -80,3 +80,14 @@ export type Integer = TypeTag<'@app.Integer'> & number export function isInteger(num: number): num is Integer { return !isNaN(num) && parseInt(String(num), 10) === num } + +export type LaTeXString = TypeTag<'@app.LaTeXString'> & string +export type HTMLString = TypeTag<'@app.HTMLString'> & string +export type StatementID = TypeTag<'@app.StatementID'> & string +export type VariableName = TypeTag<'@app.VariableName'> & string +export type EvaluatedValue = TypeTag<'@app.EvaluatedValue'> & string + +export interface EvaluationResult { + variables: Record + statements: Record +} diff --git a/vite.config.ts b/vite.config.ts index 7a2b334..365a48c 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,10 +1,23 @@ import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' -import vuetify from '@vuetify/vite-plugin' +import { quasar, transformAssetUrls } from '@quasar/vite-plugin' // https://vitejs.dev/config/ export default defineConfig({ - plugins: [vue(), - vuetify({ autoImport: true }), // Enabled by default + server: { + proxy: { + '/api': { + target: 'http://localhost:8000/', + }, + }, + }, + plugins: [ + vue({ + template: { transformAssetUrls }, + }), + + quasar({ + sassVariables: 'src/quasar-variables.sass', + }), ], })