├── app ├── public │ ├── css │ │ └── app.css │ ├── main.wasm │ ├── img │ │ ├── logo.png │ │ └── logo.svg │ ├── manifest.json │ ├── sw.js │ ├── .htaccess │ ├── index.html │ └── js │ │ └── wasm_exec.js ├── .vscode │ └── extensions.json ├── embed.go ├── vite.config.js ├── .gitignore ├── README.md ├── src │ ├── style.css │ ├── assets │ │ └── vue.svg │ ├── components │ │ └── About.vue │ ├── main.js │ └── App.vue ├── package.json ├── index.html └── package-lock.json ├── cmd ├── server │ ├── .env.example │ ├── docs │ │ ├── swagger.yaml │ │ ├── swagger.json │ │ └── docs.go │ └── main.go └── wasm │ └── main.go ├── Caddyfile.dev ├── .gitignore ├── Dockerfile ├── README.md ├── wasm_funcs.go ├── Taskfile.yml ├── LICENSE ├── go.mod ├── model.go └── go.sum /app/public/css/app.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /cmd/server/.env.example: -------------------------------------------------------------------------------- 1 | PAPERMAKER_ADDRESS="localhost:7765" -------------------------------------------------------------------------------- /app/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["Vue.volar"] 3 | } 4 | -------------------------------------------------------------------------------- /Caddyfile.dev: -------------------------------------------------------------------------------- 1 | localhost:3400 { 2 | root public 3 | file_server browse 4 | } 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | /dist/ 3 | ftpdeploy.js 4 | *.zip 5 | index.zip 6 | .env 7 | go.sum -------------------------------------------------------------------------------- /app/public/main.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zikani03/papermaker/HEAD/app/public/main.wasm -------------------------------------------------------------------------------- /app/embed.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import "embed" 4 | 5 | //go:embed dist 6 | var StaticFS embed.FS 7 | -------------------------------------------------------------------------------- /app/public/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zikani03/papermaker/HEAD/app/public/img/logo.png -------------------------------------------------------------------------------- /app/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import vue from '@vitejs/plugin-vue' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [vue()] 7 | }) 8 | -------------------------------------------------------------------------------- /cmd/wasm/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "syscall/js" 5 | 6 | "github.com/zikani03/papermaker" 7 | ) 8 | 9 | var done chan struct{} 10 | 11 | func main() { 12 | js.Global().Set("GeneratePaper", js.FuncOf(papermaker.GeneratePaper)) 13 | <-done 14 | } 15 | -------------------------------------------------------------------------------- /app/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "PMA: Paper Maker App", 3 | "short_name": "PMA", 4 | "theme_color": "#301453", 5 | "background_color": "#301453", 6 | "display": "standalone", 7 | "orientation": "portrait", 8 | "scope": "/papermaker/", 9 | "start_url": "/papermaker/" 10 | } -------------------------------------------------------------------------------- /app/public/sw.js: -------------------------------------------------------------------------------- 1 | self.addEventListener('install', function(event) { 2 | let cacheKey = "papermaker.labs.zikani.me:Cache" 3 | 4 | event.waitUntil(caches.open(cacheKey).then((cache) => { 5 | return cache.addAll([ 6 | '/js/wasm_exec.js', 7 | '/main.wasm', 8 | ]) 9 | })) 10 | }); -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /app/README.md: -------------------------------------------------------------------------------- 1 | # Vue 3 + Vite 2 | 3 | This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 ` 11 | 12 | 13 | 14 | 15 |
16 |
17 |
18 | 19 |

Paper Maker App

20 |

Loading ...

