Compare commits
5 Commits
Author | SHA1 | Date |
---|---|---|
|
9a9eef5a7c | 5 days ago |
|
fe33e9fd1a | 6 days ago |
|
9a5776d586 | 6 days ago |
|
f1833df97b | 1 week ago |
|
403e6608d3 | 2 weeks ago |
92 changed files with 1950 additions and 1285 deletions
@ -0,0 +1,113 @@ |
|||
# https://zhuanlan.zhihu.com/p/164901026 |
|||
# https://www.antmoe.com/posts/18c087cf/ |
|||
# https://zhuanlan.zhihu.com/p/348712087 |
|||
# https://cloud.tencent.com/developer/article/1949574 |
|||
# 此工作流的名字 |
|||
name: Build |
|||
# 工作流的执行时机,可以设定为定时执行,每次push后执行,手动执行等 |
|||
on: |
|||
# workflow_dispatch为在Github仓库的Actions面板中手动执行 |
|||
# workflow_dispatch: |
|||
push: |
|||
branches: |
|||
- master |
|||
# 工作/任务,这里的工作是可以并行的。 |
|||
jobs: |
|||
# 工作的名称“编译windows版” |
|||
build: |
|||
# 运行的操作系统 windows |
|||
runs-on: ${{ matrix.os }} |
|||
env: |
|||
GH_TOKEN: ${{ secrets.ELECTRON_TOKEN }} |
|||
IS_ACTIONS: true |
|||
strategy: |
|||
matrix: |
|||
# https://www.likecs.com/ask-314443.html |
|||
node-version: [18.17.1] |
|||
os: [windows-2022, ubuntu-latest] |
|||
# 步骤 |
|||
steps: |
|||
# 使用预制action:拉取最新的代码 |
|||
- uses: actions/checkout@v3 |
|||
with: |
|||
ref: master |
|||
# https://pnpm.io/zh/continuous-integration/#github-actions |
|||
- uses: pnpm/action-setup@v2.2.4 |
|||
name: Install pnpm |
|||
id: pnpm-install |
|||
with: |
|||
version: 8.7.6 |
|||
# 安装node |
|||
- name: Use Node.js ${{ matrix.node-version }} |
|||
uses: actions/setup-node@v3 |
|||
with: |
|||
node-version: ${{ matrix.node-version }} |
|||
cache: 'pnpm' |
|||
- name: Install dependencies |
|||
run: pnpm install |
|||
# 安装python |
|||
- name: Use Python 3.9.13 |
|||
uses: actions/setup-python@v4 |
|||
with: |
|||
python-version: 3.9.13 |
|||
env: |
|||
PYTHON_VERSION: 3.9.13 |
|||
# https://docs.microsoft.com/zh-cn/visualstudio/releases/2017/vs2017-system-requirements-vs |
|||
# 将windows设置成windows-2016,2016要取消支持了,可换成2022 |
|||
# - name: set msvs |
|||
# run: npm config set msvs_version 2022 |
|||
# https://github.com/wxWidgets/wxWidgets/blob/master/.github/workflows/ci_msw.yml |
|||
# https://github.com/microsoft/setup-msbuild |
|||
- name: Add msbuild to PATH |
|||
if: matrix.os == 'windows-2022' |
|||
uses: microsoft/setup-msbuild@v1.1 |
|||
with: |
|||
vs-prerelease: true |
|||
# 步骤一的名称: |
|||
- name: Build |
|||
# 该步骤运行的终端命令,运行编译命令 |
|||
run: npm run build |
|||
# 步骤二的名称:将编译后的结果上传 |
|||
# - name: Upload File |
|||
# # 使用预制action:上传文件,可以将执行路径打包成zip上传 |
|||
# uses: actions/upload-artifact@v3 |
|||
# with: |
|||
# # 上传后文件的名称 |
|||
# name: windows |
|||
# # 打包的路径以及文件过滤,此为仅打包dist目录下的exe文件 |
|||
# path: out/*exe |
|||
- name: 读取当前版本号 |
|||
id: version |
|||
uses: ashley-taylor/read-json-property-action@v1.1 |
|||
with: |
|||
path: ./dist/package.json |
|||
property: version |
|||
- name: 读取描述文件 |
|||
id: description |
|||
uses: juliangruber/read-file-action@v1 |
|||
with: |
|||
path: ./changelog/${{steps.version.outputs.value}}.md |
|||
# step5: cleanup artifacts in dist_electron |
|||
# - name: 清理不必要的资产 |
|||
# run: | |
|||
# npx rimraf "out/!(*.exe|*.dmg)" |
|||
# - name: Generate release tag |
|||
# id: tag |
|||
# run: | |
|||
# echo "::set-output name=release_tag::UserBuild_$(date +"%Y.%m.%d_%H-%M")" |
|||
- name: Generate release tag |
|||
id: tag |
|||
run: | |
|||
echo "::set-output name=release_tag::v${{steps.version.outputs.value}}" |
|||
# echo "release_tag=v${{steps.version.outputs.value}}" >> $GITHUB_OUTPUT |
|||
- name: release # https://github.com/softprops/action-gh-release/issues/20 |
|||
uses: softprops/action-gh-release@v1 |
|||
with: |
|||
tag_name: ${{ steps.tag.outputs.release_tag }} |
|||
name: ${{ steps.tag.outputs.release_tag }} |
|||
files: "out/*exe,out/*dmg,out/*AppImage,out/*yml" |
|||
body: ${{steps.description.outputs.content}} |
|||
draft: false |
|||
prerelease: false |
|||
env: |
|||
GITHUB_TOKEN: ${{ secrets.ELECTRON_TOKEN }} |
@ -0,0 +1,3 @@ |
|||
## 0.0.1 |
|||
|
|||
第一个版本 |
@ -1,3 +1,5 @@ |
|||
// @ts-nocheck window.api 不需要检查
|
|||
|
|||
import { IApiClient } from "./abstract" |
|||
|
|||
export class ElectronApiClient implements IApiClient { |
@ -1,27 +1,13 @@ |
|||
import { injectable } from "inversify" |
|||
import Setting from "setting/main" |
|||
import { CustomAdapter, CustomLow } from "./custom" |
|||
import path from "node:path" |
|||
import BaseClass from "main/base/base" |
|||
import _logger from "logger/main" |
|||
// import _logger from "logger/main"
|
|||
|
|||
const logger = _logger.createNamespace("db") |
|||
// const logger = _logger.createNamespace("db")
|
|||
|
|||
@injectable() |
|||
class DB extends BaseClass { |
|||
destroy() { |
|||
logger.debug(`DB destroy`) |
|||
} |
|||
class DB { |
|||
Modules: Record<string, CustomLow<any>> = {} |
|||
|
|||
constructor() { |
|||
super() |
|||
} |
|||
|
|||
init() { |
|||
console.log("DB Init") |
|||
} |
|||
|
|||
create(filepath) { |
|||
const adapter = new CustomAdapter<any>(filepath) |
|||
const db = new CustomLow<object>(adapter, {}) |
@ -1,3 +1,15 @@ |
|||
import { UpdateInfo } from "electron-updater" |
|||
|
|||
export interface UpdaterCommand { |
|||
checkForUpdates: () => void |
|||
} |
|||
|
|||
export type EventMaps = { |
|||
"update-progress": (data: { percent: number; all: number; now: number }) => void |
|||
"update-progress": (data: { speed: number; percent: number; all: number; now: number }) => void |
|||
error: (err: any) => void |
|||
"updater:error": (info: UpdateInfo) => void |
|||
"checking-for-update": () => void |
|||
"update-available": (info: UpdateInfo) => void |
|||
"update-not-available": (info: UpdateInfo) => void |
|||
"updater:downloaded": (p: any) => void |
|||
} |
|||
|
@ -0,0 +1,7 @@ |
|||
import type { IOnFunc } from "setting/main" |
|||
|
|||
export type EventMaps = { |
|||
init: IOnFunc |
|||
update: IOnFunc |
|||
change: (key: string, value: any) => void |
|||
} |
@ -1,6 +1,4 @@ |
|||
import { buildEmitter } from "base/event/main" |
|||
import type { IOnFunc } from "setting/main" |
|||
import { EventMaps } from "setting/common" |
|||
|
|||
export const emitter = buildEmitter<{ |
|||
update: IOnFunc |
|||
}>() |
|||
export const emitter = buildEmitter<EventMaps>() |
|||
|
@ -1,5 +0,0 @@ |
|||
import { PlatForm } from "." |
|||
|
|||
export function usePlatForm() { |
|||
return PlatForm.getInstance<PlatForm>() |
|||
} |
@ -1,5 +0,0 @@ |
|||
import { Snippet } from "." |
|||
|
|||
export function useSnippet() { |
|||
return Snippet.getInstance() |
|||
} |
@ -1,18 +0,0 @@ |
|||
import { BaseSingleton } from "base" |
|||
import { ApiFactory } from "common/lib/abstract" |
|||
|
|||
class Snippet extends BaseSingleton { |
|||
constructor() { |
|||
super() |
|||
} |
|||
|
|||
private get api() { |
|||
return ApiFactory.getApiClient() |
|||
} |
|||
|
|||
getTree = async () => { |
|||
return this.api.call("SnippetCommand.getTree") |
|||
} |
|||
} |
|||
|
|||
export { Snippet } |
@ -1,16 +0,0 @@ |
|||
import { EventMaps } from "helper/updater/common" |
|||
import { defineStore } from "pinia" |
|||
|
|||
export const useSettingStore = defineStore( |
|||
"Updater", |
|||
() => { |
|||
getApi<EventMaps>().on("update-progress", (_, data) => { |
|||
console.log(data) |
|||
}) |
|||
|
|||
return {} |
|||
}, |
|||
{ |
|||
persist: false, |
|||
}, |
|||
) |
@ -1,7 +0,0 @@ |
|||
## event |
|||
|
|||
通用事件处理模块 |
|||
|
|||
- main/**/* 处理主进程的模块 |
|||
- main/command.ts 会通过ioc收集,进入依赖管理中 |
|||
- 其他 处理渲染进程的模块 |
@ -1,165 +0,0 @@ |
|||
import { inject, injectable } from "inversify" |
|||
// import Setting from "./modules/setting"
|
|||
// import DB from "./modules/db"
|
|||
import Api from "./modules/api" |
|||
import WindowManager from "./modules/window-manager" |
|||
import { app, nativeTheme, protocol, WebContentsView } from "electron" |
|||
import { electronApp } from "@electron-toolkit/utils" |
|||
// import Tabs from "./modules/tabs/Tabs"
|
|||
import { getFileUrl } from "./utils" |
|||
import BaseClass from "./base/base" |
|||
|
|||
protocol.registerSchemesAsPrivileged([ |
|||
// {
|
|||
// scheme: "http",
|
|||
// privileges: { standard: true, bypassCSP: true, allowServiceWorkers: true, supportFetchAPI: true, corsEnabled: true, stream: true },
|
|||
// },
|
|||
// {
|
|||
// scheme: "https",
|
|||
// privileges: { standard: true, bypassCSP: true, allowServiceWorkers: true, supportFetchAPI: true, corsEnabled: true, stream: true },
|
|||
// },
|
|||
// { scheme: "mailto", privileges: { standard: true } },
|
|||
{ |
|||
scheme: "api", |
|||
privileges: { |
|||
standard: true, |
|||
secure: true, |
|||
supportFetchAPI: true, |
|||
}, |
|||
}, |
|||
]) |
|||
|
|||
@injectable() |
|||
class App extends BaseClass { |
|||
destroy() { |
|||
// destroyAll()
|
|||
// 这里是应用正常退出
|
|||
} |
|||
// private _setting: Setting
|
|||
// private _db: DB
|
|||
private _Api: Api |
|||
private _windowManager: WindowManager |
|||
// private _tabs: Tabs
|
|||
|
|||
constructor( |
|||
// @inject(Setting) setting: Setting,
|
|||
// @inject(DB) db: DB,
|
|||
@inject(Api) Api: Api, |
|||
@inject(WindowManager) windowManager: WindowManager, |
|||
// @inject(Tabs) tabs: Tabs,
|
|||
) { |
|||
super() |
|||
// this._setting = setting
|
|||
// this._db = db
|
|||
this._Api = Api |
|||
this._windowManager = windowManager |
|||
// this._tabs = tabs
|
|||
} |
|||
|
|||
async init() { |
|||
this._windowManager.init() |
|||
app.whenReady().then(() => { |
|||
electronApp.setAppUserModelId("top.xieyaxin") |
|||
this.create() |
|||
this._Api.init() |
|||
}) |
|||
app.on("window-all-closed", () => { |
|||
if (process.platform !== "darwin") { |
|||
app.quit() |
|||
} |
|||
}) |
|||
app.on("will-quit", () => { |
|||
this.destroy() |
|||
}) |
|||
} |
|||
|
|||
create() { |
|||
this._windowManager.showMainWindow() |
|||
const mainWindow = this._windowManager.getMainWindow() |
|||
if (mainWindow) { |
|||
nativeTheme.themeSource = "light" |
|||
mainWindow.setTitleBarOverlay({ |
|||
height: 29, // the smallest size of the title bar on windows accounting for the border on windows 11
|
|||
color: "#F8F8F8", |
|||
symbolColor: "#000000", |
|||
}) |
|||
this._windowManager.showWindow("main-top") |
|||
const mainTopWindow = this._windowManager.get("main-top") |
|||
setTimeout(() => { |
|||
// console.log(mainWindow.getParentWindow());
|
|||
setTimeout(() => { |
|||
mainWindow.contentView.children.length = 0 |
|||
const view = new WebContentsView() |
|||
view.addChildView(mainTopWindow!.contentView) |
|||
view.webContents.loadURL(getFileUrl("about.html")) |
|||
// mainTopWindow!.contentView.setBounds({ x: 0, y: 0, width: 100, height: 30 })
|
|||
// view.setBounds({ x: 0, y: 0, width: 100, height: 30 })
|
|||
mainWindow.contentView.addChildView(view) |
|||
// mainWindow.contentView.children.sort()
|
|||
console.log(mainWindow.contentView.children) |
|||
}, 5000) |
|||
// mainWindow.webContents = mainTopWindow!.webContents
|
|||
mainWindow.reload() |
|||
console.log(mainWindow.webContents.getURL()) |
|||
|
|||
// mainTopWindow?.destroy()
|
|||
// mainWindow.contentView.addChildView(mainWindow.contentView)
|
|||
console.log(`child count: `, mainWindow.contentView.children.length) |
|||
}, 2000) |
|||
// if (mainTopWindow) {
|
|||
// mainTopWindow.setParentWindow(mainWindow)
|
|||
// mainTopWindow.setIgnoreMouseEvents(true, { forward: false })
|
|||
// const listenMove = () => {
|
|||
// if (mainWindow && mainTopWindow) {
|
|||
// const pos = mainWindow.getPosition()
|
|||
// mainTopWindow.setPosition(pos[0], pos[1])
|
|||
// }
|
|||
// }
|
|||
// mainWindow?.on("move", listenMove)
|
|||
// const listenResize = () => {
|
|||
// if (mainWindow && mainTopWindow) {
|
|||
// const size = mainWindow.getSize()
|
|||
// console.log(size)
|
|||
// mainTopWindow.setSize(size[0], size[1])
|
|||
// const pos = mainWindow.getPosition()
|
|||
// mainTopWindow.setPosition(pos[0], pos[1])
|
|||
// }
|
|||
// }
|
|||
// listenResize()
|
|||
// mainWindow?.on("resize", listenResize)
|
|||
// }
|
|||
} |
|||
// 考虑双browserwindow模式
|
|||
/** |
|||
* 因为browserwindow可以设置穿透,考虑将tab放在底层window上,其他组件放在上层window上。 |
|||
*/ |
|||
// const webContentsView = new WebContentsView({
|
|||
// webPreferences: {
|
|||
// preload: join(__dirname, "../preload/index.mjs"),
|
|||
// transparent: true,
|
|||
// nodeIntegration: true,
|
|||
// spellcheck: false,
|
|||
// contextIsolation: true,
|
|||
// },
|
|||
// })
|
|||
// // mainWindow!.contentView = webContentsView
|
|||
// // setTimeout(() => {
|
|||
// mainWindow!.contentView.addChildView(webContentsView)
|
|||
// // mainWindow?.setIgnoreMouseEvents(true, { forward: true })
|
|||
// // }, 2000);
|
|||
// webContentsView.webContents.loadURL(getFileUrl("index.html"))
|
|||
// const listenResize = () => {
|
|||
// const size = mainWindow!.getSize()
|
|||
// webContentsView.setBounds({ x: 0, y: 0, width: size[0], height: size[1] })
|
|||
// }
|
|||
// listenResize()
|
|||
// mainWindow!.addListener("resize", listenResize)
|
|||
|
|||
// this._tabs.add("https://baidu.com", true)
|
|||
// this._tabs.add("https://zhihu.com")
|
|||
return mainWindow |
|||
} |
|||
} |
|||
|
|||
export default App |
|||
export { App } |
@ -1,8 +1,12 @@ |
|||
import Setting, { IConfig } from "setting/main" |
|||
import { broadcast } from "utils/main" |
|||
|
|||
export default class SettingCommand { |
|||
static init() { |
|||
console.log("SettingCommand init") |
|||
Setting.events.on("change", (k, value) => { |
|||
console.log(k, value) |
|||
broadcast("SettingCommand.change", k, value) |
|||
}) |
|||
} |
|||
sync() { |
|||
return Setting.config() |
@ -0,0 +1,8 @@ |
|||
import type { EventMaps } from "setting/common" |
|||
|
|||
export interface SettingCommand { |
|||
save: () => void |
|||
reset: () => void |
|||
} |
|||
|
|||
export { EventMaps } |
@ -0,0 +1,13 @@ |
|||
.slide-fade-enter-active { |
|||
transition: all 0.2s ease-out; |
|||
} |
|||
|
|||
.slide-fade-leave-active { |
|||
transition: all 0.2s cubic-bezier(1, 0.5, 0.8, 1); |
|||
} |
|||
|
|||
.slide-fade-enter-from, |
|||
.slide-fade-leave-to { |
|||
// transform: translateX(20px); |
|||
opacity: 0; |
|||
} |
@ -0,0 +1,5 @@ |
|||
@use "bulma/sass/base"; |
|||
@use "bulma/sass/themes"; |
|||
|
|||
@use "bulma/sass/elements/button"; |
|||
@use "bulma/sass/form/input-textarea"; |
@ -0,0 +1,33 @@ |
|||
import { monaco } from "./monaco" |
|||
|
|||
const defaultOptions: monaco.editor.IStandaloneEditorConstructionOptions = { |
|||
fontSize: 14, |
|||
readOnly: false, |
|||
theme: "vs-light", |
|||
fontFamily: "Cascadia Mono, Consolas, 'Courier New', monospace", |
|||
lineHeight: 22, |
|||
scrollBeyondLastLine: false, |
|||
automaticLayout: true, |
|||
minimap: { |
|||
enabled: false, |
|||
}, |
|||
} |
|||
|
|||
function getOptions(opt = {}) { |
|||
return { |
|||
...defaultOptions, |
|||
...opt, |
|||
} |
|||
} |
|||
|
|||
export function useMonacoEditor(editor: HTMLDivElement) { |
|||
let editor: monaco.editor.IStandaloneCodeEditor | null = null |
|||
let placeholderWidget: PlaceholderContentWidget | null = null |
|||
|
|||
|
|||
return { |
|||
scrollTop() { |
|||
|
|||
} |
|||
} |
|||
} |
@ -1,4 +1,4 @@ |
|||
import { ApiFactory } from "common/lib/abstract" |
|||
import { ApiFactory } from "base/api/abstract" |
|||
import { BaseSingleton } from "base" |
|||
import { LogLevel } from "packages/logger/common" |
|||
|
@ -0,0 +1,41 @@ |
|||
import { LogLevel } from "logger/common" |
|||
import { PlatForm } from "./_" |
|||
|
|||
export function useApiPlatForm() { |
|||
const plat = PlatForm.getInstance<PlatForm>() |
|||
|
|||
// 全屏状态
|
|||
const isFullScreen = ref(false) |
|||
;(async () => { |
|||
isFullScreen.value = await plat.isFullScreen() |
|||
})() |
|||
|
|||
const toggleFullScreen = async () => { |
|||
await plat.toggleFullScreen() |
|||
isFullScreen.value = !isFullScreen.value |
|||
} |
|||
// 全屏状态 END
|
|||
|
|||
const curLogLevel = ref<LogLevel>() |
|||
;(async () => { |
|||
curLogLevel.value = await plat.logGetLevel() |
|||
})() |
|||
const isOpenDebug = computed(() => curLogLevel.value === LogLevel.TRACE) |
|||
const toggleDebugMode = async () => { |
|||
if (curLogLevel.value === LogLevel.TRACE) { |
|||
await plat.logSetLevel(LogLevel.INFO) |
|||
curLogLevel.value = LogLevel.INFO |
|||
return |
|||
} |
|||
await plat.logSetLevel(LogLevel.TRACE) |
|||
curLogLevel.value = LogLevel.TRACE |
|||
} |
|||
|
|||
return { |
|||
power: plat, |
|||
isOpenDebug, |
|||
toggleDebugMode, |
|||
toggleFullScreen, |
|||
isFullScreen, |
|||
} |
|||
} |
@ -1,4 +1,4 @@ |
|||
import { ApiFactory } from "common/lib/abstract" |
|||
import { ApiFactory } from "base/api/abstract" |
|||
import { BaseSingleton } from "base" |
|||
import { IConfig } from "config" |
|||
|
@ -1,12 +1,20 @@ |
|||
import { defineStore } from "pinia" |
|||
import { Setting } from "." |
|||
import { Setting } from "./_" |
|||
import type { IConfig } from "config" |
|||
import type { EventMaps, SettingCommand } from "command/Setting/type" |
|||
|
|||
let rawConfig: IConfig = Setting.getInstance().sync() as unknown as IConfig |
|||
|
|||
export const useSettingStore = defineStore( |
|||
export const useApiSetting = defineStore( |
|||
"Setting", |
|||
() => { |
|||
const api = getApi<SettingCommand, EventMaps, "SettingCommand">() |
|||
|
|||
api.on("SettingCommand.change", (_, k, v) => { |
|||
rawConfig[k] = v |
|||
config.value = JSON.parse(JSON.stringify(rawConfig)) |
|||
}) |
|||
|
|||
const config = ref(JSON.parse(JSON.stringify(rawConfig))) |
|||
const diffKeys = ref<(keyof IConfig)[]>([]) |
|||
const isSame = computed(() => { |
@ -1,4 +1,4 @@ |
|||
import { BaseEvent } from "common/lib/abstract" |
|||
import { BaseEvent } from "base/api/abstract" |
|||
|
|||
class Updater extends BaseEvent { |
|||
constructor() { |
@ -0,0 +1,70 @@ |
|||
import { EventMaps, UpdaterCommand } from "helper/updater/common" |
|||
import { defineStore } from "pinia" |
|||
|
|||
export const enum ApiUpdaterStatus { |
|||
Idle = "idle", |
|||
Checking = "checking", |
|||
StartChecking = "start-checking", |
|||
UpdateAvailable = "update-available", |
|||
UpdateNotAvailable = "update-not-available", |
|||
Downloading = "downloading", |
|||
Error = "error", |
|||
} |
|||
|
|||
export const useApiUpdater = defineStore( |
|||
"Updater", |
|||
() => { |
|||
const curStatus = ref(ApiUpdaterStatus.Idle) |
|||
const speed = ref(0) |
|||
const percent = ref(0) |
|||
const all = ref(0) |
|||
const now = ref(0) |
|||
|
|||
const isNeedUpdate = ref(false) |
|||
|
|||
const api = getApi<UpdaterCommand, EventMaps, "UpdaterCommand">() |
|||
api.on("UpdaterCommand.error", (_, data) => { |
|||
curStatus.value = ApiUpdaterStatus.Error |
|||
console.log(data) |
|||
}) |
|||
api.on("UpdaterCommand.update-not-available", () => { |
|||
curStatus.value = ApiUpdaterStatus.UpdateNotAvailable |
|||
isNeedUpdate.value = false |
|||
}) |
|||
api.on("UpdaterCommand.update-available", () => { |
|||
curStatus.value = ApiUpdaterStatus.UpdateAvailable |
|||
isNeedUpdate.value = true |
|||
}) |
|||
api.on("UpdaterCommand.update-progress", (_, data) => { |
|||
curStatus.value = ApiUpdaterStatus.Downloading |
|||
speed.value = +(data.speed / 1000).toFixed(2) // Convert to KB/s
|
|||
percent.value = data.percent |
|||
all.value = data.all |
|||
now.value = data.now |
|||
isNeedUpdate.value = false |
|||
}) |
|||
api.on("UpdaterCommand.checking-for-update", () => { |
|||
curStatus.value = ApiUpdaterStatus.Checking |
|||
}) |
|||
if (import.meta.env.PROD) { |
|||
api.callLong("UpdaterCommand.checkForUpdates") |
|||
} |
|||
return { |
|||
status: curStatus, |
|||
speed: speed, |
|||
percent: percent, |
|||
all: all, |
|||
now: now, |
|||
isNeedUpdate, |
|||
checkForUpdates() { |
|||
if (curStatus.value === ApiUpdaterStatus.Checking) return |
|||
if (curStatus.value === ApiUpdaterStatus.Downloading) return |
|||
curStatus.value = ApiUpdaterStatus.StartChecking |
|||
api.callLong("UpdaterCommand.checkForUpdates") |
|||
}, |
|||
} |
|||
}, |
|||
{ |
|||
persist: false, |
|||
}, |
|||
) |
@ -1,3 +0,0 @@ |
|||
export function useTest() { |
|||
console.log("test") |
|||
} |
@ -1,7 +1,33 @@ |
|||
<script setup lang="ts"></script> |
|||
<script setup lang="ts"> |
|||
// import { useUpdaterStore } from "common/event/Updater/hook" |
|||
|
|||
// const UpdaterStore = useUpdaterStore() |
|||
|
|||
const code = ref("") |
|||
const aa = ref(true) |
|||
</script> |
|||
|
|||
<template> |
|||
<div h-full flex flex-col>sad</div> |
|||
<div gap="20px" h-full> |
|||
<input type="text" v-model="code" placeholder="请输入文本" class="input" /> |
|||
<input type="checkbox" v-model="aa"> {{ aa }} |
|||
<div h-300px> |
|||
<code-editor :options="{ readOnly: aa }" v-model="code" placeholder="请输入文本"></code-editor> |
|||
</div> |
|||
<div p="20px" flex flex-wrap items-start gap="20px" justify-start rounded> |
|||
<div v-for="i in 20" :key="i" class="w-[calc((100%-4*20px)/5)] <lg:w-[calc((100%-2*20px)/3)]" shadow> |
|||
<div p-2 text-lg font-bold>MarkdownUtils</div> |
|||
<div p-2 pt-0 text-sm>这是一个导航站</div> |
|||
<div flex gap="10px" px-4 py-2 tex border-t="1px solid #E5E5E5"> |
|||
<div cursor="pointer" text-sm leading-1 py-2 px-3 border="1px solid #E5E5E5" rounded>查看</div> |
|||
<div cursor="pointer" text-sm leading-1 py-2 px-3 border="1px solid #E5E5E5" rounded>访问</div> |
|||
</div> |
|||
</div> |
|||
<button class="button">Button</button> |
|||
<!-- <button @click="UpdaterStore.checkForUpdates()">更新</button> --> |
|||
<button @click="$router.push('/browser')">浏览器</button> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<style lang="scss" scoped></style> |
|||
|
@ -0,0 +1,136 @@ |
|||
<script setup lang="ts"> |
|||
import Simplebar from "simplebar-vue" |
|||
|
|||
const settingStore = useApiSetting() |
|||
|
|||
const router = useRouter() |
|||
|
|||
const active = ref(0) |
|||
|
|||
const allApp = reactive([ |
|||
{ label: "基础设置", path: "/setting/" }, |
|||
{ label: "更新设置", path: "/setting/update" }, |
|||
{ label: "开发设置", path: "/setting/dev" }, |
|||
]) |
|||
|
|||
watchEffect(() => { |
|||
const currentPath = router.currentRoute.value.path |
|||
const index = allApp.findIndex(app => app.path === currentPath) |
|||
if (index !== -1) { |
|||
active.value = index |
|||
} |
|||
}) |
|||
|
|||
function onClick(app: any, index: number) { |
|||
active.value = index |
|||
router.replace(app.path) |
|||
} |
|||
|
|||
function onClickSave() { |
|||
settingStore |
|||
.save() |
|||
.then(() => { |
|||
toast("设置已保存", { type: "success" }) |
|||
}) |
|||
.catch(() => { |
|||
toast("保存失败,请重试", { type: "error" }) |
|||
}) |
|||
} |
|||
|
|||
function onClickReset() { |
|||
settingStore.reset() |
|||
toast("设置已重置", { type: "info" }) |
|||
} |
|||
</script> |
|||
|
|||
<template> |
|||
<div h-full flex> |
|||
<div w="100px" h-full relative max-w="500px" min-w="100px"> |
|||
<Simplebar h-full> |
|||
<div |
|||
v-for="(app, index) in allApp" |
|||
:key="index" |
|||
p="8px 10px" |
|||
text="12px" |
|||
border |
|||
border-b |
|||
h="30px" |
|||
cursor="pointer" |
|||
hover:bg-gray-50 |
|||
class="item" |
|||
transition-all |
|||
:class="{ active: active === index }" |
|||
@click="onClick(app, index)" |
|||
> |
|||
<div class="text" transition-all position="absolute" left="10px">{{ app.label }}</div> |
|||
</div> |
|||
</Simplebar> |
|||
<AdjustLine></AdjustLine> |
|||
</div> |
|||
<div class="content" relative b-l="1px solid #E5E5E5" flex-1 w-0 overflow-auto flex flex-col> |
|||
<RouterView v-slot="{ Component, route }"> |
|||
<Transition name="slide-fade" mode="out-in"> |
|||
<component :is="Component" :key="route.fullPath" /> |
|||
</Transition> |
|||
</RouterView> |
|||
</div> |
|||
<div v-if="!settingStore.isSame" absolute bottom-20px right-20px flex flex-col gap-20px> |
|||
<div |
|||
:disabled="settingStore.isSaving" |
|||
shadow |
|||
flex-center |
|||
cursor-pointer |
|||
rounded="50%" |
|||
p="10px" |
|||
bg="blue-500" |
|||
color="white" |
|||
@click="onClickSave" |
|||
> |
|||
<icon-material-symbols:save></icon-material-symbols:save> |
|||
</div> |
|||
<div :disabled="settingStore.isSaving" shadow flex-center cursor-pointer rounded="50%" p="10px" bg="white" @click="onClickReset"> |
|||
<icon-ix:reset></icon-ix:reset> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<style lang="scss" scoped> |
|||
.item { |
|||
position: relative; |
|||
display: flex; |
|||
align-items: center; |
|||
white-space: nowrap; |
|||
&::before { |
|||
content: ""; |
|||
position: absolute; |
|||
left: 0; |
|||
top: 0; |
|||
height: 100%; |
|||
width: 6px; |
|||
background-color: #f3f4f6; |
|||
transition: all linear 300ms; |
|||
} |
|||
&:hover { |
|||
&::before { |
|||
width: 30px; |
|||
} |
|||
.text { |
|||
left: 20px; |
|||
} |
|||
} |
|||
&.active { |
|||
@apply: text-black; |
|||
&::before { |
|||
width: 100%; |
|||
} |
|||
.text { |
|||
left: 50%; |
|||
transform: translateX(-50%); |
|||
} |
|||
} |
|||
.text { |
|||
transition-duration: 300ms; |
|||
} |
|||
} |
|||
</style> |
@ -0,0 +1,42 @@ |
|||
<script setup lang="ts"> |
|||
const settingStore = useApiSetting() |
|||
</script> |
|||
|
|||
<template> |
|||
<div h-full> |
|||
<div class="form"> |
|||
<div class="form-item" :class="{ ['not-save']: settingStore.diffKeys.includes('dev:debug') }"> |
|||
<div class="form-item__label">存储地址</div> |
|||
<div class="form-item__value"> |
|||
<div class="select"> |
|||
<select v-model="settingStore.config['dev:debug']"> |
|||
<option :value="0">开启调试模式</option> |
|||
<option :value="2">关闭调试模式</option> |
|||
</select> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<style lang="scss" scoped> |
|||
.form { |
|||
padding: 20px; |
|||
.form-item { |
|||
display: flex; |
|||
align-items: center; |
|||
+ .form-item { |
|||
margin-top: 15px; |
|||
} |
|||
.form-item__label { |
|||
width: 100px; |
|||
font-weight: bold; |
|||
flex-basis: 100px; |
|||
} |
|||
.form-item__value { |
|||
width: 600px; |
|||
} |
|||
} |
|||
} |
|||
</style> |
@ -1,18 +1,133 @@ |
|||
<script setup lang="ts"> |
|||
const settingStore = useApiSetting() |
|||
</script> |
|||
|
|||
<template> |
|||
<div h-full flex> |
|||
<div w="200px" border-r="#E5E5E5 solid 1px">左侧菜单</div> |
|||
<div flex="1" w="0"> |
|||
{{ settingStore.isSame }}--{{ settingStore.diffKeys }} |
|||
<input v-model="settingStore.config['update.repo']" type="text" /> |
|||
{{ settingStore.config["update.repo"] }} |
|||
<button v-if="!settingStore.isSame" :disabled="settingStore.isSaving" @click="settingStore.save()">保存</button> |
|||
<div h-full> |
|||
<div class="form"> |
|||
<div class="form-item" :class="{ ['not-save']: settingStore.diffKeys.includes('storagePath') }"> |
|||
<div class="form-item__label">存储地址</div> |
|||
<div class="form-item__value" flex gap="10px" items-center> |
|||
<div class="input-wrapper"> |
|||
<input v-model="settingStore.config['storagePath']" class="input" readonly type="text" placeholder="请输入存储地址" /> |
|||
</div> |
|||
<button class="button">打开</button> |
|||
</div> |
|||
</div> |
|||
<div class="form-item" :class="{ ['not-save']: settingStore.diffKeys.includes('common.theme') }"> |
|||
<div class="form-item__label">主题</div> |
|||
<div class="form-item__value"> |
|||
<div class="radio-group"> |
|||
<div |
|||
class="radio" |
|||
:class="{ active: settingStore.config['common.theme'] === 'auto' }" |
|||
@click="settingStore.config['common.theme'] = 'auto'" |
|||
> |
|||
Auto |
|||
</div> |
|||
<div |
|||
class="radio" |
|||
:class="{ active: settingStore.config['common.theme'] === 'light' }" |
|||
@click="settingStore.config['common.theme'] = 'light'" |
|||
> |
|||
亮色 |
|||
</div> |
|||
<div |
|||
class="radio" |
|||
:class="{ active: settingStore.config['common.theme'] === 'dark' }" |
|||
@click="settingStore.config['common.theme'] = 'dark'" |
|||
> |
|||
暗色 |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<div class="form-item" :class="{ ['not-save']: settingStore.diffKeys.includes('language') }"> |
|||
<div class="form-item__label">语言</div> |
|||
<div class="form-item__value"> |
|||
<div class="radio-group"> |
|||
<div |
|||
class="radio" |
|||
:class="{ active: settingStore.config['language'] === 'zh' }" |
|||
@click="settingStore.config['language'] = 'zh'" |
|||
> |
|||
汉语 |
|||
</div> |
|||
<div |
|||
class="radio" |
|||
:class="{ active: settingStore.config['language'] === 'en' }" |
|||
@click="settingStore.config['language'] = 'en'" |
|||
> |
|||
中文 |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
import { useSettingStore } from "common/event/Setting/hook" |
|||
|
|||
const settingStore = useSettingStore() |
|||
console.log(settingStore.config) |
|||
</script> |
|||
<style lang="scss" scoped> |
|||
.form { |
|||
padding: 20px; |
|||
.form-item { |
|||
display: flex; |
|||
align-items: center; |
|||
+ .form-item { |
|||
margin-top: 15px; |
|||
} |
|||
.form-item__label { |
|||
width: 100px; |
|||
font-weight: bold; |
|||
flex-basis: 100px; |
|||
} |
|||
.form-item__value { |
|||
width: 600px; |
|||
} |
|||
} |
|||
} |
|||
.submit { |
|||
margin: 20px; |
|||
padding: 10px 20px; |
|||
background-color: #007bff; |
|||
color: white; |
|||
border: none; |
|||
border-radius: 5px; |
|||
cursor: pointer; |
|||
&:disabled { |
|||
background-color: #ccc; |
|||
cursor: not-allowed; |
|||
} |
|||
} |
|||
.input-wrapper { |
|||
width: 400px; |
|||
transition: width 0.3s ease; |
|||
&:focus-within { |
|||
width: 600px; |
|||
} |
|||
} |
|||
.radio-group { |
|||
display: inline-flex; |
|||
border: 1px solid #ccc; |
|||
border-radius: 5px; |
|||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); |
|||
.radio { |
|||
flex: 1; |
|||
cursor: pointer; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
padding: 10px; |
|||
&:hover { |
|||
background-color: #f0f0f0; |
|||
} |
|||
&.active { |
|||
background-color: #e0e0e0; |
|||
font-weight: bold; |
|||
} |
|||
+ .radio { |
|||
border-left: 1px solid #ccc; |
|||
} |
|||
} |
|||
} |
|||
</style> |
|||
|
@ -0,0 +1,72 @@ |
|||
<script setup lang="ts"> |
|||
</script> |
|||
|
|||
<template> |
|||
<div h-full flex>dasd</div> |
|||
</template> |
|||
|
|||
<style lang="scss" scoped> |
|||
.form { |
|||
padding: 20px; |
|||
.form-item { |
|||
display: flex; |
|||
align-items: center; |
|||
+ .form-item { |
|||
margin-top: 15px; |
|||
} |
|||
.form-item__label { |
|||
width: 100px; |
|||
font-weight: bold; |
|||
flex-basis: 100px; |
|||
} |
|||
.form-item__value { |
|||
width: 300px; |
|||
} |
|||
} |
|||
} |
|||
.submit { |
|||
margin: 20px; |
|||
padding: 10px 20px; |
|||
background-color: #007bff; |
|||
color: white; |
|||
border: none; |
|||
border-radius: 5px; |
|||
cursor: pointer; |
|||
&:disabled { |
|||
background-color: #ccc; |
|||
cursor: not-allowed; |
|||
} |
|||
} |
|||
// .input-wrapper { |
|||
// .input { |
|||
// width: 100%; |
|||
// padding: 8px; |
|||
// border: 1px solid #ccc; |
|||
// border-radius: 4px; |
|||
// } |
|||
// } |
|||
.radio-group { |
|||
display: inline-flex; |
|||
border: 1px solid #ccc; |
|||
border-radius: 5px; |
|||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); |
|||
.radio { |
|||
flex: 1; |
|||
cursor: pointer; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
padding: 10px; |
|||
&:hover { |
|||
background-color: #f0f0f0; |
|||
} |
|||
&.active { |
|||
background-color: #e0e0e0; |
|||
font-weight: bold; |
|||
} |
|||
+ .radio { |
|||
border-left: 1px solid #ccc; |
|||
} |
|||
} |
|||
} |
|||
</style> |
@ -0,0 +1,76 @@ |
|||
<template> |
|||
<div |
|||
v-if="UpdaterStore.isNeedUpdate" |
|||
text-sm |
|||
px-2 |
|||
py-1 |
|||
flex |
|||
items-center |
|||
hover:bg-gray-2 |
|||
hover:cursor-pointer |
|||
text="hover:hover" |
|||
@click="UpdaterStore.checkForUpdates" |
|||
> |
|||
<icon-grommet-icons:update |
|||
v-if=" |
|||
UpdaterStore.status === ApiUpdaterStatus.StartChecking || |
|||
UpdaterStore.status === ApiUpdaterStatus.Checking || |
|||
UpdaterStore.status === ApiUpdaterStatus.UpdateAvailable |
|||
" |
|||
:class="{ rotate: UpdaterStore.status === ApiUpdaterStatus.Checking }" |
|||
></icon-grommet-icons:update> |
|||
<icon-bxs:error v-if="UpdaterStore.status === ApiUpdaterStatus.Error" title="更新失败" class="text-red-400"></icon-bxs:error> |
|||
</div> |
|||
<div |
|||
v-if="UpdaterStore.status === ApiUpdaterStatus.Downloading" |
|||
class="progress" |
|||
style="font-size: 12px" |
|||
px-2 |
|||
flex |
|||
items-center |
|||
bg-gray-2 |
|||
hover:cursor-pointer |
|||
text="hover:hover" |
|||
> |
|||
<div class="line" :style="{ width: UpdaterStore.percent + '%' }"></div> |
|||
<div class="speed">{{ UpdaterStore.speed }} KB/s</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script lang="ts" setup> |
|||
const UpdaterStore = useApiUpdater() |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
.rotate { |
|||
animation: rotate 1.5s linear infinite forwards running; |
|||
} |
|||
|
|||
@keyframes rotate { |
|||
from { |
|||
transform: rotate(0deg); |
|||
} |
|||
to { |
|||
transform: rotate(360deg); |
|||
} |
|||
} |
|||
.progress { |
|||
background-color: rgba(229, 231, 235, 100); |
|||
position: relative; |
|||
.speed { |
|||
position: relative; |
|||
z-index: 3; |
|||
color: red; |
|||
} |
|||
.line { |
|||
content: ""; |
|||
z-index: 2; |
|||
position: absolute; |
|||
left: 0; |
|||
top: 0; |
|||
bottom: 0; |
|||
width: 0; |
|||
background-color: rgb(239, 156, 156); |
|||
} |
|||
} |
|||
</style> |
@ -0,0 +1,3 @@ |
|||
provider: "github" |
|||
owner: "npmrun" |
|||
repo: "wood-desktop" |
Loading…
Reference in new issue