├── .env ├── .github ├── README.md ├── webapp-start.png └── workflows │ └── gp-deploy.yaml ├── .gitignore ├── .prettierrc ├── .vscode ├── settings-create-vwa.json └── settings.json ├── README.md ├── eslint.config.js ├── index.html ├── package.json ├── pnpm-lock.yaml ├── public ├── assets │ └── images │ │ ├── apple-touch-icon.png │ │ ├── favicon-32x32.png │ │ ├── logo-128.png │ │ ├── logo-48.png │ │ ├── logo-512.png │ │ ├── logo-small.png │ │ ├── logo.png │ │ └── logo.svg ├── favicon.ico ├── manifest.json └── service-worker.js ├── src ├── App.vue ├── assets │ ├── images │ │ ├── about.svg │ │ ├── change-account.svg │ │ ├── contact-us.svg │ │ ├── hamburger.svg │ │ ├── home.svg │ │ ├── icon-plus-blue.svg │ │ ├── icon-plus-purple.svg │ │ ├── instagram.svg │ │ ├── logo.png │ │ ├── logo.webp │ │ ├── logo1.svg │ │ ├── logout.svg │ │ ├── moon.svg │ │ ├── sun.svg │ │ ├── twitter.svg │ │ └── youtube.svg │ └── styles │ │ ├── base.css │ │ ├── custom.css │ │ └── vars.css ├── components │ ├── AppContentPane.vue │ ├── SectionHeader.vue │ ├── drawers │ │ ├── SimpleDrawer.vue │ │ └── TouchSlideoutDrawer.vue │ ├── footers │ │ ├── DistributedFooter.vue │ │ ├── MantineRichFooter.vue │ │ ├── MantineSimpleFooter.vue │ │ ├── RichFooter.vue │ │ └── SimpleFooter.vue │ ├── headers │ │ ├── MantineLayeredHeader.vue │ │ ├── MantineSimpleHeader.vue │ │ ├── SimpleHeader.vue │ │ └── SlidingHeader.vue │ ├── navbars │ │ ├── MantineSimpleNavbar.vue │ │ └── SimpleNavbar.vue │ └── ui │ │ ├── BaseIcon.vue │ │ ├── BaseToggle.vue │ │ ├── HamburgerIcon.vue │ │ └── ThemeToggle.vue ├── composables │ ├── useAppConfig.ts │ ├── useI18nLight.ts │ ├── useScreenWidth.ts │ ├── useSplashScreen.ts │ └── useTouchSwipe.ts ├── layouts │ └── MainLayout.vue ├── main.ts ├── router │ ├── index.ts │ └── routes.ts ├── services │ └── api │ │ ├── http.ts │ │ ├── index.ts │ │ ├── interceptors.ts │ │ ├── jsonrpc.interfaces.ts │ │ ├── jsonrpc.ts │ │ ├── notes.txt │ │ ├── utils.ts │ │ └── xhr.js ├── utils │ ├── icons.ts │ ├── injections │ │ ├── gtag.html │ │ ├── injection-config.ts │ │ ├── open-graph.html │ │ ├── splash-screen.html │ │ └── sw.js │ └── locales │ │ ├── en.json │ │ └── es.json └── views │ ├── AboutView.vue │ ├── ContactsView.vue │ └── HomeView.vue ├── tsconfig.json └── vite.config.ts /.env: -------------------------------------------------------------------------------- 1 | VITE_APP_NAME="Vue 3 base website template" 2 | VITE_APP_DEFAULT_LOCALE="en" 3 | VITE_API_URL="/rpc/" 4 | -------------------------------------------------------------------------------- /.github/README.md: -------------------------------------------------------------------------------- 1 | # Vue 3 webapp builder 2 | 3 | ### 4 | 5 | Full documentation is available at [https://vue-faq.org/en/vue-webapp/](https://vue-faq.org/en/vue-webapp/) 6 | 7 | ## Short description 8 | 9 | Vue 3 webapp builder allows to scaffold a web application, with the ability to choose a business template (portfolio, blog, store, etc.), website layout, design and functional elements (API module, i18n, PWA, splash screen, auth module, themes, etc.), for further customization and content filling. 10 | 11 | ```sh 12 | $ pnpm create vue-webapp 13 | 14 | 15 | ✔ Project name: ... my-vue-project 16 | ✔ Make it PWA ( adds service worker and manifest )? ... yes 17 | ✔ Add Github Action Workflow for publishing it on GitHub Pages? ... no 18 | ✔ Select navigation drawer ' TouchSlideoutDrawer 19 | ✔ Select webapp footer ' RichFooter 20 | ✔ Add 'BaseIcon' component? ... yes 21 | ... 22 | 23 | Scaffolding project in /home/ubuntu/my-vue-project... 24 | 25 | $ cd my-vue-project 26 | $ pnpm i 27 | $ pnpm dev 28 | ``` 29 | 30 | Result in a browser ([https://vuesence.github.io/vue-webapp](https://vuesence.github.io/vue-webapp/)): 31 | 32 | ![](webapp-start.png) 33 | 34 | 35 | ## Reasons 36 | 37 | There are quite a few (mostly specific and quickly deprecated) boilerplates for creating a Vue application. Usually by these they mean creating an empty project with specific libraries. In other words, it's just setting up an environment to start development without, directly, code. 38 | 39 | At the same time, many other frontend and backend frameworks have starter-kits that allow you to quickly create a ready-made blog, online store, business card site, portfolio, documentation, etc., which greatly helps in learning the framework itself, relevant technologies and best practices, as well as for solving business tasks. 40 | 41 | As a result, one may get the impression that Vue is a rather low-level framework, and to create web applications quickly, efficiently and conveniently, you need to take some add-on or other solution - Nuxt, Vue Storefront, Astro, VitePress - which explicitly position themselves as a tool for solving certain (or a wide range of) business tasks. 42 | 43 | The idea for a tool similar to `create-vue`, scaffolding a Vue 3 webapp, came up. On the one hand, a quite workable, adaptive website with the necessary functionality so that a novice developer could familiarize himself with a proven approach to solving the tasks involved. On the other hand, it should be minimalistic enough not to impose dependencies unwanted by an experienced developer and create a skeleton of established best practices for further development of the application. 44 | 45 | Clearly, defining one or another "best practice" (fetch or axios?) can be quite a moot point, but still. 46 | -------------------------------------------------------------------------------- /.github/webapp-start.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuesence/vue-webapp/f341029e21b8bbc4839962e67188afc1857c6b7f/.github/webapp-start.png -------------------------------------------------------------------------------- /.github/workflows/gp-deploy.yaml: -------------------------------------------------------------------------------- 1 | name: Build and Deploy 2 | on: 3 | push: 4 | branches: [main] 5 | workflow_dispatch: 6 | # branches: [ "main", "development" ] 7 | permissions: 8 | contents: write 9 | # pages: write 10 | jobs: 11 | build-and-deploy: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout 🛎️ 15 | uses: actions/checkout@v3 16 | 17 | - uses: pnpm/action-setup@v2 18 | name: Install pnpm 19 | id: pnpm-install 20 | with: 21 | version: 8.5.0 22 | run_install: false 23 | 24 | - name: Install dependencies 25 | run: pnpm install 26 | 27 | - run: pnpm build 28 | 29 | - name: Deploy 🚀 30 | uses: JamesIves/github-pages-deploy-action@v4 31 | with: 32 | folder: dist 33 | branch: gh-pages 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | node_modules 6 | dist 7 | *.local 8 | 9 | # Editor directories and files 10 | #.vscode/* 11 | .idea 12 | .DS_Store 13 | 14 | .history 15 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | trailingComma: "es5" 2 | semi: true 3 | singleQuote: false 4 | printWidth: 110 -------------------------------------------------------------------------------- /.vscode/settings-create-vwa.json: -------------------------------------------------------------------------------- 1 | { 2 | // Enable the ESlint flat config support 3 | "eslint.experimental.useFlatConfig": true, 4 | // Disable the default formatter, use eslint instead 5 | "prettier.enable": false, 6 | "editor.formatOnSave": false, 7 | // Auto fix 8 | "editor.codeActionsOnSave": { 9 | "source.fixAll.eslint": "explicit", 10 | "source.organizeImports": "never" 11 | }, 12 | // Enable eslint for all supported languages 13 | "eslint.validate": [ 14 | "javascript", 15 | "javascriptreact", 16 | "typescript", 17 | "typescriptreact", 18 | "vue", 19 | "html", 20 | "markdown", 21 | "json", 22 | "jsonc", 23 | "yaml" 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // { 2 | { 3 | // "i18n-ally.localesPaths": ["src/resources/lang"], 4 | // "i18n-ally.keystyle": "nested", 5 | // "cSpell.words": ["Splide"], 6 | // "editor.codeActionsOnSave": { 7 | // "source.fixAll.eslint": true, 8 | // "source.fixAll": false 9 | // }, 10 | // "editor.formatOnSave": false, 11 | "workbench.colorTheme": "Default Dark Modern", 12 | // "workbench.editor.tabCloseButton": "left", 13 | "workbench.fontAliasing": "antialiased", 14 | // "workbench.iconTheme": "file-icons", 15 | "workbench.list.smoothScrolling": true, 16 | // "workbench.preferredDarkColorTheme": "Vitesse Dark", 17 | // "workbench.preferredLightColorTheme": "Vitesse Light", 18 | // "workbench.productIconTheme": "icons-carbon", 19 | // "workbench.sideBar.location": "right", 20 | // "workbench.startupEditor": "newUntitledFile", 21 | "workbench.tree.expandMode": "singleClick", 22 | "workbench.tree.indent": 10, 23 | "editor.fontFamily": "Fira Code, Input Mono, monospace", 24 | "editor.fontLigatures": "'ss01', 'ss02', 'ss03', 'ss06', 'zero'", 25 | // Editor 26 | "editor.accessibilitySupport": "off", 27 | "editor.cursorSmoothCaretAnimation": "on", 28 | "editor.find.addExtraSpaceOnTop": false, 29 | "editor.guides.bracketPairs": "active", 30 | "editor.inlineSuggest.enabled": true, 31 | // "editor.lineNumbers": "interval", 32 | // "editor.multiCursorModifier": "ctrlCmd", 33 | "editor.renderWhitespace": "boundary", 34 | "editor.suggestSelection": "first", 35 | "editor.tabSize": 2, 36 | "editor.unicodeHighlight.invisibleCharacters": false, 37 | "editor.stickyScroll.enabled": true, 38 | "editor.hover.sticky": true, 39 | "editor.wordSeparators": "-`~!@#%^&*()=+[{]}\\|;:'\",.<>/?", 40 | // "explorer.confirmDelete": false, 41 | // "explorer.confirmDragAndDrop": false, 42 | "window.autoDetectColorScheme": false, 43 | "window.dialogStyle": "custom", 44 | "window.nativeTabs": true, // this is great, macOS only 45 | "window.titleBarStyle": "custom", 46 | "workbench.editor.closeOnFileDelete": true, 47 | "workbench.editor.highlightModifiedTabs": true, 48 | "workbench.editor.limit.enabled": true, 49 | "workbench.editor.limit.perEditorGroup": true, 50 | "workbench.editor.limit.value": 30, 51 | "extensions.autoUpdate": "onlyEnabledExtensions", 52 | "extensions.ignoreRecommendations": true, 53 | "files.eol": "\n", 54 | "files.insertFinalNewline": true, 55 | "files.simpleDialog.enable": true, 56 | "git.autofetch": false, 57 | "git.confirmSync": false, 58 | "git.enableSmartCommit": true, 59 | "git.untrackedChanges": "separate", 60 | "terminal.integrated.cursorBlinking": true, 61 | "terminal.integrated.cursorStyle": "line", 62 | "terminal.integrated.fontWeight": "300", 63 | "terminal.integrated.persistentSessionReviveProcess": "never", 64 | "terminal.integrated.tabs.enabled": true, 65 | "scm.diffDecorationsGutterWidth": 2, 66 | "debug.onTaskErrors": "debugAnyway", 67 | "diffEditor.ignoreTrimWhitespace": false, 68 | "search.exclude": { 69 | "**/.git": true, 70 | "**/.github": true, 71 | "**/.nuxt": true, 72 | "**/.output": true, 73 | "**/.pnpm": true, 74 | "**/.vscode": true, 75 | "**/.yarn": true, 76 | "**/bower_components": true, 77 | "**/dist/**": true, 78 | "**/logs": true, 79 | "**/node_modules": true, 80 | "**/out/**": true, 81 | "**/package-lock.json": true, 82 | "**/pnpm-lock.yaml": true, 83 | "**/tmp": true, 84 | "**/yarn.lock": true 85 | }, 86 | // Extension configs 87 | "emmet.showSuggestionsAsSnippets": true, 88 | "emmet.triggerExpansionOnTab": false, 89 | // ESLint config: https://github.com/antfu/eslint-config 90 | "eslint.codeAction.showDocumentation": { 91 | "enable": true 92 | }, 93 | "eslint.quiet": false, 94 | "cSpell.allowCompoundWords": true, 95 | "cSpell.language": "en,en-US,ru,ru-RU", 96 | "css.lint.hexColorLength": "ignore", 97 | "githubIssues.workingIssueFormatScm": "#${issueNumberLabel}", 98 | "githubPullRequests.fileListLayout": "tree", 99 | "gitlens.codeLens.authors.enabled": false, 100 | "gitlens.codeLens.enabled": false, 101 | "gitlens.codeLens.recentChange.enabled": false, 102 | "gitlens.menus": { 103 | "editor": { 104 | "blame": false, 105 | "clipboard": true, 106 | "compare": true, 107 | "history": false, 108 | "remote": false 109 | }, 110 | "editorGroup": { 111 | "blame": true, 112 | "compare": false 113 | }, 114 | "editorTab": { 115 | "clipboard": true, 116 | "compare": true, 117 | "history": true, 118 | "remote": true 119 | }, 120 | "explorer": { 121 | "clipboard": true, 122 | "compare": true, 123 | "history": true, 124 | "remote": true 125 | }, 126 | "scm": { 127 | "authors": true 128 | }, 129 | "scmGroup": { 130 | "compare": true, 131 | "openClose": true, 132 | "stash": true 133 | }, 134 | "scmGroupInline": { 135 | "stash": true 136 | }, 137 | "scmItem": { 138 | "clipboard": true, 139 | "compare": true, 140 | "history": true, 141 | "remote": false, 142 | "stash": true 143 | } 144 | }, 145 | "i18n-ally.autoDetection": false, 146 | "i18n-ally.displayLanguage": "en", 147 | "i18n-ally.ignoredLocales": [], 148 | "iconify.annotations": true, 149 | "iconify.inplace": true, 150 | "svg.preview.mode": "svg", 151 | // I only use Prettier for manually formatting 152 | // "prettier.enable": true, 153 | // "prettier.printWidth": 200, 154 | // "prettier.semi": false, 155 | // "prettier.singleQuote": true, 156 | "volar.autoCompleteRefs": false, 157 | "volar.codeLens.pugTools": false, 158 | "volar.completion.preferredTagNameCase": "pascal", 159 | // File Nesting 160 | // this might not be up to date with the repo, please check yourself 161 | // https://github.com/antfu/vscode-file-nesting-config 162 | "explorer.fileNesting.enabled": true, 163 | "explorer.fileNesting.expand": false, 164 | "explorer.fileNesting.patterns": { 165 | "*.asax": "$(capture).*.cs, $(capture).*.vb", 166 | "*.ascx": "$(capture).*.cs, $(capture).*.vb", 167 | "*.ashx": "$(capture).*.cs, $(capture).*.vb", 168 | "*.aspx": "$(capture).*.cs, $(capture).*.vb", 169 | "*.bloc.dart": "$(capture).event.dart, $(capture).state.dart", 170 | "*.c": "$(capture).h", 171 | "*.cc": "$(capture).hpp, $(capture).h, $(capture).hxx", 172 | "*.cjs": "$(capture).cjs.map, $(capture).*.cjs, $(capture)_*.cjs", 173 | "*.component.ts": "$(capture).component.html, $(capture).component.spec.ts, $(capture).component.css, $(capture).component.scss, $(capture).component.sass, $(capture).component.less", 174 | "*.cpp": "$(capture).hpp, $(capture).h, $(capture).hxx", 175 | "*.cs": "$(capture).*.cs", 176 | "*.cshtml": "$(capture).cshtml.cs", 177 | "*.csproj": "*.config, *proj.user, appsettings.*, bundleconfig.json", 178 | "*.css": "$(capture).css.map, $(capture).*.css", 179 | "*.cxx": "$(capture).hpp, $(capture).h, $(capture).hxx", 180 | "*.dart": "$(capture).freezed.dart, $(capture).g.dart", 181 | "*.ex": "$(capture).html.eex, $(capture).html.heex, $(capture).html.leex", 182 | "*.go": "$(capture)_test.go", 183 | "*.java": "$(capture).class", 184 | "*.js": "$(capture).js.map, $(capture).*.js, $(capture)_*.js", 185 | "*.jsx": "$(capture).js, $(capture).*.jsx, $(capture)_*.js, $(capture)_*.jsx", 186 | "*.master": "$(capture).*.cs, $(capture).*.vb", 187 | "*.mjs": "$(capture).mjs.map, $(capture).*.mjs, $(capture)_*.mjs", 188 | "*.module.ts": "$(capture).resolver.ts, $(capture).controller.ts, $(capture).service.ts", 189 | "*.pubxml": "$(capture).pubxml.user", 190 | "*.resx": "$(capture).*.resx, $(capture).designer.cs, $(capture).designer.vb", 191 | "*.tex": "$(capture).acn, $(capture).acr, $(capture).alg, $(capture).aux, $(capture).bbl, $(capture).blg, $(capture).fdb_latexmk, $(capture).fls, $(capture).glg, $(capture).glo, $(capture).gls, $(capture).idx, $(capture).ind, $(capture).ist, $(capture).lof, $(capture).log, $(capture).lot, $(capture).out, $(capture).pdf, $(capture).synctex.gz, $(capture).toc, $(capture).xdv", 192 | "*.ts": "$(capture).js, $(capture).d.ts.map, $(capture).*.ts, $(capture)_*.js, $(capture)_*.ts", 193 | "*.tsx": "$(capture).ts, $(capture).*.tsx, $(capture)_*.ts, $(capture)_*.tsx", 194 | "*.vbproj": "*.config, *proj.user, appsettings.*, bundleconfig.json", 195 | "*.vue": "$(capture).*.ts, $(capture).*.js, $(capture).story.vue", 196 | "*.xaml": "$(capture).xaml.cs", 197 | "+layout.svelte": "+layout.ts,+layout.ts,+layout.js,+layout.server.ts,+layout.server.js,+layout.gql", 198 | "+page.svelte": "+page.server.ts,+page.server.js,+page.ts,+page.js,+page.gql", 199 | ".clang-tidy": ".clang-format, .clangd, compile_commands.json", 200 | ".env": "*.env, .env.*, .envrc, env.d.ts", 201 | ".gitignore": ".gitattributes, .gitmodules, .gitmessage, .mailmap, .git-blame*", 202 | ".project": ".classpath", 203 | "//": "Last update at 4/29/2023, 2:04:58 PM", 204 | "BUILD.bazel": "*.bzl, *.bazel, *.bazelrc, bazel.rc, .bazelignore, .bazelproject, WORKSPACE", 205 | "CMakeLists.txt": "*.cmake, *.cmake.in, .cmake-format.yaml, CMakePresets.json", 206 | "I*.cs": "$(capture).cs", 207 | "artisan": "*.env, .babelrc*, .codecov, .cssnanorc*, .env.*, .envrc, .htmlnanorc*, .lighthouserc.*, .mocha*, .postcssrc*, .terserrc*, api-extractor.json, ava.config.*, babel.config.*, contentlayer.config.*, cssnano.config.*, cypress.*, env.d.ts, formkit.config.*, formulate.config.*, histoire.config.*, htmlnanorc.*, jasmine.*, jest.config.*, jsconfig.*, karma*, lighthouserc.*, playwright.config.*, postcss.config.*, puppeteer.config.*, rspack.config.*, server.php, svgo.config.*, tailwind.config.*, tsconfig.*, tsdoc.*, uno.config.*, unocss.config.*, vitest.config.*, webpack.config.*, webpack.mix.js, windi.config.*", 208 | "astro.config.*": "*.env, .babelrc*, .codecov, .cssnanorc*, .env.*, .envrc, .htmlnanorc*, .lighthouserc.*, .mocha*, .postcssrc*, .terserrc*, api-extractor.json, ava.config.*, babel.config.*, contentlayer.config.*, cssnano.config.*, cypress.*, env.d.ts, formkit.config.*, formulate.config.*, histoire.config.*, htmlnanorc.*, jasmine.*, jest.config.*, jsconfig.*, karma*, lighthouserc.*, playwright.config.*, postcss.config.*, puppeteer.config.*, rspack.config.*, svgo.config.*, tailwind.config.*, tsconfig.*, tsdoc.*, uno.config.*, unocss.config.*, vitest.config.*, webpack.config.*, windi.config.*", 209 | "cargo.toml": ".clippy.toml, .rustfmt.toml, cargo.lock, clippy.toml, cross.toml, rust-toolchain.toml, rustfmt.toml", 210 | "composer.json": ".php*.cache, composer.lock, phpunit.xml*, psalm*.xml", 211 | "default.nix": "shell.nix", 212 | "deno.json*": "*.env, .env.*, .envrc, api-extractor.json, deno.lock, env.d.ts, import-map.json, import_map.json, jsconfig.*, tsconfig.*, tsdoc.*", 213 | "dockerfile": ".dockerignore, docker-compose.*, dockerfile*", 214 | "flake.nix": "flake.lock", 215 | "gatsby-config.*": "*.env, .babelrc*, .codecov, .cssnanorc*, .env.*, .envrc, .htmlnanorc*, .lighthouserc.*, .mocha*, .postcssrc*, .terserrc*, api-extractor.json, ava.config.*, babel.config.*, contentlayer.config.*, cssnano.config.*, cypress.*, env.d.ts, formkit.config.*, formulate.config.*, gatsby-browser.*, gatsby-node.*, gatsby-ssr.*, gatsby-transformer.*, histoire.config.*, htmlnanorc.*, jasmine.*, jest.config.*, jsconfig.*, karma*, lighthouserc.*, playwright.config.*, postcss.config.*, puppeteer.config.*, rspack.config.*, svgo.config.*, tailwind.config.*, tsconfig.*, tsdoc.*, uno.config.*, unocss.config.*, vitest.config.*, webpack.config.*, windi.config.*", 216 | "gemfile": ".ruby-version, gemfile.lock", 217 | "go.mod": ".air*, go.sum", 218 | "go.work": "go.work.sum", 219 | "mix.exs": ".credo.exs, .dialyzer_ignore.exs, .formatter.exs, .iex.exs, .tool-versions, mix.lock", 220 | "next.config.*": "*.env, .babelrc*, .codecov, .cssnanorc*, .env.*, .envrc, .htmlnanorc*, .lighthouserc.*, .mocha*, .postcssrc*, .terserrc*, api-extractor.json, ava.config.*, babel.config.*, contentlayer.config.*, cssnano.config.*, cypress.*, env.d.ts, formkit.config.*, formulate.config.*, histoire.config.*, htmlnanorc.*, jasmine.*, jest.config.*, jsconfig.*, karma*, lighthouserc.*, next-env.d.ts, playwright.config.*, postcss.config.*, puppeteer.config.*, rspack.config.*, svgo.config.*, tailwind.config.*, tsconfig.*, tsdoc.*, uno.config.*, unocss.config.*, vitest.config.*, webpack.config.*, windi.config.*", 221 | "nuxt.config.*": "*.env, .babelrc*, .codecov, .cssnanorc*, .env.*, .envrc, .htmlnanorc*, .lighthouserc.*, .mocha*, .postcssrc*, .terserrc*, api-extractor.json, ava.config.*, babel.config.*, contentlayer.config.*, cssnano.config.*, cypress.*, env.d.ts, formkit.config.*, formulate.config.*, histoire.config.*, htmlnanorc.*, jasmine.*, jest.config.*, jsconfig.*, karma*, lighthouserc.*, playwright.config.*, postcss.config.*, puppeteer.config.*, rspack.config.*, svgo.config.*, tailwind.config.*, tsconfig.*, tsdoc.*, uno.config.*, unocss.config.*, vitest.config.*, webpack.config.*, windi.config.*", 222 | "package.json": ".browserslist*, .circleci*, .commitlint*, .cz-config.js, .czrc, .dlint.json, .dprint.json, .editorconfig, .eslint*, .firebase*, .flowconfig, .github*, .gitlab*, .gitpod*, .huskyrc*, .jslint*, .lintstagedrc*, .markdownlint*, .node-version, .nodemon*, .npm*, .nvmrc, .pm2*, .pnp.*, .pnpm*, .prettier*, .releaserc*, .sentry*, .simple-git-hooks*, .stackblitz*, .styleci*, .stylelint*, .tazerc*, .textlint*, .tool-versions, .travis*, .versionrc*, .vscode*, .watchman*, .xo-config*, .yamllint*, .yarnrc*, Procfile, apollo.config.*, appveyor*, azure-pipelines*, bower.json, build.config.*, commitlint*, crowdin*, dangerfile*, dlint.json, dprint.json, eslint*, firebase.json, grunt*, gulp*, jenkins*, lerna*, lint-staged*, nest-cli.*, netlify*, nodemon*, npm-shrinkwrap.json, nx.*, package-lock.json, package.nls*.json, phpcs.xml, pm2.*, pnpm*, prettier*, pullapprove*, pyrightconfig.json, release-tasks.sh, release.config.*, renovate*, rollup.config.*, rspack*, simple-git-hooks*, stylelint*, tslint*, tsup.config.*, turbo*, typedoc*, unlighthouse*, vercel*, vetur.config.*, webpack*, workspace.json, xo.config.*, yarn*", 223 | "pubspec.yaml": ".metadata, .packages, all_lint_rules.yaml, analysis_options.yaml, build.yaml, pubspec.lock, pubspec_overrides.yaml", 224 | "pyproject.toml": ".pdm.toml, pdm.lock, pyproject.toml", 225 | "quasar.conf.js": "*.env, .babelrc*, .codecov, .cssnanorc*, .env.*, .envrc, .htmlnanorc*, .lighthouserc.*, .mocha*, .postcssrc*, .terserrc*, api-extractor.json, ava.config.*, babel.config.*, contentlayer.config.*, cssnano.config.*, cypress.*, env.d.ts, formkit.config.*, formulate.config.*, histoire.config.*, htmlnanorc.*, jasmine.*, jest.config.*, jsconfig.*, karma*, lighthouserc.*, playwright.config.*, postcss.config.*, puppeteer.config.*, quasar.extensions.json, rspack.config.*, svgo.config.*, tailwind.config.*, tsconfig.*, tsdoc.*, uno.config.*, unocss.config.*, vitest.config.*, webpack.config.*, windi.config.*", 226 | "readme*": "authors, backers*, changelog*, citation*, code_of_conduct*, codeowners, contributing*, contributors, copying, credits, governance.md, history.md, license*, maintainers, readme*, security.md, sponsors*", 227 | "remix.config.*": "*.env, .babelrc*, .codecov, .cssnanorc*, .env.*, .envrc, .htmlnanorc*, .lighthouserc.*, .mocha*, .postcssrc*, .terserrc*, api-extractor.json, ava.config.*, babel.config.*, contentlayer.config.*, cssnano.config.*, cypress.*, env.d.ts, formkit.config.*, formulate.config.*, histoire.config.*, htmlnanorc.*, jasmine.*, jest.config.*, jsconfig.*, karma*, lighthouserc.*, playwright.config.*, postcss.config.*, puppeteer.config.*, remix.*, rspack.config.*, svgo.config.*, tailwind.config.*, tsconfig.*, tsdoc.*, uno.config.*, unocss.config.*, vitest.config.*, webpack.config.*, windi.config.*", 228 | "rush.json": ".browserslist*, .circleci*, .commitlint*, .cz-config.js, .czrc, .dlint.json, .dprint.json, .editorconfig, .eslint*, .firebase*, .flowconfig, .github*, .gitlab*, .gitpod*, .huskyrc*, .jslint*, .lintstagedrc*, .markdownlint*, .node-version, .nodemon*, .npm*, .nvmrc, .pm2*, .pnp.*, .pnpm*, .prettier*, .releaserc*, .sentry*, .simple-git-hooks*, .stackblitz*, .styleci*, .stylelint*, .tazerc*, .textlint*, .tool-versions, .travis*, .versionrc*, .vscode*, .watchman*, .xo-config*, .yamllint*, .yarnrc*, Procfile, apollo.config.*, appveyor*, azure-pipelines*, bower.json, build.config.*, commitlint*, crowdin*, dangerfile*, dlint.json, dprint.json, eslint*, firebase.json, grunt*, gulp*, jenkins*, lerna*, lint-staged*, nest-cli.*, netlify*, nodemon*, npm-shrinkwrap.json, nx.*, package-lock.json, package.nls*.json, phpcs.xml, pm2.*, pnpm*, prettier*, pullapprove*, pyrightconfig.json, release-tasks.sh, release.config.*, renovate*, rollup.config.*, rspack*, simple-git-hooks*, stylelint*, tslint*, tsup.config.*, turbo*, typedoc*, unlighthouse*, vercel*, vetur.config.*, webpack*, workspace.json, xo.config.*, yarn*", 229 | "shims.d.ts": "*.d.ts", 230 | "svelte.config.*": "*.env, .babelrc*, .codecov, .cssnanorc*, .env.*, .envrc, .htmlnanorc*, .lighthouserc.*, .mocha*, .postcssrc*, .terserrc*, api-extractor.json, ava.config.*, babel.config.*, contentlayer.config.*, cssnano.config.*, cypress.*, env.d.ts, formkit.config.*, formulate.config.*, histoire.config.*, houdini.config.*, htmlnanorc.*, jasmine.*, jest.config.*, jsconfig.*, karma*, lighthouserc.*, mdsvex.config.js, playwright.config.*, postcss.config.*, puppeteer.config.*, rspack.config.*, svgo.config.*, tailwind.config.*, tsconfig.*, tsdoc.*, uno.config.*, unocss.config.*, vite.config.*, vitest.config.*, webpack.config.*, windi.config.*", 231 | "vite.config.*": "*.env, .babelrc*, .codecov, .cssnanorc*, .env.*, .envrc, .htmlnanorc*, .lighthouserc.*, .mocha*, .postcssrc*, .terserrc*, api-extractor.json, ava.config.*, babel.config.*, contentlayer.config.*, cssnano.config.*, cypress.*, env.d.ts, formkit.config.*, formulate.config.*, histoire.config.*, htmlnanorc.*, jasmine.*, jest.config.*, jsconfig.*, karma*, lighthouserc.*, playwright.config.*, postcss.config.*, puppeteer.config.*, rspack.config.*, svgo.config.*, tailwind.config.*, tsconfig.*, tsdoc.*, uno.config.*, unocss.config.*, vitest.config.*, webpack.config.*, windi.config.*", 232 | "vue.config.*": "*.env, .babelrc*, .codecov, .cssnanorc*, .env.*, .envrc, .htmlnanorc*, .lighthouserc.*, .mocha*, .postcssrc*, .terserrc*, api-extractor.json, ava.config.*, babel.config.*, contentlayer.config.*, cssnano.config.*, cypress.*, env.d.ts, formkit.config.*, formulate.config.*, histoire.config.*, htmlnanorc.*, jasmine.*, jest.config.*, jsconfig.*, karma*, lighthouserc.*, playwright.config.*, postcss.config.*, puppeteer.config.*, rspack.config.*, svgo.config.*, tailwind.config.*, tsconfig.*, tsdoc.*, uno.config.*, unocss.config.*, vitest.config.*, webpack.config.*, windi.config.*" 233 | }, 234 | // Enable the ESlint flat config support 235 | "eslint.experimental.useFlatConfig": true, 236 | // Disable the default formatter, use eslint instead 237 | "prettier.enable": false, 238 | "editor.formatOnSave": false, 239 | // Auto fix 240 | "editor.codeActionsOnSave": { 241 | "source.fixAll.eslint": "explicit", 242 | "source.organizeImports": "never" 243 | }, 244 | 245 | "eslint.workingDirectories": [ 246 | { "pattern": "./src/*/" }, 247 | { "pattern": "./*" } 248 | ], 249 | // Silent the stylistic rules in you IDE, but still auto fix them 250 | "eslint.rules.customizations": [ 251 | { 252 | "rule": "style/*", 253 | "severity": "off" 254 | }, 255 | { 256 | "rule": "*-indent", 257 | "severity": "off" 258 | }, 259 | { 260 | "rule": "*-spacing", 261 | "severity": "off" 262 | }, 263 | { 264 | "rule": "*-spaces", 265 | "severity": "off" 266 | }, 267 | { 268 | "rule": "*-order", 269 | "severity": "off" 270 | }, 271 | { 272 | "rule": "*-dangle", 273 | "severity": "off" 274 | }, 275 | { 276 | "rule": "*-newline", 277 | "severity": "off" 278 | }, 279 | { 280 | "rule": "*quotes", 281 | "severity": "off" 282 | }, 283 | { 284 | "rule": "*semi", 285 | "severity": "off" 286 | } 287 | ], 288 | // Enable eslint for all supported languages 289 | "eslint.validate": [ 290 | "javascript", 291 | "javascriptreact", 292 | "typescript", 293 | "typescriptreact", 294 | "vue", 295 | "html", 296 | "markdown", 297 | "json", 298 | "jsonc", 299 | "yaml" 300 | ] 301 | } 302 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vue webapp template 2 | 3 | 4 | Empty NPM package for the purpose of existence of `create-vue-webapp` package. 5 | 6 | Full documentation is available at [https://vue-faq.org/en/vue-webapp/](https://vue-faq.org/en/vue-webapp/) 7 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import antfu from "@antfu/eslint-config"; 2 | 3 | export default antfu({ 4 | ignores: [".vscode/settings.json", ".vscode/settings.json/**", "src/assets/locales/*.json", "src/assets/locales/*.json/**"], 5 | rules: { 6 | "ts/semi": "off", 7 | "curly": ["error", "all"], 8 | "no-console": "off", 9 | "no-alert": "off", 10 | "vue/html-self-closing": "off", 11 | "style/semi": ["error", "always"], 12 | "style/indent": 2, // 4, or 'tab' 13 | "style/quotes": [ 14 | "error", 15 | "double", 16 | ], 17 | "style/brace-style": ["error", "1tbs", { allowSingleLine: true }], 18 | }, 19 | }); 20 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | <!-- title placeholder --> 10 | 11 | 12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-webapp", 3 | "type": "module", 4 | "version": "1.0.6", 5 | "description": "A basic template for building a new Vue 3 web application using the latest technologies and best practices", 6 | "license": "MIT", 7 | "homepage": "https://github.com/vuesence/vue-webapp", 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/vuesence/vue-webapp.git" 11 | }, 12 | "bugs": { 13 | "url": "https://github.com/vuesence/vue-webapp/issues" 14 | }, 15 | "keywords": [ 16 | "vue", 17 | "vite", 18 | "website", 19 | "webapp", 20 | "template", 21 | "scaffold" 22 | ], 23 | "scripts": { 24 | "dev": "vite", 25 | "build": "vite build", 26 | "build:tsc": "vue-tsc && vite build", 27 | "preview": "vite preview", 28 | "lint": "eslint .", 29 | "lint:fix": "eslint . --fix" 30 | }, 31 | "dependencies": { 32 | "vue": "^3.4.33", 33 | "vue-router": "^4.4.0" 34 | }, 35 | "devDependencies": { 36 | "@antfu/eslint-config": "^2.23.2", 37 | "@types/node": "^20.14.11", 38 | "@vitejs/plugin-vue": "^5.0.5", 39 | "eslint": "^9.7.0", 40 | "typescript": "^5.5.4", 41 | "vite": "^5.3.4", 42 | "vite-plugin-html-injection": "^1.4.2" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /public/assets/images/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuesence/vue-webapp/f341029e21b8bbc4839962e67188afc1857c6b7f/public/assets/images/apple-touch-icon.png -------------------------------------------------------------------------------- /public/assets/images/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuesence/vue-webapp/f341029e21b8bbc4839962e67188afc1857c6b7f/public/assets/images/favicon-32x32.png -------------------------------------------------------------------------------- /public/assets/images/logo-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuesence/vue-webapp/f341029e21b8bbc4839962e67188afc1857c6b7f/public/assets/images/logo-128.png -------------------------------------------------------------------------------- /public/assets/images/logo-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuesence/vue-webapp/f341029e21b8bbc4839962e67188afc1857c6b7f/public/assets/images/logo-48.png -------------------------------------------------------------------------------- /public/assets/images/logo-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuesence/vue-webapp/f341029e21b8bbc4839962e67188afc1857c6b7f/public/assets/images/logo-512.png -------------------------------------------------------------------------------- /public/assets/images/logo-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuesence/vue-webapp/f341029e21b8bbc4839962e67188afc1857c6b7f/public/assets/images/logo-small.png -------------------------------------------------------------------------------- /public/assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuesence/vue-webapp/f341029e21b8bbc4839962e67188afc1857c6b7f/public/assets/images/logo.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuesence/vue-webapp/f341029e21b8bbc4839962e67188afc1857c6b7f/public/favicon.ico -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "Acme Corporation webapp", 3 | "dir": "auto", 4 | "display": "standalone", 5 | "name": "Acme Inc.", 6 | "orientation": "any", 7 | "scope": "/", 8 | "short_name": "Acme", 9 | "start_url": "/", 10 | "categories": [ 11 | "it", 12 | "development", 13 | "education" 14 | ], 15 | "icons": [ 16 | { 17 | "src": "/assets/images/logo-512.png", 18 | "type": "image/png", 19 | "sizes": "512x512", 20 | "purpose": "any maskable" 21 | }, 22 | { 23 | "src": "/assets/images/logo-48.png", 24 | "type": "image/png", 25 | "sizes": "48x48", 26 | "purpose": "maskable" 27 | }, 28 | { 29 | "src": "/assets/images/logo-128.png", 30 | "type": "image/png", 31 | "sizes": "128x128", 32 | "purpose": "maskable" 33 | }, 34 | { 35 | "src": "/favicon.ico", 36 | "type": "image/x-icon" 37 | } 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /public/service-worker.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-restricted-globals */ 2 | const VERSION = 1.0; 3 | // const HOSTNAME = self.location.hostname; 4 | 5 | const nonCached = ["/", "/build.json"]; 6 | 7 | const CHACHE_LIST = { 8 | assets: `assets-v${VERSION}`, 9 | images: `images-v${VERSION}`, 10 | fonts: `fonts-v${VERSION}`, 11 | }; 12 | 13 | self.addEventListener("install", () => { 14 | self.skipWaiting(); 15 | console.log("Service Worker has been installed"); 16 | }); 17 | 18 | self.addEventListener("activate", (event) => { 19 | const expectedCacheNames = Object.keys(CHACHE_LIST).map((key) => { 20 | return CHACHE_LIST[key]; 21 | }); 22 | 23 | // Delete out of date caches 24 | event.waitUntil( 25 | caches.keys().then((cacheNames) => { 26 | return Promise.all( 27 | cacheNames.map((cacheName) => { 28 | if (!expectedCacheNames.includes(cacheName)) { 29 | console.log("Deleting out of date cache:", cacheName); 30 | return caches.delete(cacheName); 31 | } 32 | return null; 33 | }), 34 | ); 35 | }), 36 | ); 37 | console.log("Service Worker has been activated"); 38 | }); 39 | 40 | self.addEventListener("fetch", (event) => { 41 | // console.log("Fetching:", event.request.url); 42 | event.respondWith( 43 | (async function () { 44 | // console.log(event.request); 45 | const cachedResponse = await caches.match(event.request); 46 | if (cachedResponse) { 47 | // console.log("\tCached version found: " + event.request.url); 48 | return cachedResponse; 49 | } else { 50 | console.log(`\tGetting from the Internet:${event.request.url}`); 51 | return await fetchAndCache(event.request); 52 | } 53 | })(), 54 | ); 55 | }); 56 | 57 | function fetchAndCache(request) { 58 | return fetch(request) 59 | .then((response) => { 60 | // Check if we received a valid response 61 | if (!response.ok) { 62 | return response; 63 | } 64 | // throw Error(response.statusText); 65 | 66 | const url = new URL(request.url); 67 | // console.log(url); 68 | if ( 69 | response.status < 400 70 | && response.type === "basic" 71 | && !url.search.includes("mode=nocache") 72 | && !nonCached.includes(url.pathname) 73 | && url.protocol !== "chrome-extension:" 74 | ) { 75 | const cur_cache = getCache(response); 76 | if (cur_cache) { 77 | // console.log("\tCaching the response to", request.url); 78 | return caches.open(cur_cache).then((cache) => { 79 | cache.put(request, response.clone()); 80 | return response; 81 | }); 82 | } 83 | } 84 | return response; 85 | }) 86 | .catch((error) => { 87 | console.log(`Request failed for: ${request.url}`, error); 88 | throw error; 89 | }); 90 | } 91 | 92 | function getCache(response) { 93 | const contentType = response.headers.get("content-type"); 94 | if (contentType) { 95 | if (contentType.includes("image")) { 96 | return CHACHE_LIST.images; 97 | } else if ( 98 | ["application/javascript", "text/css", "font/woff2"].includes(contentType) 99 | ) { return CHACHE_LIST.assets; } 100 | } 101 | return false; 102 | } 103 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 19 | 20 | 22 | -------------------------------------------------------------------------------- /src/assets/images/about.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/assets/images/change-account.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/assets/images/contact-us.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/assets/images/hamburger.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/images/home.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/assets/images/icon-plus-blue.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/images/icon-plus-purple.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/images/instagram.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuesence/vue-webapp/f341029e21b8bbc4839962e67188afc1857c6b7f/src/assets/images/logo.png -------------------------------------------------------------------------------- /src/assets/images/logo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuesence/vue-webapp/f341029e21b8bbc4839962e67188afc1857c6b7f/src/assets/images/logo.webp -------------------------------------------------------------------------------- /src/assets/images/logo1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /src/assets/images/logout.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/assets/images/moon.svg: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /src/assets/images/sun.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/images/youtube.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/styles/base.css: -------------------------------------------------------------------------------- 1 | @import "./vars.css"; 2 | /* // @import url("https://fonts.googleapis.com/css2?family=Roboto+Slab:wght@500&display=swap"); 3 | // @import url('https://fonts.googleapis.com/css2?family=Roboto&display=swap'); */ 4 | 5 | :root { 6 | /* // font-family: 'Roboto', sans-serif; 7 | // font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; 8 | // line-height: 1.5; 9 | // font-weight: 400; 10 | 11 | // color-scheme: light dark; 12 | // color: #213547; 13 | // background-color: #ffffff; 14 | 15 | // font-synthesis: none; 16 | // text-rendering: optimizeLegibility; 17 | // -webkit-font-smoothing: antialiased; 18 | // -moz-osx-font-smoothing: grayscale; 19 | // -webkit-text-size-adjust: 100%; */ 20 | } 21 | 22 | @media screen and (min-width: 960px) { 23 | html { 24 | overflow-x: hidden; 25 | margin-right: calc(-1 * (100vw - 100%)); 26 | } 27 | } 28 | 29 | 30 | *, 31 | ::before, 32 | ::after { 33 | box-sizing: border-box; 34 | } 35 | 36 | html { 37 | line-height: 1.4; 38 | font-size: 16px; 39 | -webkit-text-size-adjust: 100%; 40 | } 41 | 42 | html.dark { 43 | color-scheme: dark; 44 | } 45 | 46 | body { 47 | /* // margin: 0; */ 48 | display: flex; 49 | place-items: center; 50 | min-width: 320px; 51 | /* // background-color: white; 52 | // min-height: 100vh; */ 53 | margin: 0; 54 | width: 100%; 55 | min-width: 320px; 56 | min-height: 100vh; 57 | line-height: 24px; 58 | font-family: var(--vwa-font-family-base); 59 | font-size: 16px; 60 | font-weight: 400; 61 | color: var(--vwa-c-text-1); 62 | background-color: var(--vwa-c-bg); 63 | direction: ltr; 64 | font-synthesis: style; 65 | text-rendering: optimizeLegibility; 66 | -webkit-font-smoothing: antialiased; 67 | -moz-osx-font-smoothing: grayscale; 68 | } 69 | 70 | a { 71 | /* // font-weight: 500; 72 | // color: #646cff; */ 73 | color: inherit; 74 | text-decoration: inherit; 75 | transition: color .25s; 76 | /* // &:hover { 77 | // color: #535bf2; 78 | // } */ 79 | } 80 | 81 | ol, 82 | ul { 83 | list-style: none; 84 | margin: 0; 85 | padding: 0; 86 | } 87 | 88 | img, 89 | svg, 90 | video, 91 | canvas, 92 | audio, 93 | iframe, 94 | embed, 95 | object { 96 | display: block; 97 | } 98 | img, 99 | video { 100 | max-width: 100%; 101 | height: auto; 102 | } 103 | 104 | 105 | button, 106 | input, 107 | optgroup, 108 | select, 109 | textarea { 110 | border: 1px solid var(--vwa-c-border); 111 | padding: 0; 112 | line-height: inherit; 113 | color: inherit; 114 | } 115 | 116 | button { 117 | padding: 0; 118 | font-family: inherit; 119 | background-color: transparent; 120 | background-image: none; 121 | } 122 | 123 | button:enabled, 124 | [role='button']:enabled { 125 | cursor: pointer; 126 | } 127 | 128 | button:focus, 129 | button:focus-visible { 130 | outline: 1px dotted; 131 | outline: 4px auto -webkit-focus-ring-color; 132 | } 133 | 134 | button:focus:not(:focus-visible) { 135 | outline: none !important; 136 | } 137 | 138 | input:focus, 139 | textarea:focus, 140 | select:focus { 141 | outline: none; 142 | } 143 | 144 | input, textarea { 145 | background-color: transparent; 146 | } 147 | input:-ms-input-placeholder, 148 | textarea:-ms-input-placeholder { 149 | color: var(--vwa-c-text-3); 150 | } 151 | 152 | input::-ms-input-placeholder, 153 | textarea::-ms-input-placeholder { 154 | color: var(--vwa-c-text-3); 155 | } 156 | 157 | input::placeholder, 158 | textarea::placeholder { 159 | color: var(--vwa-c-text-3); 160 | } 161 | 162 | input::-webkit-outer-spin-button, 163 | input::-webkit-inner-spin-button { 164 | -webkit-appearance: none; 165 | margin: 0; 166 | } 167 | 168 | input[type='number'] { 169 | -moz-appearance: textfield; 170 | } 171 | select { 172 | -webkit-appearance: none; 173 | } 174 | 175 | #app { 176 | /* // min-height: 100vh; */ 177 | width: var(--vwa-layout-max-width); 178 | max-width: var(--vwa-layout-max-width); 179 | margin: 0 auto; 180 | /* // padding: 2em 2em 0; 181 | // text-align: center; */ 182 | } 183 | -------------------------------------------------------------------------------- /src/assets/styles/custom.css: -------------------------------------------------------------------------------- 1 | 2 | // layout-placeholder 3 | -------------------------------------------------------------------------------- /src/assets/styles/vars.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Typography 3 | * -------------------------------------------------------------------------- */ 4 | 5 | @import url('https://fonts.googleapis.com/css2?family=Roboto&display=swap'); 6 | 7 | :root { 8 | --vwa-font-family-base: 'Roboto', sans-serif; 9 | --vwa-font-family-mono: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Monaco, 10 | Consolas, 'Liberation Mono', 'Courier New', monospace; 11 | } 12 | 13 | 14 | /** 15 | * Colors: Solid 16 | * -------------------------------------------------------------------------- */ 17 | 18 | :root { 19 | --vwa-c-white: #ffffff; 20 | --vwa-c-black: #000000; 21 | 22 | --vwa-c-neutral: var(--vwa-c-black); 23 | --vwa-c-neutral-inverse: var(--vwa-c-white); 24 | } 25 | 26 | .dark { 27 | --vwa-c-neutral: var(--vwa-c-white); 28 | --vwa-c-neutral-inverse: var(--vwa-c-black); 29 | } 30 | 31 | /** 32 | * Colors: Palette 33 | * 34 | * The primitive colors used for accent colors. These colors are referenced 35 | * by functional colors such as "Text", "Background", or "Brand". 36 | * 37 | * Each colors have exact same color scale system with 3 levels of solid 38 | * colors with different brightness, and 1 soft color. 39 | * 40 | * - `XXX-1`: The most solid color used mainly for colored text. It must 41 | * satisfy the contrast ratio against when used on top of `XXX-soft`. 42 | * 43 | * - `XXX-2`: The color used mainly for hover state of the button. 44 | * 45 | * - `XXX-3`: The color for solid background, such as bg color of the button. 46 | * It must satisfy the contrast ratio with pure white (#ffffff) text on 47 | * top of it. 48 | * 49 | * - `XXX-soft`: The color used for subtle background such as custom container 50 | * or badges. It must satisfy the contrast ratio when putting `XXX-1` colors 51 | * on top of it. 52 | * 53 | * The soft color must be semi transparent alpha channel. This is crucial 54 | * because it allows adding multiple "soft" colors on top of each other 55 | * to create a accent, such as when having inline code block inside 56 | * custom containers. 57 | * -------------------------------------------------------------------------- */ 58 | 59 | :root { 60 | --vwa-c-gray-1: #dddde3; 61 | --vwa-c-gray-2: #e4e4e9; 62 | --vwa-c-gray-3: #ebebef; 63 | --vwa-c-gray-soft: rgba(142, 150, 170, 0.14); 64 | 65 | --vwa-c-indigo-1: #3451b2; 66 | --vwa-c-indigo-2: #3a5ccc; 67 | --vwa-c-indigo-3: #5672cd; 68 | --vwa-c-indigo-soft: rgba(100, 108, 255, 0.14); 69 | 70 | --vwa-c-green-1: #18794e; 71 | --vwa-c-green-2: #299764; 72 | --vwa-c-green-3: #30a46c; 73 | --vwa-c-green-soft: rgba(16, 185, 129, 0.14); 74 | 75 | --vwa-c-yellow-1: #915930; 76 | --vwa-c-yellow-2: #946300; 77 | --vwa-c-yellow-3: #9f6a00; 78 | --vwa-c-yellow-soft: rgba(234, 179, 8, 0.14); 79 | 80 | --vwa-c-red-1: #b8272c; 81 | --vwa-c-red-2: #d5393e; 82 | --vwa-c-red-3: #e0575b; 83 | --vwa-c-red-soft: rgba(244, 63, 94, 0.14); 84 | 85 | --vwa-c-sponsor: #db2777; 86 | } 87 | 88 | .dark { 89 | --vwa-c-gray-1: #515c67; 90 | --vwa-c-gray-2: #414853; 91 | --vwa-c-gray-3: #32363f; 92 | --vwa-c-gray-soft: rgba(101, 117, 133, 0.16); 93 | 94 | --vwa-c-indigo-1: #a8b1ff; 95 | --vwa-c-indigo-2: #5c73e7; 96 | --vwa-c-indigo-3: #3e63dd; 97 | --vwa-c-indigo-soft: rgba(100, 108, 255, 0.16); 98 | 99 | --vwa-c-green-1: #3dd68c; 100 | --vwa-c-green-2: #30a46c; 101 | --vwa-c-green-3: #298459; 102 | --vwa-c-green-soft: rgba(16, 185, 129, 0.16); 103 | 104 | --vwa-c-yellow-1: #f9b44e; 105 | --vwa-c-yellow-2: #da8b17; 106 | --vwa-c-yellow-3: #a46a0a; 107 | --vwa-c-yellow-soft: rgba(234, 179, 8, 0.16); 108 | 109 | --vwa-c-red-1: #f66f81; 110 | --vwa-c-red-2: #f14158; 111 | --vwa-c-red-3: #b62a3c; 112 | --vwa-c-red-soft: rgba(244, 63, 94, 0.16); 113 | } 114 | 115 | /** 116 | * Colors: Background 117 | * 118 | * - `bg`: The bg color used for main screen. 119 | * 120 | * - `bg-alt`: The alternative bg color used in places such as "sidebar", 121 | * or "code block". 122 | * 123 | * - `bg-elv`: The elevated bg color. This is used at parts where it "floats", 124 | * such as "dialog". 125 | * 126 | * - `bg-soft`: The bg color to slightly distinguish some components from 127 | * the page. Used for things like "carbon ads" or "table". 128 | * -------------------------------------------------------------------------- */ 129 | 130 | :root { 131 | --vwa-c-bg: #ffffff; 132 | --vwa-c-bg-alt: #f6f6f7; 133 | --vwa-c-bg-elv: #ffffff; 134 | --vwa-c-bg-soft: #f6f6f7; 135 | } 136 | 137 | .dark { 138 | --vwa-c-bg: #1b1b1f; 139 | --vwa-c-bg-alt: #161618; 140 | --vwa-c-bg-elv: #202127; 141 | --vwa-c-bg-soft: #202127; 142 | } 143 | 144 | /** 145 | * Colors: Borders 146 | * 147 | * - `divider`: This is used for separators. This is used to divide sections 148 | * within the same components, such as having separator on "h2" heading. 149 | * 150 | * - `border`: This is designed for borders on interactive components. 151 | * For example this should be used for a button outline. 152 | * 153 | * - `gutter`: This is used to divide components in the page. For example 154 | * the header and the lest of the page. 155 | * -------------------------------------------------------------------------- */ 156 | 157 | :root { 158 | --vwa-c-border: #c2c2c4; 159 | --vwa-c-divider: #e2e2e3; 160 | --vwa-c-gutter: #e2e2e3; 161 | } 162 | 163 | .dark { 164 | --vwa-c-border: #3c3f44; 165 | --vwa-c-divider: #2e2e32; 166 | --vwa-c-gutter: #000000; 167 | } 168 | 169 | /** 170 | * Colors: Text 171 | * 172 | * - `text-1`: Used for primary text. 173 | * 174 | * - `text-2`: Used for muted texts, such as "inactive menu" or "info texts". 175 | * 176 | * - `text-3`: Used for subtle texts, such as "placeholders" or "caret icon". 177 | * -------------------------------------------------------------------------- */ 178 | 179 | :root { 180 | --vwa-c-text-1: rgba(60, 60, 67); 181 | --vwa-c-text-2: rgba(60, 60, 67, 0.78); 182 | --vwa-c-text-3: rgba(60, 60, 67, 0.56); 183 | } 184 | 185 | .dark { 186 | --vwa-c-text-1: rgba(255, 255, 245, 0.86); 187 | --vwa-c-text-2: rgba(235, 235, 245, 0.6); 188 | --vwa-c-text-3: rgba(235, 235, 245, 0.38); 189 | } 190 | 191 | /** 192 | * Colors: Function 193 | * 194 | * - `default`: The color used purely for subtle indication without any 195 | * special meanings attached to it such as bg color for menu hover state. 196 | * 197 | * - `brand`: Used for primary brand colors, such as link text, button with 198 | * brand theme, etc. 199 | * 200 | * - `tip`: Used to indicate useful information. The default theme uses the 201 | * brand color for this by default. 202 | * 203 | * - `warning`: Used to indicate warning to the users. Used in custom 204 | * container, badges, etc. 205 | * 206 | * - `danger`: Used to show error, or dangerous message to the users. Used 207 | * in custom container, badges, etc. 208 | * 209 | * To understand the scaling system, refer to "Colors: Palette" section. 210 | * -------------------------------------------------------------------------- */ 211 | 212 | :root { 213 | --vwa-c-default-1: var(--vwa-c-gray-1); 214 | --vwa-c-default-2: var(--vwa-c-gray-2); 215 | --vwa-c-default-3: var(--vwa-c-gray-3); 216 | --vwa-c-default-soft: var(--vwa-c-gray-soft); 217 | 218 | --vwa-c-brand-1: var(--vwa-c-indigo-1); 219 | --vwa-c-brand-2: var(--vwa-c-indigo-2); 220 | --vwa-c-brand-3: var(--vwa-c-indigo-3); 221 | --vwa-c-brand-soft: var(--vwa-c-indigo-soft); 222 | 223 | --vwa-c-tip-1: var(--vwa-c-brand-1); 224 | --vwa-c-tip-2: var(--vwa-c-brand-2); 225 | --vwa-c-tip-3: var(--vwa-c-brand-3); 226 | --vwa-c-tip-soft: var(--vwa-c-brand-soft); 227 | 228 | --vwa-c-warning-1: var(--vwa-c-yellow-1); 229 | --vwa-c-warning-2: var(--vwa-c-yellow-2); 230 | --vwa-c-warning-3: var(--vwa-c-yellow-3); 231 | --vwa-c-warning-soft: var(--vwa-c-yellow-soft); 232 | 233 | --vwa-c-danger-1: var(--vwa-c-red-1); 234 | --vwa-c-danger-2: var(--vwa-c-red-2); 235 | --vwa-c-danger-3: var(--vwa-c-red-3); 236 | --vwa-c-danger-soft: var(--vwa-c-red-soft); 237 | } 238 | 239 | 240 | /** 241 | * Shadows 242 | * -------------------------------------------------------------------------- */ 243 | 244 | :root { 245 | --vwa-shadow-1: 0 1px 2px rgba(0, 0, 0, 0.04), 0 1px 2px rgba(0, 0, 0, 0.06); 246 | --vwa-shadow-2: 0 3px 12px rgba(0, 0, 0, 0.07), 0 1px 4px rgba(0, 0, 0, 0.07); 247 | --vwa-shadow-3: 0 12px 32px rgba(0, 0, 0, 0.1), 0 2px 6px rgba(0, 0, 0, 0.08); 248 | --vwa-shadow-4: 0 14px 44px rgba(0, 0, 0, 0.12), 0 3px 9px rgba(0, 0, 0, 0.12); 249 | --vwa-shadow-5: 0 18px 56px rgba(0, 0, 0, 0.16), 0 4px 12px rgba(0, 0, 0, 0.16); 250 | } 251 | 252 | /** 253 | * Z-indexes 254 | * -------------------------------------------------------------------------- */ 255 | 256 | :root { 257 | --vwa-z-index-footer: 10; 258 | --vwa-z-index-local-nav: 20; 259 | --vwa-z-index-nav: 30; 260 | --vwa-z-index-layout-top: 40; 261 | --vwa-z-index-backdrop: 50; 262 | --vwa-z-index-sidebar: 60; 263 | } 264 | 265 | /** 266 | * Icons 267 | * -------------------------------------------------------------------------- */ 268 | 269 | :root { 270 | --vwa-icon-copy: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' height='20' width='20' stroke='rgba(128,128,128,1)' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M9 5H7a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-2M9 5a2 2 0 0 0 2 2h2a2 2 0 0 0 2-2M9 5a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2'/%3E%3C/svg%3E"); 271 | --vwa-icon-copied: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' height='20' width='20' stroke='rgba(128,128,128,1)' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M9 5H7a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-2M9 5a2 2 0 0 0 2 2h2a2 2 0 0 0 2-2M9 5a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2m-6 9 2 2 4-4'/%3E%3C/svg%3E"); 272 | } 273 | 274 | /** 275 | * Layouts 276 | * -------------------------------------------------------------------------- */ 277 | 278 | :root { 279 | --vwa-layout-max-width: 1280px; 280 | } 281 | 282 | /** 283 | * Component: Header Anchor 284 | * -------------------------------------------------------------------------- */ 285 | 286 | :root { 287 | --vwa-header-anchor-symbol: '#'; 288 | } 289 | 290 | /** 291 | * Component: Button 292 | * -------------------------------------------------------------------------- */ 293 | 294 | :root { 295 | --vwa-button-brand-border: transparent; 296 | --vwa-button-brand-text: var(--vwa-c-white); 297 | --vwa-button-brand-bg: var(--vwa-c-brand-3); 298 | --vwa-button-brand-hover-border: transparent; 299 | --vwa-button-brand-hover-text: var(--vwa-c-white); 300 | --vwa-button-brand-hover-bg: var(--vwa-c-brand-2); 301 | --vwa-button-brand-active-border: transparent; 302 | --vwa-button-brand-active-text: var(--vwa-c-white); 303 | --vwa-button-brand-active-bg: var(--vwa-c-brand-1); 304 | 305 | --vwa-button-alt-border: transparent; 306 | --vwa-button-alt-text: var(--vwa-c-text-1); 307 | --vwa-button-alt-bg: var(--vwa-c-default-3); 308 | --vwa-button-alt-hover-border: transparent; 309 | --vwa-button-alt-hover-text: var(--vwa-c-text-1); 310 | --vwa-button-alt-hover-bg: var(--vwa-c-default-2); 311 | --vwa-button-alt-active-border: transparent; 312 | --vwa-button-alt-active-text: var(--vwa-c-text-1); 313 | --vwa-button-alt-active-bg: var(--vwa-c-default-1); 314 | 315 | --vwa-button-sponsor-border: var(--vwa-c-text-2); 316 | --vwa-button-sponsor-text: var(--vwa-c-text-2); 317 | --vwa-button-sponsor-bg: transparent; 318 | --vwa-button-sponsor-hover-border: var(--vwa-c-sponsor); 319 | --vwa-button-sponsor-hover-text: var(--vwa-c-sponsor); 320 | --vwa-button-sponsor-hover-bg: transparent; 321 | --vwa-button-sponsor-active-border: var(--vwa-c-sponsor); 322 | --vwa-button-sponsor-active-text: var(--vwa-c-sponsor); 323 | --vwa-button-sponsor-active-bg: transparent; 324 | } 325 | 326 | /** 327 | * Component: Custom Block 328 | * -------------------------------------------------------------------------- */ 329 | 330 | :root { 331 | --vwa-custom-block-font-size: 14px; 332 | --vwa-custom-block-code-font-size: 13px; 333 | 334 | --vwa-custom-block-info-border: transparent; 335 | --vwa-custom-block-info-text: var(--vwa-c-text-1); 336 | --vwa-custom-block-info-bg: var(--vwa-c-default-soft); 337 | --vwa-custom-block-info-code-bg: var(--vwa-c-default-soft); 338 | 339 | --vwa-custom-block-tip-border: transparent; 340 | --vwa-custom-block-tip-text: var(--vwa-c-text-1); 341 | --vwa-custom-block-tip-bg: var(--vwa-c-brand-soft); 342 | --vwa-custom-block-tip-code-bg: var(--vwa-c-brand-soft); 343 | 344 | --vwa-custom-block-warning-border: transparent; 345 | --vwa-custom-block-warning-text: var(--vwa-c-text-1); 346 | --vwa-custom-block-warning-bg: var(--vwa-c-warning-soft); 347 | --vwa-custom-block-warning-code-bg: var(--vwa-c-warning-soft); 348 | 349 | --vwa-custom-block-danger-border: transparent; 350 | --vwa-custom-block-danger-text: var(--vwa-c-text-1); 351 | --vwa-custom-block-danger-bg: var(--vwa-c-danger-soft); 352 | --vwa-custom-block-danger-code-bg: var(--vwa-c-danger-soft); 353 | 354 | --vwa-custom-block-details-border: var(--vwa-custom-block-info-border); 355 | --vwa-custom-block-details-text: var(--vwa-custom-block-info-text); 356 | --vwa-custom-block-details-bg: var(--vwa-custom-block-info-bg); 357 | --vwa-custom-block-details-code-bg: var(--vwa-custom-block-info-code-bg); 358 | } 359 | 360 | /** 361 | * Component: Input 362 | * -------------------------------------------------------------------------- */ 363 | 364 | :root { 365 | --vwa-input-border-color: var(--vwa-c-border); 366 | --vwa-input-bg-color: var(--vwa-c-bg-alt); 367 | 368 | --vwa-input-switch-bg-color: var(--vwa-c-gray-soft); 369 | } 370 | 371 | /** 372 | * Component: Nav 373 | * -------------------------------------------------------------------------- */ 374 | 375 | :root { 376 | --vwa-nav-height: 64px; 377 | --vwa-nav-bg-color: var(--vwa-c-bg); 378 | --vwa-nav-screen-bg-color: var(--vwa-c-bg); 379 | --vwa-nav-logo-height: 24px; 380 | } 381 | 382 | .hide-nav { 383 | --vwa-nav-height: 0px; 384 | } 385 | 386 | .hide-nav .VPSidebar { 387 | --vwa-nav-height: 22px; 388 | } 389 | 390 | /** 391 | * Component: Local Nav 392 | * -------------------------------------------------------------------------- */ 393 | 394 | :root { 395 | --vwa-local-nav-bg-color: var(--vwa-c-bg); 396 | } 397 | 398 | /** 399 | * Component: Sidebar 400 | * -------------------------------------------------------------------------- */ 401 | 402 | :root { 403 | --vwa-sidebar-width: 272px; 404 | --vwa-sidebar-bg-color: var(--vwa-c-bg-alt); 405 | } 406 | 407 | /** 408 | * Colors Backdrop 409 | * -------------------------------------------------------------------------- */ 410 | 411 | :root { 412 | --vwa-backdrop-bg-color: rgba(0, 0, 0, 0.6); 413 | } 414 | 415 | /** 416 | * Component: Home 417 | * -------------------------------------------------------------------------- */ 418 | 419 | :root { 420 | --vwa-home-hero-name-color: var(--vwa-c-brand-1); 421 | --vwa-home-hero-name-background: transparent; 422 | 423 | --vwa-home-hero-image-background-image: none; 424 | --vwa-home-hero-image-filter: none; 425 | } 426 | 427 | /** 428 | * Component: Badge 429 | * -------------------------------------------------------------------------- */ 430 | 431 | :root { 432 | --vwa-badge-info-border: transparent; 433 | --vwa-badge-info-text: var(--vwa-c-text-2); 434 | --vwa-badge-info-bg: var(--vwa-c-default-soft); 435 | 436 | --vwa-badge-tip-border: transparent; 437 | --vwa-badge-tip-text: var(--vwa-c-brand-1); 438 | --vwa-badge-tip-bg: var(--vwa-c-brand-soft); 439 | 440 | --vwa-badge-warning-border: transparent; 441 | --vwa-badge-warning-text: var(--vwa-c-warning-1); 442 | --vwa-badge-warning-bg: var(--vwa-c-warning-soft); 443 | 444 | --vwa-badge-danger-border: transparent; 445 | --vwa-badge-danger-text: var(--vwa-c-danger-1); 446 | --vwa-badge-danger-bg: var(--vwa-c-danger-soft); 447 | } 448 | 449 | /** 450 | * Component: Carbon Ads 451 | * -------------------------------------------------------------------------- */ 452 | 453 | :root { 454 | --vwa-carbon-ads-text-color: var(--vwa-c-text-1); 455 | --vwa-carbon-ads-poweredby-color: var(--vwa-c-text-2); 456 | --vwa-carbon-ads-bg-color: var(--vwa-c-bg-soft); 457 | --vwa-carbon-ads-hover-text-color: var(--vwa-c-brand-1); 458 | --vwa-carbon-ads-hover-poweredby-color: var(--vwa-c-text-1); 459 | } 460 | 461 | /** 462 | * Component: Local Search 463 | * -------------------------------------------------------------------------- */ 464 | 465 | :root { 466 | --vwa-local-search-bg: var(--vwa-c-bg); 467 | --vwa-local-search-result-bg: var(--vwa-c-bg); 468 | --vwa-local-search-result-border: var(--vwa-c-divider); 469 | --vwa-local-search-result-selected-bg: var(--vwa-c-bg); 470 | --vwa-local-search-result-selected-border: var(--vwa-c-brand-1); 471 | --vwa-local-search-highlight-bg: var(--vwa-c-brand-1); 472 | --vwa-local-search-highlight-text: var(--vwa-c-neutral-inverse); 473 | } 474 | -------------------------------------------------------------------------------- /src/components/AppContentPane.vue: -------------------------------------------------------------------------------- 1 | 3 | 4 | 19 | 20 | 34 | -------------------------------------------------------------------------------- /src/components/SectionHeader.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 24 | 25 | 101 | -------------------------------------------------------------------------------- /src/components/drawers/SimpleDrawer.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 15 | 16 | 63 | -------------------------------------------------------------------------------- /src/components/drawers/TouchSlideoutDrawer.vue: -------------------------------------------------------------------------------- 1 | 209 | 210 | 224 | 225 | 284 | -------------------------------------------------------------------------------- /src/components/footers/DistributedFooter.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 45 | 46 | 201 | -------------------------------------------------------------------------------- /src/components/footers/MantineRichFooter.vue: -------------------------------------------------------------------------------- 1 | 40 | 41 | 80 | 81 | 203 | -------------------------------------------------------------------------------- /src/components/footers/MantineSimpleFooter.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 68 | 69 | 157 | -------------------------------------------------------------------------------- /src/components/footers/RichFooter.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 102 | 103 | 236 | -------------------------------------------------------------------------------- /src/components/footers/SimpleFooter.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 23 | -------------------------------------------------------------------------------- /src/components/headers/MantineLayeredHeader.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 58 | 59 | 159 | -------------------------------------------------------------------------------- /src/components/headers/MantineSimpleHeader.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 36 | 37 | 90 | -------------------------------------------------------------------------------- /src/components/headers/SimpleHeader.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 18 | 19 | 41 | -------------------------------------------------------------------------------- /src/components/headers/SlidingHeader.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 39 | 40 | 68 | -------------------------------------------------------------------------------- /src/components/navbars/MantineSimpleNavbar.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 51 | 52 | 105 | -------------------------------------------------------------------------------- /src/components/navbars/SimpleNavbar.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 30 | 31 | 56 | -------------------------------------------------------------------------------- /src/components/ui/BaseIcon.vue: -------------------------------------------------------------------------------- 1 | 43 | 44 | 70 | 71 | 85 | -------------------------------------------------------------------------------- /src/components/ui/BaseToggle.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 63 | -------------------------------------------------------------------------------- /src/components/ui/HamburgerIcon.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | 11 | 55 | -------------------------------------------------------------------------------- /src/components/ui/ThemeToggle.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 29 | 30 | 42 | -------------------------------------------------------------------------------- /src/composables/useAppConfig.ts: -------------------------------------------------------------------------------- 1 | import { ref, watch } from "vue"; 2 | 3 | const isDrawerOpen = ref(false); 4 | const isDarkTheme = ref(false); 5 | const APPEARANCE_KEY = "vwa-theme-appearance"; 6 | 7 | watch(isDarkTheme, () => { 8 | if (isDarkTheme.value) { 9 | document.documentElement.classList.add("dark"); 10 | localStorage.setItem(APPEARANCE_KEY, "dark"); 11 | } else { 12 | document.documentElement.classList.remove("dark"); 13 | localStorage.setItem(APPEARANCE_KEY, ""); 14 | } 15 | }); 16 | 17 | export function initAppearance() { 18 | // document.documentElement.classList.add("dark"); 19 | const preference = localStorage.getItem(APPEARANCE_KEY) || ""; 20 | const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches; 21 | if (!preference || preference === "auto" ? prefersDark : preference === "dark") { 22 | document.documentElement.classList.add("dark"); 23 | }; 24 | } 25 | 26 | export function useAppConfig() { 27 | function closeDrawer() { 28 | isDrawerOpen.value = false; 29 | } 30 | 31 | return { 32 | isDrawerOpen, 33 | isDarkTheme, 34 | closeDrawer, 35 | initAppearance, 36 | }; 37 | } 38 | -------------------------------------------------------------------------------- /src/composables/useI18nLight.ts: -------------------------------------------------------------------------------- 1 | import { type Ref, ref } from "vue"; 2 | import enLocale from "@/utils/locales/en.json"; 3 | import esLocale from "@/utils/locales/es.json"; 4 | 5 | interface Locale { 6 | code: string 7 | name: string 8 | flag?: string 9 | } 10 | 11 | const locales: Locale[] = [ 12 | { 13 | code: "en", 14 | name: "English", 15 | }, 16 | { 17 | code: "es", 18 | name: "Español", 19 | }, 20 | ]; 21 | 22 | const locale: Ref = ref(locales[0]); 23 | const messages: Record = { 24 | en: enLocale, 25 | es: esLocale, 26 | }; 27 | 28 | export const t = useI18n().t; 29 | 30 | export function useI18n() { 31 | async function initI18n() { 32 | const code 33 | = localStorage.getItem("vue-webapp_lang") 34 | || import.meta.env.VITE_APP_DEFAULT_LOCALE 35 | || "en"; 36 | // || window.navigator.language.substring(0, 2); 37 | setLocale(code); 38 | } 39 | 40 | // It can load locales from remote server 41 | async function setLocale(code: string) { 42 | if (locale.value.code !== code) { 43 | locale.value = locales.find(l => l.code === code); 44 | localStorage.setItem("vue-webapp_lang", locale.value.code); 45 | } 46 | } 47 | 48 | function t(msg: string, params: Record = null) { 49 | if (!msg || !locale.value) { 50 | return ""; 51 | } 52 | 53 | let val 54 | = msg.split(".").reduce((val, part) => { 55 | return val[part] ?? null; 56 | }, messages[locale.value.code]) ?? msg; 57 | if (params) { 58 | for (const [key, value] of Object.entries(params)) { 59 | val = val.replace(`{${key}}`, value); 60 | } 61 | } 62 | return val ?? msg; 63 | } 64 | 65 | return { 66 | t, 67 | locale, 68 | locales, 69 | setLocale, 70 | initI18n, 71 | }; 72 | } 73 | -------------------------------------------------------------------------------- /src/composables/useScreenWidth.ts: -------------------------------------------------------------------------------- 1 | import { ref } from "vue"; 2 | 3 | export function useScreenWidth(breakpoints: object) { 4 | const screenWidthFactor = ref(""); 5 | const screenSizeMatches = []; 6 | 7 | const breakpointArray = Object.entries(breakpoints).sort((a, b) => +a[1] - +b[1]); 8 | 9 | for (let i = 0; i < breakpointArray.length; i++) { 10 | const clauses = []; 11 | if (i > 0) { 12 | clauses.push(`(min-width: ${breakpointArray[i - 1][1] + 1}px)`); 13 | } 14 | 15 | if (i < breakpointArray.length - 1) { 16 | clauses.push(`(max-width: ${breakpointArray[i][1]}px)`); 17 | } 18 | 19 | const mediaMatch = window.matchMedia(clauses.join(" and ")); 20 | mediaMatch.addEventListener("change", (e) => { 21 | if (e.matches) { 22 | screenWidthFactor.value = breakpointArray[i][0]; 23 | document.body.classList.add(breakpointArray[i][0]); 24 | } else { 25 | document.body.classList.remove(breakpointArray[i][0]); 26 | } 27 | }); 28 | if (mediaMatch.matches) { 29 | screenWidthFactor.value = breakpointArray[i][0]; 30 | document.body.classList.add(breakpointArray[i][0]); 31 | } else { 32 | document.body.classList.remove(breakpointArray[i][0]); 33 | } 34 | screenSizeMatches.push(mediaMatch); 35 | } 36 | 37 | return { 38 | screenWidthFactor, 39 | }; 40 | } 41 | -------------------------------------------------------------------------------- /src/composables/useSplashScreen.ts: -------------------------------------------------------------------------------- 1 | import { onMounted } from "vue"; 2 | 3 | export function useSplashScreen() { 4 | onMounted(() => { 5 | setTimeout(() => { 6 | document.querySelector(".splash-screen")?.classList.add("fade-out"); 7 | setTimeout(() => { 8 | document.querySelector("body").classList.remove("splash"); 9 | document.body.style.position = "initial"; 10 | }, 500); 11 | }, 1000); 12 | }); 13 | } 14 | -------------------------------------------------------------------------------- /src/composables/useTouchSwipe.ts: -------------------------------------------------------------------------------- 1 | import type { ComputedRef, Ref } from "vue"; 2 | import { computed, reactive, ref } from "vue"; 3 | 4 | export type ISwipeDirection = "up" | "down" | "left" | "right" | "none"; 5 | 6 | export interface IPoint { 7 | x: number 8 | y: number 9 | } 10 | 11 | export interface ISwipeOptions { 12 | /* 13 | * Specify a custom `window` instance, e.g. working with iframes or in testing environments. 14 | */ 15 | window?: Window 16 | 17 | /** 18 | * Register events as passive 19 | * 20 | * @default true 21 | */ 22 | passive?: boolean 23 | 24 | /** 25 | * @default 50 26 | */ 27 | threshold?: number 28 | 29 | /** 30 | * Callback on swipe start 31 | */ 32 | onSwipeStart?: (e: TouchEvent) => void 33 | 34 | /** 35 | * Callback on swipe moves 36 | */ 37 | onSwipe?: (e: TouchEvent) => void 38 | 39 | /** 40 | * Callback on swipe ends 41 | */ 42 | onSwipeEnd?: (e: TouchEvent, direction: ISwipeDirection) => void 43 | } 44 | 45 | export interface ISwipeReturn { 46 | isSwiping: Ref 47 | direction: ComputedRef 48 | coordsStart: Readonly 49 | coordsEnd: Readonly 50 | stop: () => void 51 | } 52 | 53 | /** 54 | * Reactive touch swipe detection. 55 | * 56 | * @param target 57 | * @param options 58 | */ 59 | export function useTouchSwipe( 60 | // target: MaybeRefOrGetter, 61 | target: EventTarget, 62 | options: ISwipeOptions = {}, 63 | ): ISwipeReturn { 64 | const { threshold = 50, onSwipe, onSwipeEnd, onSwipeStart } = options; 65 | 66 | const isSwiping = ref(false); 67 | const coordsStart = reactive({ x: 0, y: 0 }); 68 | const coordsEnd = reactive({ x: 0, y: 0 }); 69 | 70 | const { max, abs, round } = Math; 71 | 72 | const diffX = computed(() => round(coordsStart.x - coordsEnd.x)); 73 | const diffY = computed(() => round(coordsStart.y - coordsEnd.y)); 74 | 75 | const isThresholdExceeded = computed(() => 76 | isThresholdExceeded.value || max(abs(diffX.value), abs(diffY.value)) >= threshold); 77 | 78 | const direction = computed((): ISwipeDirection => { 79 | if (!isThresholdExceeded.value) { 80 | return "none"; 81 | } 82 | 83 | if (abs(diffX.value) > abs(diffY.value)) { 84 | return diffX.value > 0 ? "left" : "right"; 85 | } else { return diffY.value > 0 ? "up" : "down"; } 86 | }); 87 | 88 | const listenerOptions: { passive?: boolean; capture?: boolean } = { passive: true, capture: false }; 89 | 90 | const onTouchEnd = (e: TouchEvent) => { 91 | if (isSwiping.value) { 92 | onSwipeEnd?.(e, direction.value); 93 | isSwiping.value = false; 94 | } 95 | }; 96 | 97 | const onTouchStart = (e: TouchEvent) => { 98 | if (e.touches.length !== 1) { 99 | return; 100 | } 101 | 102 | if (listenerOptions.capture && !listenerOptions.passive) { 103 | e.preventDefault(); 104 | } 105 | 106 | coordsStart.x = coordsEnd.x = round(e.touches[0].clientX); 107 | coordsStart.y = coordsEnd.y = round(e.touches[0].clientY); 108 | onSwipeStart?.(e); 109 | }; 110 | 111 | const onTouchMove = (e: TouchEvent) => { 112 | if (e.touches.length !== 1) { 113 | return; 114 | } 115 | 116 | coordsEnd.x = round(e.touches[0].clientX); 117 | coordsEnd.y = round(e.touches[0].clientY); 118 | if (!isSwiping.value && isThresholdExceeded.value) { 119 | isSwiping.value = true; 120 | } 121 | 122 | if (isSwiping.value) { 123 | onSwipe?.(e); 124 | } 125 | }; 126 | 127 | // useEventListener(target, ["touchend", "touchcancel"], onTouchEnd, listenerOptions), 128 | target.addEventListener("touchstart", onTouchStart, listenerOptions); 129 | target.addEventListener("touchmove", onTouchMove, listenerOptions); 130 | target.addEventListener("touchend", onTouchEnd, listenerOptions); 131 | target.addEventListener("touchcancel", onTouchEnd, listenerOptions); 132 | 133 | function stop() { 134 | target.removeEventListener("touchstart", onTouchStart, listenerOptions); 135 | target.removeEventListener("touchmove", onTouchMove, listenerOptions); 136 | target.removeEventListener("touchend", onTouchEnd, listenerOptions); 137 | target.removeEventListener("touchcancel", onTouchEnd, listenerOptions); 138 | }; 139 | 140 | return { 141 | isSwiping, 142 | direction, 143 | coordsStart, 144 | coordsEnd, 145 | stop, 146 | }; 147 | } 148 | -------------------------------------------------------------------------------- /src/layouts/MainLayout.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 48 | 49 | 62 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from "vue"; 2 | 3 | // import "./assets/styles/vars.css"; 4 | // import "./styles/base.css"; 5 | 6 | import "./assets/styles/base.css"; 7 | import "./assets/styles/custom.css"; 8 | import App from "./App.vue"; 9 | import { router } from "./router"; 10 | import { loadIcons } from "@/utils/icons"; 11 | import { initAppearance } from "@/composables/useAppConfig"; 12 | import { api } from "@/services/api"; 13 | 14 | // i18n placeholder 1 15 | 16 | const app = createApp(App); 17 | app.use(router); 18 | loadIcons(); 19 | initAppearance(); 20 | api.init(); 21 | // i18n placeholder 2 22 | 23 | await Promise.all([router.isReady()]); 24 | 25 | app.mount("#app"); 26 | -------------------------------------------------------------------------------- /src/router/index.ts: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHashHistory } from "vue-router"; 2 | 3 | import { routes } from "./routes"; 4 | 5 | const router = createRouter({ 6 | history: createWebHashHistory(), 7 | routes, 8 | }); 9 | 10 | export { router }; 11 | -------------------------------------------------------------------------------- /src/router/routes.ts: -------------------------------------------------------------------------------- 1 | import type { RouteRecordRaw } from "vue-router"; 2 | import HomeView from "@/views/HomeView.vue"; 3 | import AboutView from "@/views/AboutView.vue"; 4 | import ContactsView from "@/views/ContactsView.vue"; 5 | 6 | const routes: RouteRecordRaw[] = [ 7 | { 8 | path: "/", 9 | name: "home", 10 | component: HomeView, 11 | }, 12 | { 13 | path: "/contacts", 14 | name: "contacts", 15 | component: ContactsView, 16 | }, 17 | { 18 | path: "/about", 19 | name: "about", 20 | component: AboutView, 21 | }, 22 | ]; 23 | 24 | export { routes }; 25 | -------------------------------------------------------------------------------- /src/services/api/http.ts: -------------------------------------------------------------------------------- 1 | // import axios from "axios"; 2 | // import type { AxiosRequestConfig } from "axios"; 3 | // import HttpRequest from "./xhr"; 4 | 5 | interface IOptions { 6 | baseUrl: string 7 | headers?: Record 8 | token?: Function 9 | logout?: Function 10 | } 11 | 12 | let options: IOptions = null; 13 | 14 | const http = { 15 | 16 | setOptions(_options) { 17 | options = _options; 18 | }, 19 | 20 | async post(data: any, uri: string) { 21 | if (options.token()) { 22 | options.headers.Authorization = `Bearer ${options.token()}`; 23 | } 24 | return postFetch(data, uri); 25 | // return postXhr(data, uri); 26 | // return postAxios(data, uri); 27 | }, 28 | 29 | async get(url) { 30 | try { 31 | return fetch(url) 32 | .then(response => response.json()); 33 | } catch (error) { 34 | console.log(error); 35 | } 36 | }, 37 | 38 | }; 39 | 40 | async function postFetch(data: any, uri: string) { 41 | const url = uri.startsWith("http") ? uri : `${options.baseUrl}${uri}`; 42 | return fetch(url, { 43 | method: "POST", 44 | headers: options.headers, 45 | // credentials: "include", 46 | body: JSON.stringify(data), 47 | }) 48 | .then(response => response.json()); 49 | } 50 | 51 | // Uncomment, if you are using `XMLHttpRequest` 52 | 53 | // async function postXhr(data: any, uri: string) { 54 | // const xhr = new HttpRequest("POST", `${options.baseUrl}${uri}`, "application/json", options.headers); 55 | // xhr.xhr.withCredentials = true; 56 | // const response = await xhr.send(data); 57 | // return response.json; 58 | // } 59 | 60 | // Uncomment, if you are using `axios` 61 | 62 | // async postAxios(data: any, uri: String) { 63 | // const config: AxiosRequestConfig = { 64 | // method: "POST", 65 | // url: `${options.baseUrl}${uri}`, 66 | // data, 67 | // withCredentials: true, 68 | // headers: options.headers, 69 | // }; 70 | // try { 71 | // return await axios.request(config); 72 | // } catch (error) { 73 | // console.log(error); 74 | // } 75 | // }, 76 | 77 | export default http; 78 | -------------------------------------------------------------------------------- /src/services/api/index.ts: -------------------------------------------------------------------------------- 1 | // import auth from "./auth"; 2 | import utils from "./utils"; 3 | import { authInterceptor, notificationInterceptor } from "./interceptors"; 4 | import jsonrpc from "./jsonrpc"; 5 | import http from "./http"; 6 | 7 | // import { useAuth } from "@/user-account/composables/useAuth"; 8 | 9 | // const auth: any = null; 10 | 11 | const api = { 12 | utils, 13 | http, 14 | init() { 15 | http.setOptions({ 16 | baseUrl: import.meta.env.VITE_API_URL, 17 | headers: { "Content-Type": "application/json" }, 18 | token: () => null, 19 | logout: () => null, 20 | }); 21 | jsonrpc.addResponseInterceptor(notificationInterceptor); 22 | // jsonrpc.addResponseInterceptor(authInterceptor); 23 | }, 24 | }; 25 | 26 | export { api }; 27 | export default api; 28 | -------------------------------------------------------------------------------- /src/services/api/interceptors.ts: -------------------------------------------------------------------------------- 1 | import type { JsonRpcResponseMessage } from "./jsonrpc"; 2 | 3 | // import { t } from "@/app/composables/useI18n"; 4 | // import { toast } from "@/app/utils/notification"; 5 | // import { useAuth } from "@/user-account/composables/useAuth.js"; 6 | // import { router } from "@/app/router"; 7 | 8 | export const notificationInterceptor = { 9 | process(data: JsonRpcResponseMessage) { 10 | // const message = data.error?.message || data.result?.message; 11 | if (data.result?.message) { 12 | // const msg = data.result.data?.i18n ? t(`messages.${data.result.data?.i18n}`) : data.result.message; 13 | const msg = data.result.message; 14 | // toast.info(msg); 15 | console.log("JSON-RPC message: ", msg); 16 | } 17 | if (data.error?.message) { 18 | // const msg = data.error.data?.i18n ? t(`errors.${data.error.data?.i18n}`) : data.error.message; 19 | const msg = data.error.message; 20 | // toast.warn(msg); 21 | console.log("JSON-RPC error: ", msg, data.error.data); 22 | } 23 | }, 24 | }; 25 | 26 | export const authInterceptor = { 27 | /** 28 | * Logout user if response returned error code 401 29 | * We use it in JSON-RPC instead of HTTP 401 response code 30 | */ 31 | process(data: JsonRpcResponseMessage) { 32 | if (data.error?.code === 401) { 33 | // useAuth().logout(); 34 | } 35 | }, 36 | }; 37 | -------------------------------------------------------------------------------- /src/services/api/jsonrpc.interfaces.ts: -------------------------------------------------------------------------------- 1 | interface JsonRpcMessage { 2 | jsonrpc: string 3 | } 4 | interface JsonRpcRequestMessage extends JsonRpcMessage { 5 | id?: number | string 6 | method: string 7 | // params?: object 8 | params?: Record 9 | } 10 | interface JsonRpcResponseMessage extends JsonRpcMessage { 11 | /** 12 | * The request id. 13 | */ 14 | id: number | string | null 15 | 16 | /** 17 | * The result of a request. This member is REQUIRED on success. 18 | * This member MUST NOT exist if there was an error invoking the method. 19 | */ 20 | result?: any 21 | // result?: string | number | boolean | object | null | any 22 | 23 | /** 24 | * The error object in case a request fails. 25 | */ 26 | error?: ResponseError 27 | } 28 | interface ResponseError { 29 | /** 30 | * A number indicating the error type that occurred. 31 | */ 32 | code: number 33 | 34 | /** 35 | * A string providing a short description of the error. 36 | */ 37 | message: string 38 | 39 | /** 40 | * A primitive or structured value that contains additional 41 | * information about the error. Can be omitted. 42 | */ 43 | data?: string | number | boolean | Array | object | null | any 44 | } 45 | 46 | interface JsonRpcPayload { 47 | id?: string | number 48 | method: string 49 | // params?: { origin: string }; 50 | params?: JsonRpcPayloadParams 51 | } 52 | 53 | interface JsonRpcPayloadParams { 54 | [key: string]: string | number | boolean | object 55 | } 56 | 57 | interface JsonRpcPayloadOptions { 58 | isNotification?: boolean 59 | uri?: string 60 | token?: string 61 | fullResponse?: boolean 62 | } 63 | 64 | export type { 65 | JsonRpcMessage, 66 | JsonRpcRequestMessage, 67 | JsonRpcResponseMessage, 68 | ResponseError, 69 | JsonRpcPayload, 70 | JsonRpcPayloadParams, 71 | JsonRpcPayloadOptions, 72 | }; 73 | 74 | // export ErrorCodes { 75 | // // Defined by JSON-RPC 76 | // export const ParseError: integer = -32700; 77 | // export const InvalidRequest: integer = -32600; 78 | // export const MethodNotFound: integer = -32601; 79 | // export const InvalidParams: integer = -32602; 80 | // export const InternalError: integer = -32603; 81 | 82 | // /** 83 | // * This is the start range of JSON-RPC reserved error codes. 84 | // * It doesn't denote a real error code. No LSP error codes should 85 | // * be defined between the start and end range. For backwards 86 | // * compatibility the `ServerNotInitialized` and the `UnknownErrorCode` 87 | // * are left in the range. 88 | // * 89 | // * @since 3.16.0 90 | // */ 91 | // export const jsonrpcReservedErrorRangeStart: integer = -32099; 92 | // /** @deprecated use jsonrpcReservedErrorRangeStart */ 93 | // export const serverErrorStart: integer = jsonrpcReservedErrorRangeStart; 94 | 95 | // /** 96 | // * Error code indicating that a server received a notification or 97 | // * request before the server has received the `initialize` request. 98 | // */ 99 | // export const ServerNotInitialized: integer = -32002; 100 | // export const UnknownErrorCode: integer = -32001; 101 | 102 | // /** 103 | // * This is the end range of JSON-RPC reserved error codes. 104 | // * It doesn't denote a real error code. 105 | // * 106 | // * @since 3.16.0 107 | // */ 108 | // export const jsonrpcReservedErrorRangeEnd = -32000; 109 | // /** @deprecated use jsonrpcReservedErrorRangeEnd */ 110 | // export const serverErrorEnd: integer = jsonrpcReservedErrorRangeEnd; 111 | 112 | // /** 113 | // * This is the start range of LSP reserved error codes. 114 | // * It doesn't denote a real error code. 115 | // * 116 | // * @since 3.16.0 117 | // */ 118 | // export const lspReservedErrorRangeStart: integer = -32899; 119 | 120 | // /** 121 | // * A request failed but it was syntactically correct, e.g the 122 | // * method name was known and the parameters were valid. The error 123 | // * message should contain human readable information about why 124 | // * the request failed. 125 | // * 126 | // * @since 3.17.0 127 | // */ 128 | // export const RequestFailed: integer = -32803; 129 | 130 | // /** 131 | // * The server cancelled the request. This error code should 132 | // * only be used for requests that explicitly support being 133 | // * server cancellable. 134 | // * 135 | // * @since 3.17.0 136 | // */ 137 | // export const ServerCancelled: integer = -32802; 138 | 139 | // /** 140 | // * The server detected that the content of a document got 141 | // * modified outside normal conditions. A server should 142 | // * NOT send this error code if it detects a content change 143 | // * in it unprocessed messages. The result even computed 144 | // * on an older state might still be useful for the client. 145 | // * 146 | // * If a client decides that a result is not of any use anymore 147 | // * the client should cancel the request. 148 | // */ 149 | // export const ContentModified: integer = -32801; 150 | 151 | // /** 152 | // * The client has canceled a request and a server as detected 153 | // * the cancel. 154 | // */ 155 | // export const RequestCancelled: integer = -32800; 156 | 157 | // /** 158 | // * This is the end range of LSP reserved error codes. 159 | // * It doesn't denote a real error code. 160 | // * 161 | // * @since 3.16.0 162 | // */ 163 | // export const lspReservedErrorRangeEnd: integer = -32800; 164 | // } 165 | -------------------------------------------------------------------------------- /src/services/api/jsonrpc.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | JsonRpcPayload, 3 | JsonRpcPayloadOptions, 4 | JsonRpcPayloadParams, 5 | JsonRpcRequestMessage, 6 | JsonRpcResponseMessage, 7 | } from "./jsonrpc.interfaces"; 8 | import http from "./http"; 9 | 10 | let jsonCounter = 0; 11 | let logout; 12 | let metaDataCallback = () => {}; 13 | 14 | const responseInterceptors = []; 15 | 16 | const jsonrpc = { 17 | 18 | addResponseInterceptor(interceptor) { 19 | responseInterceptors.push(interceptor); 20 | }, 21 | setLogoutCallback(_logout) { 22 | logout = _logout; 23 | }, 24 | setMetaDataCallback(_metaDataCallback) { 25 | metaDataCallback = _metaDataCallback; 26 | }, 27 | 28 | }; 29 | 30 | /** 31 | * Executes a JSON-RPC request and returns the response data. 32 | * 33 | * @param {JsonRpcPayload | Array} payload - The JSON-RPC payload(s) to execute. 34 | * @param {JsonRpcPayloadOptions} [options] - Options for the JSON-RPC request. 35 | * @return {Promise} A promise that resolves with the response data. 36 | */ 37 | async function jsonRpc( 38 | payload: JsonRpcPayload | Array, 39 | options?: JsonRpcPayloadOptions, 40 | ) { 41 | let data: JsonRpcRequestMessage | Array; 42 | // const auth = useAuth(); 43 | try { 44 | if (Array.isArray(payload)) { 45 | data = payload.map((message) => { 46 | return buildRequestMessage(message, options); 47 | }); 48 | } else { 49 | data = buildRequestMessage(payload, options); 50 | } 51 | 52 | const response = await http.post(data, buildUri(payload, options)); 53 | // const response = await axios.request(config); 54 | if (options?.fullResponse) { 55 | responseInterceptors.forEach((interceptor) => { 56 | interceptor.process(response); 57 | }); 58 | return response; 59 | } 60 | 61 | if (Array.isArray(response)) { 62 | response.forEach((msg: JsonRpcResponseMessage) => { 63 | responseInterceptors.forEach((interceptor) => { 64 | interceptor.process(msg); 65 | }); 66 | }); 67 | return response; 68 | } else { 69 | responseInterceptors.forEach((interceptor) => { 70 | interceptor.process(response); 71 | }); 72 | if (response.result) { 73 | return response.result; 74 | } 75 | } 76 | } catch (error) { 77 | console.log(error); 78 | if (error.response?.status === 401) { 79 | logout(); 80 | } 81 | } 82 | } 83 | /** 84 | * Builds a JSON-RPC request message from the given payload and options. 85 | * 86 | */ 87 | function buildRequestMessage( 88 | payload: JsonRpcPayload, 89 | options?: JsonRpcPayloadOptions, 90 | ): JsonRpcRequestMessage { 91 | const message: JsonRpcRequestMessage = { 92 | jsonrpc: "2.0", 93 | method: payload.method, 94 | params: payload.params || {}, 95 | }; 96 | 97 | if (!options?.isNotification) { 98 | message.id = payload.id ?? jsonCounter++; 99 | } 100 | message.params.meta = metaDataCallback(); 101 | // message.params.meta = { 102 | // ...metaData, 103 | // at: getToken(), 104 | // }; 105 | // Object.assign(message.params, { meta: {...metaData, { token: getToken() }}); 106 | return message; 107 | } 108 | 109 | /** 110 | * Returns the URI based on the given payload and options. 111 | * 112 | * @param {JsonRpcPayload | Array} payload - The payload or array of payloads. 113 | * @param {JsonRpcPayloadOptions} [options] - The options that may contain the URI. 114 | * @return {string} The URI for the given payload and options. 115 | */ 116 | function buildUri( 117 | payload: JsonRpcPayload | Array, 118 | options?: JsonRpcPayloadOptions, 119 | ) { 120 | if (options?.uri) { 121 | return options?.uri; 122 | } 123 | if (Array.isArray(payload)) { 124 | return ( 125 | `batch[${payload.map((p: JsonRpcPayload) => p.method).join("+")}]` 126 | ); 127 | } else { 128 | return payload.method.replace(".", "/"); 129 | } 130 | } 131 | 132 | // export function _jsonRpc( 133 | // payload: JsonRpcPayload | Array, 134 | // options?: JsonRpcPayloadOptions, 135 | // ) { 136 | // return jsonrpc.jsonRpc(payload, options); 137 | // } 138 | 139 | export { 140 | type JsonRpcPayloadOptions, 141 | type JsonRpcPayload, 142 | type JsonRpcPayloadParams, 143 | type JsonRpcRequestMessage, 144 | type JsonRpcResponseMessage, 145 | jsonRpc, 146 | }; 147 | 148 | export default jsonrpc; 149 | -------------------------------------------------------------------------------- /src/services/api/notes.txt: -------------------------------------------------------------------------------- 1 | import { jsonRpc } from "#/services/json-rpc/jsonrpc"; 2 | 3 | async providers() { 4 | const providers = await jsonRpc( 5 | { 6 | method: "utils.resources:list", 7 | params: { 8 | database: "slotegrator", 9 | resource: "provider", 10 | // where: "id < 3" 11 | }, 12 | }, 13 | { uri: "casino.providers:list" }, 14 | ); 15 | return providers.map((provider) => { 16 | return { 17 | ...provider, 18 | slug: provider.name.toLowerCase().replaceAll(" ", "-"), 19 | }; 20 | }); 21 | }, 22 | 23 | async updateFavourite(params) { 24 | return await jsonRpc({ 25 | method: "casino.favourites:update", 26 | params, 27 | }); 28 | }, 29 | -------------------------------------------------------------------------------- /src/services/api/utils.ts: -------------------------------------------------------------------------------- 1 | import http from "./http"; 2 | import { jsonRpc } from "./jsonrpc"; 3 | 4 | const utils = { 5 | 6 | async testRest() { 7 | return http.get("https://jsonplaceholder.typicode.com/todos/1"); 8 | }, 9 | 10 | async testJsonRpc() { 11 | return jsonRpc({ 12 | method: "getBestBlockHash", 13 | params: {}, 14 | }, { uri: "https://seed-1.testnet.networks.dash.org:1443/" }); 15 | }, 16 | 17 | }; 18 | 19 | export default utils; 20 | -------------------------------------------------------------------------------- /src/services/api/xhr.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | class HttpRequest { 4 | constructor(method, uri, contentType, headers) { 5 | this._fetchingPromise = null; 6 | this.xhr = new XMLHttpRequest(); 7 | if (!!method && !!uri) { 8 | this.xhr.open(method, uri); 9 | } 10 | if (contentType) { 11 | this.xhr.setRequestHeader("Content-Type", contentType); 12 | } 13 | // if (headers && headers.length) { 14 | if (headers) { 15 | Object.keys(headers).forEach((key) => { 16 | this.xhr.setRequestHeader(key, headers[key]); 17 | }); 18 | // headers.forEach((h) => { 19 | // this.xhr.setRequestHeader(h.header, h.value); 20 | // }); 21 | } 22 | } 23 | 24 | open(method, uri) { 25 | if (!!method && !!uri) { 26 | this.xhr.open(method, uri); 27 | } 28 | } 29 | 30 | setRequestHeader(header, value) { 31 | if (!!header && !!value) { 32 | this.xhr.setRequestHeader(header, value); 33 | } 34 | } 35 | 36 | getAllResponseHeaders() { 37 | return this.headers; 38 | } 39 | 40 | fetchFromServer(payload) { 41 | let ret = null; 42 | 43 | ret = new Promise((resolve, reject) => { 44 | this.xhr.onload = function () { 45 | if (this.readyState === this.HEADERS_RECEIVED) { 46 | this.headers = this.xhr.getAllResponseHeaders(); 47 | } 48 | 49 | if (this.xhr.status === 200) { 50 | const d = JSON.parse(this.xhr.responseText, (k, v) => { 51 | if (!!v && ((k === "createdon" || k === "updatedon" || k === "askedon" 52 | || k === "publishedon" || k === "lastUpdated") && Date.parse(v))) { 53 | return new Date(v); 54 | } 55 | if (!!v && (k === "json") && typeof v == "string") { 56 | return JSON.parse(v); 57 | } 58 | return v; 59 | }); 60 | resolve( 61 | { 62 | json: d, 63 | headers: this.headers, 64 | }, 65 | ); 66 | } else { 67 | reject(this.xhr.statusText || (`status ${this.xhr.status}`)); 68 | } 69 | this._fetchingPromise = null; 70 | }.bind(this); 71 | 72 | this.xhr.onerror = (e) => { 73 | reject(e.target.status); 74 | this._fetchingPromise = null; 75 | }; 76 | 77 | if (payload instanceof FormData) { 78 | this.xhr.send(payload); 79 | } else { 80 | this.xhr.send(JSON.stringify(payload)); 81 | } 82 | 83 | // this.xhr.onreadystatechange = function () { 84 | // if (this.readyState == this.HEADERS_RECEIVED) { 85 | // console.log("headers", request.getAllResponseHeaders()); 86 | // } 87 | // } 88 | }); 89 | 90 | return ret; 91 | } 92 | 93 | send(payload) { 94 | if (!this._fetchingPromise) { 95 | this._fetchingPromise = this.fetchFromServer(payload); 96 | } 97 | 98 | return this._fetchingPromise; 99 | } 100 | } 101 | 102 | export default HttpRequest; 103 | -------------------------------------------------------------------------------- /src/utils/icons.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | const svgResources = new Map(); 4 | const imageResources = new Map(); 5 | 6 | function loadIcons() { 7 | let modules = import.meta.glob("@/assets/images/**/*.svg", { 8 | query: "?raw", 9 | import: "default", 10 | eager: true, 11 | }); 12 | for (const fileName in modules) { 13 | const name = fileName.substring(fileName.lastIndexOf("/") + 1, fileName.length - 4); 14 | svgResources.set(name, modules[fileName]); 15 | } 16 | 17 | modules = import.meta.glob("@/assets/images/**/*.png", { 18 | query: "?url", 19 | import: "default", 20 | eager: true, 21 | }); 22 | // debugger; 23 | for (const fileName in modules) { 24 | const name = fileName.substring(fileName.lastIndexOf("/") + 1, fileName.length - 4); 25 | imageResources.set(name, modules[fileName]); 26 | } 27 | // console.log(imageResources); 28 | // console.log(getIcon("lobby")); 29 | } 30 | 31 | function getSvgIcon(name) { 32 | return svgResources.get(name); 33 | } 34 | 35 | function getImageUrl(name) { 36 | return imageResources.get(name); 37 | } 38 | 39 | // export svgResources; 40 | 41 | // export default iconMap; 42 | // export { iconMap, getIcon, svgResources }; 43 | export { loadIcons, getSvgIcon, getImageUrl, svgResources }; 44 | -------------------------------------------------------------------------------- /src/utils/injections/gtag.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | -------------------------------------------------------------------------------- /src/utils/injections/injection-config.ts: -------------------------------------------------------------------------------- 1 | import type { IHtmlInjectionConfig } from "vite-plugin-html-injection"; 2 | 3 | export const htmlInjectionConfig: IHtmlInjectionConfig = { 4 | injections: [ 5 | { 6 | name: "Open Graph", 7 | path: "./src/utils/injections/open-graph.html", 8 | type: "raw", 9 | injectTo: "head", 10 | }, 11 | // { 12 | // name: "Version checker", 13 | // path: "./src/utils/injections/version-checker.js", 14 | // type: "js", 15 | // injectTo: "head-prepend", 16 | // }, 17 | { 18 | name: "Splash screen", 19 | path: "./src/utils/injections/splash-screen.html", 20 | type: "raw", 21 | injectTo: "body-prepend", 22 | }, 23 | { 24 | name: "Service worker", 25 | path: "./src/utils/injections/sw.js", 26 | type: "js", 27 | injectTo: "head", 28 | }, 29 | { 30 | name: "Google analytics", 31 | path: "./src/utils/injections/gtag.html", 32 | type: "raw", 33 | injectTo: "body", 34 | }, 35 | ], 36 | }; 37 | -------------------------------------------------------------------------------- /src/utils/injections/open-graph.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/utils/injections/splash-screen.html: -------------------------------------------------------------------------------- 1 | 152 |
153 |
154 | 155 | 156 |
157 |
158 |
159 |
160 |
161 |
162 |
project-name
163 |
164 | -------------------------------------------------------------------------------- /src/utils/injections/sw.js: -------------------------------------------------------------------------------- 1 | if ("serviceWorker" in navigator) { 2 | navigator.serviceWorker.register("/service-worker.js"); 3 | } 4 | -------------------------------------------------------------------------------- /src/utils/locales/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "msg": "Hello" 3 | } 4 | -------------------------------------------------------------------------------- /src/utils/locales/es.json: -------------------------------------------------------------------------------- 1 | { 2 | "msg": "Hola" 3 | } 4 | -------------------------------------------------------------------------------- /src/views/AboutView.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /src/views/ContactsView.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 |