21 | Built by zikani03 22 |
23 |
24 |
25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Taskfile.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | tasks: 4 | 5 | build-server: 6 | deps: [ build-wasm ] 7 | cmds: 8 | - gox -osarch="windows/amd64" -ldflags "-s -w" -output "dist/papermaker_windows_amd64.exe" ./cmd/server/ 9 | - gox -osarch="linux/amd64" -ldflags "-s -w" -output "dist/papermaker_linux_amd64" ./cmd/server/ 10 | - gox -osarch="linux/amd64" -ldflags="-s -w -d" -output="dist/papermaker_linux_amd64" ./cmd/server/ 11 | 12 | build-wasm: 13 | cmds: 14 | # - cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" . 15 | - cd ${PWD}/app && npm run build 16 | - GOOS=js GOARCH=wasm go build -ldflags "-s -w -d" -o app/public/main.wasm cmd/wasm/main.go 17 | 18 | build: 19 | deps: [ build-server ] 20 | cmds: 21 | - echo "Build completed" 22 | 23 | run: 24 | deps: [ build ] 25 | cmds: 26 | - caddy run -config Caddyfile.dev 27 | 28 | deploy: 29 | deps: [ build ] 30 | cmds: 31 | - node ftpdeploy.js 32 | 33 | test: 34 | cmds: 35 | - go test -timeout 2m -cover -coverprofile=coverage.txt -covermode=atomic ./... 36 | -------------------------------------------------------------------------------- /cmd/server/docs/swagger.yaml: -------------------------------------------------------------------------------- 1 | basePath: /api/v1 2 | host: papermaker.labs.zikani.me 3 | info: 4 | contact: 5 | email: zikani.nmwase[at]ymail.com 6 | name: Zikani Nyirenda Mwase 7 | url: https://papermaker.labs.zikani.me 8 | description: PaperMaker API server. 9 | license: 10 | name: MIT 11 | termsOfService: https://papermaker.labs.zikani.me 12 | title: Paper Maker 13 | version: 0.1.0 14 | paths: 15 | /accounts: 16 | get: 17 | consumes: 18 | - application/json 19 | description: generates a .docx paper 20 | produces: 21 | - text/plain 22 | responses: 23 | "200": 24 | description: OK 25 | schema: 26 | type: string 27 | "400": 28 | description: Bad Request 29 | schema: 30 | type: string 31 | "404": 32 | description: Not Found 33 | schema: 34 | type: string 35 | "500": 36 | description: Internal Server Error 37 | schema: 38 | type: string 39 | summary: Generate a Paper 40 | tags: 41 | - paper 42 | swagger: "2.0" 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Zikani Nyirenda Mwase 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/zikani03/papermaker 2 | 3 | go 1.21 4 | 5 | require ( 6 | github.com/gingfrederik/docx v0.0.1 7 | github.com/go-chi/chi/v5 v5.0.7 8 | github.com/go-chi/cors v1.2.1 9 | github.com/gonfva/docxlib v0.0.0-20210517191039-d8f39cecf1ad 10 | github.com/joho/godotenv v1.4.0 11 | github.com/swaggo/http-swagger v1.3.3 12 | github.com/swaggo/swag v1.8.1 13 | ) 14 | 15 | require ( 16 | github.com/KyleBanks/depth v1.2.1 // indirect 17 | github.com/fumiama/go-docx v0.0.0-20231116125324-b3e1a27db995 // indirect 18 | github.com/fumiama/imgsz v0.0.2 // indirect 19 | github.com/go-openapi/jsonpointer v0.19.5 // indirect 20 | github.com/go-openapi/jsonreference v0.20.0 // indirect 21 | github.com/go-openapi/spec v0.20.6 // indirect 22 | github.com/go-openapi/swag v0.19.15 // indirect 23 | github.com/golang/glog v0.0.0-20210429001901-424d2337a529 // indirect 24 | github.com/josharian/intern v1.0.0 // indirect 25 | github.com/mailru/easyjson v0.7.6 // indirect 26 | github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe // indirect 27 | golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 // indirect 28 | golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect 29 | golang.org/x/tools v0.1.10 // indirect 30 | gopkg.in/yaml.v2 v2.4.0 // indirect 31 | ) 32 | -------------------------------------------------------------------------------- /app/src/components/About.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /cmd/server/docs/swagger.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "description": "PaperMaker API server.", 5 | "title": "Paper Maker", 6 | "termsOfService": "https://papermaker.labs.zikani.me", 7 | "contact": { 8 | "name": "Zikani Nyirenda Mwase", 9 | "url": "https://papermaker.labs.zikani.me", 10 | "email": "zikani.nmwase[at]ymail.com" 11 | }, 12 | "license": { 13 | "name": "MIT" 14 | }, 15 | "version": "0.1.0" 16 | }, 17 | "host": "papermaker.labs.zikani.me", 18 | "basePath": "/api/v1", 19 | "paths": { 20 | "/accounts": { 21 | "get": { 22 | "description": "generates a .docx paper", 23 | "consumes": [ 24 | "application/json" 25 | ], 26 | "produces": [ 27 | "text/plain" 28 | ], 29 | "tags": [ 30 | "paper" 31 | ], 32 | "summary": "Generate a Paper", 33 | "responses": { 34 | "200": { 35 | "description": "OK", 36 | "schema": { 37 | "type": "string" 38 | } 39 | }, 40 | "400": { 41 | "description": "Bad Request", 42 | "schema": { 43 | "type": "string" 44 | } 45 | }, 46 | "404": { 47 | "description": "Not Found", 48 | "schema": { 49 | "type": "string" 50 | } 51 | }, 52 | "500": { 53 | "description": "Internal Server Error", 54 | "schema": { 55 | "type": "string" 56 | } 57 | } 58 | } 59 | } 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /app/src/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import { 3 | Button, 4 | Cell, 5 | CellGroup, 6 | Checkbox, 7 | Col, 8 | Collapse, 9 | CollapseItem, 10 | Divider, 11 | Empty, 12 | Field, 13 | List, 14 | NavBar, 15 | Notify, 16 | Picker, 17 | Popup, 18 | Switch, 19 | Radio, 20 | RadioGroup, 21 | Tab, 22 | Tabs, 23 | } from 'vant' 24 | import './style.css' 25 | import 'vant/lib/index.css'; 26 | import App from './App.vue' 27 | 28 | const app = createApp(App) 29 | 30 | app.use(Button) 31 | app.use(Cell) 32 | app.use(CellGroup) 33 | app.use(Checkbox) 34 | app.use(Col) 35 | app.use(Collapse) 36 | app.use(CollapseItem) 37 | app.use(Divider) 38 | app.use(Empty) 39 | app.use(Field) 40 | app.use(List) 41 | app.use(NavBar) 42 | app.use(Notify) 43 | app.use(Picker) 44 | app.use(Popup) 45 | app.use(Radio) 46 | app.use(RadioGroup) 47 | app.use(Switch) 48 | app.use(Tab) 49 | app.use(Tabs) 50 | // Register Lazyload directive 51 | // app.use(vant.Lazyload); 52 | 53 | window._wasmModuleLoaded = false; 54 | let mountedApp = app.mount('#app') 55 | 56 | fetch("main.wasm").then(wasmModule => { 57 | const go = new Go(); 58 | WebAssembly.instantiateStreaming(wasmModule, go.importObject) 59 | .then((result) => { 60 | window._wasmModuleLoaded = true; 61 | mountedApp.isWasmModuleLoaded = true; 62 | // vant.Notify({ type: 'success', message: 'Loaded resources for Offline functionality... You can use the app offline' }); 63 | go.run(result.instance); 64 | }) 65 | .catch(err => { 66 | vant.Notify({ type: 'warning', message: 'Failed to load resources for Offline functionality. You can still use the app but will need an internet connection. Try to reload the page' }); 67 | console.error("Failed to load WASM module, try to reload the page... will use online API to process requests") 68 | window._wasmModuleLoaded = false; 69 | mountedApp.isWasmModuleLoaded = false; 70 | mountedApp.useOfflineMode = false; 71 | }) 72 | }) 73 | 74 | window.addEventListener('load', function() { 75 | if ('serviceWorker' in navigator) { 76 | navigator.serviceWorker.register('/sw.js').then(() => { 77 | console.log("Service worker registered") 78 | }) 79 | .catch(err => { 80 | console.warn("Failed to register service worker") 81 | console.warn(err) 82 | }) 83 | } 84 | }) 85 | -------------------------------------------------------------------------------- /app/public/.htaccess: -------------------------------------------------------------------------------- 1 | 2 | Options -MultiViews 3 | 4 | 5 | 6 | RewriteEngine On 7 | RewriteBase / 8 | RewriteRule ^index\.html$ - [L] 9 | RewriteCond %{REQUEST_FILENAME} !-f 10 | RewriteCond %{REQUEST_FILENAME} !-d 11 | RewriteRule . /index.html [L] 12 | 13 | 14 | 15 | 16 | Header set Content-Encoding "gzip" 17 | 18 | 19 | 20 | # Header set Content-Encoding "gzip" 21 | Header set Content-Type "application/wasm" 22 | 23 | 24 | 25 | 26 | 27 | 28 | # Force deflate for mangled headers developer.yahoo.com/blogs/ydn/posts/2010/12/pushing-beyond-gzipping/ 29 | 30 | 31 | SetEnvIfNoCase ^(Accept-EncodXng|X-cept-Encoding|X{15}|~{15}|-{15})$ ^((gzip|deflate)\s*,?\s*)+|[X~-]{4,13}$ HAVE_Accept-Encoding 32 | RequestHeader append Accept-Encoding "gzip,deflate" env=HAVE_Accept-Encoding 33 | 34 | 35 | 36 | # Compress all output labeled with one of the following MIME-types 37 | # (for Apache versions below 2.3.7, you don't need to enable `mod_filter` 38 | # and can remove the `` and `` lines as 39 | # `AddOutputFilterByType` is still in the core directives) 40 | 41 | AddOutputFilterByType DEFLATE application/atom+xml \ 42 | application/javascript \ 43 | application/json \ 44 | application/rss+xml \ 45 | application/vnd.ms-fontobject \ 46 | application/x-font-ttf \ 47 | application/xhtml+xml \ 48 | application/xml \ 49 | application/wasm \ 50 | font/opentype \ 51 | image/svg+xml \ 52 | image/x-icon \ 53 | text/css \ 54 | text/html \ 55 | text/plain \ 56 | text/x-component \ 57 | text/xml 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /cmd/server/docs/docs.go: -------------------------------------------------------------------------------- 1 | // Package docs GENERATED BY SWAG; DO NOT EDIT 2 | // This file was generated by swaggo/swag 3 | package docs 4 | 5 | import "github.com/swaggo/swag" 6 | 7 | const docTemplate = `{ 8 | "schemes": {{ marshal .Schemes }}, 9 | "swagger": "2.0", 10 | "info": { 11 | "description": "{{escape .Description}}", 12 | "title": "{{.Title}}", 13 | "termsOfService": "https://papermaker.labs.zikani.me", 14 | "contact": { 15 | "name": "Zikani Nyirenda Mwase", 16 | "url": "https://papermaker.labs.zikani.me", 17 | "email": "zikani.nmwase[at]ymail.com" 18 | }, 19 | "license": { 20 | "name": "MIT" 21 | }, 22 | "version": "{{.Version}}" 23 | }, 24 | "host": "{{.Host}}", 25 | "basePath": "{{.BasePath}}", 26 | "paths": { 27 | "/accounts": { 28 | "get": { 29 | "description": "generates a .docx paper", 30 | "consumes": [ 31 | "application/json" 32 | ], 33 | "produces": [ 34 | "text/plain" 35 | ], 36 | "tags": [ 37 | "paper" 38 | ], 39 | "summary": "Generate a Paper", 40 | "responses": { 41 | "200": { 42 | "description": "OK", 43 | "schema": { 44 | "type": "string" 45 | } 46 | }, 47 | "400": { 48 | "description": "Bad Request", 49 | "schema": { 50 | "type": "string" 51 | } 52 | }, 53 | "404": { 54 | "description": "Not Found", 55 | "schema": { 56 | "type": "string" 57 | } 58 | }, 59 | "500": { 60 | "description": "Internal Server Error", 61 | "schema": { 62 | "type": "string" 63 | } 64 | } 65 | } 66 | } 67 | } 68 | } 69 | }` 70 | 71 | // SwaggerInfo holds exported Swagger Info so clients can modify it 72 | var SwaggerInfo = &swag.Spec{ 73 | Version: "0.1.0", 74 | Host: "papermaker.labs.zikani.me", 75 | BasePath: "/api/v1", 76 | Schemes: []string{}, 77 | Title: "Paper Maker", 78 | Description: "PaperMaker API server.", 79 | InfoInstanceName: "swagger", 80 | SwaggerTemplate: docTemplate, 81 | } 82 | 83 | func init() { 84 | swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo) 85 | } 86 | -------------------------------------------------------------------------------- /cmd/server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/base64" 6 | "encoding/json" 7 | "fmt" 8 | "io/fs" 9 | "io/ioutil" 10 | "log" 11 | "net/http" 12 | "os" 13 | "strings" 14 | 15 | "github.com/go-chi/chi/v5" 16 | "github.com/go-chi/chi/v5/middleware" 17 | "github.com/go-chi/cors" 18 | _ "github.com/joho/godotenv/autoload" 19 | 20 | httpSwagger "github.com/swaggo/http-swagger" 21 | 22 | "github.com/zikani03/papermaker" 23 | public "github.com/zikani03/papermaker/app" 24 | _ "github.com/zikani03/papermaker/cmd/server/docs" 25 | ) 26 | 27 | // @title Paper Maker 28 | // @version 0.1.0 29 | // @description PaperMaker API server. 30 | // @termsOfService https://papermaker.labs.zikani.me 31 | 32 | // @contact.name Zikani Nyirenda Mwase 33 | // @contact.url https://papermaker.labs.zikani.me 34 | // @contact.email zikani.nmwase[at]ymail.com 35 | 36 | // @license.name MIT 37 | // @license.url 38 | 39 | // @host papermaker.labs.zikani.me 40 | // @BasePath /api/v1 41 | func main() { 42 | r := chi.NewRouter() 43 | 44 | r.Use(cors.Handler(cors.Options{ 45 | // AllowedOrigins: []string{"https://foo.com"}, // Use this to allow specific origin hosts 46 | AllowedOrigins: []string{"https://*", "http://*", "https://labs.zikani.me"}, 47 | // AllowOriginFunc: func(r *http.Request, origin string) bool { return true }, 48 | AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, 49 | AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"}, 50 | ExposedHeaders: []string{"Link"}, 51 | AllowCredentials: false, 52 | MaxAge: 300, // Maximum value not ignored by any of major browsers 53 | })) 54 | 55 | r.Use(middleware.RealIP) 56 | r.Use(middleware.Logger) 57 | 58 | r.Get("/healthcheck", func(w http.ResponseWriter, r *http.Request) { 59 | w.Header().Add("X-Healthcheck", "ok") 60 | w.Write([]byte("Healthy :)")) 61 | }) 62 | 63 | r.Get("/apidocs/*", httpSwagger.Handler( 64 | httpSwagger.URL("/apidocs/doc.json"), //The url pointing to API definition 65 | )) 66 | 67 | r.Post("/api/v1/generate", apiV1Generate) 68 | 69 | distFS, err := fs.Sub(public.StaticFS, "dist") 70 | if err != nil { 71 | log.Fatal(err) 72 | } 73 | FileServer(r, "/", http.FS(distFS)) 74 | 75 | // FileServer(r, "/", http.Dir("./public")) 76 | log.Println("Server started") 77 | 78 | address := os.Getenv("PAPERMAKER_ADDRESS") 79 | if address == "" { 80 | address = "localhost:8000" 81 | } 82 | 83 | if err := http.ListenAndServe(address, r); err != nil { 84 | log.Fatal(err) 85 | } 86 | } 87 | 88 | // GeneratePaper godoc 89 | // @Summary Generate a Paper 90 | // @Description generates a .docx paper 91 | // @Tags paper 92 | // @Accept json 93 | // @Produce text/plain 94 | // @Success 200 {string} string 95 | // @Failure 400 {object} string 96 | // @Failure 404 {object} string 97 | // @Failure 500 {object} string 98 | // @Router /accounts [get] 99 | func apiV1Generate(w http.ResponseWriter, r *http.Request) { 100 | requestBody, err := ioutil.ReadAll(r.Body) 101 | if err != nil { 102 | http.Error(w, err.Error(), http.StatusInternalServerError) 103 | return 104 | } 105 | 106 | var paperRequest papermaker.ExamPaper 107 | err = json.Unmarshal([]byte(requestBody), &paperRequest) 108 | if err != nil { 109 | http.Error(w, "failed to generate pdf", http.StatusInternalServerError) 110 | return 111 | } 112 | 113 | validationErrors := paperRequest.Validate() 114 | if validationErrors != nil { 115 | http.Error(w, validationErrors.ToJSON(), http.StatusBadRequest) 116 | return 117 | } 118 | 119 | var buf bytes.Buffer 120 | if err := paperRequest.WriteDocx(&buf); err != nil { 121 | http.Error(w, err.Error(), http.StatusInternalServerError) 122 | return 123 | } 124 | base64Encoded := base64.StdEncoding.EncodeToString(buf.Bytes()) 125 | dataURL := fmt.Sprintf("data:application/octet-stream;base64,%s", base64Encoded) 126 | w.Write([]byte(dataURL)) 127 | } 128 | 129 | // FileServer conveniently sets up a http.FileServer handler to serve 130 | // static files from a http.FileSystem. 131 | func FileServer(r chi.Router, path string, root http.FileSystem) { 132 | if strings.ContainsAny(path, "{}*") { 133 | panic("FileServer does not permit any URL parameters.") 134 | } 135 | 136 | if path != "/" && path[len(path)-1] != '/' { 137 | r.Get(path, http.RedirectHandler(path+"/", 301).ServeHTTP) 138 | path += "/" 139 | } 140 | path += "*" 141 | 142 | r.Get(path, func(w http.ResponseWriter, r *http.Request) { 143 | rctx := chi.RouteContext(r.Context()) 144 | pathPrefix := strings.TrimSuffix(rctx.RoutePattern(), "/*") 145 | fs := http.StripPrefix(pathPrefix, http.FileServer(root)) 146 | fs.ServeHTTP(w, r) 147 | }) 148 | } 149 | -------------------------------------------------------------------------------- /app/public/img/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | 22 | 25 | 29 | 33 | 34 | 43 | 44 | 62 | 66 | 71 | PMA 82 | 83 | 84 | -------------------------------------------------------------------------------- /model.go: -------------------------------------------------------------------------------- 1 | package papermaker 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "strings" 8 | 9 | docx "github.com/fumiama/go-docx" 10 | ) 11 | 12 | type EducationLevel string 13 | 14 | const ( 15 | Standard1 EducationLevel = "Standard 1" 16 | Standard2 EducationLevel = "Standard 2" 17 | Standard3 EducationLevel = "Standard 3" 18 | Standard4 EducationLevel = "Standard 4" 19 | Standard5 EducationLevel = "Standard 5" 20 | Standard6 EducationLevel = "Standard 6" 21 | Standard7 EducationLevel = "Standard 7" 22 | Standard8 EducationLevel = "Standard 8" 23 | Form1 EducationLevel = "Form 1" 24 | Form2 EducationLevel = "Form 2" 25 | Form3 EducationLevel = "Form 3" 26 | Form4 EducationLevel = "Form 4" 27 | Form5 EducationLevel = "Form 5" 28 | ALevels EducationLevel = "A-Levels" 29 | ) 30 | 31 | const MaxFreeFormLines = 4 32 | 33 | type ExamPaper struct { 34 | Title string `json:"title"` 35 | ClassName EducationLevel `json:"className"` 36 | SchoolName string `json:"schoolName"` 37 | TeacherName string `json:"teacherName"` 38 | SubjectName string `json:"subjectName"` 39 | TimeAllowed string `json:"timeAllowed"` 40 | ExamDate string `json:"examDate"` // TODO: use time.Time 41 | IsDoubleColumn bool `json:"isDoubleColumn"` 42 | Questions []ExamQuestion `json:"questions"` 43 | } 44 | 45 | type ValidationErrors []string 46 | 47 | func (v ValidationErrors) ToJSON() string { 48 | data := map[string]any{ 49 | "validationErrors": v, 50 | } 51 | b, _ := json.Marshal(data) 52 | return string(b) 53 | } 54 | 55 | func (p *ExamPaper) Validate() ValidationErrors { 56 | errs := make(ValidationErrors, 0) 57 | if p.Title == "" { 58 | errs = append(errs, "error: Title cannot be empty") 59 | } 60 | if p.ClassName == "" { 61 | errs = append(errs, "error: ClassLevel cannot be empty") 62 | } 63 | if p.SchoolName == "" { 64 | errs = append(errs, "error: SchoolName cannot be empty") 65 | } 66 | if p.TeacherName == "" { 67 | errs = append(errs, "error: TeacherName cannot be empty") 68 | } 69 | if p.SubjectName == "" { 70 | errs = append(errs, "error: SubjectName cannot be empty") 71 | } 72 | if p.TimeAllowed == "" { 73 | errs = append(errs, "error: TimeAllowed cannot be empty") 74 | } 75 | if p.ExamDate == "" { 76 | errs = append(errs, "error: ExamDate cannot be empty") 77 | } 78 | if len(p.Questions) < 1 { 79 | errs = append(errs, "error: Questions cannot be empty") 80 | } 81 | 82 | // TODO: implement better validation rules for time, format etc.. 83 | 84 | if len(errs) < 1 { 85 | return nil 86 | } 87 | 88 | return errs 89 | } 90 | 91 | // TotalMarks calculates the total marks for the Questions 92 | func (p *ExamPaper) TotalMarks() int { 93 | totalMarks := 0 94 | for _, q := range p.Questions { 95 | totalMarks += q.Marks 96 | } 97 | return totalMarks 98 | } 99 | 100 | // WriteDocx writes the paper as a Docx to an io.Writer 101 | func (p *ExamPaper) WriteDocx(w io.Writer) error { 102 | docxw := docx.NewA4() 103 | para := docxw.AddParagraph() 104 | para.AddText(strings.ToUpper(p.SchoolName)).Size("18") 105 | 106 | para = docxw.AddParagraph() 107 | para.AddText(strings.ToUpper(p.Title)).Size("14") 108 | 109 | para = docxw.AddParagraph() 110 | para.AddText(p.SubjectName).Size("14") 111 | 112 | para = docxw.AddParagraph() 113 | para.AddText(string(p.ClassName)).Size("12") 114 | 115 | para = docxw.AddParagraph() 116 | para.AddText(p.TeacherName).Size("12") 117 | 118 | para = docxw.AddParagraph() 119 | para.AddText(fmt.Sprintf("DATE: %s\t\t TIME ALLOWED: %s", p.ExamDate, p.TimeAllowed)).Size("12") 120 | 121 | for _, question := range p.Questions { 122 | question.WriteDocx(docxw) 123 | } 124 | 125 | _, err := docxw.WriteTo(w) 126 | return err 127 | } 128 | 129 | const ( 130 | FreeFormQuestion = "free_form" 131 | LabelTheDiagramQuestion = "label-the-diagram" 132 | MathematicalQuestion = "math" 133 | MultipleChoiceQuestion = "multiple_choice" 134 | MixedQuestion = "mixed" 135 | ) 136 | 137 | type MultipleChoiceOption struct { 138 | Content string `json:"content"` 139 | IsCorrect bool `json:"isCorrect"` 140 | } 141 | 142 | type ExamQuestion struct { 143 | Section string `json:"section"` 144 | Title string `json:"title"` 145 | Content string `json:"content"` 146 | QuestionType string `json:"questionType"` 147 | AnswerOptions []MultipleChoiceOption `json:"answerOptions"` 148 | SortOrder int `json:"sortOrder"` 149 | Marks int `json:"marks"` 150 | Image ImageInfo `json:"-"` 151 | } 152 | 153 | func (q *ExamQuestion) WriteDocx(docxw *docx.Docx) error { 154 | qpara := docxw.AddParagraph() 155 | qpara.AddText(fmt.Sprintf("%d. %s", q.SortOrder, q.Content)).Size("12") 156 | 157 | // TODO: Add condition block for the other type type of questions 158 | if q.QuestionType == MultipleChoiceQuestion { 159 | for idx, answers := range q.AnswerOptions { 160 | qapara := docxw.AddParagraph() 161 | qapara.AddText(fmt.Sprintf("%s ) \t %s", string("ABCDEFGHI"[idx]), answers.Content)) 162 | } 163 | } else { 164 | for i := 0; i < MaxFreeFormLines; i++ { 165 | qapara := docxw.AddParagraph() 166 | qapara.AddText("__________________________________________________________________") 167 | } 168 | } 169 | 170 | return nil 171 | } 172 | 173 | type ImageInfo struct { 174 | URL string `json:"url"` 175 | Width string `json:"width"` 176 | Height string `json:"height"` 177 | Alt string `json:"alt"` 178 | } 179 | -------------------------------------------------------------------------------- /app/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 22 |
23 |
24 |
25 | 26 |

