初次提交,原始状态
63
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
name: release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- release
|
||||
jobs:
|
||||
publish-tauri:
|
||||
permissions:
|
||||
contents: write
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- platform: macos-latest # Arm based macs
|
||||
args: --target aarch64-apple-darwin
|
||||
- platform: macos-latest # Intel based macs
|
||||
args: --target x86_64-apple-darwin
|
||||
- platform: ubuntu-22.04
|
||||
args: ""
|
||||
- platform: windows-latest
|
||||
args: ""
|
||||
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: setup node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 23.8.0
|
||||
|
||||
- name: install Rust stable
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
# Those targets are only used on macos runners so it's in an `if` to slightly speed up windows and linux builds.
|
||||
targets: ${{ matrix.platform == 'macos-latest' && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }}
|
||||
|
||||
- name: install dependencies (ubuntu only)
|
||||
if: matrix.platform == 'ubuntu-22.04'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libwebkit2gtk-4.0-dev libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
|
||||
# webkitgtk 4.0 is for Tauri v1 - webkitgtk 4.1 is for Tauri v2.
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10.13.1
|
||||
|
||||
- name: install frontend dependencies
|
||||
run: pnpm install
|
||||
|
||||
- uses: tauri-apps/tauri-action@v0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tagName: nuxtor-v__VERSION__
|
||||
releaseName: Nuxtor v__VERSION__
|
||||
releaseBody: After installing the Apple app you have to run "xattr -c /Applications/Nuxtor.app" once before launching
|
||||
releaseDraft: false
|
||||
prerelease: false
|
||||
args: ${{ matrix.args }}
|
12
.gitignore
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
node_modules
|
||||
.DS_Store
|
||||
*.log*
|
||||
.data
|
||||
.nuxt
|
||||
.nitro
|
||||
.cache
|
||||
.output
|
||||
.env
|
||||
.cargo
|
||||
dist
|
||||
src-tauri/target
|
2
.nuxtignore
Normal file
@@ -0,0 +1,2 @@
|
||||
./*
|
||||
!./app/**
|
9
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"dbaeumer.vscode-eslint",
|
||||
"antfu.file-nesting",
|
||||
"antfu.iconify",
|
||||
"vue.volar",
|
||||
"bradlc.vscode-tailwindcss"
|
||||
]
|
||||
}
|
49
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
{
|
||||
// Enable the ESlint flat config support
|
||||
"eslint.useFlatConfig": true,
|
||||
|
||||
// Use eslint as default formatter
|
||||
"editor.formatOnSave": false,
|
||||
|
||||
// Tailwind compatibility
|
||||
"tailwindCSS.validate": true,
|
||||
"tailwindCSS.experimental.classRegex": [
|
||||
[
|
||||
"ui:\\s*{([^)]*)\\s*}",
|
||||
"[\"'`]([^\"'`]*).*?[\"'`]"
|
||||
],
|
||||
[
|
||||
"/\\*\\s?ui\\s?\\*/\\s*{([^;]*)}",
|
||||
":\\s*[\"'`]([^\"'`]*).*?[\"'`]"
|
||||
]
|
||||
],
|
||||
"tailwindCSS.classAttributes": [
|
||||
"class",
|
||||
"ui"
|
||||
],
|
||||
"files.associations": {
|
||||
"*.css": "tailwindcss"
|
||||
},
|
||||
"editor.quickSuggestions": {
|
||||
"strings": "on"
|
||||
},
|
||||
|
||||
// Auto fix
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": "explicit",
|
||||
"source.organizeImports": "never"
|
||||
},
|
||||
|
||||
// Enable eslint for all supported languages
|
||||
"eslint.validate": [
|
||||
"javascript",
|
||||
"typescript",
|
||||
"vue",
|
||||
"html",
|
||||
"markdown",
|
||||
"json",
|
||||
"jsonc",
|
||||
"yaml",
|
||||
"toml"
|
||||
]
|
||||
}
|
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 Nicola Spadari<https://github.com/NicolaSpadari>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
97
README.md
Normal file
@@ -0,0 +1,97 @@
|
||||
<p align="center">
|
||||
<img width="150" src="./public/logo.png" alt="logo">
|
||||
</p>
|
||||
<h1 align="center">NUXTOR</h1>
|
||||
<p align="center">
|
||||
A spiritual successor of <a href="https://github.com/NicolaSpadari/vitauri">ViTauri</a>, made with <a href="https://nuxt.com">Nuxt 4</a> and <a href="https://v2.tauri.app">Tauri 2</a>
|
||||
<br>
|
||||
Build super fast desktop applications!
|
||||
</p>
|
||||
|
||||
<br />
|
||||
|
||||
<p float="left">
|
||||
<img src="https://img.shields.io/github/package-json/v/NicolaSpadari/nuxtor" />
|
||||
<img src="https://img.shields.io/github/license/NicolaSpadari/nuxtor" />
|
||||
</p>
|
||||
|
||||
<br />
|
||||
|
||||
<div align="center">
|
||||
<img src="./public/screenshot.png">
|
||||
</div>
|
||||
|
||||
<p align="center">Powered by Nuxt 4</p>
|
||||
|
||||
Check more screenshots at [preview](https://github.com/NicolaSpadari/nuxtor/blob/main/preview.md)
|
||||
|
||||
<br />
|
||||
|
||||
## Technologies run-down
|
||||
|
||||
- Nuxt v4
|
||||
- Tauri v2
|
||||
- NuxtUI v3
|
||||
- TailwindCSS v4
|
||||
- Typescript
|
||||
- ESLint
|
||||
- Auto imports (for Tauri api too!)
|
||||
|
||||
## Functionalities
|
||||
|
||||
- Run shell commands from the app
|
||||
- Send custom notifications to the client (remember to turn on/grant notifications in your computer settings)
|
||||
- Display OS related informations
|
||||
- Store and retrieve data locally
|
||||
- Show tray icon
|
||||
- Support all Nuxt functionalities (routing/layout/middleware/modules/etc...)
|
||||
|
||||
## Setup
|
||||
|
||||
- Before running this app, you need to configure your environment with Rust. Take a look at the [Tauri docs](https://v2.tauri.app/start/prerequisites).
|
||||
- This project enforces [pnpm](https://pnpm.io). In order to use another package manager you need to update `package.json` and `tauri.conf.json`
|
||||
- The frontend runs on the usual port `3000` of Nuxt, the Tauri server uses the port `3001`. This settings are customizable in the `nuxt.config.ts` and `tauri.conf.json`.
|
||||
- Once ready, follow these commands:
|
||||
|
||||
```sh
|
||||
# use this template
|
||||
$ npx degit NicolaSpadari/nuxtor my-nuxtor-app
|
||||
|
||||
# go into the folder
|
||||
$ cd my-nuxtor-app
|
||||
|
||||
# install dependencies
|
||||
$ pnpm install
|
||||
|
||||
# start the project
|
||||
$ pnpm run tauri:dev
|
||||
```
|
||||
|
||||
This will run the Nuxt frontend and will launch the Tauri window.
|
||||
|
||||
## Build
|
||||
|
||||
```sh
|
||||
$ pnpm run tauri:build
|
||||
```
|
||||
|
||||
This command will generate the Nuxt static output and bundle the project under `src-tauri/target`.
|
||||
|
||||
## Debug
|
||||
|
||||
```sh
|
||||
$ pnpm run tauri:build:debug
|
||||
```
|
||||
|
||||
The same Tauri bundle will generate under `src-tauri/target`, but with the ability to open the console.
|
||||
|
||||
## Notes
|
||||
|
||||
- Tauri v2 brings some big refactors, such as packages names and permission management. New permissions have to be granted under `src-tauri/capabilities/main.json`
|
||||
- Tauri functions are auto imported with the help of a custom module, named like `useTauri<LibraryName>`. If another Tauri plugin is added, then the module has to be updated to support its functions under `app/modules/tauri.ts`
|
||||
- As per [documentation](https://v2.tauri.app/start/frontend/nuxt/#checklist), Nuxt SSR must be disabled in order for Tauri to act as the backend. Still, all Nuxt goodies will be functional.
|
||||
- NuxtUI is a very powerful UI library that consolidates design over the entire application. While there is a more complete pro version, it requires a license. It's up to you to buy the pro version, or stick with the free version.
|
||||
|
||||
## License
|
||||
|
||||
MIT License © 2024-PRESENT [NicolaSpadari](https://github.com/NicolaSpadari)
|
73
app/app.config.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
export default defineAppConfig({
|
||||
app: {
|
||||
name: "Nuxtor",
|
||||
author: "Nicola Spadari",
|
||||
repo: "https://github.com/NicolaSpadari/nuxtor",
|
||||
tauriSite: "https://tauri.app",
|
||||
nuxtSite: "https://nuxt.com",
|
||||
nuxtUiSite: "https://ui.nuxt.dev"
|
||||
},
|
||||
pageCategories: {
|
||||
system: {
|
||||
label: "System",
|
||||
icon: "lucide:square-terminal"
|
||||
},
|
||||
storage: {
|
||||
label: "Storage",
|
||||
icon: "lucide:archive"
|
||||
},
|
||||
interface: {
|
||||
label: "Interface",
|
||||
icon: "lucide:app-window-mac"
|
||||
},
|
||||
other: {
|
||||
label: "Other",
|
||||
icon: "lucide:folder"
|
||||
}
|
||||
},
|
||||
ui: {
|
||||
colors: {
|
||||
primary: "green",
|
||||
neutral: "zinc"
|
||||
},
|
||||
button: {
|
||||
slots: {
|
||||
base: "cursor-pointer"
|
||||
}
|
||||
},
|
||||
formField: {
|
||||
slots: {
|
||||
root: "w-full"
|
||||
}
|
||||
},
|
||||
input: {
|
||||
slots: {
|
||||
root: "w-full"
|
||||
}
|
||||
},
|
||||
textarea: {
|
||||
slots: {
|
||||
root: "w-full",
|
||||
base: "resize-none"
|
||||
}
|
||||
},
|
||||
accordion: {
|
||||
slots: {
|
||||
trigger: "cursor-pointer",
|
||||
item: "md:py-2"
|
||||
}
|
||||
},
|
||||
navigationMenu: {
|
||||
slots: {
|
||||
link: "cursor-pointer"
|
||||
},
|
||||
variants: {
|
||||
disabled: {
|
||||
true: {
|
||||
link: "cursor-text"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
11
app/app.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<template>
|
||||
<Html class="overflow-x-hidden">
|
||||
<Body class="font-sans antialiased">
|
||||
<UApp>
|
||||
<NuxtLayout>
|
||||
<NuxtPage />
|
||||
</NuxtLayout>
|
||||
</UApp>
|
||||
</Body>
|
||||
</Html>
|
||||
</template>
|
49
app/assets/css/main.css
Normal file
@@ -0,0 +1,49 @@
|
||||
@import "tailwindcss";
|
||||
@import "@nuxt/ui";
|
||||
|
||||
@theme {
|
||||
--font-heading: "Montserrat", sans-serif;
|
||||
--font-sans: "Inter", sans-serif;
|
||||
|
||||
--color-primary: var(--ui-color-primary-500);
|
||||
--color-secondary: var(--ui-color-secondary-500);
|
||||
--color-success: var(--ui-color-success-500);
|
||||
--color-info: var(--ui-color-info-500);
|
||||
--color-warning: var(--ui-color-warning-500);
|
||||
--color-error: var(--ui-color-error-500)
|
||||
}
|
||||
|
||||
@layer base {
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
img{
|
||||
user-select: none;
|
||||
-webkit-user-drag: none;
|
||||
}
|
||||
.flex-center {
|
||||
@apply flex justify-center items-center;
|
||||
}
|
||||
.absolute-center-h {
|
||||
@apply left-1/2 transform -translate-x-1/2;
|
||||
}
|
||||
.absolute-center-v {
|
||||
@apply top-1/2 transform -translate-y-1/2;
|
||||
}
|
||||
}
|
||||
|
||||
.page-enter-active,
|
||||
.page-leave-active {
|
||||
@apply transition-opacity ease-in-out duration-300;
|
||||
}
|
||||
.layout-enter-active,
|
||||
.layout-leave-active {
|
||||
@apply transition-opacity ease-in-out duration-500;
|
||||
}
|
||||
.page-enter-from,
|
||||
.page-leave-to,
|
||||
.layout-enter-from,
|
||||
.layout-leave-to {
|
||||
@apply opacity-0;
|
||||
}
|
4
app/assets/icons/logo.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg fill="#fff" xmlns="http://www.w3.org/2000/svg" viewBox="0 -0.018652355298399925 149.6599884033203 150.74864196777344">
|
||||
<path d="M74.59 150.73H4.11c-4 0-4.08-.08-4.08-4.15Q0 88.14 0 29.69c0-2.5.7-4.09 2.9-5.32 8.55-4.8 17-9.71 25.51-14.61 1.69-1 2.53-.64 2.52 1.38v2.28c0 27.38 0 54.77-.05 82.15a5.59 5.59 0 0 0 3.24 5.6Q53.3 112 72.3 123.2a4.62 4.62 0 0 0 5.18 0Q95 113 112.51 102.84a.92.92 0 0 1 .21-.13c6.06-1.78 6.63-6.17 6.57-11.77-.28-25.77-.12-51.56-.11-77.34v-2.28c0-1.83.8-2.29 2.38-1.4 2.65 1.48 5.31 3 7.93 4.49 6.06 3.52 12.08 7.11 18.17 10.58 1.79 1 2 2.49 2 4.27v117.15c0 4-.28 4.3-4.34 4.3H74.59z" />
|
||||
<path d="M51.79 43.09V3.82C51.8.28 52 .07 55.5.05c2.87 0 5.74.09 8.61 0 2.23-.09 3 .84 2.93 3-.11 13 .56 26 .44 39-.15 16.21 0 32.43 0 48.64 0 3.66-.5 3.9-3.74 2-3.07-1.76-6.12-3.57-9.28-5.18a4.54 4.54 0 0 1-2.69-4.63c.08-13.26 0-26.52 0-39.77zM97.91 43.09v39.55a4.89 4.89 0 0 1-2.94 5c-3.22 1.66-6.25 3.69-9.42 5.45-2.51 1.39-3.41.87-3.33-2 .74-29.39.29-58.79.38-88.18 0-2.24.88-3 3-2.92 3 .11 6.08.16 9.12 0C97.31-.13 98 1 97.93 3.3c-.07 13.26 0 26.53 0 39.79z" />
|
||||
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
4
app/assets/logo.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -0.018652355298399925 149.6599884033203 150.74864196777344">
|
||||
<path d="M74.59 150.73H4.11c-4 0-4.08-.08-4.08-4.15Q0 88.14 0 29.69c0-2.5.7-4.09 2.9-5.32 8.55-4.8 17-9.71 25.51-14.61 1.69-1 2.53-.64 2.52 1.38v2.28c0 27.38 0 54.77-.05 82.15a5.59 5.59 0 0 0 3.24 5.6Q53.3 112 72.3 123.2a4.62 4.62 0 0 0 5.18 0Q95 113 112.51 102.84a.92.92 0 0 1 .21-.13c6.06-1.78 6.63-6.17 6.57-11.77-.28-25.77-.12-51.56-.11-77.34v-2.28c0-1.83.8-2.29 2.38-1.4 2.65 1.48 5.31 3 7.93 4.49 6.06 3.52 12.08 7.11 18.17 10.58 1.79 1 2 2.49 2 4.27v117.15c0 4-.28 4.3-4.34 4.3H74.59z" fill="#00DC82"></path>
|
||||
<path d="M51.79 43.09V3.82C51.8.28 52 .07 55.5.05c2.87 0 5.74.09 8.61 0 2.23-.09 3 .84 2.93 3-.11 13 .56 26 .44 39-.15 16.21 0 32.43 0 48.64 0 3.66-.5 3.9-3.74 2-3.07-1.76-6.12-3.57-9.28-5.18a4.54 4.54 0 0 1-2.69-4.63c.08-13.26 0-26.52 0-39.77zM97.91 43.09v39.55a4.89 4.89 0 0 1-2.94 5c-3.22 1.66-6.25 3.69-9.42 5.45-2.51 1.39-3.41.87-3.33-2 .74-29.39.29-58.79.38-88.18 0-2.24.88-3 3-2.92 3 .11 6.08.16 9.12 0C97.31-.13 98 1 97.93 3.3c-.07 13.26 0 26.53 0 39.79z" fill="#FFC131"></path>
|
||||
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
11
app/components/Design/BottomBlob.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<template>
|
||||
<div class="top=1/5 pointer-events-none absolute inset-x-0 transform-gpu blur-3xl -z-10" aria-hidden="true">
|
||||
<div class="blob relative left-[calc(50%+36rem)] aspect-[1155/678] w-[72.1875rem] from-(--color-warning) to-(--color-success) bg-gradient-to-br opacity-30 -translate-x-1/2" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.blob{
|
||||
clip-path: polygon(74.1% 44.1%, 100% 61.6%, 97.5% 26.9%, 85.5% 0.1%, 80.7% 2%, 72.5% 32.5%, 60.2% 62.4%, 52.4% 68.1%, 47.5% 58.3%, 45.2% 34.5%, 27.5% 76.7%, 0.1% 64.9%, 17.9% 100%, 27.6% 76.8%, 76.1% 97.7%, 74.1% 44.1%);
|
||||
}
|
||||
</style>
|
11
app/components/Design/TopBlob.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<template>
|
||||
<div class="pointer-events-none absolute inset-x-0 transform-gpu blur-3xl -top-1/4 -z-10" aria-hidden="true">
|
||||
<div class="blob relative left-[calc(50%-30rem)] aspect-[1155/678] w-[72.1875rem] rotate-30 from-(--color-warning) to-(--color-success) bg-gradient-to-tr opacity-30 -translate-x-1/2" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.blob{
|
||||
clip-path: polygon(74.1% 44.1%, 100% 61.6%, 97.5% 26.9%, 85.5% 0.1%, 80.7% 2%, 72.5% 32.5%, 60.2% 62.4%, 52.4% 68.1%, 47.5% 58.3%, 45.2% 34.5%, 27.5% 76.7%, 0.1% 64.9%, 17.9% 100%, 27.6% 76.8%, 76.1% 97.7%, 74.1% 44.1%);
|
||||
}
|
||||
</style>
|
24
app/components/Layout/Tile.vue
Normal file
@@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2">
|
||||
<div class="px-6 pb-10 pt-12 sm:pt-16 lg:px-8 lg:pt-22">
|
||||
<div class="mx-auto max-w-xl lg:mx-0 lg:max-w-lg">
|
||||
<h2 class="text-3xl font-bold tracking-tight">
|
||||
{{ props.title }}
|
||||
</h2>
|
||||
<p class="mt-6 text-lg text-(--ui-text-muted) leading-8">
|
||||
{{ props.description }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-6 pb-10 pt-12 sm:pt-16 lg:px-8 lg:pt-22">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const props = defineProps<{
|
||||
title: string
|
||||
description: string
|
||||
}>();
|
||||
</script>
|
72
app/components/Site/Navbar.vue
Normal file
@@ -0,0 +1,72 @@
|
||||
<template>
|
||||
<header class="top-0 z-10">
|
||||
<UContainer class="md:py-2">
|
||||
<UNavigationMenu
|
||||
:items="mobileItems"
|
||||
variant="link"
|
||||
:ui="{
|
||||
root: 'md:hidden'
|
||||
}"
|
||||
/>
|
||||
<UNavigationMenu
|
||||
:items="desktopItems"
|
||||
variant="link"
|
||||
:ui="{
|
||||
root: 'hidden md:flex',
|
||||
viewportWrapper: 'max-w-2xl absolute-center-h',
|
||||
list: 'md:gap-x-2'
|
||||
}"
|
||||
/>
|
||||
</UContainer>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const { pages } = usePages();
|
||||
const { showSidebar } = useSidebar();
|
||||
const tauriVersion = await useTauriAppGetTauriVersion();
|
||||
|
||||
const mobileItems = ref<any[]>([
|
||||
[
|
||||
{
|
||||
avatar: {
|
||||
icon: "local:logo",
|
||||
size: "xl",
|
||||
ui: {
|
||||
root: "bg-transparent"
|
||||
}
|
||||
},
|
||||
to: "/"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
icon: "lucide:menu",
|
||||
onSelect: () => showSidebar.value = true
|
||||
}
|
||||
]
|
||||
]);
|
||||
|
||||
const desktopItems = ref<any[]>([
|
||||
[
|
||||
{
|
||||
avatar: {
|
||||
icon: "local:logo",
|
||||
size: "3xl",
|
||||
ui: {
|
||||
root: "group bg-transparent",
|
||||
icon: "opacity-70 group-hover:opacity-100"
|
||||
}
|
||||
},
|
||||
to: "/"
|
||||
}
|
||||
],
|
||||
pages,
|
||||
[
|
||||
{
|
||||
label: `v${tauriVersion}`,
|
||||
disabled: true
|
||||
}
|
||||
]
|
||||
]);
|
||||
</script>
|
37
app/components/Site/Sidebar.vue
Normal file
@@ -0,0 +1,37 @@
|
||||
<template>
|
||||
<USlideover :open="showSidebar" @update:open="showSidebar = false">
|
||||
<template #title>
|
||||
<div class="flex gap-x-3">
|
||||
<Icon name="local:logo" class="size-6" />
|
||||
<span class="uppercase">{{ name }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #description>
|
||||
<VisuallyHidden>Description</VisuallyHidden>
|
||||
</template>
|
||||
|
||||
<template #body>
|
||||
<UNavigationMenu
|
||||
orientation="vertical"
|
||||
:items="items"
|
||||
/>
|
||||
</template>
|
||||
</USlideover>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const { app: { name } } = useAppConfig();
|
||||
const { pages } = usePages();
|
||||
const { showSidebar } = useSidebar();
|
||||
const tauriVersion = await useTauriAppGetTauriVersion();
|
||||
|
||||
const items = ref<any[]>([
|
||||
pages,
|
||||
[
|
||||
{
|
||||
label: `v${tauriVersion}`,
|
||||
disabled: true
|
||||
}
|
||||
]
|
||||
]);
|
||||
</script>
|
35
app/composables/pages.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
export const usePages = () => {
|
||||
const router = useRouter();
|
||||
const { pageCategories } = useAppConfig();
|
||||
|
||||
const routes = router.getRoutes().filter((route) => route.name !== "index" && route.name !== "all");
|
||||
|
||||
const categorizedRoutes = routes.reduce((acc, route) => {
|
||||
const category = route.meta.category as string || "other";
|
||||
if (!category) return acc;
|
||||
|
||||
if (!acc[category]) {
|
||||
acc[category] = {
|
||||
label: pageCategories[category as keyof typeof pageCategories]?.label,
|
||||
icon: pageCategories[category as keyof typeof pageCategories]?.icon || "i-lucide-folder",
|
||||
to: route.path,
|
||||
children: []
|
||||
};
|
||||
}
|
||||
|
||||
acc[category].children.push({
|
||||
label: route.meta.name as string || route.name,
|
||||
description: route.meta.description as string,
|
||||
icon: route.meta.icon || "i-lucide-file",
|
||||
to: route.path
|
||||
});
|
||||
|
||||
return acc;
|
||||
}, {} as Record<string, any>);
|
||||
|
||||
const pages = Object.values(categorizedRoutes);
|
||||
|
||||
return {
|
||||
pages
|
||||
};
|
||||
};
|
7
app/composables/sidebar.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export const useSidebar = () => {
|
||||
const showSidebar = useState("showSidebar", () => false);
|
||||
|
||||
return {
|
||||
showSidebar
|
||||
};
|
||||
};
|
8
app/layouts/blank.vue
Normal file
@@ -0,0 +1,8 @@
|
||||
<template>
|
||||
<div>
|
||||
<SiteNavbar class="sticky bg-(--ui-bg)/75 backdrop-blur" />
|
||||
<SiteSidebar />
|
||||
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
10
app/layouts/default.vue
Normal file
@@ -0,0 +1,10 @@
|
||||
<template>
|
||||
<div>
|
||||
<SiteNavbar class="sticky bg-(--ui-bg)/75 backdrop-blur" />
|
||||
<SiteSidebar />
|
||||
|
||||
<UContainer>
|
||||
<slot />
|
||||
</UContainer>
|
||||
</div>
|
||||
</template>
|
13
app/layouts/home.vue
Normal file
@@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<div>
|
||||
<SiteNavbar class="fixed w-full" />
|
||||
<SiteSidebar />
|
||||
|
||||
<div class="relative overflow-hidden px-6 lg:px-8">
|
||||
<DesignTopBlob />
|
||||
<DesignBottomBlob />
|
||||
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
4
app/middleware/sidebar.global.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export default defineNuxtRouteMiddleware(() => {
|
||||
const { showSidebar } = useSidebar();
|
||||
showSidebar.value = false;
|
||||
});
|
41
app/modules/tauri.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import * as tauriApp from "@tauri-apps/api/app";
|
||||
import * as tauriWebviewWindow from "@tauri-apps/api/webviewWindow";
|
||||
import * as tauriFs from "@tauri-apps/plugin-fs";
|
||||
import * as tauriNotification from "@tauri-apps/plugin-notification";
|
||||
import * as tauriOs from "@tauri-apps/plugin-os";
|
||||
import * as tauriShell from "@tauri-apps/plugin-shell";
|
||||
import * as tauriStore from "@tauri-apps/plugin-store";
|
||||
import { addImports, defineNuxtModule } from "nuxt/kit";
|
||||
|
||||
const capitalize = (name: string) => {
|
||||
return name.charAt(0).toUpperCase() + name.slice(1);
|
||||
};
|
||||
|
||||
const tauriModules = [
|
||||
{ module: tauriApp, prefix: "App", importPath: "@tauri-apps/api/app" },
|
||||
{ module: tauriWebviewWindow, prefix: "WebviewWindow", importPath: "@tauri-apps/api/webviewWindow" },
|
||||
{ module: tauriShell, prefix: "Shell", importPath: "@tauri-apps/plugin-shell" },
|
||||
{ module: tauriOs, prefix: "Os", importPath: "@tauri-apps/plugin-os" },
|
||||
{ module: tauriNotification, prefix: "Notification", importPath: "@tauri-apps/plugin-notification" },
|
||||
{ module: tauriFs, prefix: "Fs", importPath: "@tauri-apps/plugin-fs" },
|
||||
{ module: tauriStore, prefix: "Store", importPath: "@tauri-apps/plugin-store" }
|
||||
];
|
||||
|
||||
export default defineNuxtModule<ModuleOptions>({
|
||||
meta: {
|
||||
name: "nuxt-tauri",
|
||||
configKey: "tauri"
|
||||
},
|
||||
defaults: {
|
||||
prefix: "useTauri"
|
||||
},
|
||||
setup(options) {
|
||||
tauriModules.forEach(({ module, prefix, importPath }) => {
|
||||
Object.keys(module).filter((name) => name !== "default").forEach((name) => {
|
||||
const prefixedName = `${options.prefix}${prefix}` || "";
|
||||
const as = prefixedName ? prefixedName + capitalize(name) : name;
|
||||
addImports({ from: importPath, name, as });
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
26
app/pages/[...all].vue
Normal file
@@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<div class="grid place-items-center py-12 md:py-24">
|
||||
<div class="flex flex-col items-center gap-y-4 md:gap-y-8">
|
||||
<p class="text-(--ui-success) font-semibold">
|
||||
404
|
||||
</p>
|
||||
<div class="text-center space-y-3">
|
||||
<h1 class="text-3xl font-bold tracking-tight" sm="text-5xl">
|
||||
Page not found
|
||||
</h1>
|
||||
<p class="text-base text-(--ui-muted) leading-7">
|
||||
Sorry, we couldn't find the page you're looking for.
|
||||
</p>
|
||||
</div>
|
||||
<UButton to="/" variant="outline" size="lg" :ui="{ base: 'px-5' }">
|
||||
Go home
|
||||
</UButton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
definePageMeta({
|
||||
layout: "blank"
|
||||
});
|
||||
</script>
|
63
app/pages/commands.vue
Normal file
@@ -0,0 +1,63 @@
|
||||
<template>
|
||||
<LayoutTile
|
||||
title="Commands"
|
||||
description="Access the system shell. Allows you to spawn child processes and manage files and URLs using their default application."
|
||||
>
|
||||
<div class="space-y-6 md:space-y-8">
|
||||
<UForm :state="inputState" :schema="schema" class="flex flex-col gap-y-4 items-end" @submit="sendCommand">
|
||||
<UFormField label="Command input" name="input">
|
||||
<UInput v-model="inputState.input" variant="subtle" size="lg" />
|
||||
</UFormField>
|
||||
|
||||
<UButton type="submit" size="lg">
|
||||
Send command
|
||||
</UButton>
|
||||
</UForm>
|
||||
|
||||
<UForm :state="outputState" class="flex flex-col gap-y-4 items-end">
|
||||
<UFormField label="Command output" name="command-output">
|
||||
<UTextarea v-model="outputState.output" variant="subtle" size="lg" :rows="8" readonly />
|
||||
</UFormField>
|
||||
</UForm>
|
||||
</div>
|
||||
</LayoutTile>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
definePageMeta({
|
||||
name: "Shell commands",
|
||||
icon: "lucide:terminal",
|
||||
description: "Execute shell commands",
|
||||
category: "system"
|
||||
});
|
||||
|
||||
const schema = z.object({
|
||||
input: z.string({
|
||||
error: "Input is required"
|
||||
}).nonempty()
|
||||
});
|
||||
|
||||
type Schema = zInfer<typeof schema>;
|
||||
|
||||
const inputState = ref<Partial<Schema>>({
|
||||
input: undefined
|
||||
});
|
||||
const outputState = ref({
|
||||
output: ""
|
||||
});
|
||||
|
||||
const sendCommand = async () => {
|
||||
try {
|
||||
const response = await useTauriShellCommand.create("exec-sh", [
|
||||
"-c",
|
||||
inputState.value.input!
|
||||
]).execute();
|
||||
|
||||
outputState.value.output = JSON.stringify(response, null, 4);
|
||||
} catch (error) {
|
||||
outputState.value.output = JSON.stringify(error, null, 4);
|
||||
} finally {
|
||||
inputState.value.input = undefined;
|
||||
}
|
||||
};
|
||||
</script>
|
83
app/pages/file.vue
Normal file
@@ -0,0 +1,83 @@
|
||||
<template>
|
||||
<LayoutTile
|
||||
title="File System"
|
||||
description="Access the file system. For this demo the only allowed permission is read/write to the Documents folder (no sub directories)."
|
||||
>
|
||||
<UForm :state="inputState" :schema="schema" class="flex flex-col gap-y-4 items-end" @submit="createFile">
|
||||
<UFormField label="Text file name (with extension)" name="fileName">
|
||||
<UInput v-model="inputState.fileName" variant="subtle" size="lg" />
|
||||
</UFormField>
|
||||
|
||||
<UFormField label="File content" name="fileContent">
|
||||
<UTextarea v-model="inputState.fileContent" variant="subtle" size="lg" :rows="10" />
|
||||
</UFormField>
|
||||
|
||||
<UButton type="submit" size="lg">
|
||||
Create file
|
||||
</UButton>
|
||||
</UForm>
|
||||
</LayoutTile>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
definePageMeta({
|
||||
name: "Files",
|
||||
icon: "lucide:file-text",
|
||||
category: "storage",
|
||||
description: "Create and manage files"
|
||||
});
|
||||
|
||||
const schema = z.object({
|
||||
fileName: z.string({
|
||||
error: "File name is required"
|
||||
}).nonempty().regex(/^[\w,\s-]+\.[A-Z0-9]+$/i, {
|
||||
message: "Invalid filename format"
|
||||
}),
|
||||
fileContent: z.string({
|
||||
error: "File content is required"
|
||||
}).nonempty()
|
||||
});
|
||||
|
||||
type Schema = zInfer<typeof schema>;
|
||||
|
||||
const inputState = ref<Partial<Schema>>({
|
||||
fileName: undefined,
|
||||
fileContent: undefined
|
||||
});
|
||||
|
||||
const toast = useToast();
|
||||
|
||||
const createFile = async () => {
|
||||
try {
|
||||
const fileExists = await useTauriFsExists(inputState.value.fileName!, {
|
||||
baseDir: useTauriFsBaseDirectory.Document
|
||||
});
|
||||
|
||||
if (fileExists) {
|
||||
toast.add({
|
||||
title: "Error",
|
||||
description: "The file already exists",
|
||||
color: "error"
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await useTauriFsWriteTextFile(inputState.value.fileName!, inputState.value.fileContent!, {
|
||||
baseDir: useTauriFsBaseDirectory.Document
|
||||
});
|
||||
toast.add({
|
||||
title: "Success",
|
||||
description: "The file has been created",
|
||||
color: "success"
|
||||
});
|
||||
inputState.value.fileName = inputState.value.fileContent = undefined;
|
||||
} catch (err) {
|
||||
toast.add({
|
||||
title: "Error",
|
||||
description: String(err),
|
||||
color: "error"
|
||||
});
|
||||
}
|
||||
};
|
||||
</script>
|
73
app/pages/index.vue
Normal file
@@ -0,0 +1,73 @@
|
||||
<template>
|
||||
<UContainer class="relative overflow-hidden h-screen">
|
||||
<div class="grid size-full place-content-center gap-y-8">
|
||||
<SvgoLogo :filled="true" :font-controlled="false" class="mx-auto size-40" />
|
||||
|
||||
<div class="flex flex-col items-center gap-y-3">
|
||||
<h1 class="animate-pulse text-3xl sm:text-4xl text-pretty font-bold font-heading md:mb-5">
|
||||
{{ app.name.toUpperCase() }}
|
||||
</h1>
|
||||
<p class="leading-7 text-pretty">
|
||||
Powered by
|
||||
</p>
|
||||
|
||||
<div class="flex flex-wrap justify-center gap-1 md:gap-3">
|
||||
<UButton
|
||||
variant="ghost"
|
||||
size="xl"
|
||||
:to="app.nuxtSite"
|
||||
target="_blank"
|
||||
external
|
||||
>
|
||||
Nuxt 4
|
||||
</UButton>
|
||||
<UButton
|
||||
variant="ghost"
|
||||
size="xl"
|
||||
:to="app.tauriSite"
|
||||
target="_blank"
|
||||
external
|
||||
>
|
||||
Tauri 2
|
||||
</UButton>
|
||||
<UButton
|
||||
variant="ghost"
|
||||
size="xl"
|
||||
:to="app.nuxtUiSite"
|
||||
target="_blank"
|
||||
external
|
||||
>
|
||||
NuxtUI 3
|
||||
</UButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-center">
|
||||
<UButton
|
||||
:to="app.repo"
|
||||
>
|
||||
Star on GitHub
|
||||
</UButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="fixed bottom-6 text-sm absolute-center-h">
|
||||
<div class="flex items-center gap-1 text-(--ui-text-muted)">
|
||||
<p class="text-sm">
|
||||
Made by
|
||||
</p>
|
||||
<ULink :to="app.repo" external target="_blank">
|
||||
{{ app.author }}
|
||||
</ULink>
|
||||
</div>
|
||||
</div>
|
||||
</UContainer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const { app } = useAppConfig();
|
||||
|
||||
definePageMeta({
|
||||
layout: "home"
|
||||
});
|
||||
</script>
|
70
app/pages/notifications.vue
Normal file
@@ -0,0 +1,70 @@
|
||||
<template>
|
||||
<LayoutTile
|
||||
title="Notifications"
|
||||
description="Send native notifications to the client using the notification plugin."
|
||||
>
|
||||
<UForm :state="inputState" :schema="schema" class="flex flex-col gap-y-4 items-end" @submit="createNotification">
|
||||
<UFormField label="Notification title" name="notificationTitle">
|
||||
<UInput v-model="inputState.notificationTitle" variant="subtle" size="lg" />
|
||||
</UFormField>
|
||||
|
||||
<UFormField label="Notification body (optional)" name="notificationBody">
|
||||
<UInput v-model="inputState.notificationBody" variant="subtle" size="lg" />
|
||||
</UFormField>
|
||||
|
||||
<UButton type="submit" size="lg">
|
||||
Send notification
|
||||
</UButton>
|
||||
</UForm>
|
||||
</LayoutTile>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
definePageMeta({
|
||||
name: "Notifications",
|
||||
icon: "lucide:message-square-more",
|
||||
category: "interface",
|
||||
description: "Send native notifications"
|
||||
});
|
||||
|
||||
const schema = z.object({
|
||||
notificationTitle: z.string({
|
||||
error: "Title is required"
|
||||
}).nonempty(),
|
||||
notificationBody: z.string().optional()
|
||||
});
|
||||
|
||||
type Schema = zInfer<typeof schema>;
|
||||
|
||||
const inputState = ref<Partial<Schema>>({
|
||||
notificationTitle: undefined,
|
||||
notificationBody: undefined
|
||||
});
|
||||
|
||||
const toast = useToast();
|
||||
const permissionGranted = ref(false);
|
||||
|
||||
const createNotification = async () => {
|
||||
permissionGranted.value = await useTauriNotificationIsPermissionGranted();
|
||||
|
||||
if (!permissionGranted.value) {
|
||||
const permission = await useTauriNotificationRequestPermission();
|
||||
permissionGranted.value = permission === "granted";
|
||||
}
|
||||
|
||||
if (permissionGranted.value) {
|
||||
useTauriNotificationSendNotification({
|
||||
title: inputState.value.notificationTitle!,
|
||||
body: inputState.value.notificationBody
|
||||
});
|
||||
|
||||
inputState.value.notificationTitle = inputState.value.notificationBody = undefined;
|
||||
} else {
|
||||
toast.add({
|
||||
title: "Error",
|
||||
description: "Missing notifications permission",
|
||||
color: "error"
|
||||
});
|
||||
}
|
||||
};
|
||||
</script>
|
35
app/pages/os.vue
Normal file
@@ -0,0 +1,35 @@
|
||||
<template>
|
||||
<LayoutTile
|
||||
title="OS Information"
|
||||
description="Read information about the operating system using the OS Information plugin."
|
||||
>
|
||||
<UAccordion :items="items" type="multiple" />
|
||||
</LayoutTile>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
definePageMeta({
|
||||
name: "OS Informations",
|
||||
icon: "lucide:info",
|
||||
category: "system",
|
||||
description: "Read operating system informations."
|
||||
});
|
||||
|
||||
const items = ref([
|
||||
{
|
||||
label: "System",
|
||||
icon: "lucide:monitor",
|
||||
content: `${useTauriOsPlatform()} ${useTauriOsVersion()}`
|
||||
},
|
||||
{
|
||||
label: "Arch",
|
||||
icon: "lucide:microchip",
|
||||
content: useTauriOsArch()
|
||||
},
|
||||
{
|
||||
label: "Locale",
|
||||
icon: "lucide:globe",
|
||||
content: await useTauriOsLocale() || "Not detectable"
|
||||
}
|
||||
]);
|
||||
</script>
|
91
app/pages/store.vue
Normal file
@@ -0,0 +1,91 @@
|
||||
<template>
|
||||
<LayoutTile
|
||||
title="Store"
|
||||
description="Persistent key-value store. Allows you to handle state to a file which can be saved and loaded on demand including between app restarts."
|
||||
>
|
||||
<div class="space-y-6 md:space-y-8">
|
||||
<UForm :state="inputState" :schema="schema" class="flex flex-col gap-y-4 items-end" @submit="setStoreValue">
|
||||
<UFormField label="Store value" name="value">
|
||||
<UInput v-model="inputState.value" variant="subtle" size="lg" />
|
||||
</UFormField>
|
||||
|
||||
<UButton type="submit" size="lg">
|
||||
Set value
|
||||
</UButton>
|
||||
</UForm>
|
||||
|
||||
<UForm :state="outputState" class="flex flex-col gap-y-4 items-end">
|
||||
<UFormField label="Store content" name="content">
|
||||
<UTextarea v-model="outputState.content" variant="subtle" size="lg" :rows="8" readonly />
|
||||
</UFormField>
|
||||
</UForm>
|
||||
</div>
|
||||
</LayoutTile>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
definePageMeta({
|
||||
name: "Store",
|
||||
icon: "lucide:database",
|
||||
category: "storage",
|
||||
description: "Handle file creation in the file system"
|
||||
});
|
||||
|
||||
const schema = z.object({
|
||||
value: z.string({
|
||||
error: "Store key is required"
|
||||
}).nonempty()
|
||||
});
|
||||
|
||||
type Schema = zInfer<typeof schema>;
|
||||
|
||||
const inputState = ref<Partial<Schema>>({
|
||||
value: undefined
|
||||
});
|
||||
const outputState = ref({
|
||||
content: ""
|
||||
});
|
||||
|
||||
const toast = useToast();
|
||||
const autosave = ref(false);
|
||||
|
||||
const store = await useTauriStoreLoad("store.bin", {
|
||||
autoSave: autosave.value
|
||||
});
|
||||
|
||||
const getStoreValue = async () => {
|
||||
try {
|
||||
outputState.value.content = await store.get<string>("myData") || "";
|
||||
} catch (error) {
|
||||
toast.add({
|
||||
title: "Error",
|
||||
description: String(error),
|
||||
color: "error"
|
||||
});
|
||||
outputState.value.content = JSON.stringify(error, null, 4);
|
||||
}
|
||||
};
|
||||
|
||||
await getStoreValue();
|
||||
|
||||
const setStoreValue = async () => {
|
||||
try {
|
||||
await store.set("myData", inputState.value!.value);
|
||||
await getStoreValue();
|
||||
toast.add({
|
||||
title: "Success",
|
||||
description: "Store value retieved",
|
||||
color: "success"
|
||||
});
|
||||
} catch (error) {
|
||||
toast.add({
|
||||
title: "Error",
|
||||
description: String(error),
|
||||
color: "error"
|
||||
});
|
||||
outputState.value.content = JSON.stringify(error, null, 4);
|
||||
} finally {
|
||||
inputState.value.value = undefined;
|
||||
}
|
||||
};
|
||||
</script>
|
51
app/pages/webview.vue
Normal file
@@ -0,0 +1,51 @@
|
||||
<template>
|
||||
<LayoutTile
|
||||
title="Webview window"
|
||||
description="Create new webview in a detached window. This will create a new window flagged 'secondary' that has the same permissions as the main one. If you need more windows, update the permissions under capabilities > main or create a new capabilities file for the new window only."
|
||||
>
|
||||
<div class="flex flex-col items-center gap-6">
|
||||
<UButton variant="subtle" @click="openWindow((new Date).valueOf().toString(), app.repo)">
|
||||
Create external Webview
|
||||
</UButton>
|
||||
<UButton variant="subtle" @click="openWindow('secondary', '/os')">
|
||||
Create internal Webview
|
||||
</UButton>
|
||||
</div>
|
||||
</LayoutTile>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
definePageMeta({
|
||||
name: "Webview",
|
||||
icon: "lucide:app-window",
|
||||
category: "interface",
|
||||
description: "Create new webview in a detached window"
|
||||
});
|
||||
|
||||
const { app } = useAppConfig();
|
||||
const toast = useToast();
|
||||
|
||||
const openWindow = async (id: string, page: string) => {
|
||||
const webview = new useTauriWebviewWindowWebviewWindow(id, {
|
||||
title: "Nuxtor webview",
|
||||
url: page,
|
||||
width: 1280,
|
||||
height: 720
|
||||
});
|
||||
|
||||
webview.once("tauri://created", () => {
|
||||
toast.add({
|
||||
title: "Success",
|
||||
description: "Webview created",
|
||||
color: "success"
|
||||
});
|
||||
});
|
||||
webview.once("tauri://error", (error) => {
|
||||
toast.add({
|
||||
title: "Error",
|
||||
description: (error as any).payload,
|
||||
color: "error"
|
||||
});
|
||||
});
|
||||
};
|
||||
</script>
|
22
app/router.options.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import type { RouterOptions } from "@nuxt/schema";
|
||||
|
||||
export default {
|
||||
scrollBehavior(to, _from, savedPosition) {
|
||||
return new Promise((resolve, _reject) => {
|
||||
setTimeout(() => {
|
||||
if (savedPosition) {
|
||||
resolve(savedPosition);
|
||||
} else {
|
||||
if (to.hash) {
|
||||
resolve({
|
||||
el: to.hash,
|
||||
top: 0
|
||||
});
|
||||
} else {
|
||||
resolve({ top: 0 });
|
||||
}
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
}
|
||||
} satisfies RouterOptions;
|
13
bump.config.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { defineConfig } from "bumpp";
|
||||
|
||||
export default defineConfig({
|
||||
release: "prompt",
|
||||
commit: false,
|
||||
tag: false,
|
||||
push: false,
|
||||
files: [
|
||||
"package.json",
|
||||
"src-tauri/tauri.conf.json",
|
||||
"src-tauri/Cargo.toml"
|
||||
]
|
||||
});
|
46
eslint.config.mjs
Normal file
@@ -0,0 +1,46 @@
|
||||
import eslintConfig from "@antfu/eslint-config";
|
||||
import nuxtConfig from "./.nuxt/eslint.config.mjs";
|
||||
|
||||
export default eslintConfig(
|
||||
// General
|
||||
{
|
||||
typescript: true,
|
||||
vue: true,
|
||||
stylistic: {
|
||||
indent: "tab",
|
||||
quotes: "double"
|
||||
},
|
||||
rules: {
|
||||
curly: "off",
|
||||
"no-console": "off",
|
||||
"no-new-func": "off",
|
||||
"style/semi": ["error", "always"],
|
||||
"style/indent": ["error", "tab"],
|
||||
"style/quote-props": ["warn", "as-needed"],
|
||||
"style/comma-dangle": ["warn", "never"],
|
||||
"style/brace-style": ["warn", "1tbs"],
|
||||
"style/arrow-parens": ["error", "always"],
|
||||
"vue/block-order": ["error", {
|
||||
order: ["template", "script", "style"]
|
||||
}],
|
||||
"vue/script-indent": ["error", "tab", {
|
||||
baseIndent: 1
|
||||
}],
|
||||
"vue/comma-dangle": ["warn", "never"],
|
||||
"antfu/top-level-function": "off",
|
||||
"antfu/if-newline": "off",
|
||||
"new-cap": "off",
|
||||
"node/prefer-global/process": ["off"]
|
||||
}
|
||||
},
|
||||
|
||||
// Vue
|
||||
{
|
||||
files: ["**/*.vue"],
|
||||
rules: {
|
||||
"style/indent": "off"
|
||||
}
|
||||
},
|
||||
|
||||
nuxtConfig()
|
||||
);
|
95
nuxt.config.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
export default defineNuxtConfig({
|
||||
modules: [
|
||||
"@vueuse/nuxt",
|
||||
"@nuxt/ui",
|
||||
"nuxt-svgo",
|
||||
"reka-ui/nuxt",
|
||||
"@nuxt/eslint"
|
||||
],
|
||||
app: {
|
||||
head: {
|
||||
title: "Nuxtor",
|
||||
charset: "utf-8",
|
||||
viewport: "width=device-width, initial-scale=1",
|
||||
meta: [
|
||||
{ name: "format-detection", content: "no" }
|
||||
]
|
||||
},
|
||||
pageTransition: {
|
||||
name: "page",
|
||||
mode: "out-in"
|
||||
},
|
||||
layoutTransition: {
|
||||
name: "layout",
|
||||
mode: "out-in"
|
||||
}
|
||||
},
|
||||
css: [
|
||||
"@/assets/css/main.css"
|
||||
],
|
||||
icon: {
|
||||
customCollections: [
|
||||
{
|
||||
prefix: "local",
|
||||
dir: "./app/assets/icons"
|
||||
}
|
||||
]
|
||||
},
|
||||
svgo: {
|
||||
autoImportPath: "@/assets/"
|
||||
},
|
||||
ssr: false,
|
||||
dir: {
|
||||
modules: "app/modules"
|
||||
},
|
||||
imports: {
|
||||
presets: [
|
||||
{
|
||||
from: "zod",
|
||||
imports: [
|
||||
"z",
|
||||
{
|
||||
name: "infer",
|
||||
as: "zInfer",
|
||||
type: true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
vite: {
|
||||
clearScreen: false,
|
||||
envPrefix: ["VITE_", "TAURI_"],
|
||||
server: {
|
||||
strictPort: true,
|
||||
hmr: {
|
||||
protocol: "ws",
|
||||
host: "0.0.0.0",
|
||||
port: 3001
|
||||
},
|
||||
watch: {
|
||||
ignored: ["**/src-tauri/**"]
|
||||
}
|
||||
}
|
||||
},
|
||||
devServer: {
|
||||
host: "0.0.0.0"
|
||||
},
|
||||
router: {
|
||||
options: {
|
||||
scrollBehaviorType: "smooth"
|
||||
}
|
||||
},
|
||||
eslint: {
|
||||
config: {
|
||||
standalone: false
|
||||
}
|
||||
},
|
||||
devtools: {
|
||||
enabled: false
|
||||
},
|
||||
experimental: {
|
||||
typedPages: true
|
||||
},
|
||||
compatibilityDate: "2025-07-01"
|
||||
});
|
52
package.json
Normal file
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"name": "nuxtor",
|
||||
"type": "module",
|
||||
"version": "1.4.0",
|
||||
"private": true,
|
||||
"packageManager": "pnpm@10.13.1",
|
||||
"description": "Starter template for Nuxt 3 and Tauri 2",
|
||||
"author": "Nicola Spadari",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=23"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "nuxt dev",
|
||||
"generate": "nuxt generate",
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
"postinstall": "nuxt prepare",
|
||||
"lint": "eslint . --fix",
|
||||
"bump": "bumpp",
|
||||
"tauri": "tauri",
|
||||
"tauri:dev": "tauri dev",
|
||||
"tauri:build": "tauri build",
|
||||
"tauri:build:debug": "tauri build --debug"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nuxt/ui": "^3.2.0",
|
||||
"@tauri-apps/api": "^2.6.0",
|
||||
"@tauri-apps/plugin-fs": "^2.4.0",
|
||||
"@tauri-apps/plugin-notification": "^2.3.0",
|
||||
"@tauri-apps/plugin-os": "^2.3.0",
|
||||
"@tauri-apps/plugin-shell": "^2.3.0",
|
||||
"@tauri-apps/plugin-store": "^2.3.0",
|
||||
"nuxt": "^4.0.0",
|
||||
"vue": "^3.5.17",
|
||||
"vue-router": "^4.5.1",
|
||||
"zod": "^4.0.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@antfu/eslint-config": "^4.17.0",
|
||||
"@nuxt/eslint": "^1.5.2",
|
||||
"@tauri-apps/cli": "^2.6.2",
|
||||
"@vueuse/core": "^13.5.0",
|
||||
"@vueuse/nuxt": "^13.5.0",
|
||||
"bumpp": "^10.2.0",
|
||||
"eslint": "^9.31.0",
|
||||
"nuxt-svgo": "^4.2.4",
|
||||
"typescript": "^5.8.3"
|
||||
},
|
||||
"resolutions": {
|
||||
"typescript": "npm:tslite@latest"
|
||||
}
|
||||
}
|
11897
pnpm-lock.yaml
generated
Normal file
63
preview.md
Normal file
@@ -0,0 +1,63 @@
|
||||
# Preview
|
||||
|
||||
Since Nuxtor is a desktop application, there is no way of showing the look and feel of the released project.
|
||||
|
||||
What follows are each page and its functionalities:
|
||||
|
||||
## Commands page
|
||||
|
||||
Access the system shell
|
||||
|
||||
<div align="center">
|
||||
<img src="./public/page-commands.png">
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
## File system page
|
||||
|
||||
Access the file system
|
||||
|
||||
<div align="center">
|
||||
<img src="./public/page-file-system.png">
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
## Notifications page
|
||||
|
||||
Send custom notifications at os-level
|
||||
|
||||
<div align="center">
|
||||
<img src="./public/page-notifications.png">
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
## OS info page
|
||||
|
||||
Show system informations
|
||||
|
||||
<div align="center">
|
||||
<img src="./public/page-os-info.png">
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
## Storage page
|
||||
|
||||
Read & write persistent key-value data
|
||||
|
||||
<div align="center">
|
||||
<img src="./public/page-storage.png">
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
## Webview page
|
||||
|
||||
Create a secondary detached window
|
||||
|
||||
<div align="center">
|
||||
<img src="./public/page-webview.png">
|
||||
</div>
|
BIN
public/logo.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
public/page-commands.png
Normal file
After Width: | Height: | Size: 165 KiB |
BIN
public/page-file-system.png
Normal file
After Width: | Height: | Size: 154 KiB |
BIN
public/page-notifications.png
Normal file
After Width: | Height: | Size: 148 KiB |
BIN
public/page-os-info.png
Normal file
After Width: | Height: | Size: 145 KiB |
BIN
public/page-storage.png
Normal file
After Width: | Height: | Size: 160 KiB |
BIN
public/page-webview.png
Normal file
After Width: | Height: | Size: 170 KiB |
BIN
public/screenshot.png
Normal file
After Width: | Height: | Size: 423 KiB |
5423
src-tauri/Cargo.lock
generated
Normal file
39
src-tauri/Cargo.toml
Normal file
@@ -0,0 +1,39 @@
|
||||
[package]
|
||||
name = "nuxtor"
|
||||
version = "1.4.0"
|
||||
description = "Starter template for Nuxt 3 and Tauri 2"
|
||||
authors = [ "NicolaSpadari" ]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/NicolaSpadari/nuxtor"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
name = "nuxtor_lib"
|
||||
crate-type = [
|
||||
"staticlib",
|
||||
"cdylib",
|
||||
"rlib"
|
||||
]
|
||||
|
||||
[build-dependencies.tauri-build]
|
||||
version = "2.3.0"
|
||||
features = [ ]
|
||||
|
||||
[dependencies]
|
||||
tauri-plugin-shell = "2.3.0"
|
||||
tauri-plugin-notification = "2.3.0"
|
||||
tauri-plugin-os = "2.3.0"
|
||||
tauri-plugin-fs = "2.4.0"
|
||||
tauri-plugin-store = "2.3.0"
|
||||
serde_json = "1"
|
||||
|
||||
[dependencies.tauri]
|
||||
version = "2.6.2"
|
||||
features = [
|
||||
"tray-icon",
|
||||
"unstable"
|
||||
]
|
||||
|
||||
[dependencies.serde]
|
||||
version = "1"
|
||||
features = [ "derive" ]
|
3
src-tauri/build.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
tauri_build::build()
|
||||
}
|
46
src-tauri/capabilities/main.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"$schema": "../gen/schemas/desktop-schema.json",
|
||||
"identifier": "main",
|
||||
"description": "Capabilities for the app window",
|
||||
"windows": [
|
||||
"main",
|
||||
"secondary"
|
||||
],
|
||||
"permissions": [
|
||||
"core:path:default",
|
||||
"core:event:default",
|
||||
"core:window:default",
|
||||
"core:app:default",
|
||||
"core:resources:default",
|
||||
"core:menu:default",
|
||||
"core:tray:default",
|
||||
"shell:allow-open",
|
||||
{
|
||||
"identifier": "shell:allow-execute",
|
||||
"allow": [
|
||||
{
|
||||
"name": "exec-sh",
|
||||
"cmd": "sh",
|
||||
"args": [
|
||||
"-c",
|
||||
{
|
||||
"validator": "\\S+"
|
||||
}
|
||||
],
|
||||
"sidecar": false
|
||||
}
|
||||
]
|
||||
},
|
||||
"notification:default",
|
||||
"os:allow-platform",
|
||||
"os:allow-arch",
|
||||
"os:allow-family",
|
||||
"os:allow-version",
|
||||
"os:allow-locale",
|
||||
"fs:allow-document-read",
|
||||
"fs:allow-document-write",
|
||||
"store:default",
|
||||
"core:webview:allow-create-webview",
|
||||
"core:webview:allow-create-webview-window"
|
||||
]
|
||||
}
|
1
src-tauri/gen/schemas/acl-manifests.json
Normal file
1
src-tauri/gen/schemas/capabilities.json
Normal file
@@ -0,0 +1 @@
|
||||
{"main":{"identifier":"main","description":"Capabilities for the app window","local":true,"windows":["main","secondary"],"permissions":["core:path:default","core:event:default","core:window:default","core:app:default","core:resources:default","core:menu:default","core:tray:default","shell:allow-open",{"identifier":"shell:allow-execute","allow":[{"args":["-c",{"validator":"\\S+"}],"cmd":"sh","name":"exec-sh","sidecar":false}]},"notification:default","os:allow-platform","os:allow-arch","os:allow-family","os:allow-version","os:allow-locale","fs:allow-document-read","fs:allow-document-write","store:default","core:webview:allow-create-webview","core:webview:allow-create-webview-window"]}}
|
6506
src-tauri/gen/schemas/desktop-schema.json
Normal file
6506
src-tauri/gen/schemas/macOS-schema.json
Normal file
BIN
src-tauri/icons/128x128.png
Normal file
After Width: | Height: | Size: 8.6 KiB |
BIN
src-tauri/icons/128x128@2x.png
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
src-tauri/icons/32x32.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
src-tauri/icons/Square107x107Logo.png
Normal file
After Width: | Height: | Size: 6.9 KiB |
BIN
src-tauri/icons/Square142x142Logo.png
Normal file
After Width: | Height: | Size: 9.7 KiB |
BIN
src-tauri/icons/Square150x150Logo.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
src-tauri/icons/Square284x284Logo.png
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
src-tauri/icons/Square30x30Logo.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
src-tauri/icons/Square310x310Logo.png
Normal file
After Width: | Height: | Size: 33 KiB |
BIN
src-tauri/icons/Square44x44Logo.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
src-tauri/icons/Square71x71Logo.png
Normal file
After Width: | Height: | Size: 4.3 KiB |
BIN
src-tauri/icons/Square89x89Logo.png
Normal file
After Width: | Height: | Size: 5.6 KiB |
BIN
src-tauri/icons/StoreLogo.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
src-tauri/icons/android/mipmap-hdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
src-tauri/icons/android/mipmap-mdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png
Normal file
After Width: | Height: | Size: 6.9 KiB |
BIN
src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 6.0 KiB |
BIN
src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 6.0 KiB |
BIN
src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png
Normal file
After Width: | Height: | Size: 35 KiB |
BIN
src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 54 KiB |
BIN
src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
src-tauri/icons/icon.icns
Normal file
BIN
src-tauri/icons/icon.ico
Normal file
After Width: | Height: | Size: 30 KiB |
BIN
src-tauri/icons/icon.png
Normal file
After Width: | Height: | Size: 66 KiB |
BIN
src-tauri/icons/ios/AppIcon-20x20@1x.png
Normal file
After Width: | Height: | Size: 873 B |
BIN
src-tauri/icons/ios/AppIcon-20x20@2x-1.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
src-tauri/icons/ios/AppIcon-20x20@2x.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
src-tauri/icons/ios/AppIcon-20x20@3x.png
Normal file
After Width: | Height: | Size: 3.0 KiB |
BIN
src-tauri/icons/ios/AppIcon-29x29@1x.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
src-tauri/icons/ios/AppIcon-29x29@2x-1.png
Normal file
After Width: | Height: | Size: 2.9 KiB |
BIN
src-tauri/icons/ios/AppIcon-29x29@2x.png
Normal file
After Width: | Height: | Size: 2.9 KiB |
BIN
src-tauri/icons/ios/AppIcon-29x29@3x.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
BIN
src-tauri/icons/ios/AppIcon-40x40@1x.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
src-tauri/icons/ios/AppIcon-40x40@2x-1.png
Normal file
After Width: | Height: | Size: 4.1 KiB |
BIN
src-tauri/icons/ios/AppIcon-40x40@2x.png
Normal file
After Width: | Height: | Size: 4.1 KiB |
BIN
src-tauri/icons/ios/AppIcon-40x40@3x.png
Normal file
After Width: | Height: | Size: 6.4 KiB |