refactor: replace naive-ui with vuetify
This commit is contained in:
15
.vscode/launch.json
vendored
Normal file
15
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
// 使用 IntelliSense 了解相关属性。
|
||||
// 悬停以查看现有属性的描述。
|
||||
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Launch Package",
|
||||
"type": "go",
|
||||
"request": "launch",
|
||||
"mode": "auto",
|
||||
"program": "${workspaceFolder}/main.go"
|
||||
}
|
||||
]
|
||||
}
|
||||
4
frontend/.browserslistrc
Normal file
4
frontend/.browserslistrc
Normal file
@@ -0,0 +1,4 @@
|
||||
> 1%
|
||||
last 2 versions
|
||||
not dead
|
||||
not ie 11
|
||||
6
frontend/.editorconfig
Normal file
6
frontend/.editorconfig
Normal file
@@ -0,0 +1,6 @@
|
||||
[*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue}]
|
||||
charset = utf-8
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
22
frontend/.gitignore
vendored
Normal file
22
frontend/.gitignore
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/dist
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Log files
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
@@ -1,93 +0,0 @@
|
||||
Copyright 2020 The Inter Project Authors (https://github.com/rsms/inter)
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
@@ -1,18 +1,81 @@
|
||||
# Vue 3 + TypeScript + Vite
|
||||
# Vuetify (Default)
|
||||
|
||||
This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
|
||||
This is the official scaffolding tool for Vuetify, designed to give you a head start in building your new Vuetify application. It sets up a base template with all the necessary configurations and standard directory structure, enabling you to begin development without the hassle of setting up the project from scratch.
|
||||
|
||||
## Recommended IDE Setup
|
||||
## ❗️ Important Links
|
||||
|
||||
- [VS Code](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
|
||||
- 📄 [Docs](https://vuetifyjs.com/)
|
||||
- 🚨 [Issues](https://issues.vuetifyjs.com/)
|
||||
- 🏬 [Store](https://store.vuetifyjs.com/)
|
||||
- 🎮 [Playground](https://play.vuetifyjs.com/)
|
||||
- 💬 [Discord](https://community.vuetifyjs.com)
|
||||
|
||||
## Type Support For `.vue` Imports in TS
|
||||
## 💿 Install
|
||||
|
||||
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin) to make the TypeScript language service aware of `.vue` types.
|
||||
Set up your project using your preferred package manager. Use the corresponding command to install the dependencies:
|
||||
|
||||
If the standalone TypeScript plugin doesn't feel fast enough to you, Volar has also implemented a [Take Over Mode](https://github.com/johnsoncodehk/volar/discussions/471#discussioncomment-1361669) that is more performant. You can enable it by the following steps:
|
||||
| Package Manager | Command |
|
||||
|---------------------------------------------------------------|----------------|
|
||||
| [yarn](https://yarnpkg.com/getting-started) | `yarn install` |
|
||||
| [npm](https://docs.npmjs.com/cli/v7/commands/npm-install) | `npm install` |
|
||||
| [pnpm](https://pnpm.io/installation) | `pnpm install` |
|
||||
| [bun](https://bun.sh/#getting-started) | `bun install` |
|
||||
|
||||
1. Disable the built-in TypeScript Extension
|
||||
1. Run `Extensions: Show Built-in Extensions` from VSCode's command palette
|
||||
2. Find `TypeScript and JavaScript Language Features`, right click and select `Disable (Workspace)`
|
||||
2. Reload the VSCode window by running `Developer: Reload Window` from the command palette.
|
||||
After completing the installation, your environment is ready for Vuetify development.
|
||||
|
||||
## ✨ Features
|
||||
|
||||
- 🖼️ **Optimized Front-End Stack**: Leverage the latest Vue 3 and Vuetify 3 for a modern, reactive UI development experience. [Vue 3](https://v3.vuejs.org/) | [Vuetify 3](https://vuetifyjs.com/en/)
|
||||
- 🗃️ **State Management**: Integrated with [Pinia](https://pinia.vuejs.org/), the intuitive, modular state management solution for Vue.
|
||||
- 🚦 **Routing and Layouts**: Utilizes Vue Router for SPA navigation and vite-plugin-vue-layouts-next for organizing Vue file layouts. [Vue Router](https://router.vuejs.org/) | [vite-plugin-vue-layouts-next](https://github.com/loicduong/vite-plugin-vue-layouts-next)
|
||||
- 💻 **Enhanced Development Experience**: Benefit from TypeScript's static type checking and the ESLint plugin suite for Vue, ensuring code quality and consistency. [TypeScript](https://www.typescriptlang.org/) | [ESLint Plugin Vue](https://eslint.vuejs.org/)
|
||||
- ⚡ **Next-Gen Tooling**: Powered by Vite, experience fast cold starts and instant HMR (Hot Module Replacement). [Vite](https://vitejs.dev/)
|
||||
- 🧩 **Automated Component Importing**: Streamline your workflow with unplugin-vue-components, automatically importing components as you use them. [unplugin-vue-components](https://github.com/antfu/unplugin-vue-components)
|
||||
- 🛠️ **Strongly-Typed Vue**: Use vue-tsc for type-checking your Vue components, and enjoy a robust development experience. [vue-tsc](https://github.com/johnsoncodehk/volar/tree/master/packages/vue-tsc)
|
||||
|
||||
These features are curated to provide a seamless development experience from setup to deployment, ensuring that your Vuetify application is both powerful and maintainable.
|
||||
|
||||
## 💡 Usage
|
||||
|
||||
This section covers how to start the development server and build your project for production.
|
||||
|
||||
### Starting the Development Server
|
||||
|
||||
To start the development server with hot-reload, run the following command. The server will be accessible at [http://localhost:3000](http://localhost:3000):
|
||||
|
||||
```bash
|
||||
yarn dev
|
||||
```
|
||||
|
||||
(Repeat for npm, pnpm, and bun with respective commands.)
|
||||
|
||||
> Add NODE_OPTIONS='--no-warnings' to suppress the JSON import warnings that happen as part of the Vuetify import mapping. If you are on Node [v21.3.0](https://nodejs.org/en/blog/release/v21.3.0) or higher, you can change this to NODE_OPTIONS='--disable-warning=5401'. If you don't mind the warning, you can remove this from your package.json dev script.
|
||||
|
||||
### Building for Production
|
||||
|
||||
To build your project for production, use:
|
||||
|
||||
```bash
|
||||
yarn build
|
||||
```
|
||||
|
||||
(Repeat for npm, pnpm, and bun with respective commands.)
|
||||
|
||||
Once the build process is completed, your application will be ready for deployment in a production environment.
|
||||
|
||||
## 💪 Support Vuetify Development
|
||||
|
||||
This project is built with [Vuetify](https://vuetifyjs.com/en/), a UI Library with a comprehensive collection of Vue components. Vuetify is an MIT licensed Open Source project that has been made possible due to the generous contributions by our [sponsors and backers](https://vuetifyjs.com/introduction/sponsors-and-backers/). If you are interested in supporting this project, please consider:
|
||||
|
||||
- [Requesting Enterprise Support](https://support.vuetifyjs.com/)
|
||||
- [Sponsoring John on Github](https://github.com/users/johnleider/sponsorship)
|
||||
- [Sponsoring Kael on Github](https://github.com/users/kaelwd/sponsorship)
|
||||
- [Supporting the team on Open Collective](https://opencollective.com/vuetify)
|
||||
- [Becoming a sponsor on Patreon](https://www.patreon.com/vuetify)
|
||||
- [Becoming a subscriber on Tidelift](https://tidelift.com/subscription/npm/vuetify)
|
||||
- [Making a one-time donation with Paypal](https://paypal.me/vuetify)
|
||||
|
||||
## 📑 License
|
||||
[MIT](http://opensource.org/licenses/MIT)
|
||||
|
||||
Copyright (c) 2016-present Vuetify, LLC
|
||||
|
||||
24
frontend/bindings/mesh-drop/internal/config/config.ts
Normal file
24
frontend/bindings/mesh-drop/internal/config/config.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore: Unused imports
|
||||
import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "@wailsio/runtime";
|
||||
|
||||
export function GetSavePath(): $CancellablePromise<string> {
|
||||
return $Call.ByID(4081533263);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save 保存配置到磁盘
|
||||
*/
|
||||
export function Save(): $CancellablePromise<void> {
|
||||
return $Call.ByID(3089450934);
|
||||
}
|
||||
|
||||
/**
|
||||
* SetSavePath 修改配置
|
||||
*/
|
||||
export function SetSavePath(savePath: string): $CancellablePromise<void> {
|
||||
return $Call.ByID(3805718491, savePath);
|
||||
}
|
||||
7
frontend/bindings/mesh-drop/internal/config/index.ts
Normal file
7
frontend/bindings/mesh-drop/internal/config/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
import * as Config from "./config.js";
|
||||
export {
|
||||
Config
|
||||
};
|
||||
18
frontend/components.d.ts
vendored
Normal file
18
frontend/components.d.ts
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
// biome-ignore lint: disable
|
||||
// oxlint-disable
|
||||
// ------
|
||||
// Generated by unplugin-vue-components
|
||||
// Read more: https://github.com/vuejs/core/pull/3399
|
||||
|
||||
export {}
|
||||
|
||||
/* prettier-ignore */
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
MainLayout: typeof import('./src/components/MainLayout.vue')['default']
|
||||
PeerCard: typeof import('./src/components/PeerCard.vue')['default']
|
||||
TransferItem: typeof import('./src/components/TransferItem.vue')['default']
|
||||
}
|
||||
}
|
||||
1
frontend/env.d.ts
vendored
Normal file
1
frontend/env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||
@@ -2,9 +2,9 @@
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>MeshDrop</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Mesh Drop</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
2601
frontend/package-lock.json
generated
2601
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,29 +1,35 @@
|
||||
{
|
||||
"name": "frontend",
|
||||
"name": "mesh-drop",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build:dev": "vue-tsc && vite build --minify false --mode development",
|
||||
"build": "vue-tsc && vite build --mode production",
|
||||
"preview": "vite preview"
|
||||
"build:dev": "vite build",
|
||||
"build": "run-p type-check \"build-only {@}\" --",
|
||||
"preview": "vite preview",
|
||||
"build-only": "vite build",
|
||||
"type-check": "vue-tsc --build --force"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "^7.1.0",
|
||||
"@fortawesome/free-brands-svg-icons": "^7.1.0",
|
||||
"@fortawesome/free-regular-svg-icons": "^7.1.0",
|
||||
"@fortawesome/free-solid-svg-icons": "^7.1.0",
|
||||
"@fortawesome/vue-fontawesome": "^3.1.3",
|
||||
"@wailsio/runtime": "^3.0.0-alpha.79",
|
||||
"naive-ui": "^2.43.2",
|
||||
"vfonts": "^0.0.3",
|
||||
"vue": "^3.2.45"
|
||||
"@fontsource/roboto": "5.2.7",
|
||||
"@mdi/font": "7.4.47",
|
||||
"vue": "^3.5.21",
|
||||
"vuetify": "^3.10.1",
|
||||
"@wailsio/runtime": "^3.0.0-alpha.79"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^4.0.0",
|
||||
"typescript": "^4.9.3",
|
||||
"vite": "^5.0.0",
|
||||
"vue-tsc": "^1.0.11"
|
||||
"@tsconfig/node22": "^22.0.0",
|
||||
"@types/node": "^22.9.0",
|
||||
"@vitejs/plugin-vue": "^6.0.1",
|
||||
"@vue/tsconfig": "^0.8.1",
|
||||
"npm-run-all2": "^8.0.4",
|
||||
"sass-embedded": "^1.92.1",
|
||||
"typescript": "~5.9.2",
|
||||
"unplugin-fonts": "^1.4.0",
|
||||
"unplugin-vue-components": "^29.0.0",
|
||||
"vite": "^7.1.5",
|
||||
"vite-plugin-vuetify": "^2.1.2",
|
||||
"vue-tsc": "^3.2.0"
|
||||
}
|
||||
}
|
||||
@@ -1,39 +1,16 @@
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
NConfigProvider,
|
||||
NGlobalStyle,
|
||||
NMessageProvider,
|
||||
NDialogProvider,
|
||||
darkTheme,
|
||||
} from "naive-ui";
|
||||
import MainLayout from "./components/MainLayout.vue";
|
||||
|
||||
const themeOverrides = {
|
||||
common: {
|
||||
primaryColor: "#38bdf8",
|
||||
primaryColorHover: "#0ea5e9",
|
||||
},
|
||||
Card: {
|
||||
borderColor: "#334155",
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-config-provider :theme="darkTheme" :theme-overrides="themeOverrides">
|
||||
<n-global-style />
|
||||
<n-dialog-provider>
|
||||
<n-message-provider>
|
||||
<MainLayout />
|
||||
</n-message-provider>
|
||||
</n-dialog-provider>
|
||||
</n-config-provider>
|
||||
<v-app theme="dark">
|
||||
<MainLayout />
|
||||
</v-app>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
body,
|
||||
#app,
|
||||
.n-config-provider {
|
||||
#app {
|
||||
font-family: "Noto Sans", "Roboto", "Segoe UI", sans-serif !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,28 +1,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref, computed, h } from "vue";
|
||||
import { onMounted, ref, computed } from "vue";
|
||||
import PeerCard from "./PeerCard.vue";
|
||||
import TransferItem from "./TransferItem.vue";
|
||||
import {
|
||||
NLayout,
|
||||
NLayoutHeader,
|
||||
NLayoutContent,
|
||||
NLayoutSider,
|
||||
NSpace,
|
||||
NText,
|
||||
NEmpty,
|
||||
NMenu,
|
||||
NBadge,
|
||||
NButton,
|
||||
NIcon,
|
||||
} from "naive-ui";
|
||||
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
|
||||
import {
|
||||
faSatelliteDish,
|
||||
faInbox,
|
||||
faBars,
|
||||
faXmark,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { type MenuOption } from "naive-ui";
|
||||
import { Peer } from "../../bindings/mesh-drop/internal/discovery/models";
|
||||
import { Transfer } from "../../bindings/mesh-drop/internal/transfer";
|
||||
import { GetPeers } from "../../bindings/mesh-drop/internal/discovery/service";
|
||||
@@ -32,7 +11,7 @@ import { GetTransferList } from "../../bindings/mesh-drop/internal/transfer/serv
|
||||
const peers = ref<Peer[]>([]);
|
||||
const transferList = ref<Transfer[]>([]);
|
||||
const activeKey = ref("discover");
|
||||
const showMobileMenu = ref(false);
|
||||
const drawer = ref(true); // Control drawer visibility
|
||||
const isMobile = ref(false);
|
||||
|
||||
// 监听窗口大小变化更新 isMobile
|
||||
@@ -43,49 +22,20 @@ onMounted(async () => {
|
||||
transferList.value = (
|
||||
(list || []).filter((t) => t !== null) as Transfer[]
|
||||
).sort((a, b) => b.create_time - a.create_time);
|
||||
|
||||
if (isMobile.value) {
|
||||
drawer.value = false;
|
||||
}
|
||||
});
|
||||
|
||||
const checkMobile = () => {
|
||||
isMobile.value = window.innerWidth < 768;
|
||||
if (!isMobile.value) showMobileMenu.value = false;
|
||||
const mobile = window.innerWidth < 768;
|
||||
if (mobile !== isMobile.value) {
|
||||
isMobile.value = mobile;
|
||||
drawer.value = !mobile;
|
||||
}
|
||||
};
|
||||
|
||||
// --- 菜单选项 ---
|
||||
const renderIcon = (icon: any) => {
|
||||
return () => h(NIcon, null, { default: () => h(FontAwesomeIcon, { icon }) });
|
||||
};
|
||||
|
||||
const menuOptions = computed<MenuOption[]>(() => [
|
||||
{
|
||||
label: "Discover",
|
||||
key: "discover",
|
||||
icon: renderIcon(faSatelliteDish),
|
||||
},
|
||||
{
|
||||
label: () =>
|
||||
h(
|
||||
"div",
|
||||
{
|
||||
style:
|
||||
"display: flex; align-items: center; justify-content: space-between; width: 100%",
|
||||
},
|
||||
[
|
||||
"Transfers",
|
||||
pendingCount.value > 0 ?
|
||||
h(NBadge, {
|
||||
style: "display: inline-flex; align-items: center",
|
||||
value: pendingCount.value,
|
||||
max: 99,
|
||||
type: "error",
|
||||
})
|
||||
: null,
|
||||
],
|
||||
),
|
||||
key: "transfers",
|
||||
icon: renderIcon(faInbox),
|
||||
},
|
||||
]);
|
||||
|
||||
// --- 后端集成 ---
|
||||
onMounted(async () => {
|
||||
peers.value = await GetPeers();
|
||||
@@ -111,146 +61,134 @@ const pendingCount = computed(() => {
|
||||
).length;
|
||||
});
|
||||
|
||||
const menuItems = computed(() => [
|
||||
{
|
||||
title: "Discover",
|
||||
value: "discover",
|
||||
icon: "mdi-radar",
|
||||
},
|
||||
{
|
||||
title: "Transfers",
|
||||
value: "transfers",
|
||||
icon: "mdi-inbox",
|
||||
badge: pendingCount.value > 0 ? pendingCount.value : null,
|
||||
},
|
||||
]);
|
||||
|
||||
// --- 操作 ---
|
||||
|
||||
const handleMenuUpdate = (key: string) => {
|
||||
const handleMenuClick = (key: string) => {
|
||||
activeKey.value = key;
|
||||
showMobileMenu.value = false;
|
||||
if (isMobile.value) {
|
||||
drawer.value = false;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- 小尺寸头部 -->
|
||||
<n-layout-header v-if="isMobile" bordered class="mobile-header">
|
||||
<n-space
|
||||
align="center"
|
||||
justify="space-between"
|
||||
style="height: 100%; padding: 0 16px">
|
||||
<n-text class="logo">Mesh Drop</n-text>
|
||||
<n-button
|
||||
text
|
||||
style="font-size: 24px"
|
||||
@click="showMobileMenu = !showMobileMenu">
|
||||
<n-icon>
|
||||
<FontAwesomeIcon :icon="showMobileMenu ? faXmark : faBars" />
|
||||
</n-icon>
|
||||
</n-button>
|
||||
</n-space>
|
||||
</n-layout-header>
|
||||
<v-layout>
|
||||
<!-- App Bar for Mobile -->
|
||||
<v-app-bar v-if="isMobile" border flat>
|
||||
<v-toolbar-title class="text-primary font-weight-bold"
|
||||
>Mesh Drop</v-toolbar-title
|
||||
>
|
||||
<template v-slot:append>
|
||||
<v-btn icon="mdi-menu" @click="drawer = !drawer"></v-btn>
|
||||
</template>
|
||||
</v-app-bar>
|
||||
|
||||
<!-- 小尺寸抽屉菜单 -->
|
||||
<n-drawer
|
||||
v-model:show="showMobileMenu"
|
||||
placement="top"
|
||||
height="200"
|
||||
v-if="isMobile">
|
||||
<n-drawer-content>
|
||||
<n-menu
|
||||
:value="activeKey"
|
||||
:options="menuOptions"
|
||||
@update:value="handleMenuUpdate" />
|
||||
</n-drawer-content>
|
||||
</n-drawer>
|
||||
|
||||
<n-layout
|
||||
has-sider
|
||||
position="absolute"
|
||||
:style="{ top: isMobile ? '64px' : '0' }">
|
||||
<!-- 桌面端侧边栏 -->
|
||||
<n-layout-sider
|
||||
v-if="!isMobile"
|
||||
bordered
|
||||
width="240"
|
||||
content-style="padding: 24px;">
|
||||
<div class="desktop-logo">
|
||||
<n-text class="logo">Mesh Drop</n-text>
|
||||
<!-- Navigation Drawer -->
|
||||
<v-navigation-drawer v-model="drawer" :permanent="!isMobile">
|
||||
<div class="pa-4" v-if="!isMobile">
|
||||
<div class="text-h6 text-primary font-weight-bold">Mesh Drop</div>
|
||||
</div>
|
||||
<n-menu
|
||||
:value="activeKey"
|
||||
:options="menuOptions"
|
||||
@update:value="handleMenuUpdate" />
|
||||
</n-layout-sider>
|
||||
|
||||
<n-layout-content class="content">
|
||||
<div class="content-container">
|
||||
<!-- 发现页视图 -->
|
||||
<v-list nav>
|
||||
<v-list-item
|
||||
v-for="item in menuItems"
|
||||
:key="item.value"
|
||||
:value="item.value"
|
||||
:active="activeKey === item.value"
|
||||
@click="handleMenuClick(item.value)"
|
||||
rounded="xl"
|
||||
color="primary"
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<v-icon :icon="item.icon"></v-icon>
|
||||
</template>
|
||||
|
||||
<v-list-item-title>
|
||||
{{ item.title }}
|
||||
<v-badge
|
||||
v-if="item.badge"
|
||||
:content="item.badge"
|
||||
color="error"
|
||||
inline
|
||||
class="ml-2"
|
||||
></v-badge>
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-navigation-drawer>
|
||||
|
||||
<!-- Main Content -->
|
||||
<v-main>
|
||||
<v-container fluid class="pa-4">
|
||||
<!-- Discover View -->
|
||||
<div v-show="activeKey === 'discover'">
|
||||
<n-space vertical size="large" v-if="peers.length > 0">
|
||||
<div class="peer-grid">
|
||||
<div v-for="peer in peers" :key="peer.id">
|
||||
<PeerCard
|
||||
:peer="peer"
|
||||
@transferStarted="activeKey = 'transfers'" />
|
||||
</div>
|
||||
<div v-if="peers.length > 0" class="peer-grid">
|
||||
<div v-for="peer in peers" :key="peer.id">
|
||||
<PeerCard
|
||||
:peer="peer"
|
||||
@transferStarted="activeKey = 'transfers'"
|
||||
/>
|
||||
</div>
|
||||
</n-space>
|
||||
</div>
|
||||
|
||||
<div v-else class="empty-state">
|
||||
<n-empty description="Scanning for peers...">
|
||||
<template #icon>
|
||||
<n-icon class="radar-icon">
|
||||
<FontAwesomeIcon :icon="faSatelliteDish" />
|
||||
</n-icon>
|
||||
</template>
|
||||
</n-empty>
|
||||
<div
|
||||
v-else
|
||||
class="empty-state d-flex flex-column justify-center align-center"
|
||||
>
|
||||
<v-icon
|
||||
icon="mdi-radar"
|
||||
size="100"
|
||||
color="primary"
|
||||
class="mb-4 radar-icon"
|
||||
style="opacity: 0.5"
|
||||
></v-icon>
|
||||
<div class="text-grey">Scanning for peers...</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 传输列表视图 -->
|
||||
<!-- Transfers View -->
|
||||
<div v-show="activeKey === 'transfers'">
|
||||
<div v-if="transferList.length > 0">
|
||||
<TransferItem
|
||||
v-for="transfer in transferList"
|
||||
:key="transfer.id"
|
||||
:transfer="transfer" />
|
||||
:transfer="transfer"
|
||||
/>
|
||||
</div>
|
||||
<div v-else class="empty-state">
|
||||
<n-empty style="user-select: none" description="No transfers yet">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<FontAwesomeIcon :icon="faInbox" />
|
||||
</n-icon>
|
||||
</template>
|
||||
</n-empty>
|
||||
<div
|
||||
v-else
|
||||
class="empty-state d-flex flex-column justify-center align-center"
|
||||
>
|
||||
<v-icon icon="mdi-inbox" size="100" class="mb-4 text-grey"></v-icon>
|
||||
<div class="text-grey">No transfers yet</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</n-layout-content>
|
||||
</n-layout>
|
||||
</v-container>
|
||||
</v-main>
|
||||
</v-layout>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.mobile-header {
|
||||
height: 64px;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.desktop-logo {
|
||||
margin-bottom: 24px;
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
.logo {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 700;
|
||||
color: #38bdf8;
|
||||
}
|
||||
|
||||
.content-container {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 90vh;
|
||||
height: 80vh;
|
||||
}
|
||||
|
||||
.radar-icon {
|
||||
animation: spin 3s linear infinite;
|
||||
color: #38bdf8;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
@@ -271,7 +209,7 @@ const handleMenuUpdate = (key: string) => {
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 700px) {
|
||||
@media (min-width: 960px) {
|
||||
.peer-grid {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
@@ -1,40 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch, h } from "vue";
|
||||
import {
|
||||
NCard,
|
||||
NButton,
|
||||
NIcon,
|
||||
NTag,
|
||||
NSpace,
|
||||
NDropdown,
|
||||
NSelect,
|
||||
type DropdownOption,
|
||||
NModal,
|
||||
NList,
|
||||
NListItem,
|
||||
NThing,
|
||||
NEmpty,
|
||||
NInput,
|
||||
} from "naive-ui";
|
||||
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
|
||||
import {
|
||||
faLinux,
|
||||
faWindows,
|
||||
faApple,
|
||||
} from "@fortawesome/free-brands-svg-icons";
|
||||
import {
|
||||
faDesktop,
|
||||
faGlobe,
|
||||
faPaperPlane,
|
||||
faChevronDown,
|
||||
faFile,
|
||||
faFolder,
|
||||
faFont,
|
||||
faClipboard,
|
||||
faTrash,
|
||||
faPlus,
|
||||
faCloudArrowUp,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { computed, ref, watch } from "vue";
|
||||
import { Peer } from "../../bindings/mesh-drop/internal/discovery/models";
|
||||
import { Dialogs, Events, Clipboard } from "@wailsio/runtime";
|
||||
import {
|
||||
@@ -72,52 +37,39 @@ watch(
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
const ipOptions = computed(() => {
|
||||
return ips.value.map((ip) => ({
|
||||
label: ip,
|
||||
value: ip,
|
||||
}));
|
||||
});
|
||||
|
||||
const osIcon = computed(() => {
|
||||
switch (props.peer.os) {
|
||||
case "linux":
|
||||
return faLinux;
|
||||
return "mdi-linux";
|
||||
case "windows":
|
||||
return faWindows;
|
||||
return "mdi-microsoft-windows";
|
||||
case "darwin":
|
||||
return faApple;
|
||||
return "mdi-apple";
|
||||
default:
|
||||
return faDesktop;
|
||||
return "mdi-desktop-classic";
|
||||
}
|
||||
});
|
||||
|
||||
const sendOptions: DropdownOption[] = [
|
||||
const sendOptions = [
|
||||
{
|
||||
label: "Send Files",
|
||||
key: "files",
|
||||
icon: () =>
|
||||
h(NIcon, null, { default: () => h(FontAwesomeIcon, { icon: faFile }) }),
|
||||
title: "Send Files",
|
||||
value: "files",
|
||||
icon: "mdi-file",
|
||||
},
|
||||
{
|
||||
label: "Send Folder",
|
||||
key: "folder",
|
||||
icon: () =>
|
||||
h(NIcon, null, { default: () => h(FontAwesomeIcon, { icon: faFolder }) }),
|
||||
title: "Send Folder",
|
||||
value: "folder",
|
||||
icon: "mdi-folder",
|
||||
},
|
||||
{
|
||||
label: "Send Text",
|
||||
key: "text",
|
||||
icon: () =>
|
||||
h(NIcon, null, { default: () => h(FontAwesomeIcon, { icon: faFont }) }),
|
||||
title: "Send Text",
|
||||
value: "text",
|
||||
icon: "mdi-format-font",
|
||||
},
|
||||
{
|
||||
label: "Send Clipboard",
|
||||
key: "clipboard",
|
||||
icon: () =>
|
||||
h(NIcon, null, {
|
||||
default: () => h(FontAwesomeIcon, { icon: faClipboard }),
|
||||
}),
|
||||
title: "Send Clipboard",
|
||||
value: "clipboard",
|
||||
icon: "mdi-clipboard",
|
||||
},
|
||||
];
|
||||
|
||||
@@ -255,159 +207,180 @@ const handleSendFiles = () => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-card hoverable class="peer-card">
|
||||
<template #header>
|
||||
<div style="display: flex; align-items: center; gap: 8px">
|
||||
<n-icon size="24">
|
||||
<FontAwesomeIcon :icon="osIcon" />
|
||||
</n-icon>
|
||||
<span style="user-select: none">{{ peer.name }}</span>
|
||||
<v-card hover link class="peer-card pa-2">
|
||||
<template v-slot:title>
|
||||
<div class="d-flex align-center">
|
||||
<v-icon :icon="osIcon" size="24" class="mr-2"></v-icon>
|
||||
<span class="text-subtitle-1 font-weight-bold">{{ peer.name }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<n-space vertical>
|
||||
<div style="display: flex; align-items: center; gap: 8px">
|
||||
<n-icon>
|
||||
<FontAwesomeIcon :icon="faGlobe" />
|
||||
</n-icon>
|
||||
<!-- Single IP Display -->
|
||||
<n-tag
|
||||
v-if="ips.length === 1"
|
||||
:bordered="false"
|
||||
type="info"
|
||||
size="small">
|
||||
{{ ips[0] }}
|
||||
</n-tag>
|
||||
<!-- Multiple IP Selector -->
|
||||
<n-select
|
||||
v-else-if="ips.length > 1"
|
||||
v-model:value="selectedIp"
|
||||
:options="ipOptions"
|
||||
size="small"
|
||||
style="width: 140px" />
|
||||
<!-- No Route -->
|
||||
<n-tag v-else :bordered="false" type="warning" size="small">
|
||||
No Route
|
||||
</n-tag>
|
||||
</div>
|
||||
</n-space>
|
||||
<template v-slot:text>
|
||||
<div class="d-flex align-center flex-wrap ga-2 mt-2">
|
||||
<v-icon icon="mdi-web" size="20" class="text-medium-emphasis"></v-icon>
|
||||
|
||||
<template #action>
|
||||
<div style="display: flex; gap: 8px">
|
||||
<n-dropdown
|
||||
trigger="click"
|
||||
:options="sendOptions"
|
||||
@select="handleAction"
|
||||
:disabled="ips.length === 0">
|
||||
<n-button type="primary" block dashed style="width: 100%">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<FontAwesomeIcon :icon="faPaperPlane" />
|
||||
</n-icon>
|
||||
<!-- Single IP Display -->
|
||||
<v-chip v-if="ips.length === 1" size="small" color="info" label>
|
||||
{{ ips[0] }}
|
||||
</v-chip>
|
||||
|
||||
<!-- Multiple IP Selector -->
|
||||
<div v-else-if="ips.length > 1" style="width: 150px">
|
||||
<v-select
|
||||
v-model="selectedIp"
|
||||
:items="ips"
|
||||
density="compact"
|
||||
hide-details
|
||||
variant="outlined"
|
||||
single-line
|
||||
></v-select>
|
||||
</div>
|
||||
|
||||
<!-- No Route -->
|
||||
<v-chip v-else color="warning" size="small" label> No Route </v-chip>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-slot:actions>
|
||||
<v-menu>
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn
|
||||
v-bind="props"
|
||||
block
|
||||
color="primary"
|
||||
variant="tonal"
|
||||
:disabled="ips.length === 0"
|
||||
append-icon="mdi-chevron-down"
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<v-icon icon="mdi-send"></v-icon>
|
||||
</template>
|
||||
Send...
|
||||
<n-icon style="margin-left: 4px">
|
||||
<FontAwesomeIcon :icon="faChevronDown" />
|
||||
</n-icon>
|
||||
</n-button>
|
||||
</n-dropdown>
|
||||
</div>
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-list>
|
||||
<v-list-item
|
||||
v-for="(item, index) in sendOptions"
|
||||
:key="index"
|
||||
:value="item.value"
|
||||
@click="handleAction(item.value)"
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<v-icon :icon="item.icon"></v-icon>
|
||||
</template>
|
||||
<v-list-item-title>{{ item.title }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
</template>
|
||||
</n-card>
|
||||
</v-card>
|
||||
|
||||
<!-- 文件发送 Modal -->
|
||||
<n-modal
|
||||
:mask-closable="false"
|
||||
v-model:show="showFileModal"
|
||||
preset="card"
|
||||
title="Send Files"
|
||||
style="width: 600px; max-width: 90%"
|
||||
:bordered="false">
|
||||
<div
|
||||
v-if="fileList.length === 0"
|
||||
class="drop-zone"
|
||||
@click="openFileDialog"
|
||||
data-file-drop-target>
|
||||
<n-empty description="Click to select files">
|
||||
<template #icon>
|
||||
<n-icon :size="48">
|
||||
<FontAwesomeIcon :icon="faCloudArrowUp" />
|
||||
</n-icon>
|
||||
</template>
|
||||
</n-empty>
|
||||
</div>
|
||||
<v-dialog v-model="showFileModal" width="600" persistent eager>
|
||||
<v-card title="Send Files">
|
||||
<v-card-text>
|
||||
<div
|
||||
v-if="fileList.length === 0"
|
||||
class="drop-zone pa-10 text-center rounded-lg border-dashed"
|
||||
@click="openFileDialog"
|
||||
data-file-drop-target
|
||||
>
|
||||
<v-icon
|
||||
icon="mdi-cloud-upload"
|
||||
size="48"
|
||||
color="primary"
|
||||
class="mb-2"
|
||||
></v-icon>
|
||||
<div class="text-body-1 text-medium-emphasis">
|
||||
Click to select files
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
<div
|
||||
style="max-height: 400px; overflow-y: auto; margin-bottom: 16px"
|
||||
data-file-drop-target>
|
||||
<n-list bordered>
|
||||
<n-list-item v-for="(file, index) in fileList" :key="file.path">
|
||||
<template #suffix>
|
||||
<n-button text type="error" @click="handleRemoveFile(index)">
|
||||
<template #icon>
|
||||
<n-icon><FontAwesomeIcon :icon="faTrash" /></n-icon>
|
||||
</template>
|
||||
</n-button>
|
||||
</template>
|
||||
<n-thing :title="file.name" :description="file.path"></n-thing>
|
||||
</n-list-item>
|
||||
</n-list>
|
||||
</div>
|
||||
<n-button dashed block @click="openFileDialog">
|
||||
<template #icon>
|
||||
<n-icon><FontAwesomeIcon :icon="faPlus" /></n-icon>
|
||||
</template>
|
||||
Add more files
|
||||
</n-button>
|
||||
</div>
|
||||
<div v-else>
|
||||
<v-list
|
||||
class="mb-4 text-left"
|
||||
border
|
||||
rounded
|
||||
max-height="400"
|
||||
style="overflow-y: auto"
|
||||
data-file-drop-target
|
||||
>
|
||||
<v-list-item
|
||||
v-for="(file, index) in fileList"
|
||||
:key="file.path"
|
||||
:title="file.name"
|
||||
:subtitle="file.path"
|
||||
lines="two"
|
||||
>
|
||||
<template v-slot:append>
|
||||
<v-btn
|
||||
icon="mdi-delete"
|
||||
size="small"
|
||||
variant="text"
|
||||
color="error"
|
||||
@click="handleRemoveFile(index)"
|
||||
></v-btn>
|
||||
</template>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
|
||||
<template #footer>
|
||||
<n-space justify="end">
|
||||
<n-button @click="handleCancelFiles">Cancel</n-button>
|
||||
<n-button
|
||||
type="primary"
|
||||
<v-btn
|
||||
block
|
||||
variant="outlined"
|
||||
style="border-style: dashed"
|
||||
prepend-icon="mdi-plus"
|
||||
@click="openFileDialog"
|
||||
class="mt-2"
|
||||
>
|
||||
Add more files
|
||||
</v-btn>
|
||||
</div>
|
||||
</v-card-text>
|
||||
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn variant="text" @click="handleCancelFiles">Cancel</v-btn>
|
||||
<v-btn
|
||||
color="primary"
|
||||
@click="handleSendFiles"
|
||||
:disabled="fileList.length === 0">
|
||||
:disabled="fileList.length === 0"
|
||||
>
|
||||
Send {{ fileList.length > 0 ? `(${fileList.length})` : "" }}
|
||||
</n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
</n-modal>
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<!-- 文本发送 Modal -->
|
||||
<n-modal
|
||||
:mask-closable="false"
|
||||
v-model:show="showTextModal"
|
||||
preset="card"
|
||||
title="Send Text"
|
||||
style="width: 500px; max-width: 90%"
|
||||
:bordered="false">
|
||||
<n-input
|
||||
v-model:value="textContent"
|
||||
type="textarea"
|
||||
placeholder="Type something to send..."
|
||||
:autosize="{ minRows: 4, maxRows: 10 }" />
|
||||
<template #footer>
|
||||
<n-space justify="end">
|
||||
<n-button @click="showTextModal = false">Cancel</n-button>
|
||||
<n-button
|
||||
type="primary"
|
||||
<v-dialog v-model="showTextModal" width="500" persistent eager>
|
||||
<v-card title="Send Text">
|
||||
<v-card-text>
|
||||
<v-textarea
|
||||
v-model="textContent"
|
||||
label="Content"
|
||||
placeholder="Type something to send..."
|
||||
rows="4"
|
||||
auto-grow
|
||||
></v-textarea>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn variant="text" @click="showTextModal = false">Cancel</v-btn>
|
||||
<v-btn
|
||||
color="primary"
|
||||
@click="executeSendText"
|
||||
:disabled="!textContent">
|
||||
:disabled="!textContent"
|
||||
>
|
||||
Send
|
||||
</n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
</n-modal>
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.drop-zone {
|
||||
border: 2px dashed #ccc;
|
||||
border-radius: 8px;
|
||||
padding: 40px;
|
||||
text-align: center;
|
||||
border: 2px dashed #666; /* Use a darker color or theme var */
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
@@ -1,37 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, h } from "vue";
|
||||
import {
|
||||
NCard,
|
||||
NButton,
|
||||
NIcon,
|
||||
NProgress,
|
||||
NSpace,
|
||||
NText,
|
||||
NTag,
|
||||
useMessage,
|
||||
NInput,
|
||||
NDropdown,
|
||||
NButtonGroup,
|
||||
} from "naive-ui";
|
||||
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
|
||||
import {
|
||||
faArrowUp,
|
||||
faArrowDown,
|
||||
faCircleExclamation,
|
||||
faUser,
|
||||
faFile,
|
||||
faFileLines,
|
||||
faFolder,
|
||||
faClock,
|
||||
faChevronDown,
|
||||
faEye,
|
||||
faCopy,
|
||||
faTrash,
|
||||
faXmark,
|
||||
faStop,
|
||||
faCheck,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
import { computed, ref, h } from "vue";
|
||||
import { Transfer } from "../../bindings/mesh-drop/internal/transfer";
|
||||
import {
|
||||
ResolvePendingRequest,
|
||||
@@ -40,8 +8,6 @@ import {
|
||||
} from "../../bindings/mesh-drop/internal/transfer/service";
|
||||
import { Dialogs, Clipboard } from "@wailsio/runtime";
|
||||
|
||||
import { useDialog } from "naive-ui";
|
||||
|
||||
const props = defineProps<{
|
||||
transfer: Transfer;
|
||||
}>();
|
||||
@@ -72,10 +38,10 @@ const percentage = computed(() =>
|
||||
),
|
||||
),
|
||||
);
|
||||
const progressStatus = computed(() => {
|
||||
const progressColor = computed(() => {
|
||||
if (props.transfer.status === "error") return "error";
|
||||
if (props.transfer.status === "completed") return "success";
|
||||
return "default";
|
||||
return "primary";
|
||||
});
|
||||
|
||||
const acceptTransfer = () => {
|
||||
@@ -99,10 +65,10 @@ const acceptToFolder = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
const dropdownOptions = [
|
||||
const dropdownItems = [
|
||||
{
|
||||
label: "Accept To Folder",
|
||||
key: "folder",
|
||||
title: "Accept To Folder",
|
||||
value: "folder",
|
||||
},
|
||||
];
|
||||
|
||||
@@ -116,31 +82,18 @@ const handleDelete = () => {
|
||||
DeleteTransfer(props.transfer.id);
|
||||
};
|
||||
|
||||
const message = useMessage();
|
||||
|
||||
const handleCopy = async () => {
|
||||
Clipboard.SetText(props.transfer.text)
|
||||
.then(() => {
|
||||
message.success("Copied to clipboard");
|
||||
})
|
||||
// .then(() => {
|
||||
// message.success("Copied to clipboard");
|
||||
// })
|
||||
.catch(() => {
|
||||
message.error("Failed to copy to clipboard");
|
||||
// message.error("Failed to copy to clipboard");
|
||||
console.error("Failed to copy");
|
||||
});
|
||||
};
|
||||
|
||||
const dialog = useDialog();
|
||||
const handleOpen = async () => {
|
||||
const d = dialog.create({
|
||||
title: "Text Content",
|
||||
content: () =>
|
||||
h(NInput, {
|
||||
value: props.transfer.text,
|
||||
readonly: true,
|
||||
type: "textarea",
|
||||
rows: 10,
|
||||
}),
|
||||
});
|
||||
};
|
||||
const showContentDialog = ref(false);
|
||||
|
||||
const canCancel = computed(() => {
|
||||
if (
|
||||
@@ -186,269 +139,212 @@ const canAccept = computed(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-card size="small" class="transfer-item">
|
||||
<div class="transfer-row">
|
||||
<!-- 图标 -->
|
||||
<div class="icon-wrapper">
|
||||
<n-icon size="24" v-if="props.transfer.type === 'send'" color="#38bdf8">
|
||||
<FontAwesomeIcon :icon="faArrowUp" />
|
||||
</n-icon>
|
||||
<n-icon
|
||||
size="24"
|
||||
v-else-if="props.transfer.type === 'receive'"
|
||||
color="#22c55e">
|
||||
<FontAwesomeIcon :icon="faArrowDown" />
|
||||
</n-icon>
|
||||
<n-icon size="24" v-else color="#f59e0b">
|
||||
<FontAwesomeIcon :icon="faCircleExclamation" />
|
||||
</n-icon>
|
||||
</div>
|
||||
|
||||
<!-- 信息 -->
|
||||
<div class="info-wrapper">
|
||||
<div class="header-line">
|
||||
<n-text
|
||||
v-if="props.transfer.content_type === 'file'"
|
||||
strong
|
||||
class="filename"
|
||||
:title="props.transfer.file_name">
|
||||
<n-icon>
|
||||
<FontAwesomeIcon :icon="faFile" />
|
||||
</n-icon>
|
||||
{{ props.transfer.file_name }}
|
||||
</n-text>
|
||||
<n-text
|
||||
v-else-if="props.transfer.content_type === 'text'"
|
||||
strong
|
||||
class="filename"
|
||||
title="Text">
|
||||
<n-icon> <FontAwesomeIcon :icon="faFileLines" /> </n-icon>
|
||||
Text</n-text
|
||||
>
|
||||
<n-text
|
||||
v-else-if="props.transfer.content_type === 'folder'"
|
||||
strong
|
||||
class="filename"
|
||||
title="Folder">
|
||||
<n-icon> <FontAwesomeIcon :icon="faFolder" /> </n-icon>
|
||||
{{ props.transfer.file_name || "Folder" }}</n-text
|
||||
>
|
||||
<n-tag
|
||||
size="small"
|
||||
:bordered="false"
|
||||
v-if="
|
||||
props.transfer.sender.name && props.transfer.type === 'receive'
|
||||
">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<FontAwesomeIcon :icon="faUser" />
|
||||
</n-icon>
|
||||
</template>
|
||||
{{ props.transfer.sender.name }}
|
||||
</n-tag>
|
||||
<n-tag
|
||||
size="small"
|
||||
:bordered="false"
|
||||
v-if="props.transfer.create_time">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<FontAwesomeIcon :icon="faClock" />
|
||||
</n-icon>
|
||||
</template>
|
||||
{{ formatTime(props.transfer.create_time) }}
|
||||
</n-tag>
|
||||
<v-card class="transfer-item mb-2" variant="outlined">
|
||||
<v-card-text class="py-2 px-3">
|
||||
<div class="d-flex align-center flex-wrap ga-2">
|
||||
<!-- 图标 -->
|
||||
<div>
|
||||
<v-icon
|
||||
size="24"
|
||||
v-if="props.transfer.type === 'send'"
|
||||
color="info"
|
||||
icon="mdi-arrow-up"
|
||||
></v-icon>
|
||||
<v-icon
|
||||
size="24"
|
||||
v-else-if="props.transfer.type === 'receive'"
|
||||
color="success"
|
||||
icon="mdi-arrow-down"
|
||||
></v-icon>
|
||||
<v-icon
|
||||
size="24"
|
||||
v-else
|
||||
color="warning"
|
||||
icon="mdi-alert-circle"
|
||||
></v-icon>
|
||||
</div>
|
||||
|
||||
<div class="meta-line">
|
||||
<n-text depth="3" class="size">{{
|
||||
formatSize(props.transfer.file_size)
|
||||
}}</n-text>
|
||||
<!-- 信息 -->
|
||||
<div class="info-wrapper flex-grow-1" style="min-width: 0">
|
||||
<div class="d-flex align-center ga-2 mb-1 flex-wrap">
|
||||
<div class="font-weight-bold text-truncate d-flex align-center">
|
||||
<v-icon
|
||||
size="small"
|
||||
class="mr-1"
|
||||
v-if="props.transfer.content_type === 'file'"
|
||||
icon="mdi-file"
|
||||
></v-icon>
|
||||
<v-icon
|
||||
size="small"
|
||||
class="mr-1"
|
||||
v-else-if="props.transfer.content_type === 'text'"
|
||||
icon="mdi-file-document"
|
||||
></v-icon>
|
||||
<v-icon
|
||||
size="small"
|
||||
class="mr-1"
|
||||
v-else-if="props.transfer.content_type === 'folder'"
|
||||
icon="mdi-folder"
|
||||
></v-icon>
|
||||
{{
|
||||
props.transfer.file_name ||
|
||||
(props.transfer.content_type === "text" ? "Text" : "Folder")
|
||||
}}
|
||||
</div>
|
||||
|
||||
<!-- 状态文本(进行中/已完成) -->
|
||||
<span>
|
||||
<n-text depth="3" v-if="props.transfer.status === 'active'">
|
||||
- {{ formatSpeed(props.transfer.progress.speed) }}</n-text
|
||||
<v-chip
|
||||
size="x-small"
|
||||
v-if="
|
||||
props.transfer.sender.name && props.transfer.type === 'receive'
|
||||
"
|
||||
prepend-icon="mdi-account"
|
||||
>
|
||||
<n-text
|
||||
depth="3"
|
||||
{{ props.transfer.sender.name }}
|
||||
</v-chip>
|
||||
|
||||
<v-chip
|
||||
size="x-small"
|
||||
v-if="props.transfer.create_time"
|
||||
prepend-icon="mdi-clock-outline"
|
||||
>
|
||||
{{ formatTime(props.transfer.create_time) }}
|
||||
</v-chip>
|
||||
</div>
|
||||
|
||||
<div class="text-caption text-medium-emphasis d-flex align-center">
|
||||
<span>{{ formatSize(props.transfer.file_size) }}</span>
|
||||
|
||||
<!-- 状态文本 -->
|
||||
<span v-if="props.transfer.status === 'active'">
|
||||
- {{ formatSpeed(props.transfer.progress.speed) }}
|
||||
</span>
|
||||
<span
|
||||
v-if="props.transfer.status === 'completed'"
|
||||
type="success">
|
||||
- Completed</n-text
|
||||
class="text-success"
|
||||
>
|
||||
<n-text
|
||||
depth="3"
|
||||
v-if="props.transfer.status === 'error'"
|
||||
type="error">
|
||||
- {{ props.transfer.error_msg || "Error" }}</n-text
|
||||
>
|
||||
<n-text
|
||||
depth="3"
|
||||
v-if="props.transfer.status === 'canceled'"
|
||||
type="info">
|
||||
- Canceled</n-text
|
||||
>
|
||||
<n-text
|
||||
depth="3"
|
||||
- Completed
|
||||
</span>
|
||||
<span v-if="props.transfer.status === 'error'" class="text-error">
|
||||
- {{ props.transfer.error_msg || "Error" }}
|
||||
</span>
|
||||
<span v-if="props.transfer.status === 'canceled'" class="text-info">
|
||||
- Canceled
|
||||
</span>
|
||||
<span
|
||||
v-if="props.transfer.status === 'rejected'"
|
||||
type="error">
|
||||
- Rejected</n-text
|
||||
class="text-error"
|
||||
>
|
||||
<n-text
|
||||
depth="3"
|
||||
- Rejected
|
||||
</span>
|
||||
<span
|
||||
v-if="props.transfer.status === 'pending'"
|
||||
type="warning">
|
||||
- Waiting for accept</n-text
|
||||
class="text-warning"
|
||||
>
|
||||
</span>
|
||||
- Waiting for accept
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- 进度条 -->
|
||||
<v-progress-linear
|
||||
v-if="props.transfer.status === 'active'"
|
||||
:model-value="percentage"
|
||||
:color="progressColor"
|
||||
height="4"
|
||||
striped
|
||||
class="mt-1"
|
||||
></v-progress-linear>
|
||||
</div>
|
||||
|
||||
<!-- 进度条 -->
|
||||
<n-progress
|
||||
v-if="props.transfer.status === 'active'"
|
||||
type="line"
|
||||
:percentage="percentage"
|
||||
:status="progressStatus"
|
||||
:height="4"
|
||||
:show-indicator="false"
|
||||
processing
|
||||
style="margin-top: 4px" />
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div class="actions-wrapper">
|
||||
<n-space>
|
||||
<n-button-group size="small">
|
||||
<n-button v-if="canAccept" type="success" @click="acceptTransfer">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<FontAwesomeIcon :icon="faCheck" />
|
||||
</n-icon>
|
||||
</template>
|
||||
</n-button>
|
||||
<n-dropdown
|
||||
trigger="click"
|
||||
:options="dropdownOptions"
|
||||
@select="handleSelect"
|
||||
v-if="canAccept && props.transfer.content_type !== 'text'">
|
||||
<n-button type="success">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<FontAwesomeIcon :icon="faChevronDown" />
|
||||
</n-icon>
|
||||
</template>
|
||||
</n-button>
|
||||
</n-dropdown>
|
||||
<n-button
|
||||
<!-- 操作按钮 -->
|
||||
<div class="actions-wrapper">
|
||||
<v-btn-group density="compact" variant="outlined" divided>
|
||||
<v-btn
|
||||
v-if="canAccept"
|
||||
size="small"
|
||||
type="error"
|
||||
@click="rejectTransfer">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<FontAwesomeIcon :icon="faXmark" />
|
||||
</n-icon>
|
||||
</template>
|
||||
</n-button>
|
||||
color="success"
|
||||
icon="mdi-check"
|
||||
@click="acceptTransfer"
|
||||
></v-btn>
|
||||
|
||||
<n-button type="success" @click="handleOpen" v-if="canCopy"
|
||||
><template #icon>
|
||||
<n-icon>
|
||||
<FontAwesomeIcon :icon="faEye" />
|
||||
</n-icon>
|
||||
<v-menu v-if="canAccept && props.transfer.content_type !== 'text'">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn
|
||||
color="success"
|
||||
icon="mdi-chevron-down"
|
||||
v-bind="props"
|
||||
></v-btn>
|
||||
</template>
|
||||
</n-button>
|
||||
<n-button type="success" @click="handleCopy" v-if="canCopy"
|
||||
><template #icon>
|
||||
<n-icon>
|
||||
<FontAwesomeIcon :icon="faCopy" />
|
||||
</n-icon>
|
||||
</template>
|
||||
</n-button>
|
||||
<n-button
|
||||
type="success"
|
||||
@click="handleDelete"
|
||||
<v-list>
|
||||
<v-list-item
|
||||
v-for="(item, index) in dropdownItems"
|
||||
:key="index"
|
||||
:value="item.value"
|
||||
@click="handleSelect(item.value)"
|
||||
>
|
||||
<v-list-item-title>{{ item.title }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
|
||||
<v-btn
|
||||
v-if="canAccept"
|
||||
color="error"
|
||||
icon="mdi-close"
|
||||
@click="rejectTransfer"
|
||||
></v-btn>
|
||||
|
||||
<v-btn
|
||||
v-if="canCopy"
|
||||
color="success"
|
||||
icon="mdi-eye"
|
||||
@click="showContentDialog = true"
|
||||
></v-btn>
|
||||
|
||||
<v-btn
|
||||
v-if="canCopy"
|
||||
color="success"
|
||||
icon="mdi-content-copy"
|
||||
@click="handleCopy"
|
||||
></v-btn>
|
||||
|
||||
<v-btn
|
||||
v-if="
|
||||
props.transfer.status === 'completed' ||
|
||||
props.transfer.status === 'error' ||
|
||||
props.transfer.status === 'canceled' ||
|
||||
props.transfer.status === 'rejected'
|
||||
">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<FontAwesomeIcon :icon="faTrash" />
|
||||
</n-icon>
|
||||
</template>
|
||||
</n-button>
|
||||
<n-button
|
||||
"
|
||||
color="info"
|
||||
icon="mdi-delete"
|
||||
@click="handleDelete"
|
||||
></v-btn>
|
||||
|
||||
<v-btn
|
||||
v-if="canCancel"
|
||||
size="small"
|
||||
type="error"
|
||||
color="error"
|
||||
icon="mdi-stop"
|
||||
@click="CancelTransfer(props.transfer.id)"
|
||||
><template #icon>
|
||||
<n-icon>
|
||||
<FontAwesomeIcon :icon="faStop" />
|
||||
</n-icon>
|
||||
</template>
|
||||
</n-button>
|
||||
</n-button-group>
|
||||
</n-space>
|
||||
></v-btn>
|
||||
</v-btn-group>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</n-card>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
|
||||
<v-dialog v-model="showContentDialog" width="600">
|
||||
<v-card title="Text Content">
|
||||
<v-card-text>
|
||||
<v-textarea
|
||||
:model-value="props.transfer.text"
|
||||
readonly
|
||||
rows="10"
|
||||
></v-textarea>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.transfer-item {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.transfer-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.icon-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.info-wrapper {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.header-line {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.filename {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.meta-line {
|
||||
font-size: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.actions-wrapper {
|
||||
width: 100%;
|
||||
margin-top: 8px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.transfer-row {
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,4 +1,23 @@
|
||||
import { createApp } from 'vue'
|
||||
/**
|
||||
* main.ts
|
||||
*
|
||||
* Bootstraps Vuetify and other plugins then mounts the App`
|
||||
*/
|
||||
|
||||
// Plugins
|
||||
import { registerPlugins } from '@/plugins'
|
||||
|
||||
// Components
|
||||
import App from './App.vue'
|
||||
|
||||
createApp(App).mount('#app')
|
||||
// Composables
|
||||
import { createApp } from 'vue'
|
||||
|
||||
// Styles
|
||||
import 'unfonts.css'
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
registerPlugins(app)
|
||||
|
||||
app.mount('#app')
|
||||
|
||||
15
frontend/src/plugins/index.ts
Normal file
15
frontend/src/plugins/index.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* plugins/index.ts
|
||||
*
|
||||
* Automatically included in `./src/main.ts`
|
||||
*/
|
||||
|
||||
// Plugins
|
||||
import vuetify from './vuetify'
|
||||
|
||||
// Types
|
||||
import type { App } from 'vue'
|
||||
|
||||
export function registerPlugins (app: App) {
|
||||
app.use(vuetify)
|
||||
}
|
||||
19
frontend/src/plugins/vuetify.ts
Normal file
19
frontend/src/plugins/vuetify.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* plugins/vuetify.ts
|
||||
*
|
||||
* Framework documentation: https://vuetifyjs.com`
|
||||
*/
|
||||
|
||||
// Styles
|
||||
import '@mdi/font/css/materialdesignicons.css'
|
||||
import 'vuetify/styles'
|
||||
|
||||
// Composables
|
||||
import { createVuetify } from 'vuetify'
|
||||
|
||||
// https://vuetifyjs.com/en/introduction/why-vuetify/#feature-guides
|
||||
export default createVuetify({
|
||||
theme: {
|
||||
defaultTheme: 'system',
|
||||
},
|
||||
})
|
||||
@@ -11,9 +11,18 @@
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"esModuleInterop": true,
|
||||
"lib": ["ESNext", "DOM"],
|
||||
"lib": [
|
||||
"ESNext",
|
||||
"DOM"
|
||||
],
|
||||
"skipLibCheck": true,
|
||||
"noEmit": true
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue", "bindings"],
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"src/**/*.d.ts",
|
||||
"src/**/*.tsx",
|
||||
"src/**/*.vue",
|
||||
"bindings"
|
||||
],
|
||||
}
|
||||
55
frontend/vite.config.mts
Normal file
55
frontend/vite.config.mts
Normal file
@@ -0,0 +1,55 @@
|
||||
// Plugins
|
||||
import Components from 'unplugin-vue-components/vite'
|
||||
import Vue from '@vitejs/plugin-vue'
|
||||
import Vuetify, { transformAssetUrls } from 'vite-plugin-vuetify'
|
||||
import Fonts from 'unplugin-fonts/vite'
|
||||
import wails from "@wailsio/runtime/plugins/vite";
|
||||
|
||||
// Utilities
|
||||
import { defineConfig } from 'vite'
|
||||
import { fileURLToPath, URL } from 'node:url'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
Vue({
|
||||
template: { transformAssetUrls },
|
||||
}),
|
||||
wails("./bindings"),
|
||||
// https://github.com/vuetifyjs/vuetify-loader/tree/master/packages/vite-plugin#readme
|
||||
Vuetify(),
|
||||
Components(),
|
||||
Fonts({
|
||||
fontsource: {
|
||||
families: [
|
||||
{
|
||||
name: 'Roboto',
|
||||
weights: [100, 300, 400, 500, 700, 900],
|
||||
styles: ['normal', 'italic'],
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
],
|
||||
optimizeDeps: {
|
||||
exclude: ['vuetify'],
|
||||
},
|
||||
define: { 'process.env': {} },
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('src', import.meta.url)),
|
||||
},
|
||||
extensions: [
|
||||
'.js',
|
||||
'.json',
|
||||
'.jsx',
|
||||
'.mjs',
|
||||
'.ts',
|
||||
'.tsx',
|
||||
'.vue',
|
||||
],
|
||||
},
|
||||
server: {
|
||||
port: 3000,
|
||||
},
|
||||
})
|
||||
@@ -1,8 +0,0 @@
|
||||
import { defineConfig } from "vite";
|
||||
import vue from "@vitejs/plugin-vue";
|
||||
import wails from "@wailsio/runtime/plugins/vite";
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [vue(), wails("./bindings")],
|
||||
});
|
||||
2
go.mod
2
go.mod
@@ -5,6 +5,7 @@ go 1.25
|
||||
require (
|
||||
github.com/gin-gonic/gin v1.11.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/spf13/viper v1.21.0
|
||||
github.com/wailsapp/wails/v3 v3.0.0-alpha.67
|
||||
)
|
||||
|
||||
@@ -64,7 +65,6 @@ require (
|
||||
github.com/spf13/afero v1.15.0 // indirect
|
||||
github.com/spf13/cast v1.10.0 // indirect
|
||||
github.com/spf13/pflag v1.0.10 // indirect
|
||||
github.com/spf13/viper v1.21.0 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.3.0 // indirect
|
||||
|
||||
2
go.sum
2
go.sum
@@ -34,6 +34,8 @@ github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o
|
||||
github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
|
||||
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
||||
|
||||
3
main.go
3
main.go
@@ -61,6 +61,9 @@ func main() {
|
||||
X: conf.WindowState.X,
|
||||
Y: conf.WindowState.Y,
|
||||
EnableFileDrop: true,
|
||||
Linux: application.LinuxWindow{
|
||||
WebviewGpuPolicy: application.WebviewGpuPolicyAlways,
|
||||
},
|
||||
})
|
||||
|
||||
window.OnWindowEvent(events.Common.WindowFilesDropped, func(event *application.WindowEvent) {
|
||||
|
||||
Reference in New Issue
Block a user