feat: update electron app with touchbar and menu.
6
.npmrc
|
@ -1,4 +1,4 @@
|
|||
# 如果发现 npm / yarn 安装太慢,可以解除注释
|
||||
registry=https://registry.npm.taobao.org/
|
||||
ELECTRON_MIRROR=https://npm.taobao.org/mirrors/electron
|
||||
phantomjs_cdnurl=https://npm.taobao.org/dist/phantomjs
|
||||
# registry=https://registry.npm.taobao.org/
|
||||
# ELECTRON_MIRROR=https://npm.taobao.org/mirrors/electron
|
||||
# phantomjs_cdnurl=https://npm.taobao.org/dist/phantomjs
|
|
@ -56,8 +56,7 @@
|
|||
"vue-i18n": "^8.22.0",
|
||||
"vue-router": "^3.4.3",
|
||||
"vue-slider-component": "^3.2.5",
|
||||
"vuex": "^3.4.0",
|
||||
"vuex-electron": "^1.0.3"
|
||||
"vuex": "^3.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sentry/browser": "^5.27.0",
|
||||
|
|
BIN
public/img/icons/touchbar/like.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
public/img/icons/touchbar/liked.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
public/img/icons/touchbar/next.png
Normal file
After Width: | Height: | Size: 1005 B |
BIN
public/img/icons/touchbar/play.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
public/img/icons/touchbar/previous.png
Normal file
After Width: | Height: | Size: 1021 B |
BIN
public/img/icons/touchbar/repeat.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
public/img/icons/touchbar/search.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
public/img/icons/touchbar/search2.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
public/img/icons/touchbar/shuffle.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
35
src/App.vue
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div id="app">
|
||||
<Navbar />
|
||||
<Navbar ref="navbar"/>
|
||||
<main>
|
||||
<keep-alive>
|
||||
<router-view v-if="$route.meta.keepAlive"></router-view>
|
||||
|
@ -40,6 +40,7 @@ export default {
|
|||
},
|
||||
created() {
|
||||
if (this.isElectron) {
|
||||
const self = this
|
||||
// 添加专有的类名
|
||||
document.body.classList.add("is-electron");
|
||||
// ipc message channel
|
||||
|
@ -49,38 +50,42 @@ export default {
|
|||
// inside this Vue instance, according to what path the main process requires.
|
||||
// responds to Menu click() events at the main process and changes the route accordingly.
|
||||
ipcRenderer.on("changeRouteTo", (event, path) => {
|
||||
console.log(event);
|
||||
this.$router.push(path);
|
||||
self.$router.push(path);
|
||||
});
|
||||
ipcRenderer.on("search", () => {
|
||||
// 触发数据响应
|
||||
self.$refs.navbar.$refs.searchInput.focus()
|
||||
self.$refs.navbar.inputFocus = true
|
||||
})
|
||||
ipcRenderer.on("play", () => {
|
||||
this.$refs.player.play();
|
||||
self.$refs.player.play();
|
||||
});
|
||||
ipcRenderer.on("next", () => {
|
||||
this.$refs.player.next();
|
||||
self.$refs.player.next();
|
||||
});
|
||||
ipcRenderer.on("previous", () => {
|
||||
this.$refs.player.previous();
|
||||
self.$refs.player.previous();
|
||||
});
|
||||
ipcRenderer.on("increaseVolume", () => {
|
||||
if (this.$refs.player.volume + 0.1 >= 1) {
|
||||
return (this.$refs.player.volume = 1);
|
||||
if (self.$refs.player.volume + 0.1 >= 1) {
|
||||
return (self.$refs.player.volume = 1);
|
||||
}
|
||||
this.$refs.player.volume += 0.1;
|
||||
self.$refs.player.volume += 0.1;
|
||||
});
|
||||
ipcRenderer.on("decreaseVolume", () => {
|
||||
if (this.$refs.player.volume - 0.1 <= 0) {
|
||||
return (this.$refs.player.volume = 0);
|
||||
if (self.$refs.player.volume - 0.1 <= 0) {
|
||||
return (self.$refs.player.volume = 0);
|
||||
}
|
||||
this.$refs.player.volume -= 0.1;
|
||||
self.$refs.player.volume -= 0.1;
|
||||
});
|
||||
ipcRenderer.on("like", () => {
|
||||
this.$refs.player.likeCurrentSong();
|
||||
self.$refs.player.likeCurrentSong();
|
||||
});
|
||||
ipcRenderer.on("repeat", () => {
|
||||
this.$refs.player.repeat();
|
||||
self.$refs.player.repeat();
|
||||
});
|
||||
ipcRenderer.on("shuffle", () => {
|
||||
this.$refs.player.shuffle();
|
||||
self.$refs.player.shuffle();
|
||||
});
|
||||
}
|
||||
},
|
||||
|
|
|
@ -1,48 +1,59 @@
|
|||
"use strict";
|
||||
|
||||
import path from "path";
|
||||
const path = require('path')
|
||||
// import { autoUpdater } from "electron-updater"
|
||||
import {
|
||||
app,
|
||||
protocol,
|
||||
BrowserWindow,
|
||||
ipcMain,
|
||||
dialog,
|
||||
globalShortcut,
|
||||
globalShortcut
|
||||
} from "electron";
|
||||
|
||||
import { createProtocol } from "vue-cli-plugin-electron-builder/lib";
|
||||
import installExtension, { VUEJS_DEVTOOLS } from "electron-devtools-installer";
|
||||
|
||||
const isDevelopment = process.env.NODE_ENV !== "production";
|
||||
const { createTouchBar } = require("./electron/touchbar");
|
||||
const { createMenu } = require("./electron/menu");
|
||||
const { setAppBounced } = require("./electron/bounced")
|
||||
const { setIcon } = require("./electron/setIcon")
|
||||
|
||||
// Keep a global reference of the window object, if you don't, the window will
|
||||
// be closed automatically when the JavaScript object is garbage collected.
|
||||
let win;
|
||||
let touchbar
|
||||
|
||||
const iconString = path.join(__static, "img/icons/apple-touch-icon.png");
|
||||
|
||||
// Make vuex copy for electron.
|
||||
global.vuexCopy = null
|
||||
// 同步 vuex 状态,由于 player 有循环引用问题,拷贝部分属性
|
||||
ipcMain.on('vuex-state', (e, state) => {
|
||||
global.vuexCopy = state
|
||||
})
|
||||
ipcMain.on("close", () => {
|
||||
win.close();
|
||||
app.quit();
|
||||
});
|
||||
ipcMain.on("minimize", () => {
|
||||
win.minimize();
|
||||
});
|
||||
|
||||
// Scheme must be registered before the app is ready
|
||||
protocol.registerSchemesAsPrivileged([
|
||||
{ scheme: "app", privileges: { secure: true, standard: true } },
|
||||
]);
|
||||
const iconString = path.join(__static, "img/icons/apple-touch-icon.png");
|
||||
|
||||
let bounceId = app.dock.bounce();
|
||||
app.dock.setIcon(iconString);
|
||||
setAppBounced(app)
|
||||
setIcon(app, iconString)
|
||||
|
||||
function createWindow() {
|
||||
const touchbar = require("./electron/touchbar.js");
|
||||
const tray = require("./electron/tray.js");
|
||||
const createMenu = require("./electron/menu.js");
|
||||
tray.on("click", function () {
|
||||
if (win.isVisible()) {
|
||||
win.hide();
|
||||
} else {
|
||||
win.show();
|
||||
}
|
||||
});
|
||||
win = new BrowserWindow({
|
||||
width: 1440,
|
||||
height: 768,
|
||||
icon: iconString,
|
||||
backgroundColor: '#2e2c29',
|
||||
titleBarStyle: "default",
|
||||
webPreferences: {
|
||||
webSecurity: false,
|
||||
|
@ -51,15 +62,13 @@ function createWindow() {
|
|||
preload: path.join(__dirname, "./electron/preload.js"),
|
||||
});
|
||||
|
||||
try {
|
||||
createMenu(win);
|
||||
win.setTouchBar(touchbar);
|
||||
win.setAutoHideCursor(true);
|
||||
app.dock.cancelBounce(bounceId);
|
||||
// autoUpdater.checkForUpdatesAndNotify()
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
tray.on("click", () => {
|
||||
if (win && win.isVisible()) {
|
||||
win.hide();
|
||||
} else {
|
||||
win.show();
|
||||
}
|
||||
});
|
||||
|
||||
if (process.env.WEBPACK_DEV_SERVER_URL) {
|
||||
// Load the url of the dev server if in development mode
|
||||
|
@ -74,6 +83,7 @@ function createWindow() {
|
|||
win.on("closed", () => {
|
||||
win = null;
|
||||
});
|
||||
return win
|
||||
}
|
||||
|
||||
// Quit when all windows are closed.
|
||||
|
@ -112,14 +122,13 @@ app.on("ready", async () => {
|
|||
win.webContents.openDevTools();
|
||||
});
|
||||
createWindow();
|
||||
});
|
||||
|
||||
ipcMain.on("close", () => {
|
||||
win.close();
|
||||
app.quit();
|
||||
});
|
||||
ipcMain.on("minimize", () => {
|
||||
win.minimize();
|
||||
win.once("ready-to-show", () => {
|
||||
win.show()
|
||||
})
|
||||
createMenu(win);
|
||||
touchbar = createTouchBar(win)
|
||||
win.setTouchBar(touchbar);
|
||||
// autoUpdater.checkForUpdatesAndNotify()
|
||||
});
|
||||
|
||||
// autoUpdater.on("checking-for-update", () => {});
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
<svg-icon icon-class="search" />
|
||||
<div class="input">
|
||||
<input
|
||||
ref="searchInput"
|
||||
:placeholder="inputFocus ? '' : $t('nav.search')"
|
||||
v-model="keywords"
|
||||
@keydown.enter="goToSearchPage"
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
<div class="controls">
|
||||
<div class="playing">
|
||||
<img
|
||||
:src="currentTrack.al.picUrl | resizeImage(224)"
|
||||
:src="currentTrack.al && currentTrack.al.picUrl | resizeImage(224)"
|
||||
@click="goToAlbum"
|
||||
/>
|
||||
<div class="track-info">
|
||||
|
@ -141,9 +141,12 @@ export default {
|
|||
oldVolume: 0.5,
|
||||
};
|
||||
},
|
||||
created() {
|
||||
mounted() {
|
||||
setInterval(() => {
|
||||
this.progress = ~~this.howler.seek();
|
||||
// fix 歌曲播放完还设置进度的问题,及 _id 不存在的问题
|
||||
if (this.howler && this.howler._sounds[0]._id) {
|
||||
this.progress = ~~this.howler.seek();
|
||||
}
|
||||
}, 1000);
|
||||
if (isAccountLoggedIn()) {
|
||||
userLikedSongsIDs(this.data.user.userId).then((data) => {
|
||||
|
@ -167,9 +170,12 @@ export default {
|
|||
},
|
||||
playing() {
|
||||
if (this.howler.state() === "loading") {
|
||||
this.updatePlayerState({ key: "playing", value: true });
|
||||
return true;
|
||||
}
|
||||
return this.howler.playing();
|
||||
const status = this.howler.playing()
|
||||
this.updatePlayerState({ key: "playing", value: status });
|
||||
return status
|
||||
},
|
||||
progressMax() {
|
||||
let max = ~~(this.currentTrack.dt / 1000);
|
||||
|
|
6
src/electron/bounced.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
export function setAppBounced(app) {
|
||||
let bounceId = app.dock.bounce();
|
||||
app.on('ready', () => {
|
||||
app.dock.cancelBounce(bounceId);
|
||||
})
|
||||
};
|
|
@ -4,7 +4,7 @@ const { app, Menu } = require("electron");
|
|||
|
||||
const isMac = process.platform === "darwin";
|
||||
|
||||
function createMenu(win) {
|
||||
export function createMenu(win) {
|
||||
let menu = null;
|
||||
const template = [
|
||||
...(isMac
|
||||
|
@ -196,5 +196,3 @@ function createMenu(win) {
|
|||
menu = Menu.buildFromTemplate(template);
|
||||
Menu.setApplicationMenu(menu);
|
||||
}
|
||||
|
||||
module.exports = createMenu;
|
||||
|
|
3
src/electron/setIcon.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
export function setIcon(app, iconString) {
|
||||
app.dock.setIcon(iconString);
|
||||
}
|
|
@ -1,104 +1,131 @@
|
|||
// 运用 ipdMain 请求用户喜欢的歌手的数据,随机抽几个歌手进行随机
|
||||
const { TouchBar } = require("electron");
|
||||
const { TouchBar, nativeImage, ipcMain } = require("electron");
|
||||
const path = require("path");
|
||||
|
||||
const { TouchBarLabel, TouchBarButton, TouchBarSpacer } = TouchBar;
|
||||
const {
|
||||
TouchBarButton,
|
||||
TouchBarGroup,
|
||||
TouchBarSpacer,
|
||||
TouchBarSegmentedControl,
|
||||
} = TouchBar;
|
||||
|
||||
let spinning = false;
|
||||
function getNativeIcon(name, width = 24, height = 24) {
|
||||
return nativeImage
|
||||
.createFromPath(path.join(__static, "img/icons/touchbar/", name))
|
||||
.resize({
|
||||
width,
|
||||
height,
|
||||
});
|
||||
}
|
||||
|
||||
// Reel labels
|
||||
const reel1 = new TouchBarLabel();
|
||||
const reel2 = new TouchBarLabel();
|
||||
const reel3 = new TouchBarLabel();
|
||||
|
||||
// Spin result label
|
||||
const result = new TouchBarLabel();
|
||||
|
||||
// Spin button
|
||||
const spin = new TouchBarButton({
|
||||
label: "🎰 Spin",
|
||||
backgroundColor: "#7851A9",
|
||||
click: () => {
|
||||
// Ignore clicks if already spinning
|
||||
if (spinning) {
|
||||
return;
|
||||
}
|
||||
|
||||
spinning = true;
|
||||
result.label = "";
|
||||
|
||||
let timeout = 10;
|
||||
const spinLength = 4 * 1000; // 4 seconds
|
||||
const startTime = Date.now();
|
||||
|
||||
const spinReels = () => {
|
||||
updateReels();
|
||||
|
||||
if (Date.now() - startTime >= spinLength) {
|
||||
finishSpin();
|
||||
} else {
|
||||
// Slow down a bit on each spin
|
||||
timeout *= 1.1;
|
||||
setTimeout(spinReels, timeout);
|
||||
export function createSegmentedControl(renderer) {
|
||||
const segments = [
|
||||
{
|
||||
icon: getNativeIcon("previous.png"),
|
||||
},
|
||||
{
|
||||
icon: getNativeIcon("play.png", 20, 20),
|
||||
},
|
||||
{
|
||||
icon: getNativeIcon("next.png"),
|
||||
},
|
||||
];
|
||||
const segmentedControl = new TouchBarSegmentedControl({
|
||||
segments,
|
||||
change: (selectedIndex) => {
|
||||
const temp = Object.assign([], segmentedControl.segments);
|
||||
if (selectedIndex === 0) {
|
||||
renderer.send("previous");
|
||||
}
|
||||
};
|
||||
if (selectedIndex === 1) {
|
||||
ipcMain.on("vuex-state", (e, { player }) => {
|
||||
const playing = player.playing;
|
||||
if (playing === true) {
|
||||
// To be paused
|
||||
temp[1].icon = getNativeIcon("play.png", 20, 20);
|
||||
segmentedControl.segments = temp;
|
||||
} else {
|
||||
temp[1].icon = getNativeIcon("play.png", 20, 20);
|
||||
segmentedControl.segments = temp;
|
||||
}
|
||||
});
|
||||
renderer.send("play");
|
||||
}
|
||||
if (selectedIndex === 2) {
|
||||
renderer.send("next");
|
||||
}
|
||||
},
|
||||
mode: "buttons",
|
||||
});
|
||||
return segmentedControl;
|
||||
}
|
||||
|
||||
spinReels();
|
||||
},
|
||||
});
|
||||
export function createPreferGroup(renderer) {
|
||||
const search = new TouchBarButton({
|
||||
icon: getNativeIcon("search.png", 22, 22),
|
||||
click: () => {
|
||||
renderer.send("search");
|
||||
},
|
||||
});
|
||||
const like = new TouchBarButton({
|
||||
icon: getNativeIcon("like.png"),
|
||||
click: () => {
|
||||
ipcMain.on("vuex-state", (e, { liked, player }) => {
|
||||
const currentTrack = player.currentTrack;
|
||||
if (liked.songs.includes(currentTrack.id)) {
|
||||
like.icon = getNativeIcon("liked.png");
|
||||
} else {
|
||||
like.icon = getNativeIcon("like.png");
|
||||
}
|
||||
});
|
||||
renderer.send("like");
|
||||
},
|
||||
});
|
||||
const repeat = new TouchBarButton({
|
||||
icon: getNativeIcon("repeat.png"),
|
||||
click: () => {
|
||||
ipcMain.on("vuex-state", (e, { player }) => {
|
||||
const repeat = player.repeat;
|
||||
if (repeat === "on") {
|
||||
repeat.icon = getNativeIcon("repeat.png");
|
||||
} else if (repeat === "one") {
|
||||
repeat.icon = getNativeIcon("repeat.png");
|
||||
} else {
|
||||
repeat.icon = getNativeIcon("repeat.png");
|
||||
}
|
||||
});
|
||||
renderer.send("repeat");
|
||||
},
|
||||
});
|
||||
const shuffle = new TouchBarButton({
|
||||
icon: getNativeIcon("shuffle.png"),
|
||||
click: () => {
|
||||
ipcMain.on("vuex-state", (e, { player }) => {
|
||||
const shuffle = player.shuffle;
|
||||
if (shuffle === true) {
|
||||
shuffle.icon = getNativeIcon("shuffle.png");
|
||||
} else {
|
||||
shuffle.icon = getNativeIcon("shuffle.png");
|
||||
}
|
||||
});
|
||||
renderer.send("shuffle");
|
||||
},
|
||||
});
|
||||
return new TouchBar({
|
||||
items: [search, like, repeat, shuffle],
|
||||
});
|
||||
}
|
||||
|
||||
const getRandomValue = () => {
|
||||
const values = ["🍒", "💎", "7️⃣", "🍊", "🔔", "⭐", "🍇", "🍀"];
|
||||
return values[Math.floor(Math.random() * values.length)];
|
||||
};
|
||||
|
||||
const updateReels = () => {
|
||||
reel1.label = getRandomValue();
|
||||
reel2.label = getRandomValue();
|
||||
reel3.label = getRandomValue();
|
||||
};
|
||||
|
||||
const finishSpin = () => {
|
||||
const uniqueValues = new Set([reel1.label, reel2.label, reel3.label]).size;
|
||||
if (uniqueValues === 1) {
|
||||
// All 3 values are the same
|
||||
result.label = "💰 Jackpot!";
|
||||
result.textColor = "#FDFF00";
|
||||
} else if (uniqueValues === 2) {
|
||||
// 2 values are the same
|
||||
result.label = "😍 Winner!";
|
||||
result.textColor = "#FDFF00";
|
||||
} else {
|
||||
// No values are the same
|
||||
result.label = "🙁 Spin Again";
|
||||
result.textColor = null;
|
||||
}
|
||||
spinning = false;
|
||||
};
|
||||
|
||||
const touchBar = new TouchBar({
|
||||
items: [
|
||||
spin,
|
||||
new TouchBarSpacer({ size: "large" }),
|
||||
reel1,
|
||||
new TouchBarSpacer({ size: "small" }),
|
||||
reel2,
|
||||
new TouchBarSpacer({ size: "small" }),
|
||||
reel3,
|
||||
new TouchBarSpacer({ size: "large" }),
|
||||
result,
|
||||
],
|
||||
});
|
||||
|
||||
// let window
|
||||
|
||||
// app.whenReady().then(() => {
|
||||
// window = new BrowserWindow({
|
||||
// frame: false,
|
||||
// titleBarStyle: 'hiddenInset',
|
||||
// backgroundColor: '#000'
|
||||
// })
|
||||
// window.loadURL('about:blank')
|
||||
// window.setTouchBar(touchBar)
|
||||
// })
|
||||
|
||||
module.exports = touchBar;
|
||||
export function createTouchBar(window) {
|
||||
const renderer = window.webContents;
|
||||
const segmentedControl = createSegmentedControl(renderer);
|
||||
const preferGroup = createPreferGroup(renderer);
|
||||
const touchBar = new TouchBar({
|
||||
items: [
|
||||
new TouchBarGroup({ items: preferGroup }),
|
||||
new TouchBarSpacer({ size: "large" }),
|
||||
segmentedControl,
|
||||
new TouchBarSpacer({ size: "large" }),
|
||||
],
|
||||
});
|
||||
return touchBar;
|
||||
}
|
||||
|
|
104
src/electron/touchbar1.js
Normal file
|
@ -0,0 +1,104 @@
|
|||
// 运用 ipdMain 请求用户喜欢的歌手的数据,随机抽几个歌手进行随机
|
||||
const { TouchBar } = require("electron");
|
||||
|
||||
const { TouchBarLabel, TouchBarButton, TouchBarSpacer } = TouchBar;
|
||||
|
||||
let spinning = false;
|
||||
|
||||
// Reel labels
|
||||
const reel1 = new TouchBarLabel();
|
||||
const reel2 = new TouchBarLabel();
|
||||
const reel3 = new TouchBarLabel();
|
||||
|
||||
// Spin result label
|
||||
const result = new TouchBarLabel();
|
||||
|
||||
// Spin button
|
||||
const spin = new TouchBarButton({
|
||||
label: "🎰 Spin",
|
||||
backgroundColor: "#7851A9",
|
||||
click: () => {
|
||||
// Ignore clicks if already spinning
|
||||
if (spinning) {
|
||||
return;
|
||||
}
|
||||
|
||||
spinning = true;
|
||||
result.label = "";
|
||||
|
||||
let timeout = 10;
|
||||
const spinLength = 4 * 1000; // 4 seconds
|
||||
const startTime = Date.now();
|
||||
|
||||
const spinReels = () => {
|
||||
updateReels();
|
||||
|
||||
if (Date.now() - startTime >= spinLength) {
|
||||
finishSpin();
|
||||
} else {
|
||||
// Slow down a bit on each spin
|
||||
timeout *= 1.1;
|
||||
setTimeout(spinReels, timeout);
|
||||
}
|
||||
};
|
||||
|
||||
spinReels();
|
||||
},
|
||||
});
|
||||
|
||||
const getRandomValue = () => {
|
||||
const values = ["🍒", "💎", "7️⃣", "🍊", "🔔", "⭐", "🍇", "🍀"];
|
||||
return values[Math.floor(Math.random() * values.length)];
|
||||
};
|
||||
|
||||
const updateReels = () => {
|
||||
reel1.label = getRandomValue();
|
||||
reel2.label = getRandomValue();
|
||||
reel3.label = getRandomValue();
|
||||
};
|
||||
|
||||
const finishSpin = () => {
|
||||
const uniqueValues = new Set([reel1.label, reel2.label, reel3.label]).size;
|
||||
if (uniqueValues === 1) {
|
||||
// All 3 values are the same
|
||||
result.label = "💰 Jackpot!";
|
||||
result.textColor = "#FDFF00";
|
||||
} else if (uniqueValues === 2) {
|
||||
// 2 values are the same
|
||||
result.label = "😍 Winner!";
|
||||
result.textColor = "#FDFF00";
|
||||
} else {
|
||||
// No values are the same
|
||||
result.label = "🙁 Spin Again";
|
||||
result.textColor = null;
|
||||
}
|
||||
spinning = false;
|
||||
};
|
||||
|
||||
const touchBar = new TouchBar({
|
||||
items: [
|
||||
spin,
|
||||
new TouchBarSpacer({ size: "large" }),
|
||||
reel1,
|
||||
new TouchBarSpacer({ size: "small" }),
|
||||
reel2,
|
||||
new TouchBarSpacer({ size: "small" }),
|
||||
reel3,
|
||||
new TouchBarSpacer({ size: "large" }),
|
||||
result,
|
||||
],
|
||||
});
|
||||
|
||||
// let window
|
||||
|
||||
// app.whenReady().then(() => {
|
||||
// window = new BrowserWindow({
|
||||
// frame: false,
|
||||
// titleBarStyle: 'hiddenInset',
|
||||
// backgroundColor: '#000'
|
||||
// })
|
||||
// window.loadURL('about:blank')
|
||||
// window.setTouchBar(touchBar)
|
||||
// })
|
||||
|
||||
module.exports = touchBar;
|
|
@ -1,12 +1,14 @@
|
|||
const path = require("path");
|
||||
const { Menu, Tray } = require("electron");
|
||||
const { nativeImage, Tray } = require("electron");
|
||||
|
||||
let tray = null;
|
||||
function getNativeIcon(name, width = 24, height = 24) {
|
||||
return nativeImage.createFromPath(path.join(__static, 'img/icons/', name)).resize({
|
||||
width,
|
||||
height,
|
||||
})
|
||||
}
|
||||
|
||||
const macIcon = path.join(__static, "img/icons/menu.png");
|
||||
const winIcon = path.join(__static, "img/icons/icon.ico");
|
||||
|
||||
tray = new Tray(macIcon);
|
||||
let tray = new Tray(getNativeIcon('menu@88.png', 20, 20));;
|
||||
|
||||
// Temporary no need for menu.
|
||||
// const contextMenu = Menu.buildFromTemplate([
|
||||
|
|
|
@ -3,7 +3,7 @@ import { getTrackDetail, scrobble } from "@/api/track";
|
|||
import { isAccountLoggedIn } from "@/utils/auth";
|
||||
// import { updateHttps } from "@/utils/common";
|
||||
import localforage from "localforage";
|
||||
import { cacheTrack } from "@/utils/db";
|
||||
// import { cacheTrack } from "@/utils/db";
|
||||
|
||||
export default {
|
||||
switchTrack({ state, dispatch, commit }, basicTrack) {
|
||||
|
@ -51,11 +51,13 @@ export default {
|
|||
});
|
||||
tracks.getItem(`${track.id}`).then((t) => {
|
||||
if (t !== null) {
|
||||
commitMP3(URL.createObjectURL(t.mp3));
|
||||
// commitMP3(URL.createObjectURL(t.mp3));
|
||||
commitMP3(t.mp3);
|
||||
} else {
|
||||
cacheTrack(`${track.id}`).then((t) => {
|
||||
commitMP3(URL.createObjectURL(t.mp3));
|
||||
});
|
||||
commitMP3(t.mp3);
|
||||
// cacheTrack(`${track.id}`).then((t) => {
|
||||
// commitMP3(URL.createObjectURL(t.mp3));
|
||||
// });
|
||||
}
|
||||
});
|
||||
} else {
|
||||
|
|
|
@ -7,42 +7,43 @@ import initLocalStorage from "./initLocalStorage";
|
|||
import { Howl, Howler } from "howler";
|
||||
import { changeAppearance } from "@/utils/common";
|
||||
import updateApp from "@/utils/updateApp";
|
||||
import pack from "../../package.json";
|
||||
import pkg from "../../package.json";
|
||||
// vuex 自定义插件
|
||||
import vuexBroadCast from './plugins/broadcast'
|
||||
import saveToLocalStorage from './plugins/localStorage'
|
||||
|
||||
if (localStorage.getItem("appVersion") === null) {
|
||||
localStorage.setItem("player", JSON.stringify(initLocalStorage.player));
|
||||
localStorage.setItem("settings", JSON.stringify(initLocalStorage.settings));
|
||||
localStorage.setItem("data", JSON.stringify(initLocalStorage.data));
|
||||
localStorage.setItem("appVersion", pack.version);
|
||||
localStorage.setItem("appVersion", pkg.version);
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
updateApp();
|
||||
|
||||
const saveToLocalStorage = (store) => {
|
||||
store.subscribe((mutation, state) => {
|
||||
// console.log(mutation);
|
||||
localStorage.setItem("player", JSON.stringify(state.player));
|
||||
localStorage.setItem("settings", JSON.stringify(state.settings));
|
||||
localStorage.setItem("data", JSON.stringify(state.data));
|
||||
});
|
||||
};
|
||||
|
||||
Vue.use(Vuex);
|
||||
const store = new Vuex.Store({
|
||||
state: state,
|
||||
|
||||
const options = {
|
||||
state,
|
||||
mutations,
|
||||
actions,
|
||||
plugins: [saveToLocalStorage],
|
||||
});
|
||||
plugins: [
|
||||
saveToLocalStorage,
|
||||
vuexBroadCast,
|
||||
],
|
||||
}
|
||||
|
||||
const store = new Vuex.Store(options);
|
||||
|
||||
store.state.howler = new Howl({
|
||||
src: [
|
||||
`https://music.163.com/song/media/outer/url?id=${store.state.player.currentTrack.id}`,
|
||||
],
|
||||
html5: true,
|
||||
format: ["mp3"],
|
||||
format: ["webm", "mp3"],
|
||||
});
|
||||
|
||||
Howler.volume(store.state.player.volume);
|
||||
|
||||
if ([undefined, null].includes(store.state.settings.lang)) {
|
||||
|
@ -53,6 +54,7 @@ if ([undefined, null].includes(store.state.settings.lang)) {
|
|||
}
|
||||
|
||||
changeAppearance(store.state.settings.appearance);
|
||||
|
||||
window
|
||||
.matchMedia("(prefers-color-scheme: dark)")
|
||||
.addEventListener("change", () => {
|
||||
|
|
|
@ -14,6 +14,7 @@ export default {
|
|||
src: [mp3],
|
||||
autoplay: true,
|
||||
html5: true,
|
||||
format: ["webm", "mp3"],
|
||||
});
|
||||
state.howler.play();
|
||||
},
|
||||
|
|
17
src/store/plugins/broadcast.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
// const electron = import('electron')
|
||||
const electron = window.require("electron");
|
||||
const ipcRenderer = electron.ipcRenderer;
|
||||
|
||||
export default store => {
|
||||
// 第一行初始化第一次的状态
|
||||
ipcRenderer.send('vuex-state', store.state)
|
||||
store.subscribe((mutation, { data = '', settings = '', player = {}, contextMenu = {}, liked = {} }) => {
|
||||
const copyState = { data, settings, player, contextMenu, liked }
|
||||
ipcRenderer.send('vuex-state', copyState)
|
||||
})
|
||||
store.subscribe((mutation, state) => {
|
||||
if (mutation.type === 'updateData') {
|
||||
ipcRenderer.send('updateData', state.data)
|
||||
}
|
||||
})
|
||||
}
|
8
src/store/plugins/localStorage.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
export default (store) => {
|
||||
store.subscribe((mutation, state) => {
|
||||
// console.log(mutation);
|
||||
localStorage.setItem("player", JSON.stringify(state.player));
|
||||
localStorage.setItem("settings", JSON.stringify(state.settings));
|
||||
localStorage.setItem("data", JSON.stringify(state.data));
|
||||
});
|
||||
};
|
|
@ -1,11 +1,11 @@
|
|||
import axios from "axios";
|
||||
// import axios from "axios";
|
||||
import localforage from "localforage";
|
||||
import { getMP3 } from "@/api/track";
|
||||
|
||||
export function cacheTrack(id) {
|
||||
let tracks = localforage.createInstance({
|
||||
name: "tracks",
|
||||
});
|
||||
// let tracks = localforage.createInstance({
|
||||
// name: "tracks",
|
||||
// });
|
||||
|
||||
// TODO: limit cache songs number
|
||||
// tracks.length().then(function (length) {
|
||||
|
@ -18,14 +18,15 @@ export function cacheTrack(id) {
|
|||
|
||||
// TODO: cache track details
|
||||
return getMP3(id).then((data) => {
|
||||
return axios
|
||||
.get(data.data[0].url.replace(/^http:/, "https:"), {
|
||||
responseType: "blob",
|
||||
})
|
||||
.then((data) => {
|
||||
tracks.setItem(`${id}`, { mp3: data.data });
|
||||
return { mp3: data.data };
|
||||
});
|
||||
// return axios
|
||||
// .get(data.data[0].url.replace(/^http:/, "https:"), {
|
||||
// responseType: "blob",
|
||||
// })
|
||||
// .then((data) => {
|
||||
// tracks.setItem(`${id}`, { mp3: data.data });
|
||||
// return { mp3: data.data };
|
||||
// });
|
||||
return { mp3: data.data[0].url.replace(/^http:/, "https:")}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -78,7 +78,7 @@ module.exports = {
|
|||
// window 的 icon 头标
|
||||
win: {
|
||||
publisherName: 'Yes Play Music',
|
||||
icon: 'public/img/icons/512x512.png',
|
||||
icon: 'build/icons/icon.ico',
|
||||
publish: [
|
||||
"github"
|
||||
],
|
||||
|
@ -89,7 +89,8 @@ module.exports = {
|
|||
"AppImage",
|
||||
"tar.gz",
|
||||
"deb"
|
||||
]
|
||||
],
|
||||
icon: "build/icons"
|
||||
},
|
||||
"dmg": {
|
||||
"icon": "build/icons/icon.icns"
|
||||
|
|
106
yarn.lock
|
@ -3459,17 +3459,6 @@ concat-stream@^1.5.0, concat-stream@^1.6.2:
|
|||
readable-stream "^2.2.2"
|
||||
typedarray "^0.0.6"
|
||||
|
||||
conf@^2.0.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.npm.taobao.org/conf/download/conf-2.2.0.tgz#ee282efafc1450b61e205372041ad7d866802d9a"
|
||||
integrity sha1-7igu+vwUULYeIFNyBBrX2GaALZo=
|
||||
dependencies:
|
||||
dot-prop "^4.1.0"
|
||||
env-paths "^1.0.0"
|
||||
make-dir "^1.0.0"
|
||||
pkg-up "^2.0.0"
|
||||
write-file-atomic "^2.3.0"
|
||||
|
||||
config-chain@^1.1.11:
|
||||
version "1.1.12"
|
||||
resolved "https://registry.npm.taobao.org/config-chain/download/config-chain-1.1.12.tgz#0fde8d091200eb5e808caf25fe618c02f48e4efa"
|
||||
|
@ -3979,11 +3968,6 @@ deepmerge@^1.5.2:
|
|||
resolved "https://registry.npm.taobao.org/deepmerge/download/deepmerge-1.5.2.tgz#10499d868844cdad4fee0842df8c7f6f0c95a753"
|
||||
integrity sha1-EEmdhohEza1P7ghC34x/bwyVp1M=
|
||||
|
||||
deepmerge@^2.1.1:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.npm.taobao.org/deepmerge/download/deepmerge-2.2.1.tgz#5d3ff22a01c00f645405a2fbc17d0778a1801170"
|
||||
integrity sha1-XT/yKgHAD2RUBaL7wX0HeKGAEXA=
|
||||
|
||||
default-gateway@^4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.npm.taobao.org/default-gateway/download/default-gateway-4.2.0.tgz#167104c7500c2115f6dd69b0a536bb8ed720552b"
|
||||
|
@ -4227,13 +4211,6 @@ domutils@^1.5.1, domutils@^1.7.0:
|
|||
dom-serializer "0"
|
||||
domelementtype "1"
|
||||
|
||||
dot-prop@^4.1.0:
|
||||
version "4.2.1"
|
||||
resolved "https://registry.npm.taobao.org/dot-prop/download/dot-prop-4.2.1.tgz#45884194a71fc2cda71cbb4bceb3a4dd2f433ba4"
|
||||
integrity sha1-RYhBlKcfws2nHLtLzrOk3S9DO6Q=
|
||||
dependencies:
|
||||
is-obj "^1.0.0"
|
||||
|
||||
dot-prop@^5.2.0:
|
||||
version "5.3.0"
|
||||
resolved "https://registry.npm.taobao.org/dot-prop/download/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88"
|
||||
|
@ -4414,13 +4391,6 @@ electron-publish@22.9.1:
|
|||
lazy-val "^1.0.4"
|
||||
mime "^2.4.6"
|
||||
|
||||
electron-store@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npm.taobao.org/electron-store/download/electron-store-2.0.0.tgz#1035cca2a95409d1f54c7466606345852450d64a"
|
||||
integrity sha1-EDXMoqlUCdH1THRmYGNFhSRQ1ko=
|
||||
dependencies:
|
||||
conf "^2.0.0"
|
||||
|
||||
electron-to-chromium@^1.3.570:
|
||||
version "1.3.570"
|
||||
resolved "https://registry.npm.taobao.org/electron-to-chromium/download/electron-to-chromium-1.3.570.tgz#3f5141cc39b4e3892a276b4889980dabf1d29c7f"
|
||||
|
@ -4512,11 +4482,6 @@ entities@^2.0.0:
|
|||
resolved "https://registry.npm.taobao.org/entities/download/entities-2.0.3.tgz#5c487e5742ab93c15abb5da22759b8590ec03b7f"
|
||||
integrity sha1-XEh+V0Krk8Fau12iJ1m4WQ7AO38=
|
||||
|
||||
env-paths@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.npm.taobao.org/env-paths/download/env-paths-1.0.0.tgz#4168133b42bb05c38a35b1ae4397c8298ab369e0"
|
||||
integrity sha1-QWgTO0K7BcOKNbGuQ5fIKYqzaeA=
|
||||
|
||||
env-paths@^2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.npm.taobao.org/env-paths/download/env-paths-2.2.0.tgz#cdca557dc009152917d6166e2febe1f039685e43"
|
||||
|
@ -5187,13 +5152,6 @@ find-up@^1.0.0:
|
|||
path-exists "^2.0.0"
|
||||
pinkie-promise "^2.0.0"
|
||||
|
||||
find-up@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.npm.taobao.org/find-up/download/find-up-2.1.0.tgz?cache=0&sync_timestamp=1597169842138&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ffind-up%2Fdownload%2Ffind-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7"
|
||||
integrity sha1-RdG35QbHF93UgndaK3eSCjwMV6c=
|
||||
dependencies:
|
||||
locate-path "^2.0.0"
|
||||
|
||||
find-up@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.npm.taobao.org/find-up/download/find-up-3.0.0.tgz?cache=0&sync_timestamp=1597169842138&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ffind-up%2Fdownload%2Ffind-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73"
|
||||
|
@ -6410,7 +6368,7 @@ is-number@^7.0.0:
|
|||
resolved "https://registry.npm.taobao.org/is-number/download/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
|
||||
integrity sha1-dTU0W4lnNNX4DE0GxQlVUnoU8Ss=
|
||||
|
||||
is-obj@^1.0.0, is-obj@^1.0.1:
|
||||
is-obj@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.npm.taobao.org/is-obj/download/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f"
|
||||
integrity sha1-PkcprB9f3gJc19g6iW2rn09n2w8=
|
||||
|
@ -7024,14 +6982,6 @@ localforage@^1.9.0:
|
|||
dependencies:
|
||||
lie "3.1.1"
|
||||
|
||||
locate-path@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npm.taobao.org/locate-path/download/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e"
|
||||
integrity sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=
|
||||
dependencies:
|
||||
p-locate "^2.0.0"
|
||||
path-exists "^3.0.0"
|
||||
|
||||
locate-path@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.npm.taobao.org/locate-path/download/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e"
|
||||
|
@ -7168,13 +7118,6 @@ lru-cache@^6.0.0:
|
|||
dependencies:
|
||||
yallist "^4.0.0"
|
||||
|
||||
make-dir@^1.0.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.npm.taobao.org/make-dir/download/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c"
|
||||
integrity sha1-ecEDO4BRW9bSTsmTPoYMp17ifww=
|
||||
dependencies:
|
||||
pify "^3.0.0"
|
||||
|
||||
make-dir@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.npm.taobao.org/make-dir/download/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5"
|
||||
|
@ -7963,13 +7906,6 @@ p-finally@^2.0.0:
|
|||
resolved "https://registry.npm.taobao.org/p-finally/download/p-finally-2.0.1.tgz#bd6fcaa9c559a096b680806f4d657b3f0f240561"
|
||||
integrity sha1-vW/KqcVZoJa2gIBvTWV7Pw8kBWE=
|
||||
|
||||
p-limit@^1.1.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.npm.taobao.org/p-limit/download/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8"
|
||||
integrity sha1-uGvV8MJWkJEcdZD8v8IBDVSzzLg=
|
||||
dependencies:
|
||||
p-try "^1.0.0"
|
||||
|
||||
p-limit@^2.0.0, p-limit@^2.2.0, p-limit@^2.2.1, p-limit@^2.3.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.npm.taobao.org/p-limit/download/p-limit-2.3.0.tgz?cache=0&sync_timestamp=1594559734248&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fp-limit%2Fdownload%2Fp-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1"
|
||||
|
@ -7984,13 +7920,6 @@ p-limit@^3.0.2:
|
|||
dependencies:
|
||||
p-try "^2.0.0"
|
||||
|
||||
p-locate@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npm.taobao.org/p-locate/download/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43"
|
||||
integrity sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=
|
||||
dependencies:
|
||||
p-limit "^1.1.0"
|
||||
|
||||
p-locate@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.npm.taobao.org/p-locate/download/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4"
|
||||
|
@ -8036,11 +7965,6 @@ p-retry@^3.0.1:
|
|||
dependencies:
|
||||
retry "^0.12.0"
|
||||
|
||||
p-try@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.npm.taobao.org/p-try/download/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3"
|
||||
integrity sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=
|
||||
|
||||
p-try@^2.0.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.npm.taobao.org/p-try/download/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
|
||||
|
@ -8368,13 +8292,6 @@ pkg-dir@^4.1.0, pkg-dir@^4.2.0:
|
|||
dependencies:
|
||||
find-up "^4.0.0"
|
||||
|
||||
pkg-up@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npm.taobao.org/pkg-up/download/pkg-up-2.0.0.tgz?cache=0&sync_timestamp=1589682752621&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpkg-up%2Fdownload%2Fpkg-up-2.0.0.tgz#c819ac728059a461cab1c3889a2be3c49a004d7f"
|
||||
integrity sha1-yBmscoBZpGHKscOImivjxJoATX8=
|
||||
dependencies:
|
||||
find-up "^2.1.0"
|
||||
|
||||
please-upgrade-node@^3.2.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.npm.taobao.org/please-upgrade-node/download/please-upgrade-node-3.2.0.tgz#aeddd3f994c933e4ad98b99d9a556efa0e2fe942"
|
||||
|
@ -11180,14 +11097,6 @@ vue@^2.6.11:
|
|||
resolved "https://registry.npm.taobao.org/vue/download/vue-2.6.12.tgz?cache=0&sync_timestamp=1600443052956&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fvue%2Fdownload%2Fvue-2.6.12.tgz#f5ebd4fa6bd2869403e29a896aed4904456c9123"
|
||||
integrity sha1-9evU+mvShpQD4pqJau1JBEVskSM=
|
||||
|
||||
vuex-electron@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.npm.taobao.org/vuex-electron/download/vuex-electron-1.0.3.tgz#c8ce0fd06ed9d3beb8881bce39bf76c9fcdc5611"
|
||||
integrity sha1-yM4P0G7Z0764iBvOOb92yfzcVhE=
|
||||
dependencies:
|
||||
deepmerge "^2.1.1"
|
||||
electron-store "^2.0.0"
|
||||
|
||||
vuex@^3.4.0:
|
||||
version "3.5.1"
|
||||
resolved "https://registry.npm.taobao.org/vuex/download/vuex-3.5.1.tgz#f1b8dcea649bc25254cf4f4358081dbf5da18b3d"
|
||||
|
@ -11598,19 +11507,10 @@ wrappy@1:
|
|||
resolved "https://registry.npm.taobao.org/wrappy/download/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
|
||||
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
|
||||
|
||||
write-file-atomic@^2.3.0:
|
||||
version "2.4.3"
|
||||
resolved "https://registry.npm.taobao.org/write-file-atomic/download/write-file-atomic-2.4.3.tgz#1fd2e9ae1df3e75b8d8c367443c692d4ca81f481"
|
||||
integrity sha1-H9Lprh3z51uNjDZ0Q8aS1MqB9IE=
|
||||
dependencies:
|
||||
graceful-fs "^4.1.11"
|
||||
imurmurhash "^0.1.4"
|
||||
signal-exit "^3.0.2"
|
||||
|
||||
write-file-atomic@^3.0.0:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.npm.taobao.org/write-file-atomic/download/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8"
|
||||
integrity sha1-Vr1cWlxwSBzRnFcb05q5ZaXeVug=
|
||||
resolved "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8"
|
||||
integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==
|
||||
dependencies:
|
||||
imurmurhash "^0.1.4"
|
||||
is-typedarray "^1.0.0"
|
||||
|
|