Merge pull request #265 from JsSucks/content-browser

Merge for now
This commit is contained in:
Alexei Stukov 2018-12-02 05:06:02 +02:00 committed by GitHub
commit ead51ae676
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 326 additions and 120 deletions

View File

@ -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) {

View File

@ -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

View File

@ -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;
} }
} }

View File

@ -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;

View File

@ -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;
} }
} }

View File

@ -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;

View File

@ -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;
}
} }
} }
} }

View File

@ -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 {

View File

@ -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;
}
} }
} }
} }

View File

@ -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);
} }
} }
} }

View File

@ -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
} }

View File

@ -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);

View File

@ -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;
}
} }
} }
} }

View File

@ -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);

View File

@ -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>