Paper Maker App

27 |

Loading ...

28 | Built by zikani03 29 |
30 |
31 |
32 | 33 | 244 | 245 | 246 | 247 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= 2 | github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= 3 | github.com/agiledragon/gomonkey/v2 v2.3.1 h1:k+UnUY0EMNYUFUAQVETGY9uUTxjMdnUkP0ARyJS1zzs= 4 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 5 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 7 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 8 | github.com/fumiama/go-docx v0.0.0-20231116125324-b3e1a27db995 h1:G5rapMTjd1Vp6ihsJBpebwphbe9FsHfp1LEdDjtWtrk= 9 | github.com/fumiama/go-docx v0.0.0-20231116125324-b3e1a27db995/go.mod h1:ssRF0IaB1hCcKIObp3FkZOsjTcAHpgii70JelNb4H8M= 10 | github.com/fumiama/imgsz v0.0.2 h1:fAkC0FnIscdKOXwAxlyw3EUba5NzxZdSxGaq3Uyfxak= 11 | github.com/fumiama/imgsz v0.0.2/go.mod h1:dR71mI3I2O5u6+PCpd47M9TZptzP+39tRBcbdIkoqM4= 12 | github.com/gingfrederik/docx v0.0.1 h1:XciAehRNcFThJnH1ESfOb7amAYk6IGkvFHtVyTNn0oM= 13 | github.com/gingfrederik/docx v0.0.1/go.mod h1:0+v8qYUEEQr66ZKvnQKVhrZBX59pG1MSsQpTYSYOC0A= 14 | github.com/go-chi/chi/v5 v5.0.7 h1:rDTPXLDHGATaeHvVlLcR4Qe0zftYethFucbjVQ1PxU8= 15 | github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= 16 | github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk= 17 | github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= 18 | github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4= 19 | github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= 20 | github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= 21 | github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= 22 | github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= 23 | github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= 24 | github.com/go-openapi/jsonpointer v0.20.0 h1:ESKJdU9ASRfaPNOPRx12IUyA1vn3R9GiE3KYD14BXdQ= 25 | github.com/go-openapi/jsonpointer v0.20.0/go.mod h1:6PGzBjjIIumbLYysB73Klnms1mwnU4G3YHOECG3CedA= 26 | github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA= 27 | github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= 28 | github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= 29 | github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= 30 | github.com/go-openapi/spec v0.20.6 h1:ich1RQ3WDbfoeTqTAb+5EIxNmpKVJZWBNah9RAT0jIQ= 31 | github.com/go-openapi/spec v0.20.6/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA= 32 | github.com/go-openapi/spec v0.20.9 h1:xnlYNQAwKd2VQRRfwTEI0DcK+2cbuvI/0c7jx3gA8/8= 33 | github.com/go-openapi/spec v0.20.9/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA= 34 | github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= 35 | github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM= 36 | github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= 37 | github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= 38 | github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= 39 | github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= 40 | github.com/golang/glog v0.0.0-20210429001901-424d2337a529 h1:2voWjNECnrZRbfwXxHB1/j8wa6xdKn85B5NzgVL/pTU= 41 | github.com/golang/glog v0.0.0-20210429001901-424d2337a529/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 42 | github.com/golang/glog v1.2.0 h1:uCdmnmatrKCgMBlM4rMuJZWOkPDqdbZPnrMXDY4gI68= 43 | github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= 44 | github.com/gonfva/docxlib v0.0.0-20210517191039-d8f39cecf1ad h1:eW9Uqwzg3XdbuljlUSydDF9grQIlOXsjCVjmWCz+Wps= 45 | github.com/gonfva/docxlib v0.0.0-20210517191039-d8f39cecf1ad/go.mod h1:PP43g0DZEMAA3U9VaLoO/buJL1bJoQ2fMLYEZ6RC0fA= 46 | github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg= 47 | github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= 48 | github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= 49 | github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= 50 | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= 51 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 52 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 53 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 54 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 55 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 56 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 57 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 58 | github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 59 | github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 60 | github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= 61 | github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 62 | github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= 63 | github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 64 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= 65 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= 66 | github.com/otiai10/copy v1.7.0 h1:hVoPiN+t+7d2nzzwMiDHPSOogsWAStewq3TwU05+clE= 67 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 68 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 69 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 70 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 71 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 72 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 73 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 74 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 75 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 76 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 77 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 78 | github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe h1:K8pHPVoTgxFJt1lXuIzzOX7zZhZFldJQK/CgKx9BFIc= 79 | github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe/go.mod h1:lKJPbtWzJ9JhsTN1k1gZgleJWY/cqq0psdoMmaThG3w= 80 | github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE= 81 | github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg= 82 | github.com/swaggo/http-swagger v1.3.3 h1:Hu5Z0L9ssyBLofaama21iYaF2VbWyA8jdohaaCGpHsc= 83 | github.com/swaggo/http-swagger v1.3.3/go.mod h1:sE+4PjD89IxMPm77FnkDz0sdO+p5lbXzrVWT6OTVVGo= 84 | github.com/swaggo/http-swagger v1.3.4 h1:q7t/XLx0n15H1Q9/tk3Y9L4n210XzJF5WtnDX64a5ww= 85 | github.com/swaggo/http-swagger v1.3.4/go.mod h1:9dAh0unqMBAlbp1uE2Uc2mQTxNMU/ha4UbucIg1MFkQ= 86 | github.com/swaggo/swag v1.8.1 h1:JuARzFX1Z1njbCGz+ZytBR15TFJwF2Q7fu8puJHhQYI= 87 | github.com/swaggo/swag v1.8.1/go.mod h1:ugemnJsPZm/kRwFUnzBlbHRd0JY9zE1M4F+uy2pAaPQ= 88 | github.com/swaggo/swag v1.16.2 h1:28Pp+8DkQoV+HLzLx8RGJZXNGKbFqnuvSbAAtoxiY04= 89 | github.com/swaggo/swag v1.16.2/go.mod h1:6YzXnDcpr0767iOejs318CwYkCQqyGer6BizOg03f+E= 90 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 91 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 92 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 93 | golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o= 94 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 95 | golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= 96 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 97 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 98 | golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 99 | golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 h1:HVyaeDAYux4pnY+D/SiwmLOR36ewZ4iGQIIrtnuCjFA= 100 | golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= 101 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 102 | golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 103 | golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= 104 | golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= 105 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 106 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 107 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 108 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 109 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 110 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 111 | golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= 112 | golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 113 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 114 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 115 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 116 | golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= 117 | golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 118 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 119 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 120 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 121 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 122 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 123 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 124 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 125 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 126 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 127 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 128 | golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20= 129 | golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= 130 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 131 | golang.org/x/tools v0.16.0 h1:GO788SKMRunPIBCXiQyo2AaexLstOrVhuAL5YwsckQM= 132 | golang.org/x/tools v0.16.0/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= 133 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc= 134 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 135 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 136 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 137 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 138 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= 139 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 140 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 141 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 142 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 143 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 144 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 145 | gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= 146 | gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 147 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 148 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 149 | -------------------------------------------------------------------------------- /app/public/js/wasm_exec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | "use strict"; 6 | 7 | (() => { 8 | const enosys = () => { 9 | const err = new Error("not implemented"); 10 | err.code = "ENOSYS"; 11 | return err; 12 | }; 13 | 14 | if (!globalThis.fs) { 15 | let outputBuf = ""; 16 | globalThis.fs = { 17 | constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, // unused 18 | writeSync(fd, buf) { 19 | outputBuf += decoder.decode(buf); 20 | const nl = outputBuf.lastIndexOf("\n"); 21 | if (nl != -1) { 22 | console.log(outputBuf.substr(0, nl)); 23 | outputBuf = outputBuf.substr(nl + 1); 24 | } 25 | return buf.length; 26 | }, 27 | write(fd, buf, offset, length, position, callback) { 28 | if (offset !== 0 || length !== buf.length || position !== null) { 29 | callback(enosys()); 30 | return; 31 | } 32 | const n = this.writeSync(fd, buf); 33 | callback(null, n); 34 | }, 35 | chmod(path, mode, callback) { callback(enosys()); }, 36 | chown(path, uid, gid, callback) { callback(enosys()); }, 37 | close(fd, callback) { callback(enosys()); }, 38 | fchmod(fd, mode, callback) { callback(enosys()); }, 39 | fchown(fd, uid, gid, callback) { callback(enosys()); }, 40 | fstat(fd, callback) { callback(enosys()); }, 41 | fsync(fd, callback) { callback(null); }, 42 | ftruncate(fd, length, callback) { callback(enosys()); }, 43 | lchown(path, uid, gid, callback) { callback(enosys()); }, 44 | link(path, link, callback) { callback(enosys()); }, 45 | lstat(path, callback) { callback(enosys()); }, 46 | mkdir(path, perm, callback) { callback(enosys()); }, 47 | open(path, flags, mode, callback) { callback(enosys()); }, 48 | read(fd, buffer, offset, length, position, callback) { callback(enosys()); }, 49 | readdir(path, callback) { callback(enosys()); }, 50 | readlink(path, callback) { callback(enosys()); }, 51 | rename(from, to, callback) { callback(enosys()); }, 52 | rmdir(path, callback) { callback(enosys()); }, 53 | stat(path, callback) { callback(enosys()); }, 54 | symlink(path, link, callback) { callback(enosys()); }, 55 | truncate(path, length, callback) { callback(enosys()); }, 56 | unlink(path, callback) { callback(enosys()); }, 57 | utimes(path, atime, mtime, callback) { callback(enosys()); }, 58 | }; 59 | } 60 | 61 | if (!globalThis.process) { 62 | globalThis.process = { 63 | getuid() { return -1; }, 64 | getgid() { return -1; }, 65 | geteuid() { return -1; }, 66 | getegid() { return -1; }, 67 | getgroups() { throw enosys(); }, 68 | pid: -1, 69 | ppid: -1, 70 | umask() { throw enosys(); }, 71 | cwd() { throw enosys(); }, 72 | chdir() { throw enosys(); }, 73 | } 74 | } 75 | 76 | if (!globalThis.crypto) { 77 | throw new Error("globalThis.crypto is not available, polyfill required (crypto.getRandomValues only)"); 78 | } 79 | 80 | if (!globalThis.performance) { 81 | throw new Error("globalThis.performance is not available, polyfill required (performance.now only)"); 82 | } 83 | 84 | if (!globalThis.TextEncoder) { 85 | throw new Error("globalThis.TextEncoder is not available, polyfill required"); 86 | } 87 | 88 | if (!globalThis.TextDecoder) { 89 | throw new Error("globalThis.TextDecoder is not available, polyfill required"); 90 | } 91 | 92 | const encoder = new TextEncoder("utf-8"); 93 | const decoder = new TextDecoder("utf-8"); 94 | 95 | globalThis.Go = class { 96 | constructor() { 97 | this.argv = ["js"]; 98 | this.env = {}; 99 | this.exit = (code) => { 100 | if (code !== 0) { 101 | console.warn("exit code:", code); 102 | } 103 | }; 104 | this._exitPromise = new Promise((resolve) => { 105 | this._resolveExitPromise = resolve; 106 | }); 107 | this._pendingEvent = null; 108 | this._scheduledTimeouts = new Map(); 109 | this._nextCallbackTimeoutID = 1; 110 | 111 | const setInt64 = (addr, v) => { 112 | this.mem.setUint32(addr + 0, v, true); 113 | this.mem.setUint32(addr + 4, Math.floor(v / 4294967296), true); 114 | } 115 | 116 | const getInt64 = (addr) => { 117 | const low = this.mem.getUint32(addr + 0, true); 118 | const high = this.mem.getInt32(addr + 4, true); 119 | return low + high * 4294967296; 120 | } 121 | 122 | const loadValue = (addr) => { 123 | const f = this.mem.getFloat64(addr, true); 124 | if (f === 0) { 125 | return undefined; 126 | } 127 | if (!isNaN(f)) { 128 | return f; 129 | } 130 | 131 | const id = this.mem.getUint32(addr, true); 132 | return this._values[id]; 133 | } 134 | 135 | const storeValue = (addr, v) => { 136 | const nanHead = 0x7FF80000; 137 | 138 | if (typeof v === "number" && v !== 0) { 139 | if (isNaN(v)) { 140 | this.mem.setUint32(addr + 4, nanHead, true); 141 | this.mem.setUint32(addr, 0, true); 142 | return; 143 | } 144 | this.mem.setFloat64(addr, v, true); 145 | return; 146 | } 147 | 148 | if (v === undefined) { 149 | this.mem.setFloat64(addr, 0, true); 150 | return; 151 | } 152 | 153 | let id = this._ids.get(v); 154 | if (id === undefined) { 155 | id = this._idPool.pop(); 156 | if (id === undefined) { 157 | id = this._values.length; 158 | } 159 | this._values[id] = v; 160 | this._goRefCounts[id] = 0; 161 | this._ids.set(v, id); 162 | } 163 | this._goRefCounts[id]++; 164 | let typeFlag = 0; 165 | switch (typeof v) { 166 | case "object": 167 | if (v !== null) { 168 | typeFlag = 1; 169 | } 170 | break; 171 | case "string": 172 | typeFlag = 2; 173 | break; 174 | case "symbol": 175 | typeFlag = 3; 176 | break; 177 | case "function": 178 | typeFlag = 4; 179 | break; 180 | } 181 | this.mem.setUint32(addr + 4, nanHead | typeFlag, true); 182 | this.mem.setUint32(addr, id, true); 183 | } 184 | 185 | const loadSlice = (addr) => { 186 | const array = getInt64(addr + 0); 187 | const len = getInt64(addr + 8); 188 | return new Uint8Array(this._inst.exports.mem.buffer, array, len); 189 | } 190 | 191 | const loadSliceOfValues = (addr) => { 192 | const array = getInt64(addr + 0); 193 | const len = getInt64(addr + 8); 194 | const a = new Array(len); 195 | for (let i = 0; i < len; i++) { 196 | a[i] = loadValue(array + i * 8); 197 | } 198 | return a; 199 | } 200 | 201 | const loadString = (addr) => { 202 | const saddr = getInt64(addr + 0); 203 | const len = getInt64(addr + 8); 204 | return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len)); 205 | } 206 | 207 | const timeOrigin = Date.now() - performance.now(); 208 | this.importObject = { 209 | go: { 210 | // Go's SP does not change as long as no Go code is running. Some operations (e.g. calls, getters and setters) 211 | // may synchronously trigger a Go event handler. This makes Go code get executed in the middle of the imported 212 | // function. A goroutine can switch to a new stack if the current stack is too small (see morestack function). 213 | // This changes the SP, thus we have to update the SP used by the imported function. 214 | 215 | // func wasmExit(code int32) 216 | "runtime.wasmExit": (sp) => { 217 | sp >>>= 0; 218 | const code = this.mem.getInt32(sp + 8, true); 219 | this.exited = true; 220 | delete this._inst; 221 | delete this._values; 222 | delete this._goRefCounts; 223 | delete this._ids; 224 | delete this._idPool; 225 | this.exit(code); 226 | }, 227 | 228 | // func wasmWrite(fd uintptr, p unsafe.Pointer, n int32) 229 | "runtime.wasmWrite": (sp) => { 230 | sp >>>= 0; 231 | const fd = getInt64(sp + 8); 232 | const p = getInt64(sp + 16); 233 | const n = this.mem.getInt32(sp + 24, true); 234 | fs.writeSync(fd, new Uint8Array(this._inst.exports.mem.buffer, p, n)); 235 | }, 236 | 237 | // func resetMemoryDataView() 238 | "runtime.resetMemoryDataView": (sp) => { 239 | sp >>>= 0; 240 | this.mem = new DataView(this._inst.exports.mem.buffer); 241 | }, 242 | 243 | // func nanotime1() int64 244 | "runtime.nanotime1": (sp) => { 245 | sp >>>= 0; 246 | setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000); 247 | }, 248 | 249 | // func walltime() (sec int64, nsec int32) 250 | "runtime.walltime": (sp) => { 251 | sp >>>= 0; 252 | const msec = (new Date).getTime(); 253 | setInt64(sp + 8, msec / 1000); 254 | this.mem.setInt32(sp + 16, (msec % 1000) * 1000000, true); 255 | }, 256 | 257 | // func scheduleTimeoutEvent(delay int64) int32 258 | "runtime.scheduleTimeoutEvent": (sp) => { 259 | sp >>>= 0; 260 | const id = this._nextCallbackTimeoutID; 261 | this._nextCallbackTimeoutID++; 262 | this._scheduledTimeouts.set(id, setTimeout( 263 | () => { 264 | this._resume(); 265 | while (this._scheduledTimeouts.has(id)) { 266 | // for some reason Go failed to register the timeout event, log and try again 267 | // (temporary workaround for https://github.com/golang/go/issues/28975) 268 | console.warn("scheduleTimeoutEvent: missed timeout event"); 269 | this._resume(); 270 | } 271 | }, 272 | getInt64(sp + 8) + 1, // setTimeout has been seen to fire up to 1 millisecond early 273 | )); 274 | this.mem.setInt32(sp + 16, id, true); 275 | }, 276 | 277 | // func clearTimeoutEvent(id int32) 278 | "runtime.clearTimeoutEvent": (sp) => { 279 | sp >>>= 0; 280 | const id = this.mem.getInt32(sp + 8, true); 281 | clearTimeout(this._scheduledTimeouts.get(id)); 282 | this._scheduledTimeouts.delete(id); 283 | }, 284 | 285 | // func getRandomData(r []byte) 286 | "runtime.getRandomData": (sp) => { 287 | sp >>>= 0; 288 | crypto.getRandomValues(loadSlice(sp + 8)); 289 | }, 290 | 291 | // func finalizeRef(v ref) 292 | "syscall/js.finalizeRef": (sp) => { 293 | sp >>>= 0; 294 | const id = this.mem.getUint32(sp + 8, true); 295 | this._goRefCounts[id]--; 296 | if (this._goRefCounts[id] === 0) { 297 | const v = this._values[id]; 298 | this._values[id] = null; 299 | this._ids.delete(v); 300 | this._idPool.push(id); 301 | } 302 | }, 303 | 304 | // func stringVal(value string) ref 305 | "syscall/js.stringVal": (sp) => { 306 | sp >>>= 0; 307 | storeValue(sp + 24, loadString(sp + 8)); 308 | }, 309 | 310 | // func valueGet(v ref, p string) ref 311 | "syscall/js.valueGet": (sp) => { 312 | sp >>>= 0; 313 | const result = Reflect.get(loadValue(sp + 8), loadString(sp + 16)); 314 | sp = this._inst.exports.getsp() >>> 0; // see comment above 315 | storeValue(sp + 32, result); 316 | }, 317 | 318 | // func valueSet(v ref, p string, x ref) 319 | "syscall/js.valueSet": (sp) => { 320 | sp >>>= 0; 321 | Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32)); 322 | }, 323 | 324 | // func valueDelete(v ref, p string) 325 | "syscall/js.valueDelete": (sp) => { 326 | sp >>>= 0; 327 | Reflect.deleteProperty(loadValue(sp + 8), loadString(sp + 16)); 328 | }, 329 | 330 | // func valueIndex(v ref, i int) ref 331 | "syscall/js.valueIndex": (sp) => { 332 | sp >>>= 0; 333 | storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16))); 334 | }, 335 | 336 | // valueSetIndex(v ref, i int, x ref) 337 | "syscall/js.valueSetIndex": (sp) => { 338 | sp >>>= 0; 339 | Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24)); 340 | }, 341 | 342 | // func valueCall(v ref, m string, args []ref) (ref, bool) 343 | "syscall/js.valueCall": (sp) => { 344 | sp >>>= 0; 345 | try { 346 | const v = loadValue(sp + 8); 347 | const m = Reflect.get(v, loadString(sp + 16)); 348 | const args = loadSliceOfValues(sp + 32); 349 | const result = Reflect.apply(m, v, args); 350 | sp = this._inst.exports.getsp() >>> 0; // see comment above 351 | storeValue(sp + 56, result); 352 | this.mem.setUint8(sp + 64, 1); 353 | } catch (err) { 354 | sp = this._inst.exports.getsp() >>> 0; // see comment above 355 | storeValue(sp + 56, err); 356 | this.mem.setUint8(sp + 64, 0); 357 | } 358 | }, 359 | 360 | // func valueInvoke(v ref, args []ref) (ref, bool) 361 | "syscall/js.valueInvoke": (sp) => { 362 | sp >>>= 0; 363 | try { 364 | const v = loadValue(sp + 8); 365 | const args = loadSliceOfValues(sp + 16); 366 | const result = Reflect.apply(v, undefined, args); 367 | sp = this._inst.exports.getsp() >>> 0; // see comment above 368 | storeValue(sp + 40, result); 369 | this.mem.setUint8(sp + 48, 1); 370 | } catch (err) { 371 | sp = this._inst.exports.getsp() >>> 0; // see comment above 372 | storeValue(sp + 40, err); 373 | this.mem.setUint8(sp + 48, 0); 374 | } 375 | }, 376 | 377 | // func valueNew(v ref, args []ref) (ref, bool) 378 | "syscall/js.valueNew": (sp) => { 379 | sp >>>= 0; 380 | try { 381 | const v = loadValue(sp + 8); 382 | const args = loadSliceOfValues(sp + 16); 383 | const result = Reflect.construct(v, args); 384 | sp = this._inst.exports.getsp() >>> 0; // see comment above 385 | storeValue(sp + 40, result); 386 | this.mem.setUint8(sp + 48, 1); 387 | } catch (err) { 388 | sp = this._inst.exports.getsp() >>> 0; // see comment above 389 | storeValue(sp + 40, err); 390 | this.mem.setUint8(sp + 48, 0); 391 | } 392 | }, 393 | 394 | // func valueLength(v ref) int 395 | "syscall/js.valueLength": (sp) => { 396 | sp >>>= 0; 397 | setInt64(sp + 16, parseInt(loadValue(sp + 8).length)); 398 | }, 399 | 400 | // valuePrepareString(v ref) (ref, int) 401 | "syscall/js.valuePrepareString": (sp) => { 402 | sp >>>= 0; 403 | const str = encoder.encode(String(loadValue(sp + 8))); 404 | storeValue(sp + 16, str); 405 | setInt64(sp + 24, str.length); 406 | }, 407 | 408 | // valueLoadString(v ref, b []byte) 409 | "syscall/js.valueLoadString": (sp) => { 410 | sp >>>= 0; 411 | const str = loadValue(sp + 8); 412 | loadSlice(sp + 16).set(str); 413 | }, 414 | 415 | // func valueInstanceOf(v ref, t ref) bool 416 | "syscall/js.valueInstanceOf": (sp) => { 417 | sp >>>= 0; 418 | this.mem.setUint8(sp + 24, (loadValue(sp + 8) instanceof loadValue(sp + 16)) ? 1 : 0); 419 | }, 420 | 421 | // func copyBytesToGo(dst []byte, src ref) (int, bool) 422 | "syscall/js.copyBytesToGo": (sp) => { 423 | sp >>>= 0; 424 | const dst = loadSlice(sp + 8); 425 | const src = loadValue(sp + 32); 426 | if (!(src instanceof Uint8Array || src instanceof Uint8ClampedArray)) { 427 | this.mem.setUint8(sp + 48, 0); 428 | return; 429 | } 430 | const toCopy = src.subarray(0, dst.length); 431 | dst.set(toCopy); 432 | setInt64(sp + 40, toCopy.length); 433 | this.mem.setUint8(sp + 48, 1); 434 | }, 435 | 436 | // func copyBytesToJS(dst ref, src []byte) (int, bool) 437 | "syscall/js.copyBytesToJS": (sp) => { 438 | sp >>>= 0; 439 | const dst = loadValue(sp + 8); 440 | const src = loadSlice(sp + 16); 441 | if (!(dst instanceof Uint8Array || dst instanceof Uint8ClampedArray)) { 442 | this.mem.setUint8(sp + 48, 0); 443 | return; 444 | } 445 | const toCopy = src.subarray(0, dst.length); 446 | dst.set(toCopy); 447 | setInt64(sp + 40, toCopy.length); 448 | this.mem.setUint8(sp + 48, 1); 449 | }, 450 | 451 | "debug": (value) => { 452 | console.log(value); 453 | }, 454 | } 455 | }; 456 | } 457 | 458 | async run(instance) { 459 | if (!(instance instanceof WebAssembly.Instance)) { 460 | throw new Error("Go.run: WebAssembly.Instance expected"); 461 | } 462 | this._inst = instance; 463 | this.mem = new DataView(this._inst.exports.mem.buffer); 464 | this._values = [ // JS values that Go currently has references to, indexed by reference id 465 | NaN, 466 | 0, 467 | null, 468 | true, 469 | false, 470 | globalThis, 471 | this, 472 | ]; 473 | this._goRefCounts = new Array(this._values.length).fill(Infinity); // number of references that Go has to a JS value, indexed by reference id 474 | this._ids = new Map([ // mapping from JS values to reference ids 475 | [0, 1], 476 | [null, 2], 477 | [true, 3], 478 | [false, 4], 479 | [globalThis, 5], 480 | [this, 6], 481 | ]); 482 | this._idPool = []; // unused ids that have been garbage collected 483 | this.exited = false; // whether the Go program has exited 484 | 485 | // Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory. 486 | let offset = 4096; 487 | 488 | const strPtr = (str) => { 489 | const ptr = offset; 490 | const bytes = encoder.encode(str + "\0"); 491 | new Uint8Array(this.mem.buffer, offset, bytes.length).set(bytes); 492 | offset += bytes.length; 493 | if (offset % 8 !== 0) { 494 | offset += 8 - (offset % 8); 495 | } 496 | return ptr; 497 | }; 498 | 499 | const argc = this.argv.length; 500 | 501 | const argvPtrs = []; 502 | this.argv.forEach((arg) => { 503 | argvPtrs.push(strPtr(arg)); 504 | }); 505 | argvPtrs.push(0); 506 | 507 | const keys = Object.keys(this.env).sort(); 508 | keys.forEach((key) => { 509 | argvPtrs.push(strPtr(`${key}=${this.env[key]}`)); 510 | }); 511 | argvPtrs.push(0); 512 | 513 | const argv = offset; 514 | argvPtrs.forEach((ptr) => { 515 | this.mem.setUint32(offset, ptr, true); 516 | this.mem.setUint32(offset + 4, 0, true); 517 | offset += 8; 518 | }); 519 | 520 | // The linker guarantees global data starts from at least wasmMinDataAddr. 521 | // Keep in sync with cmd/link/internal/ld/data.go:wasmMinDataAddr. 522 | const wasmMinDataAddr = 4096 + 8192; 523 | if (offset >= wasmMinDataAddr) { 524 | throw new Error("total length of command line and environment variables exceeds limit"); 525 | } 526 | 527 | this._inst.exports.run(argc, argv); 528 | if (this.exited) { 529 | this._resolveExitPromise(); 530 | } 531 | await this._exitPromise; 532 | } 533 | 534 | _resume() { 535 | if (this.exited) { 536 | throw new Error("Go program has already exited"); 537 | } 538 | this._inst.exports.resume(); 539 | if (this.exited) { 540 | this._resolveExitPromise(); 541 | } 542 | } 543 | 544 | _makeFuncWrapper(id) { 545 | const go = this; 546 | return function () { 547 | const event = { id: id, this: this, args: arguments }; 548 | go._pendingEvent = event; 549 | go._resume(); 550 | return event.result; 551 | }; 552 | } 553 | } 554 | })(); 555 | -------------------------------------------------------------------------------- /app/src/App.vue: -------------------------------------------------------------------------------- 1 | 319 | 320 | 541 | 542 | 544 | -------------------------------------------------------------------------------- /app/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "app", 3 | "version": "0.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "app", 9 | "version": "0.0.0", 10 | "dependencies": { 11 | "fast-base64": "^0.1.8", 12 | "file-saver": "^2.0.5", 13 | "vant": "^3.6.5", 14 | "vue": "^3.2.41" 15 | }, 16 | "devDependencies": { 17 | "@types/file-saver": "^2.0.5", 18 | "@vitejs/plugin-vue": "^3.2.0", 19 | "vite": "^3.2.3" 20 | } 21 | }, 22 | "node_modules/@babel/parser": { 23 | "version": "7.23.4", 24 | "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.4.tgz", 25 | "integrity": "sha512-vf3Xna6UEprW+7t6EtOmFpHNAuxw3xqPZghy+brsnusscJRW5BMUzzHZc5ICjULee81WeUV2jjakG09MDglJXQ==", 26 | "bin": { 27 | "parser": "bin/babel-parser.js" 28 | }, 29 | "engines": { 30 | "node": ">=6.0.0" 31 | } 32 | }, 33 | "node_modules/@esbuild/android-arm": { 34 | "version": "0.15.18", 35 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.18.tgz", 36 | "integrity": "sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==", 37 | "cpu": [ 38 | "arm" 39 | ], 40 | "dev": true, 41 | "optional": true, 42 | "os": [ 43 | "android" 44 | ], 45 | "engines": { 46 | "node": ">=12" 47 | } 48 | }, 49 | "node_modules/@esbuild/linux-loong64": { 50 | "version": "0.15.18", 51 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.18.tgz", 52 | "integrity": "sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==", 53 | "cpu": [ 54 | "loong64" 55 | ], 56 | "dev": true, 57 | "optional": true, 58 | "os": [ 59 | "linux" 60 | ], 61 | "engines": { 62 | "node": ">=12" 63 | } 64 | }, 65 | "node_modules/@jridgewell/sourcemap-codec": { 66 | "version": "1.4.15", 67 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", 68 | "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" 69 | }, 70 | "node_modules/@types/file-saver": { 71 | "version": "2.0.7", 72 | "resolved": "https://registry.npmjs.org/@types/file-saver/-/file-saver-2.0.7.tgz", 73 | "integrity": "sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==", 74 | "dev": true 75 | }, 76 | "node_modules/@vant/icons": { 77 | "version": "1.8.0", 78 | "resolved": "https://registry.npmjs.org/@vant/icons/-/icons-1.8.0.tgz", 79 | "integrity": "sha512-sKfEUo2/CkQFuERxvkuF6mGQZDKu3IQdj5rV9Fm0weJXtchDSSQ+zt8qPCNUEhh9Y8shy5PzxbvAfOOkCwlCXg==" 80 | }, 81 | "node_modules/@vant/popperjs": { 82 | "version": "1.3.0", 83 | "resolved": "https://registry.npmjs.org/@vant/popperjs/-/popperjs-1.3.0.tgz", 84 | "integrity": "sha512-hB+czUG+aHtjhaEmCJDuXOep0YTZjdlRR+4MSmIFnkCQIxJaXLQdSsR90XWvAI2yvKUI7TCGqR8pQg2RtvkMHw==" 85 | }, 86 | "node_modules/@vant/use": { 87 | "version": "1.6.0", 88 | "resolved": "https://registry.npmjs.org/@vant/use/-/use-1.6.0.tgz", 89 | "integrity": "sha512-PHHxeAASgiOpSmMjceweIrv2AxDZIkWXyaczksMoWvKV2YAYEhoizRuk/xFnKF+emUIi46TsQ+rvlm/t2BBCfA==", 90 | "peerDependencies": { 91 | "vue": "^3.0.0" 92 | } 93 | }, 94 | "node_modules/@vitejs/plugin-vue": { 95 | "version": "3.2.0", 96 | "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-3.2.0.tgz", 97 | "integrity": "sha512-E0tnaL4fr+qkdCNxJ+Xd0yM31UwMkQje76fsDVBBUCoGOUPexu2VDUYHL8P4CwV+zMvWw6nlRw19OnRKmYAJpw==", 98 | "dev": true, 99 | "engines": { 100 | "node": "^14.18.0 || >=16.0.0" 101 | }, 102 | "peerDependencies": { 103 | "vite": "^3.0.0", 104 | "vue": "^3.2.25" 105 | } 106 | }, 107 | "node_modules/@vue/compiler-core": { 108 | "version": "3.3.9", 109 | "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.3.9.tgz", 110 | "integrity": "sha512-+/Lf68Vr/nFBA6ol4xOtJrW+BQWv3QWKfRwGSm70jtXwfhZNF4R/eRgyVJYoxFRhdCTk/F6g99BP0ffPgZihfQ==", 111 | "dependencies": { 112 | "@babel/parser": "^7.23.3", 113 | "@vue/shared": "3.3.9", 114 | "estree-walker": "^2.0.2", 115 | "source-map-js": "^1.0.2" 116 | } 117 | }, 118 | "node_modules/@vue/compiler-dom": { 119 | "version": "3.3.9", 120 | "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.3.9.tgz", 121 | "integrity": "sha512-nfWubTtLXuT4iBeDSZ5J3m218MjOy42Vp2pmKVuBKo2/BLcrFUX8nCSr/bKRFiJ32R8qbdnnnBgRn9AdU5v0Sg==", 122 | "dependencies": { 123 | "@vue/compiler-core": "3.3.9", 124 | "@vue/shared": "3.3.9" 125 | } 126 | }, 127 | "node_modules/@vue/compiler-sfc": { 128 | "version": "3.3.9", 129 | "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.3.9.tgz", 130 | "integrity": "sha512-wy0CNc8z4ihoDzjASCOCsQuzW0A/HP27+0MDSSICMjVIFzk/rFViezkR3dzH+miS2NDEz8ywMdbjO5ylhOLI2A==", 131 | "dependencies": { 132 | "@babel/parser": "^7.23.3", 133 | "@vue/compiler-core": "3.3.9", 134 | "@vue/compiler-dom": "3.3.9", 135 | "@vue/compiler-ssr": "3.3.9", 136 | "@vue/reactivity-transform": "3.3.9", 137 | "@vue/shared": "3.3.9", 138 | "estree-walker": "^2.0.2", 139 | "magic-string": "^0.30.5", 140 | "postcss": "^8.4.31", 141 | "source-map-js": "^1.0.2" 142 | } 143 | }, 144 | "node_modules/@vue/compiler-ssr": { 145 | "version": "3.3.9", 146 | "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.3.9.tgz", 147 | "integrity": "sha512-NO5oobAw78R0G4SODY5A502MGnDNiDjf6qvhn7zD7TJGc8XDeIEw4fg6JU705jZ/YhuokBKz0A5a/FL/XZU73g==", 148 | "dependencies": { 149 | "@vue/compiler-dom": "3.3.9", 150 | "@vue/shared": "3.3.9" 151 | } 152 | }, 153 | "node_modules/@vue/reactivity": { 154 | "version": "3.3.9", 155 | "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.3.9.tgz", 156 | "integrity": "sha512-VmpIqlNp+aYDg2X0xQhJqHx9YguOmz2UxuUJDckBdQCNkipJvfk9yA75woLWElCa0Jtyec3lAAt49GO0izsphw==", 157 | "dependencies": { 158 | "@vue/shared": "3.3.9" 159 | } 160 | }, 161 | "node_modules/@vue/reactivity-transform": { 162 | "version": "3.3.9", 163 | "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.3.9.tgz", 164 | "integrity": "sha512-HnUFm7Ry6dFa4Lp63DAxTixUp8opMtQr6RxQCpDI1vlh12rkGIeYqMvJtK+IKyEfEOa2I9oCkD1mmsPdaGpdVg==", 165 | "dependencies": { 166 | "@babel/parser": "^7.23.3", 167 | "@vue/compiler-core": "3.3.9", 168 | "@vue/shared": "3.3.9", 169 | "estree-walker": "^2.0.2", 170 | "magic-string": "^0.30.5" 171 | } 172 | }, 173 | "node_modules/@vue/runtime-core": { 174 | "version": "3.3.9", 175 | "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.3.9.tgz", 176 | "integrity": "sha512-xxaG9KvPm3GTRuM4ZyU8Tc+pMVzcu6eeoSRQJ9IE7NmCcClW6z4B3Ij6L4EDl80sxe/arTtQ6YmgiO4UZqRc+w==", 177 | "dependencies": { 178 | "@vue/reactivity": "3.3.9", 179 | "@vue/shared": "3.3.9" 180 | } 181 | }, 182 | "node_modules/@vue/runtime-dom": { 183 | "version": "3.3.9", 184 | "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.3.9.tgz", 185 | "integrity": "sha512-e7LIfcxYSWbV6BK1wQv9qJyxprC75EvSqF/kQKe6bdZEDNValzeRXEVgiX7AHI6hZ59HA4h7WT5CGvm69vzJTQ==", 186 | "dependencies": { 187 | "@vue/runtime-core": "3.3.9", 188 | "@vue/shared": "3.3.9", 189 | "csstype": "^3.1.2" 190 | } 191 | }, 192 | "node_modules/@vue/server-renderer": { 193 | "version": "3.3.9", 194 | "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.3.9.tgz", 195 | "integrity": "sha512-w0zT/s5l3Oa3ZjtLW88eO4uV6AQFqU8X5GOgzq7SkQQu6vVr+8tfm+OI2kDBplS/W/XgCBuFXiPw6T5EdwXP0A==", 196 | "dependencies": { 197 | "@vue/compiler-ssr": "3.3.9", 198 | "@vue/shared": "3.3.9" 199 | }, 200 | "peerDependencies": { 201 | "vue": "3.3.9" 202 | } 203 | }, 204 | "node_modules/@vue/shared": { 205 | "version": "3.3.9", 206 | "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.9.tgz", 207 | "integrity": "sha512-ZE0VTIR0LmYgeyhurPTpy4KzKsuDyQbMSdM49eKkMnT5X4VfFBLysMzjIZhLEFQYjjOVVfbvUDHckwjDFiO2eA==" 208 | }, 209 | "node_modules/csstype": { 210 | "version": "3.1.2", 211 | "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", 212 | "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" 213 | }, 214 | "node_modules/esbuild": { 215 | "version": "0.15.18", 216 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.18.tgz", 217 | "integrity": "sha512-x/R72SmW3sSFRm5zrrIjAhCeQSAWoni3CmHEqfQrZIQTM3lVCdehdwuIqaOtfC2slvpdlLa62GYoN8SxT23m6Q==", 218 | "dev": true, 219 | "hasInstallScript": true, 220 | "bin": { 221 | "esbuild": "bin/esbuild" 222 | }, 223 | "engines": { 224 | "node": ">=12" 225 | }, 226 | "optionalDependencies": { 227 | "@esbuild/android-arm": "0.15.18", 228 | "@esbuild/linux-loong64": "0.15.18", 229 | "esbuild-android-64": "0.15.18", 230 | "esbuild-android-arm64": "0.15.18", 231 | "esbuild-darwin-64": "0.15.18", 232 | "esbuild-darwin-arm64": "0.15.18", 233 | "esbuild-freebsd-64": "0.15.18", 234 | "esbuild-freebsd-arm64": "0.15.18", 235 | "esbuild-linux-32": "0.15.18", 236 | "esbuild-linux-64": "0.15.18", 237 | "esbuild-linux-arm": "0.15.18", 238 | "esbuild-linux-arm64": "0.15.18", 239 | "esbuild-linux-mips64le": "0.15.18", 240 | "esbuild-linux-ppc64le": "0.15.18", 241 | "esbuild-linux-riscv64": "0.15.18", 242 | "esbuild-linux-s390x": "0.15.18", 243 | "esbuild-netbsd-64": "0.15.18", 244 | "esbuild-openbsd-64": "0.15.18", 245 | "esbuild-sunos-64": "0.15.18", 246 | "esbuild-windows-32": "0.15.18", 247 | "esbuild-windows-64": "0.15.18", 248 | "esbuild-windows-arm64": "0.15.18" 249 | } 250 | }, 251 | "node_modules/esbuild-android-64": { 252 | "version": "0.15.18", 253 | "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.18.tgz", 254 | "integrity": "sha512-wnpt3OXRhcjfIDSZu9bnzT4/TNTDsOUvip0foZOUBG7QbSt//w3QV4FInVJxNhKc/ErhUxc5z4QjHtMi7/TbgA==", 255 | "cpu": [ 256 | "x64" 257 | ], 258 | "dev": true, 259 | "optional": true, 260 | "os": [ 261 | "android" 262 | ], 263 | "engines": { 264 | "node": ">=12" 265 | } 266 | }, 267 | "node_modules/esbuild-android-arm64": { 268 | "version": "0.15.18", 269 | "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.18.tgz", 270 | "integrity": "sha512-G4xu89B8FCzav9XU8EjsXacCKSG2FT7wW9J6hOc18soEHJdtWu03L3TQDGf0geNxfLTtxENKBzMSq9LlbjS8OQ==", 271 | "cpu": [ 272 | "arm64" 273 | ], 274 | "dev": true, 275 | "optional": true, 276 | "os": [ 277 | "android" 278 | ], 279 | "engines": { 280 | "node": ">=12" 281 | } 282 | }, 283 | "node_modules/esbuild-darwin-64": { 284 | "version": "0.15.18", 285 | "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.18.tgz", 286 | "integrity": "sha512-2WAvs95uPnVJPuYKP0Eqx+Dl/jaYseZEUUT1sjg97TJa4oBtbAKnPnl3b5M9l51/nbx7+QAEtuummJZW0sBEmg==", 287 | "cpu": [ 288 | "x64" 289 | ], 290 | "dev": true, 291 | "optional": true, 292 | "os": [ 293 | "darwin" 294 | ], 295 | "engines": { 296 | "node": ">=12" 297 | } 298 | }, 299 | "node_modules/esbuild-darwin-arm64": { 300 | "version": "0.15.18", 301 | "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.18.tgz", 302 | "integrity": "sha512-tKPSxcTJ5OmNb1btVikATJ8NftlyNlc8BVNtyT/UAr62JFOhwHlnoPrhYWz09akBLHI9nElFVfWSTSRsrZiDUA==", 303 | "cpu": [ 304 | "arm64" 305 | ], 306 | "dev": true, 307 | "optional": true, 308 | "os": [ 309 | "darwin" 310 | ], 311 | "engines": { 312 | "node": ">=12" 313 | } 314 | }, 315 | "node_modules/esbuild-freebsd-64": { 316 | "version": "0.15.18", 317 | "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.18.tgz", 318 | "integrity": "sha512-TT3uBUxkteAjR1QbsmvSsjpKjOX6UkCstr8nMr+q7zi3NuZ1oIpa8U41Y8I8dJH2fJgdC3Dj3CXO5biLQpfdZA==", 319 | "cpu": [ 320 | "x64" 321 | ], 322 | "dev": true, 323 | "optional": true, 324 | "os": [ 325 | "freebsd" 326 | ], 327 | "engines": { 328 | "node": ">=12" 329 | } 330 | }, 331 | "node_modules/esbuild-freebsd-arm64": { 332 | "version": "0.15.18", 333 | "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.18.tgz", 334 | "integrity": "sha512-R/oVr+X3Tkh+S0+tL41wRMbdWtpWB8hEAMsOXDumSSa6qJR89U0S/PpLXrGF7Wk/JykfpWNokERUpCeHDl47wA==", 335 | "cpu": [ 336 | "arm64" 337 | ], 338 | "dev": true, 339 | "optional": true, 340 | "os": [ 341 | "freebsd" 342 | ], 343 | "engines": { 344 | "node": ">=12" 345 | } 346 | }, 347 | "node_modules/esbuild-linux-32": { 348 | "version": "0.15.18", 349 | "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.18.tgz", 350 | "integrity": "sha512-lphF3HiCSYtaa9p1DtXndiQEeQDKPl9eN/XNoBf2amEghugNuqXNZA/ZovthNE2aa4EN43WroO0B85xVSjYkbg==", 351 | "cpu": [ 352 | "ia32" 353 | ], 354 | "dev": true, 355 | "optional": true, 356 | "os": [ 357 | "linux" 358 | ], 359 | "engines": { 360 | "node": ">=12" 361 | } 362 | }, 363 | "node_modules/esbuild-linux-64": { 364 | "version": "0.15.18", 365 | "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.18.tgz", 366 | "integrity": "sha512-hNSeP97IviD7oxLKFuii5sDPJ+QHeiFTFLoLm7NZQligur8poNOWGIgpQ7Qf8Balb69hptMZzyOBIPtY09GZYw==", 367 | "cpu": [ 368 | "x64" 369 | ], 370 | "dev": true, 371 | "optional": true, 372 | "os": [ 373 | "linux" 374 | ], 375 | "engines": { 376 | "node": ">=12" 377 | } 378 | }, 379 | "node_modules/esbuild-linux-arm": { 380 | "version": "0.15.18", 381 | "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.18.tgz", 382 | "integrity": "sha512-UH779gstRblS4aoS2qpMl3wjg7U0j+ygu3GjIeTonCcN79ZvpPee12Qun3vcdxX+37O5LFxz39XeW2I9bybMVA==", 383 | "cpu": [ 384 | "arm" 385 | ], 386 | "dev": true, 387 | "optional": true, 388 | "os": [ 389 | "linux" 390 | ], 391 | "engines": { 392 | "node": ">=12" 393 | } 394 | }, 395 | "node_modules/esbuild-linux-arm64": { 396 | "version": "0.15.18", 397 | "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.18.tgz", 398 | "integrity": "sha512-54qr8kg/6ilcxd+0V3h9rjT4qmjc0CccMVWrjOEM/pEcUzt8X62HfBSeZfT2ECpM7104mk4yfQXkosY8Quptug==", 399 | "cpu": [ 400 | "arm64" 401 | ], 402 | "dev": true, 403 | "optional": true, 404 | "os": [ 405 | "linux" 406 | ], 407 | "engines": { 408 | "node": ">=12" 409 | } 410 | }, 411 | "node_modules/esbuild-linux-mips64le": { 412 | "version": "0.15.18", 413 | "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.18.tgz", 414 | "integrity": "sha512-Mk6Ppwzzz3YbMl/ZZL2P0q1tnYqh/trYZ1VfNP47C31yT0K8t9s7Z077QrDA/guU60tGNp2GOwCQnp+DYv7bxQ==", 415 | "cpu": [ 416 | "mips64el" 417 | ], 418 | "dev": true, 419 | "optional": true, 420 | "os": [ 421 | "linux" 422 | ], 423 | "engines": { 424 | "node": ">=12" 425 | } 426 | }, 427 | "node_modules/esbuild-linux-ppc64le": { 428 | "version": "0.15.18", 429 | "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.18.tgz", 430 | "integrity": "sha512-b0XkN4pL9WUulPTa/VKHx2wLCgvIAbgwABGnKMY19WhKZPT+8BxhZdqz6EgkqCLld7X5qiCY2F/bfpUUlnFZ9w==", 431 | "cpu": [ 432 | "ppc64" 433 | ], 434 | "dev": true, 435 | "optional": true, 436 | "os": [ 437 | "linux" 438 | ], 439 | "engines": { 440 | "node": ">=12" 441 | } 442 | }, 443 | "node_modules/esbuild-linux-riscv64": { 444 | "version": "0.15.18", 445 | "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.18.tgz", 446 | "integrity": "sha512-ba2COaoF5wL6VLZWn04k+ACZjZ6NYniMSQStodFKH/Pu6RxzQqzsmjR1t9QC89VYJxBeyVPTaHuBMCejl3O/xg==", 447 | "cpu": [ 448 | "riscv64" 449 | ], 450 | "dev": true, 451 | "optional": true, 452 | "os": [ 453 | "linux" 454 | ], 455 | "engines": { 456 | "node": ">=12" 457 | } 458 | }, 459 | "node_modules/esbuild-linux-s390x": { 460 | "version": "0.15.18", 461 | "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.18.tgz", 462 | "integrity": "sha512-VbpGuXEl5FCs1wDVp93O8UIzl3ZrglgnSQ+Hu79g7hZu6te6/YHgVJxCM2SqfIila0J3k0csfnf8VD2W7u2kzQ==", 463 | "cpu": [ 464 | "s390x" 465 | ], 466 | "dev": true, 467 | "optional": true, 468 | "os": [ 469 | "linux" 470 | ], 471 | "engines": { 472 | "node": ">=12" 473 | } 474 | }, 475 | "node_modules/esbuild-netbsd-64": { 476 | "version": "0.15.18", 477 | "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.18.tgz", 478 | "integrity": "sha512-98ukeCdvdX7wr1vUYQzKo4kQ0N2p27H7I11maINv73fVEXt2kyh4K4m9f35U1K43Xc2QGXlzAw0K9yoU7JUjOg==", 479 | "cpu": [ 480 | "x64" 481 | ], 482 | "dev": true, 483 | "optional": true, 484 | "os": [ 485 | "netbsd" 486 | ], 487 | "engines": { 488 | "node": ">=12" 489 | } 490 | }, 491 | "node_modules/esbuild-openbsd-64": { 492 | "version": "0.15.18", 493 | "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.18.tgz", 494 | "integrity": "sha512-yK5NCcH31Uae076AyQAXeJzt/vxIo9+omZRKj1pauhk3ITuADzuOx5N2fdHrAKPxN+zH3w96uFKlY7yIn490xQ==", 495 | "cpu": [ 496 | "x64" 497 | ], 498 | "dev": true, 499 | "optional": true, 500 | "os": [ 501 | "openbsd" 502 | ], 503 | "engines": { 504 | "node": ">=12" 505 | } 506 | }, 507 | "node_modules/esbuild-sunos-64": { 508 | "version": "0.15.18", 509 | "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.18.tgz", 510 | "integrity": "sha512-On22LLFlBeLNj/YF3FT+cXcyKPEI263nflYlAhz5crxtp3yRG1Ugfr7ITyxmCmjm4vbN/dGrb/B7w7U8yJR9yw==", 511 | "cpu": [ 512 | "x64" 513 | ], 514 | "dev": true, 515 | "optional": true, 516 | "os": [ 517 | "sunos" 518 | ], 519 | "engines": { 520 | "node": ">=12" 521 | } 522 | }, 523 | "node_modules/esbuild-windows-32": { 524 | "version": "0.15.18", 525 | "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.18.tgz", 526 | "integrity": "sha512-o+eyLu2MjVny/nt+E0uPnBxYuJHBvho8vWsC2lV61A7wwTWC3jkN2w36jtA+yv1UgYkHRihPuQsL23hsCYGcOQ==", 527 | "cpu": [ 528 | "ia32" 529 | ], 530 | "dev": true, 531 | "optional": true, 532 | "os": [ 533 | "win32" 534 | ], 535 | "engines": { 536 | "node": ">=12" 537 | } 538 | }, 539 | "node_modules/esbuild-windows-64": { 540 | "version": "0.15.18", 541 | "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.18.tgz", 542 | "integrity": "sha512-qinug1iTTaIIrCorAUjR0fcBk24fjzEedFYhhispP8Oc7SFvs+XeW3YpAKiKp8dRpizl4YYAhxMjlftAMJiaUw==", 543 | "cpu": [ 544 | "x64" 545 | ], 546 | "dev": true, 547 | "optional": true, 548 | "os": [ 549 | "win32" 550 | ], 551 | "engines": { 552 | "node": ">=12" 553 | } 554 | }, 555 | "node_modules/esbuild-windows-arm64": { 556 | "version": "0.15.18", 557 | "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.18.tgz", 558 | "integrity": "sha512-q9bsYzegpZcLziq0zgUi5KqGVtfhjxGbnksaBFYmWLxeV/S1fK4OLdq2DFYnXcLMjlZw2L0jLsk1eGoB522WXQ==", 559 | "cpu": [ 560 | "arm64" 561 | ], 562 | "dev": true, 563 | "optional": true, 564 | "os": [ 565 | "win32" 566 | ], 567 | "engines": { 568 | "node": ">=12" 569 | } 570 | }, 571 | "node_modules/estree-walker": { 572 | "version": "2.0.2", 573 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", 574 | "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" 575 | }, 576 | "node_modules/fast-base64": { 577 | "version": "0.1.8", 578 | "resolved": "https://registry.npmjs.org/fast-base64/-/fast-base64-0.1.8.tgz", 579 | "integrity": "sha512-LICiPjlLyh7/P3gcJYDjKEIX41odzqny1VHSnPsAlBb/zcSJJPYrSNHs54e2TytDRTwHZl7KG5c33IMLdT+9Eg==" 580 | }, 581 | "node_modules/file-saver": { 582 | "version": "2.0.5", 583 | "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz", 584 | "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==" 585 | }, 586 | "node_modules/fsevents": { 587 | "version": "2.3.3", 588 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 589 | "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 590 | "dev": true, 591 | "hasInstallScript": true, 592 | "optional": true, 593 | "os": [ 594 | "darwin" 595 | ], 596 | "engines": { 597 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 598 | } 599 | }, 600 | "node_modules/function-bind": { 601 | "version": "1.1.2", 602 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", 603 | "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", 604 | "dev": true, 605 | "funding": { 606 | "url": "https://github.com/sponsors/ljharb" 607 | } 608 | }, 609 | "node_modules/hasown": { 610 | "version": "2.0.0", 611 | "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", 612 | "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", 613 | "dev": true, 614 | "dependencies": { 615 | "function-bind": "^1.1.2" 616 | }, 617 | "engines": { 618 | "node": ">= 0.4" 619 | } 620 | }, 621 | "node_modules/is-core-module": { 622 | "version": "2.13.1", 623 | "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", 624 | "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", 625 | "dev": true, 626 | "dependencies": { 627 | "hasown": "^2.0.0" 628 | }, 629 | "funding": { 630 | "url": "https://github.com/sponsors/ljharb" 631 | } 632 | }, 633 | "node_modules/magic-string": { 634 | "version": "0.30.5", 635 | "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", 636 | "integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==", 637 | "dependencies": { 638 | "@jridgewell/sourcemap-codec": "^1.4.15" 639 | }, 640 | "engines": { 641 | "node": ">=12" 642 | } 643 | }, 644 | "node_modules/nanoid": { 645 | "version": "3.3.7", 646 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", 647 | "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", 648 | "funding": [ 649 | { 650 | "type": "github", 651 | "url": "https://github.com/sponsors/ai" 652 | } 653 | ], 654 | "bin": { 655 | "nanoid": "bin/nanoid.cjs" 656 | }, 657 | "engines": { 658 | "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" 659 | } 660 | }, 661 | "node_modules/path-parse": { 662 | "version": "1.0.7", 663 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", 664 | "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", 665 | "dev": true 666 | }, 667 | "node_modules/picocolors": { 668 | "version": "1.0.0", 669 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", 670 | "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" 671 | }, 672 | "node_modules/postcss": { 673 | "version": "8.4.31", 674 | "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", 675 | "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", 676 | "funding": [ 677 | { 678 | "type": "opencollective", 679 | "url": "https://opencollective.com/postcss/" 680 | }, 681 | { 682 | "type": "tidelift", 683 | "url": "https://tidelift.com/funding/github/npm/postcss" 684 | }, 685 | { 686 | "type": "github", 687 | "url": "https://github.com/sponsors/ai" 688 | } 689 | ], 690 | "dependencies": { 691 | "nanoid": "^3.3.6", 692 | "picocolors": "^1.0.0", 693 | "source-map-js": "^1.0.2" 694 | }, 695 | "engines": { 696 | "node": "^10 || ^12 || >=14" 697 | } 698 | }, 699 | "node_modules/resolve": { 700 | "version": "1.22.8", 701 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", 702 | "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", 703 | "dev": true, 704 | "dependencies": { 705 | "is-core-module": "^2.13.0", 706 | "path-parse": "^1.0.7", 707 | "supports-preserve-symlinks-flag": "^1.0.0" 708 | }, 709 | "bin": { 710 | "resolve": "bin/resolve" 711 | }, 712 | "funding": { 713 | "url": "https://github.com/sponsors/ljharb" 714 | } 715 | }, 716 | "node_modules/rollup": { 717 | "version": "2.79.1", 718 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz", 719 | "integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==", 720 | "dev": true, 721 | "bin": { 722 | "rollup": "dist/bin/rollup" 723 | }, 724 | "engines": { 725 | "node": ">=10.0.0" 726 | }, 727 | "optionalDependencies": { 728 | "fsevents": "~2.3.2" 729 | } 730 | }, 731 | "node_modules/source-map-js": { 732 | "version": "1.0.2", 733 | "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", 734 | "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", 735 | "engines": { 736 | "node": ">=0.10.0" 737 | } 738 | }, 739 | "node_modules/supports-preserve-symlinks-flag": { 740 | "version": "1.0.0", 741 | "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", 742 | "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", 743 | "dev": true, 744 | "engines": { 745 | "node": ">= 0.4" 746 | }, 747 | "funding": { 748 | "url": "https://github.com/sponsors/ljharb" 749 | } 750 | }, 751 | "node_modules/vant": { 752 | "version": "3.6.12", 753 | "resolved": "https://registry.npmjs.org/vant/-/vant-3.6.12.tgz", 754 | "integrity": "sha512-fLzwhpV0ZPQqxrTx6RU8mJVUqP7DSkpdXNeByKubp+O9vKYGcWRX9wFdEwApyy7qLZLLu+rU1Jw52d6lktPL4w==", 755 | "dependencies": { 756 | "@vant/icons": "^1.8.0", 757 | "@vant/popperjs": "^1.2.1", 758 | "@vant/use": "^1.4.2" 759 | }, 760 | "peerDependencies": { 761 | "vue": "^3.0.0" 762 | } 763 | }, 764 | "node_modules/vite": { 765 | "version": "3.2.7", 766 | "resolved": "https://registry.npmjs.org/vite/-/vite-3.2.7.tgz", 767 | "integrity": "sha512-29pdXjk49xAP0QBr0xXqu2s5jiQIXNvE/xwd0vUizYT2Hzqe4BksNNoWllFVXJf4eLZ+UlVQmXfB4lWrc+t18g==", 768 | "dev": true, 769 | "dependencies": { 770 | "esbuild": "^0.15.9", 771 | "postcss": "^8.4.18", 772 | "resolve": "^1.22.1", 773 | "rollup": "^2.79.1" 774 | }, 775 | "bin": { 776 | "vite": "bin/vite.js" 777 | }, 778 | "engines": { 779 | "node": "^14.18.0 || >=16.0.0" 780 | }, 781 | "optionalDependencies": { 782 | "fsevents": "~2.3.2" 783 | }, 784 | "peerDependencies": { 785 | "@types/node": ">= 14", 786 | "less": "*", 787 | "sass": "*", 788 | "stylus": "*", 789 | "sugarss": "*", 790 | "terser": "^5.4.0" 791 | }, 792 | "peerDependenciesMeta": { 793 | "@types/node": { 794 | "optional": true 795 | }, 796 | "less": { 797 | "optional": true 798 | }, 799 | "sass": { 800 | "optional": true 801 | }, 802 | "stylus": { 803 | "optional": true 804 | }, 805 | "sugarss": { 806 | "optional": true 807 | }, 808 | "terser": { 809 | "optional": true 810 | } 811 | } 812 | }, 813 | "node_modules/vue": { 814 | "version": "3.3.9", 815 | "resolved": "https://registry.npmjs.org/vue/-/vue-3.3.9.tgz", 816 | "integrity": "sha512-sy5sLCTR8m6tvUk1/ijri3Yqzgpdsmxgj6n6yl7GXXCXqVbmW2RCXe9atE4cEI6Iv7L89v5f35fZRRr5dChP9w==", 817 | "dependencies": { 818 | "@vue/compiler-dom": "3.3.9", 819 | "@vue/compiler-sfc": "3.3.9", 820 | "@vue/runtime-dom": "3.3.9", 821 | "@vue/server-renderer": "3.3.9", 822 | "@vue/shared": "3.3.9" 823 | }, 824 | "peerDependencies": { 825 | "typescript": "*" 826 | }, 827 | "peerDependenciesMeta": { 828 | "typescript": { 829 | "optional": true 830 | } 831 | } 832 | } 833 | } 834 | } 835 | --------------------------------------------------------------------------------