commit
ead51ae676
|
@ -19,6 +19,54 @@ const ENDPOINTS = {
|
||||||
'statistics': `${APIBASE}/statistics`
|
'statistics': `${APIBASE}/statistics`
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const dummyTags = ['tag1', 'tag2', 'tag3', 'tag4', 'tag5'];
|
||||||
|
const dummyRepo = {
|
||||||
|
name: 'ExampleRepository',
|
||||||
|
baseUri: 'https://github.com/Jiiks/ExampleRepository',
|
||||||
|
rawUri: 'https://github.com/Jiiks/ExampleRepository/raw/master'
|
||||||
|
};
|
||||||
|
const dummyVersion = () => `${Math.round(Math.random() * 3)}.${Math.round(Math.random() * 10)}.${Math.round(Math.random() * 10)}`;
|
||||||
|
const dummyFiles = {
|
||||||
|
readme: 'Example/readme.md',
|
||||||
|
previews: [{
|
||||||
|
large: 'Example/preview1-big.png',
|
||||||
|
thumb: 'Example/preview1-small.png'
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
const dummyAuthor = 'DummyAuthor';
|
||||||
|
const dummyTimestamp = () => `2018-${Math.floor((Math.random() * 12) + 1).toString().padStart(2, '0')}-${Math.floor((Math.random() * 30) + 1).toString().padStart(2, '0')}T14:51:32.057Z`;
|
||||||
|
|
||||||
|
async function dummyThemes() {
|
||||||
|
// Simulate get
|
||||||
|
await new Promise(r => setTimeout(r, Math.random() * 3000));
|
||||||
|
const dummies = [];
|
||||||
|
for (let i = 0; i < 10; i++) {
|
||||||
|
dummies.push({
|
||||||
|
id: `theme${i}${btoa(Math.random()).substring(3, 9)}`,
|
||||||
|
name: `Dummy ${i}`,
|
||||||
|
tags: dummyTags,
|
||||||
|
installs: Math.floor(Math.random() * 10000),
|
||||||
|
updated: dummyTimestamp(),
|
||||||
|
rating: Math.floor(Math.random() * 1000),
|
||||||
|
activeUsers: Math.floor(Math.random() * 1000),
|
||||||
|
rated: Math.random() > .5,
|
||||||
|
version: dummyVersion(),
|
||||||
|
repository: dummyRepo,
|
||||||
|
files: dummyFiles,
|
||||||
|
author: dummyAuthor
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
docs: dummies,
|
||||||
|
pagination: {
|
||||||
|
total: 25,
|
||||||
|
pages: 3,
|
||||||
|
limit: 9,
|
||||||
|
page: 1
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export default class BdWebApi {
|
export default class BdWebApi {
|
||||||
|
|
||||||
static get themes() {
|
static get themes() {
|
||||||
|
@ -41,11 +89,14 @@ export default class BdWebApi {
|
||||||
}
|
}
|
||||||
|
|
||||||
static getThemes(args) {
|
static getThemes(args) {
|
||||||
|
return dummyThemes();
|
||||||
|
/*
|
||||||
if (!args) return request.get(ENDPOINTS.themes);
|
if (!args) return request.get(ENDPOINTS.themes);
|
||||||
const { id } = args;
|
const { id } = args;
|
||||||
if (id) return request.get(ENDPOINTS.theme(id));
|
if (id) return request.get(ENDPOINTS.theme(id));
|
||||||
|
|
||||||
return request.get(ENDPOINTS.themes);
|
return request.get(ENDPOINTS.themes);
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
static getUsers(args) {
|
static getUsers(args) {
|
||||||
|
|
|
@ -12,6 +12,7 @@ import asar from 'asar';
|
||||||
import path, { dirname } from 'path';
|
import path, { dirname } from 'path';
|
||||||
import rimraf from 'rimraf';
|
import rimraf from 'rimraf';
|
||||||
|
|
||||||
|
import { remote } from 'electron';
|
||||||
import Content from './content';
|
import Content from './content';
|
||||||
import Globals from './globals';
|
import Globals from './globals';
|
||||||
import Database from './database';
|
import Database from './database';
|
||||||
|
@ -71,6 +72,28 @@ export default class {
|
||||||
return Globals.getPath(this.pathId);
|
return Globals.getPath(this.pathId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async packContent(path, contentPath) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
remote.dialog.showSaveDialog({
|
||||||
|
title: 'Save Package',
|
||||||
|
defaultPath: path,
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
name: 'BetterDiscord Package',
|
||||||
|
extensions: ['bd']
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}, filepath => {
|
||||||
|
if (!filepath) return;
|
||||||
|
|
||||||
|
asar.uncache(filepath);
|
||||||
|
asar.createPackage(contentPath, filepath, () => {
|
||||||
|
resolve(filepath);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load all locally stored content.
|
* Load all locally stored content.
|
||||||
* @param {bool} suppressErrors Suppress any errors that occur during loading of content
|
* @param {bool} suppressErrors Suppress any errors that occur during loading of content
|
||||||
|
|
|
@ -13,6 +13,7 @@ import Security from './security';
|
||||||
import { ReactComponents } from './reactcomponents';
|
import { ReactComponents } from './reactcomponents';
|
||||||
import Reflection from './reflection';
|
import Reflection from './reflection';
|
||||||
import DiscordApi from './discordapi';
|
import DiscordApi from './discordapi';
|
||||||
|
import ThemeManager from './thememanager';
|
||||||
|
|
||||||
export default class PackageInstaller {
|
export default class PackageInstaller {
|
||||||
|
|
||||||
|
@ -64,47 +65,39 @@ export default class PackageInstaller {
|
||||||
/**
|
/**
|
||||||
* Installs or updates defined package
|
* Installs or updates defined package
|
||||||
* @param {Byte[]|String} bytesOrPath byte array of binary or path to local file
|
* @param {Byte[]|String} bytesOrPath byte array of binary or path to local file
|
||||||
* @param {String} name Package name
|
* @param {String} nameOrId Package name
|
||||||
* @param {Boolean} update Does an older version already exist
|
* @param {Boolean} update Does an older version already exist
|
||||||
*/
|
*/
|
||||||
static async installPackage(bytesOrPath, id, update = false) {
|
static async installPackage(bytesOrPath, nameOrId, contentType, update = false) {
|
||||||
let outputPath = null;
|
let outputPath = null;
|
||||||
try {
|
try {
|
||||||
const bytes = typeof bytesOrPath === 'string' ? fs.readFileSync(bytesOrPath) : bytesOrPath;
|
|
||||||
|
|
||||||
const outputName = `${id}.bd`;
|
const bytes = typeof bytesOrPath === 'string' ? fs.readFileSync(bytesOrPath) : bytesOrPath;
|
||||||
outputPath = path.join(Globals.getPath('plugins'), outputName);
|
const outputName = `${nameOrId}.bd`;
|
||||||
|
|
||||||
|
outputPath = path.join(Globals.getPath(`${contentType}s`), outputName);
|
||||||
fs.writeFileSync(outputPath, bytes);
|
fs.writeFileSync(outputPath, bytes);
|
||||||
|
|
||||||
if (!update) return PluginManager.preloadPackedContent(outputName);
|
const manager = contentType === 'plugin' ? PluginManager : ThemeManager;
|
||||||
|
|
||||||
const oldContent = PluginManager.getPluginById(id);
|
if (!update) return manager.preloadPackedContent(outputName);
|
||||||
|
|
||||||
if (update && oldContent.packed && oldContent.packed.packageName !== id) {
|
const oldContent = manager.findContent(nameOrId);
|
||||||
await oldContent.unload(true);
|
|
||||||
|
await oldContent.unload(true);
|
||||||
|
|
||||||
|
if (oldContent.packed && oldContent.packed.packageName !== nameOrId) {
|
||||||
rimraf(oldContent.packed.packagePath, err => {
|
rimraf(oldContent.packed.packagePath, err => {
|
||||||
if(err) console.log(err);
|
if (err) throw err;
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
return PluginManager.preloadPackedContent(outputName);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (update && !oldContent.packed) {
|
|
||||||
await oldContent.unload(true);
|
|
||||||
rimraf(oldContent.contentPath, err => {
|
rimraf(oldContent.contentPath, err => {
|
||||||
if (err) console.log(err);
|
if (err) throw err;
|
||||||
});
|
});
|
||||||
|
|
||||||
return PluginManager.preloadPackedContent(outputName);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return PluginManager.reloadContent(oldContent);
|
return manager.preloadPackedContent(outputName);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (outputPath) {
|
|
||||||
rimraf(outputPath, err => {
|
|
||||||
if (err) console.log(err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,56 @@
|
||||||
.bd-pluginsview,
|
.bd-pluginsview,
|
||||||
.bd-themesview {
|
.bd-themesview {
|
||||||
.bd-onlinePh {
|
.bd-localPh {
|
||||||
|
.bd-scroller {
|
||||||
|
padding: 0 20px 0 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bd-onlinePh,
|
||||||
|
.bd-localPh {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
margin: 10% 0;
|
margin: 10px 0;
|
||||||
|
|
||||||
|
.bd-spinnerContainer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bd-onlinePhHeader {
|
||||||
|
display: flex;
|
||||||
|
padding: 0 20px 0 10px;
|
||||||
|
min-height: 80px;
|
||||||
|
|
||||||
|
.bd-flexRow {
|
||||||
|
min-height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bd-searchHint {
|
||||||
|
flex-grow: 1;
|
||||||
|
line-height: 40px;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bd-searchSort {
|
||||||
|
span {
|
||||||
|
color: #fff;
|
||||||
|
line-height: 40px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bd-onlinePhBody {
|
||||||
|
margin-top: 10px;
|
||||||
|
|
||||||
|
.bd-spinnerContainer {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bd-scroller {
|
||||||
|
padding: 0 20px 0 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
h3 {
|
h3 {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
.bd-remoteCard {
|
.bd-remoteCard {
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
padding: 10px 0;
|
padding: 10px;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
border-bottom: 1px solid rgba(114, 118, 126, .3);
|
border-bottom: 1px solid rgba(114, 118, 126, .3);
|
||||||
|
|
||||||
|
@ -52,17 +51,19 @@
|
||||||
.bd-remoteCardTags {
|
.bd-remoteCardTags {
|
||||||
color: #828a97;
|
color: #828a97;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
line-height: 20px;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-end;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bd-buttonGroup {
|
.bd-buttonGroup {
|
||||||
align-self: flex-end;
|
align-self: flex-end;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
max-height: 20px;
|
max-height: 30px;
|
||||||
|
|
||||||
.bd-button {
|
.bd-button {
|
||||||
font-size: 12px;
|
font-size: 16px;
|
||||||
padding: 5px 10px;
|
padding: 5px 10px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -164,11 +164,11 @@
|
||||||
.bd-fancySearch {
|
.bd-fancySearch {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
transform: translateY(80px) translateX(-140px);
|
// transform: translateY(80px) translateX(-140px);
|
||||||
transition: all .5s ease-in-out;
|
// transition: all .5s ease-in-out;
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
content: 'Search by name, description or tag...';
|
// content: 'Search by name, description or tag...';
|
||||||
color: #f6f6f7;
|
color: #f6f6f7;
|
||||||
position: relative;
|
position: relative;
|
||||||
top: -20px;
|
top: -20px;
|
||||||
|
@ -184,6 +184,12 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.bd-disabled {
|
||||||
|
.bd-textInput {
|
||||||
|
opacity: .8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.bd-textInput {
|
.bd-textInput {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
@ -120,8 +120,14 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.bd-settingswrapContents {
|
.bd-settingswrapContents {
|
||||||
padding: 0 20px;
|
padding: 0 0 0 20px;
|
||||||
margin-bottom: 84px;
|
}
|
||||||
|
|
||||||
|
.bd-scroller {
|
||||||
|
.bd-settingswrapContents {
|
||||||
|
margin-bottom: 84px;
|
||||||
|
padding: 0 20px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,11 +23,8 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// Imports
|
// Imports
|
||||||
import asar from 'asar';
|
|
||||||
import electron from 'electron';
|
|
||||||
import fs from 'fs';
|
|
||||||
import { Toasts } from 'ui';
|
import { Toasts } from 'ui';
|
||||||
import { Settings } from 'modules';
|
import { Settings, PluginManager } from 'modules';
|
||||||
import { ClientLogger as Logger } from 'common';
|
import { ClientLogger as Logger } from 'common';
|
||||||
import { shell } from 'electron';
|
import { shell } from 'electron';
|
||||||
import Card from './Card.vue';
|
import Card from './Card.vue';
|
||||||
|
@ -45,24 +42,13 @@
|
||||||
MiSettings, MiRefresh, MiPencil, MiDelete, MiExtension, MiBoxDownload
|
MiSettings, MiRefresh, MiPencil, MiDelete, MiExtension, MiBoxDownload
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
package() {
|
async package() {
|
||||||
electron.remote.dialog.showSaveDialog({
|
try {
|
||||||
title: 'Save Plugin Package',
|
const packagePath = await PluginManager.packContent(this.plugin.name, this.plugin.contentPath);
|
||||||
defaultPath: this.plugin.name,
|
Toasts.success(`Plugin Packaged: ${packagePath}`);
|
||||||
filters: [
|
} catch (err) {
|
||||||
{
|
Logger.log('PluginCard', err);
|
||||||
name: 'BetterDiscord Package',
|
}
|
||||||
extensions: ['bd']
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}, filepath => {
|
|
||||||
if (!filepath) return;
|
|
||||||
|
|
||||||
asar.uncache(filepath);
|
|
||||||
asar.createPackage(this.plugin.contentPath, filepath, () => {
|
|
||||||
Toasts.success('Plugin Packaged!');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
editPlugin() {
|
editPlugin() {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<SettingsWrapper headertext="Plugins">
|
<SettingsWrapper headertext="Plugins" :noscroller="true">
|
||||||
<div class="bd-tabbar" slot="header">
|
<div class="bd-tabbar" slot="header">
|
||||||
<div class="bd-button" :class="{'bd-active': local}" @click="showLocal">
|
<div class="bd-button" :class="{'bd-active': local}" @click="showLocal">
|
||||||
<h3>Installed</h3>
|
<h3>Installed</h3>
|
||||||
|
@ -22,12 +22,26 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bd-flex bd-flexCol bd-pluginsview">
|
<div class="bd-flex bd-flexCol bd-pluginsview">
|
||||||
<div v-if="local" class="bd-flex bd-flexGrow bd-flexCol bd-pluginsContainer bd-localPlugins">
|
<div v-if="local" class="bd-flex bd-flexGrow bd-flexCol bd-pluginsContainer bd-localPlugins bd-localPh">
|
||||||
<PluginCard v-for="plugin in localPlugins" :plugin="plugin" :key="plugin.id" :data-plugin-id="plugin.id" @toggle-plugin="togglePlugin(plugin)" @reload-plugin="reloadPlugin(plugin)" @delete-plugin="unload => deletePlugin(plugin, unload)" @show-settings="dont_clone => showSettings(plugin, dont_clone)" />
|
<ScrollerWrap>
|
||||||
|
<PluginCard v-for="plugin in localPlugins" :plugin="plugin" :key="plugin.id" :data-plugin-id="plugin.id" @toggle-plugin="togglePlugin(plugin)" @reload-plugin="reloadPlugin(plugin)" @delete-plugin="unload => deletePlugin(plugin, unload)" @show-settings="dont_clone => showSettings(plugin, dont_clone)" />
|
||||||
|
</ScrollerWrap>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!local" class="bd-onlinePh">
|
<div v-else class="bd-onlinePh">
|
||||||
<h3>Coming Soon</h3>
|
<div class="bd-onlinePhHeader">
|
||||||
<a href="https://v2.betterdiscord.net/plugins" target="_new">Website Browser</a>
|
<div class="bd-fancySearch" :class="{'bd-disabled': loadingOnline, 'bd-active': loadingOnline || (onlinePlugins && onlinePlugins.docs)}">
|
||||||
|
<input type="text" class="bd-textInput" placeholder="Search" @keydown.enter="searchInput" @keyup.stop />
|
||||||
|
</div>
|
||||||
|
<div v-if="loadingOnline" class="bd-spinnerContainer">
|
||||||
|
<div class="bd-spinner7" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ScrollerWrap class="bd-onlinePhBody" v-if="!loadingOnline && onlinePlugins" :scrollend="scrollend">
|
||||||
|
<RemoteCard v-if="onlinePlugins && onlinePlugins.docs" v-for="plugin in onlinePlugins.docs" :key="plugin.id" :item="plugin" />
|
||||||
|
<div v-if="loadingMore" class="bd-spinnerContainer">
|
||||||
|
<div class="bd-spinner7" />
|
||||||
|
</div>
|
||||||
|
</ScrollerWrap>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</SettingsWrapper>
|
</SettingsWrapper>
|
||||||
|
@ -38,7 +52,7 @@
|
||||||
import { PluginManager } from 'modules';
|
import { PluginManager } from 'modules';
|
||||||
import { Modals } from 'ui';
|
import { Modals } from 'ui';
|
||||||
import { ClientLogger as Logger } from 'common';
|
import { ClientLogger as Logger } from 'common';
|
||||||
import { MiRefresh } from '../common';
|
import { MiRefresh, ScrollerWrap } from '../common';
|
||||||
import SettingsWrapper from './SettingsWrapper.vue';
|
import SettingsWrapper from './SettingsWrapper.vue';
|
||||||
import PluginCard from './PluginCard.vue';
|
import PluginCard from './PluginCard.vue';
|
||||||
import RefreshBtn from '../common/RefreshBtn.vue';
|
import RefreshBtn from '../common/RefreshBtn.vue';
|
||||||
|
@ -48,12 +62,15 @@
|
||||||
return {
|
return {
|
||||||
PluginManager,
|
PluginManager,
|
||||||
local: true,
|
local: true,
|
||||||
localPlugins: PluginManager.localPlugins
|
localPlugins: PluginManager.localPlugins,
|
||||||
|
onlinePlugins: null,
|
||||||
|
loadingOnline: false,
|
||||||
|
loadingMore: false
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
SettingsWrapper, PluginCard,
|
SettingsWrapper, PluginCard,
|
||||||
MiRefresh,
|
MiRefresh, ScrollerWrap,
|
||||||
RefreshBtn
|
RefreshBtn
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -96,6 +113,24 @@
|
||||||
return Modals.contentSettings(plugin, null, {
|
return Modals.contentSettings(plugin, null, {
|
||||||
dont_clone
|
dont_clone
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
searchInput(e) {
|
||||||
|
if (this.loadingOnline || this.loadingMore) return;
|
||||||
|
this.refreshOnline();
|
||||||
|
},
|
||||||
|
async scrollend(e) {
|
||||||
|
// TODO
|
||||||
|
return;
|
||||||
|
if (this.loadingOnline || this.loadingMore) return;
|
||||||
|
this.loadingMore = true;
|
||||||
|
try {
|
||||||
|
const getPlugins = await BdWebApi.plugins.get();
|
||||||
|
this.onlinePlugins.docs = [...this.onlinePlugins.docs, ...getPlugins.docs];
|
||||||
|
} catch (err) {
|
||||||
|
Logger.err('PluginsView', err);
|
||||||
|
} finally {
|
||||||
|
this.loadingMore = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
<div class="bd-remoteCardInfoBox bd-flex bd-flexGrow bd-flexCol">
|
<div class="bd-remoteCardInfoBox bd-flex bd-flexGrow bd-flexCol">
|
||||||
<div class="bd-remoteCardInfo">{{item.installs}} Installs</div>
|
<div class="bd-remoteCardInfo">{{item.installs}} Installs</div>
|
||||||
<div class="bd-remoteCardInfo">{{item.activeUsers}} Active Users</div>
|
<div class="bd-remoteCardInfo">{{item.activeUsers}} Active Users</div>
|
||||||
<div class="bd-remoteCardInfo">Updated: Some time ago</div>
|
<div class="bd-remoteCardInfo">Updated {{fromNow()}}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -29,13 +29,16 @@
|
||||||
<div class="bd-buttonGroup">
|
<div class="bd-buttonGroup">
|
||||||
<div class="bd-button">Install</div>
|
<div class="bd-button">Install</div>
|
||||||
<div class="bd-button">Preview</div>
|
<div class="bd-button">Preview</div>
|
||||||
<div class="bd-button">Source</div>
|
<div class="bd-button" @click="openSourceUrl">Source</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { Reflection } from 'modules';
|
||||||
|
import { shell } from 'electron';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: ['item'],
|
props: ['item'],
|
||||||
data() {
|
data() {
|
||||||
|
@ -44,6 +47,15 @@
|
||||||
methods: {
|
methods: {
|
||||||
resolveThumb() {
|
resolveThumb() {
|
||||||
return `${this.item.repository.rawUri}/${this.item.files.previews[0].thumb}`;
|
return `${this.item.repository.rawUri}/${this.item.files.previews[0].thumb}`;
|
||||||
|
},
|
||||||
|
fromNow() {
|
||||||
|
const { Moment } = Reflection.modules;
|
||||||
|
return Moment(this.item.updated).fromNow();
|
||||||
|
},
|
||||||
|
openSourceUrl() {
|
||||||
|
if (!this.item.repository || !this.item.repository.baseUri) return;
|
||||||
|
if (Object.assign(document.createElement('a'), { href: this.item.repository.baseUri }).hostname !== 'github.com') return;
|
||||||
|
shell.openExternal(this.item.repository.baseUri);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,16 @@
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="bd-settingswrap">
|
<div class="bd-settingswrap">
|
||||||
<ScrollerWrap>
|
<div v-if="noscroller" class="bd-flex bd-flexCol">
|
||||||
|
<div class="bd-settingswrapHeader">
|
||||||
|
<span class="bd-settingswrapHeaderText">{{ headertext }}</span>
|
||||||
|
<slot name="header" />
|
||||||
|
</div>
|
||||||
|
<div class="bd-settingswrapContents bd-flex bd-flexGrow bd-flexCol">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ScrollerWrap v-else :scrollend="scrollend">
|
||||||
<div class="bd-settingswrapHeader">
|
<div class="bd-settingswrapHeader">
|
||||||
<span class="bd-settingswrapHeaderText">{{ headertext }}</span>
|
<span class="bd-settingswrapHeaderText">{{ headertext }}</span>
|
||||||
<slot name="header" />
|
<slot name="header" />
|
||||||
|
@ -27,7 +36,7 @@
|
||||||
import { ScrollerWrap } from '../common';
|
import { ScrollerWrap } from '../common';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: ['headertext'],
|
props: ['headertext', 'scrollend', 'noscroller'],
|
||||||
components: {
|
components: {
|
||||||
ScrollerWrap
|
ScrollerWrap
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
<Card :item="theme">
|
<Card :item="theme">
|
||||||
<SettingSwitch slot="toggle" :value="theme.enabled" @input="$emit('toggle-theme')" />
|
<SettingSwitch slot="toggle" :value="theme.enabled" @input="$emit('toggle-theme')" />
|
||||||
<ButtonGroup slot="controls" v-if="!online">
|
<ButtonGroup slot="controls" v-if="!online">
|
||||||
|
<Button v-if="devmode && !theme.packed" v-tooltip="'Package Theme'" @click="package"><MiBoxDownload size="18" /></Button>
|
||||||
<Button v-tooltip="'Settings (shift + click to open settings without cloning the set)'" v-if="theme.hasSettings" @click="$emit('show-settings', $event.shiftKey)"><MiSettings size="18" /></Button>
|
<Button v-tooltip="'Settings (shift + click to open settings without cloning the set)'" v-if="theme.hasSettings" @click="$emit('show-settings', $event.shiftKey)"><MiSettings size="18" /></Button>
|
||||||
<Button v-tooltip="'Recompile (shift + click to reload)'" @click="$emit('reload-theme', $event.shiftKey)"><MiRefresh size="18" /></Button>
|
<Button v-tooltip="'Recompile (shift + click to reload)'" @click="$emit('reload-theme', $event.shiftKey)"><MiRefresh size="18" /></Button>
|
||||||
<Button v-tooltip="'Edit'" @click="editTheme"><MiPencil size="18" /></Button>
|
<Button v-tooltip="'Edit'" @click="editTheme"><MiPencil size="18" /></Button>
|
||||||
|
@ -22,17 +23,33 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// Imports
|
// Imports
|
||||||
|
import { Toasts } from 'ui';
|
||||||
|
import { Settings, ThemeManager } from 'modules';
|
||||||
|
import { ClientLogger as Logger } from 'common';
|
||||||
import { shell } from 'electron';
|
import { shell } from 'electron';
|
||||||
import Card from './Card.vue';
|
import Card from './Card.vue';
|
||||||
import { Button, ButtonGroup, SettingSwitch, MiSettings, MiRefresh, MiPencil, MiDelete, MiExtension } from '../common';
|
import { Button, ButtonGroup, SettingSwitch, MiSettings, MiRefresh, MiPencil, MiDelete, MiExtension, MiBoxDownload } from '../common';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
devmode: Settings.getSetting('core', 'advanced', 'developer-mode').value
|
||||||
|
}
|
||||||
|
},
|
||||||
props: ['theme', 'online'],
|
props: ['theme', 'online'],
|
||||||
components: {
|
components: {
|
||||||
Card, Button, ButtonGroup, SettingSwitch,
|
Card, Button, ButtonGroup, SettingSwitch,
|
||||||
MiSettings, MiRefresh, MiPencil, MiDelete, MiExtension
|
MiSettings, MiRefresh, MiPencil, MiDelete, MiExtension, MiBoxDownload
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
async package() {
|
||||||
|
try {
|
||||||
|
const packagePath = await ThemeManager.packContent(this.theme.name, this.theme.contentPath);
|
||||||
|
Toasts.success(`Theme Packaged: ${packagePath}`);
|
||||||
|
} catch (err) {
|
||||||
|
Logger.log('ThemeCard', err);
|
||||||
|
}
|
||||||
|
},
|
||||||
editTheme() {
|
editTheme() {
|
||||||
try {
|
try {
|
||||||
shell.openItem(this.theme.contentPath);
|
shell.openItem(this.theme.contentPath);
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<SettingsWrapper headertext="Themes">
|
<SettingsWrapper headertext="Themes" :noscroller="true">
|
||||||
<div class="bd-tabbar" slot="header">
|
<div class="bd-tabbar" slot="header">
|
||||||
<div class="bd-button" :class="{'bd-active': local}" @click="showLocal">
|
<div class="bd-button" :class="{'bd-active': local}" @click="showLocal">
|
||||||
<h3>Installed</h3>
|
<h3>Installed</h3>
|
||||||
|
@ -22,15 +22,38 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bd-flex bd-flexCol bd-themesview">
|
<div class="bd-flex bd-flexCol bd-themesview">
|
||||||
<div v-if="local" class="bd-flex bd-flexGrow bd-flexCol bd-themesContainer bd-localThemes">
|
<div v-if="local" class="bd-flex bd-flexGrow bd-flexCol bd-themesContainer bd-localPh">
|
||||||
<ThemeCard v-for="theme in localThemes" :theme="theme" :key="theme.id" :data-theme-id="theme.id" @toggle-theme="toggleTheme(theme)" @reload-theme="reload => reloadTheme(theme, reload)" @show-settings="dont_clone => showSettings(theme, dont_clone)" @delete-theme="unload => deleteTheme(theme, unload)" />
|
<ScrollerWrap>
|
||||||
|
<ThemeCard v-for="theme in localThemes" :theme="theme" :key="theme.id" :data-theme-id="theme.id" @toggle-theme="toggleTheme(theme)" @reload-theme="reload => reloadTheme(theme, reload)" @show-settings="dont_clone => showSettings(theme, dont_clone)" @delete-theme="unload => deleteTheme(theme, unload)" />
|
||||||
|
</ScrollerWrap>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!local" class="bd-onlinePh">
|
<div v-else class="bd-onlinePh">
|
||||||
<div class="bd-fancySearch" :class="{'bd-active': loadingOnline || (onlineThemes && onlineThemes.docs)}">
|
<div class="bd-onlinePhHeader bd-flexCol">
|
||||||
<input type="text" class="bd-textInput" @keydown.enter="searchInput" @keyup.stop/>
|
<div class="bd-flex bd-flexRow">
|
||||||
|
<div v-if="loadingOnline" class="bd-spinnerContainer">
|
||||||
|
<div class="bd-spinner7" />
|
||||||
|
</div>
|
||||||
|
<div class="bd-searchHint">{{searchHint}}</div>
|
||||||
|
<div class="bd-fancySearch" :class="{'bd-disabled': loadingOnline, 'bd-active': loadingOnline || (onlineThemes && onlineThemes.docs)}">
|
||||||
|
<input type="text" class="bd-textInput" placeholder="Search" @keydown.enter="searchInput" @keyup.stop />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="bd-flex bd-flexRow" v-if="onlineThemes && onlineThemes.docs && onlineThemes.docs.length">
|
||||||
|
<div class="bd-searchSort bd-flex bd-flexGrow">
|
||||||
|
<span class="bd-flexGrow">Sort by:</span>
|
||||||
|
<div class="bd-sort">Name</div>
|
||||||
|
<div class="bd-sort">Updated</div>
|
||||||
|
<div class="bd-sort">Installs</div>
|
||||||
|
<div class="bd-sort">Users</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<h2 v-if="loadingOnline">Loading</h2>
|
<ScrollerWrap class="bd-onlinePhBody" v-if="!loadingOnline && onlineThemes" :scrollend="scrollend">
|
||||||
<RemoteCard v-else-if="onlineThemes && onlineThemes.docs" v-for="theme in onlineThemes.docs" :key="theme.id" :item="theme"/>
|
<RemoteCard v-if="onlineThemes && onlineThemes.docs" v-for="theme in onlineThemes.docs" :key="theme.id" :item="theme" />
|
||||||
|
<div v-if="loadingMore" class="bd-spinnerContainer">
|
||||||
|
<div class="bd-spinner7"/>
|
||||||
|
</div>
|
||||||
|
</ScrollerWrap>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</SettingsWrapper>
|
</SettingsWrapper>
|
||||||
|
@ -41,7 +64,7 @@
|
||||||
import { ThemeManager, BdWebApi } from 'modules';
|
import { ThemeManager, BdWebApi } from 'modules';
|
||||||
import { Modals } from 'ui';
|
import { Modals } from 'ui';
|
||||||
import { ClientLogger as Logger } from 'common';
|
import { ClientLogger as Logger } from 'common';
|
||||||
import { MiRefresh } from '../common';
|
import { MiRefresh, ScrollerWrap } from '../common';
|
||||||
import SettingsWrapper from './SettingsWrapper.vue';
|
import SettingsWrapper from './SettingsWrapper.vue';
|
||||||
import ThemeCard from './ThemeCard.vue';
|
import ThemeCard from './ThemeCard.vue';
|
||||||
import RemoteCard from './RemoteCard.vue';
|
import RemoteCard from './RemoteCard.vue';
|
||||||
|
@ -54,12 +77,14 @@
|
||||||
local: true,
|
local: true,
|
||||||
localThemes: ThemeManager.localThemes,
|
localThemes: ThemeManager.localThemes,
|
||||||
onlineThemes: null,
|
onlineThemes: null,
|
||||||
loadingOnline: false
|
loadingOnline: false,
|
||||||
|
loadingMore: false,
|
||||||
|
searchHint: ''
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
SettingsWrapper, ThemeCard, RemoteCard,
|
SettingsWrapper, ThemeCard, RemoteCard,
|
||||||
MiRefresh,
|
MiRefresh, ScrollerWrap,
|
||||||
RefreshBtn
|
RefreshBtn
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -74,38 +99,14 @@
|
||||||
await this.ThemeManager.refreshThemes();
|
await this.ThemeManager.refreshThemes();
|
||||||
},
|
},
|
||||||
async refreshOnline() {
|
async refreshOnline() {
|
||||||
|
this.searchHint = '';
|
||||||
|
if (this.loadingOnline || this.loadingMore) return;
|
||||||
this.loadingOnline = true;
|
this.loadingOnline = true;
|
||||||
try {
|
try {
|
||||||
// const getThemes = await BdWebApi.themes.get();
|
const getThemes = await BdWebApi.themes.get();
|
||||||
// this.onlineThemes = JSON.parse(getThemes);
|
this.onlineThemes = getThemes;
|
||||||
const dummies = [];
|
if (!this.onlineThemes.docs) return;
|
||||||
for (let i = 0; i < 10; i++) {
|
this.searchHint = `${this.onlineThemes.pagination.total} Results`;
|
||||||
dummies.push({
|
|
||||||
id: `theme${i}`,
|
|
||||||
name: `Dummy ${i}`,
|
|
||||||
tags: ['tag1', 'tag2', 'tag3', 'tag4', 'tag5'],
|
|
||||||
installs: Math.floor(Math.random() * 10000),
|
|
||||||
updated: '2018-07-21T14:51:32.057Z',
|
|
||||||
rating: Math.floor(Math.random() * 1000),
|
|
||||||
activeUsers: Math.floor(Math.random() * 1000),
|
|
||||||
rated: Math.random() > .5,
|
|
||||||
version: '1.0.0',
|
|
||||||
repository: {
|
|
||||||
name: 'ExampleRepository',
|
|
||||||
baseUri: 'https://github.com/Jiiks/ExampleRepository',
|
|
||||||
rawUri: 'https://github.com/Jiiks/ExampleRepository/raw/master'
|
|
||||||
},
|
|
||||||
files: {
|
|
||||||
readme: 'Example/readme.md',
|
|
||||||
previews: [{
|
|
||||||
large: 'Example/preview1-big.png',
|
|
||||||
thumb: 'Example/preview1-small.png'
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
author: 'Jiiks'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
this.onlineThemes = { docs: dummies };
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
Logger.err('ThemesView', err);
|
Logger.err('ThemesView', err);
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -140,8 +141,20 @@
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
searchInput(e) {
|
searchInput(e) {
|
||||||
this.loadingOnline = true;
|
if (this.loadingOnline || this.loadingMore) return;
|
||||||
setTimeout(this.refreshOnline, 1000);
|
this.refreshOnline();
|
||||||
|
},
|
||||||
|
async scrollend(e) {
|
||||||
|
if (this.loadingOnline || this.loadingMore) return;
|
||||||
|
this.loadingMore = true;
|
||||||
|
try {
|
||||||
|
const getThemes = await BdWebApi.themes.get();
|
||||||
|
this.onlineThemes.docs = [...this.onlineThemes.docs, ...getThemes.docs];
|
||||||
|
} catch (err) {
|
||||||
|
Logger.err('ThemesView', err);
|
||||||
|
} finally {
|
||||||
|
this.loadingMore = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -109,7 +109,7 @@
|
||||||
},
|
},
|
||||||
async install() {
|
async install() {
|
||||||
try {
|
try {
|
||||||
const installed = await PackageInstaller.installPackage(this.modal.filePath, this.modal.config.info.id, this.alreadyInstalled);
|
const installed = await PackageInstaller.installPackage(this.modal.filePath, this.modal.config.info.id || this.modal.config.info.name, this.modal.contentType, this.alreadyInstalled);
|
||||||
this.installed = installed;
|
this.installed = installed;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="bd-scrollerWrap" :class="{'bd-dark': dark}">
|
<div class="bd-scrollerWrap" :class="{'bd-dark': dark}">
|
||||||
<div class="bd-scroller">
|
<div class="bd-scroller" @scroll="onscroll">
|
||||||
<slot/>
|
<slot/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -18,6 +18,13 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
props: ['dark']
|
props: ['dark', 'scrollend'],
|
||||||
|
methods: {
|
||||||
|
onscroll(e) {
|
||||||
|
if (!this.scrollend) return;
|
||||||
|
const { offsetHeight, scrollTop, scrollHeight } = e.target;
|
||||||
|
if (offsetHeight + scrollTop >= scrollHeight) this.scrollend(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
Loading…
Reference in New Issue