开发中
BIN
web/build/icons/1024x1024.png
Normal file
|
After Width: | Height: | Size: 523 KiB |
BIN
web/build/icons/128x128.png
Normal file
|
After Width: | Height: | Size: 6.4 KiB |
BIN
web/build/icons/16x16.png
Normal file
|
After Width: | Height: | Size: 474 B |
BIN
web/build/icons/24x24.png
Normal file
|
After Width: | Height: | Size: 750 B |
BIN
web/build/icons/256x256.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
web/build/icons/32x32.png
Normal file
|
After Width: | Height: | Size: 965 B |
BIN
web/build/icons/48x48.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
web/build/icons/512x512.png
Normal file
|
After Width: | Height: | Size: 105 KiB |
BIN
web/build/icons/64x64.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
web/build/icons/icon.icns
Normal file
BIN
web/build/icons/icon.ico
Normal file
|
After Width: | Height: | Size: 353 KiB |
BIN
web/build/icons/icon.png
Normal file
|
After Width: | Height: | Size: 276 KiB |
BIN
web/build/icons/menu@88.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
@ -93,7 +93,7 @@
|
||||
/*! no static exports found */
|
||||
/***/ (function(module, exports, __webpack_require__) {
|
||||
|
||||
eval("const { contextBridge, ipcRenderer } = __webpack_require__(/*! electron */ \"electron\")\n\ncontextBridge.exposeInMainWorld('platform', process.platform)\ncontextBridge.exposeInMainWorld('IS_ELECTRON', true)\n\ncontextBridge.exposeInMainWorld('electronAPI', {\n minimize: () => ipcRenderer.send('minimize'),\n maximize: () => ipcRenderer.send('maximize'),\n unmaximize: () => ipcRenderer.send('unmaximize'),\n close: () => ipcRenderer.send('close'),\n create: (id) => ipcRenderer.send('create', id),\n getFileContent: (id) => ipcRenderer.invoke('getFileContent', id),\n save: (id, data) => ipcRenderer.invoke('save', id, data),\n rename: (id, name) => ipcRenderer.invoke('rename', id, name),\n openUrl: (url) => ipcRenderer.send('openUrl', url),\n getRecentFileList: () => ipcRenderer.invoke('getRecentFileList'),\n clearRecentFileList: () => ipcRenderer.invoke('clearRecentFileList'),\n openFileInDir: (file) => ipcRenderer.send('openFileInDir', file),\n deleteFile: (file) => ipcRenderer.invoke('deleteFile', file),\n onRefreshRecentFileList: (callback) => ipcRenderer.on('refreshRecentFileList', callback),\n openFile: (file) => ipcRenderer.send('openFile', file),\n selectOpenFile: () => ipcRenderer.send('selectOpenFile'),\n})\n\n//# sourceURL=webpack:///./src/electron/preload.js?");
|
||||
eval("const { contextBridge, ipcRenderer } = __webpack_require__(/*! electron */ \"electron\")\r\n\r\ncontextBridge.exposeInMainWorld('platform', process.platform)\r\ncontextBridge.exposeInMainWorld('IS_ELECTRON', true)\r\n\r\ncontextBridge.exposeInMainWorld('electronAPI', {\r\n minimize: () => ipcRenderer.send('minimize'),\r\n maximize: () => ipcRenderer.send('maximize'),\r\n unmaximize: () => ipcRenderer.send('unmaximize'),\r\n close: () => ipcRenderer.send('close'),\r\n destroy: () => ipcRenderer.send('destroy'),\r\n create: id => ipcRenderer.send('create', id),\r\n getFileContent: id => ipcRenderer.invoke('getFileContent', id),\r\n save: (id, data, fileName) => ipcRenderer.invoke('save', id, data, fileName),\r\n rename: (id, name) => ipcRenderer.invoke('rename', id, name),\r\n openUrl: url => ipcRenderer.send('openUrl', url),\r\n addRecentFileList: (fileList) => ipcRenderer.invoke('addRecentFileList', fileList),\r\n getRecentFileList: () => ipcRenderer.invoke('getRecentFileList'),\r\n clearRecentFileList: () => ipcRenderer.invoke('clearRecentFileList'),\r\n openFileInDir: file => ipcRenderer.send('openFileInDir', file),\r\n deleteFile: file => ipcRenderer.invoke('deleteFile', file),\r\n onRefreshRecentFileList: callback =>\r\n ipcRenderer.on('refreshRecentFileList', callback),\r\n openFile: file => ipcRenderer.send('openFile', file),\r\n selectOpenFile: () => ipcRenderer.send('selectOpenFile'),\r\n copyFile: file => ipcRenderer.invoke('copyFile', file)\r\n})\r\n\n\n//# sourceURL=webpack:///./src/electron/preload.js?");
|
||||
|
||||
/***/ }),
|
||||
|
||||
@ -104,7 +104,7 @@ eval("const { contextBridge, ipcRenderer } = __webpack_require__(/*! electron */
|
||||
/*! no static exports found */
|
||||
/***/ (function(module, exports, __webpack_require__) {
|
||||
|
||||
eval("module.exports = __webpack_require__(/*! /Users/lisa/wanglin/github/mind-map/web/src/electron/preload.js */\"./src/electron/preload.js\");\n\n\n//# sourceURL=webpack:///multi_./src/electron/preload.js?");
|
||||
eval("module.exports = __webpack_require__(/*! E:\\wanglin\\mind-map\\web\\src\\electron\\preload.js */\"./src/electron/preload.js\");\n\n\n//# sourceURL=webpack:///multi_./src/electron/preload.js?");
|
||||
|
||||
/***/ }),
|
||||
|
||||
|
||||
@ -15,6 +15,10 @@
|
||||
"autoBuildDoc": "node ./scripts/autoBuildDoc.js",
|
||||
"buildDoc": "node ./scripts/buildDoc.js",
|
||||
"electron:build": "vue-cli-service electron:build",
|
||||
"electron:build-all": "vue-cli-service electron:build -p never -mwl",
|
||||
"electron:build-mac": "vue-cli-service electron:build -p never -m",
|
||||
"electron:build-win": "vue-cli-service electron:build -p never -w",
|
||||
"electron:build-linux": "vue-cli-service electron:build -p never -l",
|
||||
"electron:serve": "vue-cli-service electron:serve",
|
||||
"buildLibrary": "vue-cli-service build --target lib --name simpleMindMap ../simple-mind-map/full.js --dest ../simple-mind-map/dist && esbuild ../simple-mind-map/full.js --bundle --external:buffer --format=esm --outfile=../simple-mind-map/dist/simpleMindMap.esm.js",
|
||||
"format": "prettier --write src/* src/*/* src/*/*/* src/*/*/*/*",
|
||||
|
||||
@ -1,18 +1,14 @@
|
||||
'use strict'
|
||||
|
||||
import {
|
||||
app,
|
||||
protocol,
|
||||
BrowserWindow,
|
||||
} from 'electron'
|
||||
import { app, protocol, BrowserWindow } from 'electron'
|
||||
import { createProtocol } from 'vue-cli-plugin-electron-builder/lib'
|
||||
import path from 'path'
|
||||
import { bindFileHandleEvent } from './electron/fileHandle'
|
||||
import { bindOtherHandleEvent } from './electron/otherHandle';
|
||||
import { bindOtherHandleEvent } from './electron/otherHandle'
|
||||
|
||||
const isDevelopment = process.env.NODE_ENV !== 'production'
|
||||
|
||||
// Scheme must be registered before the app is ready
|
||||
// 在应用程序准备就绪之前,必须注册方案
|
||||
protocol.registerSchemesAsPrivileged([
|
||||
{ scheme: 'app', privileges: { secure: true, standard: true } }
|
||||
])
|
||||
@ -35,12 +31,14 @@ async function createMainWindow() {
|
||||
})
|
||||
|
||||
if (process.env.WEBPACK_DEV_SERVER_URL) {
|
||||
// Load the url of the dev server if in development mode
|
||||
await mainWindow.loadURL(process.env.WEBPACK_DEV_SERVER_URL + '/#/workbenche')
|
||||
// if (!process.env.IS_TEST) mainWindow.webContents.openDevTools()
|
||||
// 如果处于开发模式,则加载开发服务器的url
|
||||
await mainWindow.loadURL(
|
||||
process.env.WEBPACK_DEV_SERVER_URL + '/#/workbenche'
|
||||
)
|
||||
if (!process.env.IS_TEST) mainWindow.webContents.openDevTools()
|
||||
} else {
|
||||
createProtocol('app')
|
||||
// Load the index.html when not in development
|
||||
// 非开发环境时加载index.html
|
||||
mainWindow.loadURL('app://./index.html/#/workbenche')
|
||||
}
|
||||
}
|
||||
@ -53,19 +51,17 @@ const bindEvent = () => {
|
||||
|
||||
// 关闭所有窗口后退出
|
||||
app.on('window-all-closed', () => {
|
||||
// On macOS it is common for applications and their menu bar
|
||||
// to stay active until the user quits explicitly with Cmd + Q
|
||||
// 在macOS上,应用程序及其菜单栏通常保持活动状态,直到用户使用Cmd+Q明确退出
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit()
|
||||
}
|
||||
})
|
||||
|
||||
app.on('activate', () => {
|
||||
// On macOS it's common to re-create a window in the app when the
|
||||
// dock icon is clicked and there are no other windows open.
|
||||
// 在macOS上,当点击dock图标且没有其他窗口打开时,通常会在应用程序中重新创建一个窗口。
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
createMainWindow()
|
||||
bindEvent()
|
||||
// bindEvent()
|
||||
}
|
||||
})
|
||||
|
||||
@ -74,7 +70,7 @@ app.on('ready', async () => {
|
||||
bindEvent()
|
||||
})
|
||||
|
||||
// Exit cleanly on request from parent process in development mode.
|
||||
// 在开发模式下,应父进程的请求干净地退出。
|
||||
if (isDevelopment) {
|
||||
if (process.platform === 'win32') {
|
||||
process.on('message', data => {
|
||||
|
||||
@ -1,196 +1,246 @@
|
||||
import {
|
||||
BrowserWindow,
|
||||
ipcMain,
|
||||
dialog,
|
||||
shell
|
||||
} from 'electron'
|
||||
import { BrowserWindow, ipcMain, dialog, shell } from 'electron'
|
||||
import fs from 'fs-extra'
|
||||
import path from 'path'
|
||||
import { saveToRecent, clearRecent, removeFileInRecent, replaceFileInRecent, getRecent } from './storage'
|
||||
import {
|
||||
saveToRecent,
|
||||
clearRecent,
|
||||
removeFileInRecent,
|
||||
replaceFileInRecent,
|
||||
getRecent,
|
||||
saveFileListToRecent
|
||||
} from './storage'
|
||||
import { v4 as uuid } from 'uuid'
|
||||
|
||||
export const bindFileHandleEvent = ({ mainWindow }) => {
|
||||
// 通知主页面刷新最近文件列表
|
||||
const notifyMainWindowRefreshRecentFileList = () => {
|
||||
mainWindow.webContents.send('refreshRecentFileList')
|
||||
}
|
||||
// 通知主页面刷新最近文件列表
|
||||
const notifyMainWindowRefreshRecentFileList = () => {
|
||||
mainWindow.webContents.send('refreshRecentFileList')
|
||||
}
|
||||
|
||||
// 新建编辑页面
|
||||
const openIds = []
|
||||
const createEditWindow = async (event, id) => {
|
||||
openIds.push(id)
|
||||
const win = new BrowserWindow({
|
||||
width: 1200,
|
||||
height: 800,
|
||||
frame: false,
|
||||
titleBarStyle: 'hiddenInset',
|
||||
webPreferences: {
|
||||
webSecurity: false,
|
||||
nodeIntegration: true,
|
||||
enableRemoteModule: true,
|
||||
contextIsolation: true,
|
||||
preload: path.join(__dirname, 'preload.js')
|
||||
}
|
||||
// 新建编辑页面
|
||||
const openIds = []
|
||||
const createEditWindow = async (event, id) => {
|
||||
openIds.push(id)
|
||||
const win = new BrowserWindow({
|
||||
width: 1200,
|
||||
height: 800,
|
||||
frame: false,
|
||||
titleBarStyle: 'hiddenInset',
|
||||
webPreferences: {
|
||||
webSecurity: false,
|
||||
nodeIntegration: true,
|
||||
enableRemoteModule: true,
|
||||
contextIsolation: true,
|
||||
preload: path.join(__dirname, 'preload.js')
|
||||
}
|
||||
})
|
||||
win.on('closed', () => {
|
||||
// 从openIds数组中删除
|
||||
let index = openIds.find(item => {
|
||||
return item === id
|
||||
})
|
||||
if (index !== -1) {
|
||||
openIds.splice(index, 1)
|
||||
}
|
||||
// 从idToFilePath中删除
|
||||
delete idToFilePath[id]
|
||||
})
|
||||
if (process.env.WEBPACK_DEV_SERVER_URL) {
|
||||
// Load the url of the dev server if in development mode
|
||||
win.loadURL(
|
||||
process.env.WEBPACK_DEV_SERVER_URL + '/#/workbenche/edit/' + id
|
||||
)
|
||||
if (!process.env.IS_TEST) win.webContents.openDevTools()
|
||||
} else {
|
||||
// Load the index.html when not in development
|
||||
win.loadURL('app://./index.html/#/workbenche/edit/' + id)
|
||||
}
|
||||
}
|
||||
ipcMain.on('create', createEditWindow)
|
||||
|
||||
// 保存文件
|
||||
const idToFilePath = {}
|
||||
ipcMain.handle('save', async (event, id, data, fileName = '未命名') => {
|
||||
if (!idToFilePath[id]) {
|
||||
const webContents = event.sender
|
||||
const win = BrowserWindow.fromWebContents(webContents)
|
||||
const res = dialog.showSaveDialogSync(win, {
|
||||
title: '保存',
|
||||
defaultPath: fileName + '.smm',
|
||||
filters: [{ name: '思维导图', extensions: ['smm'] }]
|
||||
})
|
||||
if (res) {
|
||||
idToFilePath[id] = res
|
||||
fs.writeFile(res, data)
|
||||
saveToRecent(res).then(() => {
|
||||
notifyMainWindowRefreshRecentFileList()
|
||||
})
|
||||
win.on('closed', () => {
|
||||
let index = openIds.find(item => {
|
||||
return item === id
|
||||
})
|
||||
if (index !== -1) {
|
||||
openIds.splice(index, 1)
|
||||
}
|
||||
return path.parse(idToFilePath[id]).name
|
||||
}
|
||||
} else {
|
||||
fs.writeFile(idToFilePath[id], data)
|
||||
}
|
||||
})
|
||||
|
||||
// 打开文件
|
||||
const openFile = (event, file) => {
|
||||
let id = uuid()
|
||||
idToFilePath[id] = file
|
||||
saveToRecent(file).then(() => {
|
||||
notifyMainWindowRefreshRecentFileList()
|
||||
})
|
||||
createEditWindow(null, id)
|
||||
}
|
||||
ipcMain.on('openFile', openFile)
|
||||
|
||||
// 选择打开本地文件
|
||||
ipcMain.on('selectOpenFile', event => {
|
||||
const res = dialog.showOpenDialogSync({
|
||||
title: '选择',
|
||||
filters: [{ name: '思维导图', extensions: ['smm'] }]
|
||||
})
|
||||
if (res && res[0]) {
|
||||
openFile(null, res[0])
|
||||
}
|
||||
})
|
||||
|
||||
// 获取文件内容
|
||||
ipcMain.handle('getFileContent', (event, id) => {
|
||||
return new Promise(resolve => {
|
||||
let file = idToFilePath[id]
|
||||
if (!file) {
|
||||
resolve(null)
|
||||
return
|
||||
}
|
||||
fs.readFile(file, { encoding: 'utf-8' }, (err, data) => {
|
||||
resolve({
|
||||
name: path.parse(file).name,
|
||||
content: JSON.parse(data)
|
||||
})
|
||||
if (process.env.WEBPACK_DEV_SERVER_URL) {
|
||||
// Load the url of the dev server if in development mode
|
||||
win.loadURL(
|
||||
process.env.WEBPACK_DEV_SERVER_URL + '/#/workbenche/edit/' + id
|
||||
)
|
||||
if (!process.env.IS_TEST) win.webContents.openDevTools()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// 重命名文件
|
||||
ipcMain.handle('rename', (event, id, name) => {
|
||||
return new Promise(resolve => {
|
||||
if (!idToFilePath[id]) {
|
||||
resolve('文件不存在')
|
||||
return
|
||||
}
|
||||
let oldPath = idToFilePath[id]
|
||||
let { base, ...oldPathData } = path.parse(oldPath)
|
||||
oldPathData.name = name
|
||||
let newPath = path.format(oldPathData)
|
||||
idToFilePath[id] = newPath
|
||||
fs.rename(oldPath, newPath, err => {
|
||||
if (err) {
|
||||
resolve('重命名失败')
|
||||
} else {
|
||||
// Load the index.html when not in development
|
||||
win.loadURL('app://./index.html/#/workbenche/edit/' + id)
|
||||
replaceFileInRecent(oldPath, newPath).then(() => {
|
||||
notifyMainWindowRefreshRecentFileList()
|
||||
resolve()
|
||||
})
|
||||
}
|
||||
}
|
||||
ipcMain.on('create', createEditWindow)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// 保存文件
|
||||
const idToFilePath = {}
|
||||
ipcMain.handle('save', async (event, id, data) => {
|
||||
if (!idToFilePath[id]) {
|
||||
const webContents = event.sender
|
||||
const win = BrowserWindow.fromWebContents(webContents)
|
||||
const res = dialog.showSaveDialogSync(win, {
|
||||
title: '保存',
|
||||
defaultPath: '未命名.smm',
|
||||
filters: [{ name: '思维导图', extensions: ['smm'] }]
|
||||
})
|
||||
if (res) {
|
||||
idToFilePath[id] = res
|
||||
fs.writeFile(res, data)
|
||||
saveToRecent(res)
|
||||
.then(() => {
|
||||
notifyMainWindowRefreshRecentFileList()
|
||||
})
|
||||
return path.parse(idToFilePath[id]).name
|
||||
}
|
||||
// 获取最近文件列表
|
||||
ipcMain.handle('getRecentFileList', () => {
|
||||
return getRecent().map(item => {
|
||||
let data = path.parse(item)
|
||||
return {
|
||||
url: item,
|
||||
dir: data.dir,
|
||||
name: data.name
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// 清空最近文件列表
|
||||
ipcMain.handle('clearRecentFileList', async () => {
|
||||
try {
|
||||
clearRecent()
|
||||
return ''
|
||||
} catch (error) {
|
||||
return '清空失败'
|
||||
}
|
||||
})
|
||||
|
||||
// 添加到最近文件列表
|
||||
ipcMain.handle('addRecentFileList', async (event, fileList) => {
|
||||
try {
|
||||
console.log(fileList);
|
||||
await saveFileListToRecent(fileList)
|
||||
notifyMainWindowRefreshRecentFileList()
|
||||
} catch (error) {
|
||||
return error
|
||||
}
|
||||
})
|
||||
|
||||
// 打开指定目录
|
||||
ipcMain.on('openFileInDir', (event, file) => {
|
||||
shell.showItemInFolder(file)
|
||||
})
|
||||
|
||||
// 删除指定文件
|
||||
ipcMain.handle('deleteFile', (event, file) => {
|
||||
let res = ''
|
||||
let id = Object.keys(idToFilePath).find(item => {
|
||||
return idToFilePath[item] === file
|
||||
})
|
||||
let index = -1
|
||||
if (id) {
|
||||
index = openIds.findIndex(item => {
|
||||
return item === id
|
||||
})
|
||||
}
|
||||
if (index === -1) {
|
||||
try {
|
||||
fs.rmSync(file)
|
||||
} catch (error) {}
|
||||
removeFileInRecent(file)
|
||||
} else {
|
||||
res = '该文件正在编辑,请关闭后再试'
|
||||
}
|
||||
return res
|
||||
})
|
||||
|
||||
// 复制文件
|
||||
ipcMain.handle('copyFile', async (event, file) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.pathExists(file, (err, exists) => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
} else {
|
||||
fs.writeFile(idToFilePath[id], data)
|
||||
}
|
||||
})
|
||||
|
||||
// 打开文件
|
||||
const openFile = (event, file) => {
|
||||
let id = uuid()
|
||||
idToFilePath[id] = file
|
||||
saveToRecent(file)
|
||||
.then(() => {
|
||||
notifyMainWindowRefreshRecentFileList()
|
||||
})
|
||||
createEditWindow(null, id)
|
||||
}
|
||||
ipcMain.on('openFile', openFile)
|
||||
|
||||
// 选择打开本地文件
|
||||
ipcMain.on('selectOpenFile', (event) => {
|
||||
const res = dialog.showOpenDialogSync({
|
||||
title: '选择',
|
||||
filters: [{ name: '思维导图', extensions: ['smm'] }],
|
||||
})
|
||||
if (res && res[0]) {
|
||||
openFile(null, res[0])
|
||||
}
|
||||
})
|
||||
|
||||
// 获取文件内容
|
||||
ipcMain.handle('getFileContent', (event, id) => {
|
||||
return new Promise((resolve) => {
|
||||
let file = idToFilePath[id]
|
||||
if (!file) {
|
||||
resolve(null)
|
||||
return
|
||||
}
|
||||
fs.readFile(file, { encoding: 'utf-8' }, (err, data) => {
|
||||
resolve({
|
||||
name: path.parse(file).name,
|
||||
content: JSON.parse(data)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// 重命名文件
|
||||
ipcMain.handle('rename', (event, id, name) => {
|
||||
return new Promise((resolve) => {
|
||||
if (!idToFilePath[id]) {
|
||||
resolve('文件不存在')
|
||||
return
|
||||
}
|
||||
let oldPath = idToFilePath[id]
|
||||
let { base, ...oldPathData } = path.parse(oldPath)
|
||||
oldPathData.name = name
|
||||
if (exists) {
|
||||
let { base, ...oldPathData } = path.parse(file)
|
||||
let newName = oldPathData.name + '-复制'
|
||||
let index = 1
|
||||
oldPathData.name = newName
|
||||
let newPath = path.format(oldPathData)
|
||||
idToFilePath[id] = newPath
|
||||
fs.rename(oldPath, newPath, (err) => {
|
||||
if (err) {
|
||||
resolve('重命名失败')
|
||||
} else {
|
||||
replaceFileInRecent(oldPath, newPath).then(() => {
|
||||
notifyMainWindowRefreshRecentFileList()
|
||||
resolve()
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// 获取最近文件列表
|
||||
ipcMain.handle('getRecentFileList', () => {
|
||||
return getRecent().map(item => {
|
||||
console.log(item);
|
||||
let data = path.parse(item)
|
||||
return {
|
||||
url: item,
|
||||
dir: data.dir,
|
||||
name: data.name
|
||||
// 检查新路径是否已存在
|
||||
while (fs.pathExistsSync(newPath)) {
|
||||
oldPathData.name = newName + index
|
||||
newPath = path.format(oldPathData)
|
||||
index++
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// 清空最近文件列表
|
||||
ipcMain.handle('clearRecentFileList', async () => {
|
||||
try {
|
||||
clearRecent()
|
||||
return ''
|
||||
} catch (error) {
|
||||
return '清空失败'
|
||||
}
|
||||
})
|
||||
|
||||
// 打开指定目录
|
||||
ipcMain.on('openFileInDir', (event, file) => {
|
||||
shell.showItemInFolder(file)
|
||||
})
|
||||
|
||||
// 删除指定文件
|
||||
ipcMain.handle('deleteFile', (event, file) => {
|
||||
let res = ''
|
||||
let id = Object.keys(idToFilePath).find(item => {
|
||||
return idToFilePath[item] === file
|
||||
})
|
||||
let index = -1
|
||||
if (id) {
|
||||
index = openIds.findIndex(item => {
|
||||
return item === id
|
||||
fs.copy(file, newPath, err => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
} else {
|
||||
saveToRecent(newPath).then(() => {
|
||||
notifyMainWindowRefreshRecentFileList()
|
||||
})
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
reject('文件不存在')
|
||||
}
|
||||
}
|
||||
if (index === -1) {
|
||||
res = fs.rmSync(file)
|
||||
if (!res) {
|
||||
removeFileInRecent(file)
|
||||
}
|
||||
} else {
|
||||
res = '该文件正在编辑,请关闭后再试'
|
||||
}
|
||||
return res
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -1,21 +1,18 @@
|
||||
import {
|
||||
BrowserWindow,
|
||||
ipcMain,
|
||||
} from 'electron'
|
||||
import { BrowserWindow, ipcMain } from 'electron'
|
||||
import open from 'open'
|
||||
|
||||
export const bindOtherHandleEvent = () => {
|
||||
// 处理缩放事件
|
||||
;['minimize', 'maximize', 'unmaximize', 'close'].forEach(item => {
|
||||
ipcMain.on(item, event => {
|
||||
const webContents = event.sender
|
||||
const win = BrowserWindow.fromWebContents(webContents)
|
||||
win[item]()
|
||||
})
|
||||
// 处理缩放事件
|
||||
;['minimize', 'maximize', 'unmaximize', 'close', 'destroy'].forEach(item => {
|
||||
ipcMain.on(item, event => {
|
||||
const webContents = event.sender
|
||||
const win = BrowserWindow.fromWebContents(webContents)
|
||||
win[item]()
|
||||
})
|
||||
})
|
||||
|
||||
// 使用默认浏览器打开指定url
|
||||
ipcMain.on('openUrl', (event, url) => {
|
||||
open(url)
|
||||
})
|
||||
}
|
||||
// 使用默认浏览器打开指定url
|
||||
ipcMain.on('openUrl', (event, url) => {
|
||||
open(url)
|
||||
})
|
||||
}
|
||||
|
||||
@ -4,20 +4,24 @@ contextBridge.exposeInMainWorld('platform', process.platform)
|
||||
contextBridge.exposeInMainWorld('IS_ELECTRON', true)
|
||||
|
||||
contextBridge.exposeInMainWorld('electronAPI', {
|
||||
minimize: () => ipcRenderer.send('minimize'),
|
||||
maximize: () => ipcRenderer.send('maximize'),
|
||||
unmaximize: () => ipcRenderer.send('unmaximize'),
|
||||
close: () => ipcRenderer.send('close'),
|
||||
create: (id) => ipcRenderer.send('create', id),
|
||||
getFileContent: (id) => ipcRenderer.invoke('getFileContent', id),
|
||||
save: (id, data) => ipcRenderer.invoke('save', id, data),
|
||||
rename: (id, name) => ipcRenderer.invoke('rename', id, name),
|
||||
openUrl: (url) => ipcRenderer.send('openUrl', url),
|
||||
getRecentFileList: () => ipcRenderer.invoke('getRecentFileList'),
|
||||
clearRecentFileList: () => ipcRenderer.invoke('clearRecentFileList'),
|
||||
openFileInDir: (file) => ipcRenderer.send('openFileInDir', file),
|
||||
deleteFile: (file) => ipcRenderer.invoke('deleteFile', file),
|
||||
onRefreshRecentFileList: (callback) => ipcRenderer.on('refreshRecentFileList', callback),
|
||||
openFile: (file) => ipcRenderer.send('openFile', file),
|
||||
selectOpenFile: () => ipcRenderer.send('selectOpenFile'),
|
||||
})
|
||||
minimize: () => ipcRenderer.send('minimize'),
|
||||
maximize: () => ipcRenderer.send('maximize'),
|
||||
unmaximize: () => ipcRenderer.send('unmaximize'),
|
||||
close: () => ipcRenderer.send('close'),
|
||||
destroy: () => ipcRenderer.send('destroy'),
|
||||
create: id => ipcRenderer.send('create', id),
|
||||
getFileContent: id => ipcRenderer.invoke('getFileContent', id),
|
||||
save: (id, data, fileName) => ipcRenderer.invoke('save', id, data, fileName),
|
||||
rename: (id, name) => ipcRenderer.invoke('rename', id, name),
|
||||
openUrl: url => ipcRenderer.send('openUrl', url),
|
||||
addRecentFileList: (fileList) => ipcRenderer.invoke('addRecentFileList', fileList),
|
||||
getRecentFileList: () => ipcRenderer.invoke('getRecentFileList'),
|
||||
clearRecentFileList: () => ipcRenderer.invoke('clearRecentFileList'),
|
||||
openFileInDir: file => ipcRenderer.send('openFileInDir', file),
|
||||
deleteFile: file => ipcRenderer.invoke('deleteFile', file),
|
||||
onRefreshRecentFileList: callback =>
|
||||
ipcRenderer.on('refreshRecentFileList', callback),
|
||||
openFile: file => ipcRenderer.send('openFile', file),
|
||||
selectOpenFile: () => ipcRenderer.send('selectOpenFile'),
|
||||
copyFile: file => ipcRenderer.invoke('copyFile', file)
|
||||
})
|
||||
|
||||
@ -13,7 +13,30 @@ export const saveToRecent = file => {
|
||||
list.splice(index, 1)
|
||||
}
|
||||
list.push(file)
|
||||
storage.set(RECENT_FILE_LIST, list, (err) => {
|
||||
storage.set(RECENT_FILE_LIST, list, err => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 保存到最近文件
|
||||
export const saveFileListToRecent = fileList => {
|
||||
return new Promise((resolve, reject) => {
|
||||
let list = getRecent()
|
||||
fileList.forEach(file => {
|
||||
let index = list.findIndex(item => {
|
||||
return item === file
|
||||
})
|
||||
if (index !== -1) {
|
||||
list.splice(index, 1)
|
||||
}
|
||||
list.push(file)
|
||||
})
|
||||
storage.set(RECENT_FILE_LIST, list, err => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
} else {
|
||||
@ -26,7 +49,7 @@ export const saveToRecent = file => {
|
||||
// 获取最近文件列表
|
||||
export const getRecent = () => {
|
||||
let res = storage.getSync(RECENT_FILE_LIST)
|
||||
return (Array.isArray(res) ? res : []).filter((item) => {
|
||||
return (Array.isArray(res) ? res : []).filter(item => {
|
||||
return !!item
|
||||
})
|
||||
}
|
||||
@ -34,7 +57,7 @@ export const getRecent = () => {
|
||||
// 清除最近文件列表
|
||||
export const clearRecent = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
storage.remove(RECENT_FILE_LIST, (err) => {
|
||||
storage.remove(RECENT_FILE_LIST, err => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
} else {
|
||||
@ -45,7 +68,7 @@ export const clearRecent = () => {
|
||||
}
|
||||
|
||||
// 从最近文件列表中移除指定文件
|
||||
export const removeFileInRecent = (file) => {
|
||||
export const removeFileInRecent = file => {
|
||||
return new Promise((resolve, reject) => {
|
||||
let list = getRecent()
|
||||
let index = list.findIndex(item => {
|
||||
@ -54,7 +77,7 @@ export const removeFileInRecent = (file) => {
|
||||
if (index !== -1) {
|
||||
list.splice(index, 1)
|
||||
}
|
||||
storage.set(RECENT_FILE_LIST, list, (err) => {
|
||||
storage.set(RECENT_FILE_LIST, list, err => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
} else {
|
||||
@ -75,7 +98,7 @@ export const replaceFileInRecent = (oldFile, newFile) => {
|
||||
list.splice(index, 1)
|
||||
}
|
||||
list.push(newFile)
|
||||
storage.set(RECENT_FILE_LIST, list, (err) => {
|
||||
storage.set(RECENT_FILE_LIST, list, err => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
} else {
|
||||
@ -83,4 +106,4 @@ export const replaceFileInRecent = (oldFile, newFile) => {
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -241,6 +241,7 @@ export default {
|
||||
this.setFileName(data.name)
|
||||
storeData = data.content
|
||||
} else {
|
||||
this.setFileName('未命名')
|
||||
storeData = getData()
|
||||
}
|
||||
this.mindMapData = storeData
|
||||
@ -264,7 +265,6 @@ export default {
|
||||
storeData(data)
|
||||
})
|
||||
this.$bus.$on('view_data_change', data => {
|
||||
console.log(2);
|
||||
this.setIsUnSave(true)
|
||||
storeConfig({
|
||||
view: data
|
||||
@ -437,7 +437,7 @@ export default {
|
||||
let id = this.$route.params.id
|
||||
let data = this.mindMap.getData(true)
|
||||
console.log('保存', id, data)
|
||||
let res = await window.electronAPI.save(id, JSON.stringify(data))
|
||||
let res = await window.electronAPI.save(id, JSON.stringify(data), this.fileName)
|
||||
if (res) {
|
||||
this.setFileName(res)
|
||||
}
|
||||
|
||||
@ -11,11 +11,31 @@
|
||||
<el-table-column prop="url" label="文件路径"> </el-table-column>
|
||||
<el-table-column label="操作">
|
||||
<template slot-scope="scope">
|
||||
<el-button icon="el-icon-edit" circle size="mini" @click="openFile(scope.row.url)"></el-button>
|
||||
<el-button icon="el-icon-document-copy" circle size="mini"></el-button>
|
||||
<el-button type="danger" icon="el-icon-delete" circle size="mini"
|
||||
@click="deleteFile(scope.row.url, scope.$index)"></el-button>
|
||||
<el-button icon="el-icon-folder-opened" circle size="mini" @click="openFileInDir(scope.row.url)"></el-button>
|
||||
<el-button
|
||||
icon="el-icon-edit"
|
||||
circle
|
||||
size="mini"
|
||||
@click="openFile(scope.row.url)"
|
||||
></el-button>
|
||||
<el-button
|
||||
icon="el-icon-document-copy"
|
||||
circle
|
||||
size="mini"
|
||||
@click="copyFile(scope.row.url)"
|
||||
></el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
icon="el-icon-delete"
|
||||
circle
|
||||
size="mini"
|
||||
@click="deleteFile(scope.row.url, scope.$index)"
|
||||
></el-button>
|
||||
<el-button
|
||||
icon="el-icon-folder-opened"
|
||||
circle
|
||||
size="mini"
|
||||
@click="openFileInDir(scope.row.url)"
|
||||
></el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@ -42,49 +62,68 @@ export default {
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
// 获取最近文件列表
|
||||
async getRecentFileList() {
|
||||
let list = await window.electronAPI.getRecentFileList()
|
||||
this.list = list.reverse()
|
||||
},
|
||||
|
||||
// 在文件夹里打开文件
|
||||
openFileInDir(file) {
|
||||
window.electronAPI.openFileInDir(file)
|
||||
},
|
||||
|
||||
// 删除文件
|
||||
deleteFile(file, index) {
|
||||
this.$confirm('确定删除该文件?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(async () => {
|
||||
let res = await window.electronAPI.deleteFile(file)
|
||||
if (res) {
|
||||
this.$message.error('删除失败')
|
||||
} else {
|
||||
this.list.splice(index, 1)
|
||||
this.$message.success('删除成功');
|
||||
}
|
||||
}).catch(() => { });
|
||||
})
|
||||
.then(async () => {
|
||||
let res = await window.electronAPI.deleteFile(file)
|
||||
if (res) {
|
||||
this.$message.error('删除失败')
|
||||
} else {
|
||||
this.list.splice(index, 1)
|
||||
this.$message.success('删除成功')
|
||||
}
|
||||
})
|
||||
.catch(() => {})
|
||||
},
|
||||
|
||||
// 编辑文件
|
||||
openFile(file) {
|
||||
window.electronAPI.openFile(file)
|
||||
},
|
||||
|
||||
// 清空最近文件列表
|
||||
clear() {
|
||||
this.$confirm('确定清空最近文件?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(async () => {
|
||||
let res = await window.electronAPI.clearRecentFileList()
|
||||
if (res) {
|
||||
this.$message.error('清空失败')
|
||||
} else {
|
||||
this.list = []
|
||||
this.$message.success('清空成功');
|
||||
}
|
||||
}).catch(() => { });
|
||||
})
|
||||
.then(async () => {
|
||||
let res = await window.electronAPI.clearRecentFileList()
|
||||
if (res) {
|
||||
this.$message.error('清空失败')
|
||||
} else {
|
||||
this.list = []
|
||||
this.$message.success('清空成功')
|
||||
}
|
||||
})
|
||||
.catch(() => {})
|
||||
},
|
||||
|
||||
// 复制文件
|
||||
async copyFile(file) {
|
||||
try {
|
||||
window.electronAPI.copyFile(file)
|
||||
this.$message.success('复制成功')
|
||||
} catch (error) {
|
||||
this.$message.error('复制失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,7 +4,13 @@
|
||||
<MacControl></MacControl>
|
||||
<WinControl></WinControl>
|
||||
<div class="inputBox">
|
||||
<el-input v-model="name" size="mini" placeholder=""></el-input>
|
||||
<el-input
|
||||
v-model="name"
|
||||
size="mini"
|
||||
placeholder=""
|
||||
@blur="rename"
|
||||
@keyup.enter="rename"
|
||||
></el-input>
|
||||
<div class="modifyDotBox">
|
||||
<div class="modifyDot" v-show="isUnSave"></div>
|
||||
</div>
|
||||
@ -41,37 +47,46 @@ export default {
|
||||
name(val) {
|
||||
if (!val.trim()) return
|
||||
this.setFileName(val.trim())
|
||||
// let id = this.$route.params.id
|
||||
// window.electronAPI.rename(id, val.trim())
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// window.onbeforeunload = async (e) => {
|
||||
// e.returnValue = false
|
||||
// if (!this.isUnSave) {
|
||||
// window.electronAPI.close()
|
||||
// } else {
|
||||
// try {
|
||||
// await this.checkIsClose()
|
||||
// window.electronAPI.close()
|
||||
// } catch (error) {}
|
||||
// }
|
||||
// }
|
||||
window.onbeforeunload = async e => {
|
||||
e.returnValue = false
|
||||
// 没有未保存内容直接关闭
|
||||
if (!this.isUnSave) {
|
||||
window.electronAPI.destroy()
|
||||
} else {
|
||||
try {
|
||||
// 否则询问用户是否关闭
|
||||
await this.checkIsClose()
|
||||
window.electronAPI.destroy()
|
||||
} catch (error) {}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapMutations(['setFileName']),
|
||||
|
||||
// 重命名文件
|
||||
rename() {
|
||||
let id = this.$route.params.id
|
||||
window.electronAPI.rename(id, this.name.trim())
|
||||
},
|
||||
|
||||
// 询问是否关闭页面
|
||||
checkIsClose() {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.$confirm('有操作尚未保存,是否确认关闭?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(async () => {
|
||||
resolve()
|
||||
}).catch(() => {
|
||||
reject()
|
||||
});
|
||||
})
|
||||
.then(async () => {
|
||||
resolve()
|
||||
})
|
||||
.catch(() => {
|
||||
reject()
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,11 @@
|
||||
<template>
|
||||
<div class="workbencheHomeContainer">
|
||||
<div
|
||||
class="workbencheHomeContainer"
|
||||
@drop="onDrop"
|
||||
@dragenter="onDragenter"
|
||||
@dragover="onDragover"
|
||||
@dragleave="onDragleave"
|
||||
>
|
||||
<div class="workbencheHomeHeader">
|
||||
<MacControl></MacControl>
|
||||
<WinControl></WinControl>
|
||||
@ -23,6 +29,51 @@ export default {
|
||||
MacControl,
|
||||
Sidebar,
|
||||
FileList
|
||||
},
|
||||
methods: {
|
||||
onDrop(e) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
|
||||
let df = e.dataTransfer
|
||||
let dropFiles = []
|
||||
|
||||
if (df.items !== undefined) {
|
||||
for (let i = 0; i < df.items.length; i++) {
|
||||
let item = df.items[i]
|
||||
if (item.kind === 'file' && item.webkitGetAsEntry().isFile) {
|
||||
let file = item.getAsFile()
|
||||
if (/\.smm$/.test(file.name)) {
|
||||
dropFiles.push(file)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (dropFiles.length === 1) {
|
||||
// 如果只有一个文件,直接打开编辑
|
||||
window.electronAPI.openFile(dropFiles[0].path)
|
||||
} else if (dropFiles.length > 1) {
|
||||
// 否则添加到最近文件列表
|
||||
window.electronAPI.addRecentFileList(dropFiles.map((file) => {
|
||||
return file.path
|
||||
}))
|
||||
}
|
||||
},
|
||||
|
||||
onDragenter(e) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
},
|
||||
|
||||
onDragover(e) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
},
|
||||
|
||||
onDragleave(e) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -17,10 +17,14 @@ module.exports = {
|
||||
electronBuilder: {
|
||||
preload: 'src/electron/preload.js',
|
||||
builderOptions: {
|
||||
productName: 'SimpleMindMap',
|
||||
copyright: 'Copyright © SimpleMindMap',
|
||||
productName: '思绪思维导图',
|
||||
copyright: 'Copyright © 思绪思维导图',
|
||||
// compression: "maximum", // 机器好的可以打开,配置压缩,开启后会让 .AppImage 格式的客户端启动缓慢
|
||||
asar: true,
|
||||
fileAssociations: {
|
||||
ext: 'smm',
|
||||
icon: './build/icons/icon.ico'
|
||||
},
|
||||
publish: [
|
||||
{
|
||||
provider: 'github',
|
||||
@ -55,7 +59,7 @@ module.exports = {
|
||||
arch: ['x64'],
|
||||
},
|
||||
],
|
||||
publisherName: 'SimpleMindMap',
|
||||
publisherName: '思绪思维导图',
|
||||
icon: 'build/icons/icon.ico',
|
||||
publish: ['github'],
|
||||
},
|
||||
|
||||