Compare commits

...

2 Commits

Author SHA1 Message Date
Aya Morisawa f6f493536c
Use Theme type instead of any 2019-06-04 18:41:55 +09:00
Aya Morisawa 0fd0b4f466
Introduce plugin system supporting theme registration 2019-06-04 18:14:27 +09:00
24 changed files with 158 additions and 58 deletions

View File

@ -47,7 +47,14 @@ gulp.task('build:copy:views', () =>
gulp.src('./src/server/web/views/**/*').pipe(gulp.dest('./built/server/web/views'))
);
gulp.task('build:copy', gulp.parallel('build:copy:views', () =>
gulp.task('build:copy:plugins', () =>
gulp.src([
'./src/plugins/**/*',
'!./src/plugins/**/*.ts'
]).pipe(gulp.dest('./built/plugins'))
);
gulp.task('build:copy', gulp.parallel('build:copy:views', 'build:copy:plugins', () =>
gulp.src([
'./src/const.json',
'./src/server/web/views/**/*',

View File

@ -21,11 +21,11 @@ export default async function() {
process.title = `Misskey (${cluster.isMaster ? 'master' : 'worker'})`;
if (cluster.isMaster || program.disableClustering) {
await masterMain();
if (cluster.isMaster) {
ev.mount();
}
await masterMain();
}
if (cluster.isWorker || program.disableClustering) {

View File

@ -1,3 +1,4 @@
import * as path from 'path';
import * as os from 'os';
import * as cluster from 'cluster';
import chalk from 'chalk';
@ -12,6 +13,8 @@ import * as pkg from '../../package.json';
import { program } from '../argv';
import { showMachineInfo } from '../misc/show-machine-info';
import { initDb } from '../db/postgre';
import Xev from 'xev';
import { Theme } from '../theme';
const logger = new Logger('core', 'cyan');
const bootLogger = logger.createSubLogger('boot', 'magenta', false);
@ -75,6 +78,10 @@ export async function masterMain() {
await spawnWorkers(config.clusterLimit);
}
loadPlugins();
bootLogger.succ('All plugins loaded');
if (!program.noDaemons) {
require('../daemons/server-stats').default();
require('../daemons/notes-stats').default();
@ -109,6 +116,24 @@ function showEnvironment(): void {
logger.info(`You ${isRoot() ? '' : 'do not '}have root privileges`);
}
const pluginService = {
registerTheme(theme: Theme) {
const ev = new Xev();
ev.emit('registerPluginTheme', theme);
}
};
function loadPlugins(): void {
const plugins = [
path.resolve(`${__dirname}/../plugins/featured-themes`)
];
for (const plugin of plugins) {
const pluginMeta = require(`${plugin}/plugin-meta.json`);
bootLogger.info(`Plugin loaded: ${pluginMeta.name} v${pluginMeta.version}`);
require(`${plugin}/main.js`).onActivate(pluginService);
}
}
/**
* Init app
*/

View File

@ -1,5 +1,10 @@
import * as cluster from 'cluster';
import { initDb } from '../db/postgre';
import Xev from 'xev';
import { registerTheme } from '../pluginThemes';
import { Theme } from '../theme';
const ev = new Xev();
/**
* Init worker process
@ -16,5 +21,9 @@ export async function workerMain() {
if (cluster.isWorker) {
// Send a 'ready' message to parent process
process.send!('ready');
ev.on('registerPluginTheme', (theme: Theme) => {
registerTheme(theme);
});
}
}

View File

@ -93,7 +93,7 @@
<summary><fa icon="folder-open"/> {{ $t('manage-themes') }}</summary>
<ui-select v-model="selectedThemeId" :placeholder="$t('select-theme')">
<optgroup :label="$t('builtin-themes')">
<option v-for="x in builtinThemes" :value="x.id" :key="x.id">{{ x.name }}</option>
<option v-for="x in presetThemes" :value="x.id" :key="x.id">{{ x.name }}</option>
</optgroup>
<optgroup :label="$t('my-themes')">
<option v-for="x in installedThemes.filter(t => t.author == this.$store.state.i.username)" :value="x.id" :key="x.id">{{ x.name }}</option>
@ -113,7 +113,7 @@
<span>{{ $t('theme-code') }}</span>
</ui-textarea>
<ui-button @click="export_()" link :download="`${selectedTheme.name}.misskeytheme`" ref="export"><fa icon="box"/> {{ $t('export') }}</ui-button>
<ui-button @click="uninstall()" v-if="!builtinThemes.some(t => t.id == selectedTheme.id)"><fa :icon="['far', 'trash-alt']"/> {{ $t('uninstall') }}</ui-button>
<ui-button @click="uninstall()" v-if="!presetThemes.some(t => t.id == selectedTheme.id)"><fa :icon="['far', 'trash-alt']"/> {{ $t('uninstall') }}</ui-button>
</template>
</details>
</section>
@ -123,7 +123,8 @@
<script lang="ts">
import Vue from 'vue';
import i18n from '../../../../i18n';
import { lightTheme, darkTheme, builtinThemes, applyTheme, Theme } from '../../../../theme';
import { lightTheme, darkTheme, applyTheme } from '../../../../theme';
import { Theme } from '../../../../../../theme';
import { Chrome } from 'vue-color';
import * as uuid from 'uuid';
import * as tinycolor from 'tinycolor2';
@ -138,7 +139,8 @@ export default Vue.extend({
data() {
return {
builtinThemes: builtinThemes,
themes: [],
presetThemes: [],
installThemeCode: null,
selectedThemeId: null,
myThemeBase: 'light',
@ -151,11 +153,17 @@ export default Vue.extend({
};
},
computed: {
themes(): Theme[] {
return builtinThemes.concat(this.$store.state.device.themes);
},
created() {
this.$root.getThemes().then(themes => {
this.themes = themes;
});
this.$root.getPresetThemes().then(presetThemes => {
this.presetThemes = presetThemes;
});
}
computed: {
darkThemes(): Theme[] {
return this.themes.filter(t => t.base == 'dark' || t.kind == 'dark');
},

View File

@ -16,7 +16,7 @@ import App from './app.vue';
import checkForUpdate from './common/scripts/check-for-update';
import MiOS from './mios';
import { version, codename, lang, locale } from './config';
import { builtinThemes, applyTheme, futureTheme } from './theme';
import { applyTheme, futureTheme } from './theme';
import Dialog from './common/views/components/dialog.vue';
if (localStorage.getItem('theme') == null) {
@ -364,28 +364,31 @@ export default (callback: (launch: (router: VueRouter) => [Vue, MiOS], os: MiOS)
os.store.watch(s => {
return s.device.darkmode;
}, v => {
const themes = os.store.state.device.themes.concat(builtinThemes);
const dark = themes.find(t => t.id == os.store.state.device.darkTheme);
const light = themes.find(t => t.id == os.store.state.device.lightTheme);
applyTheme(v ? dark : light);
os.getThemes().then(themes => {
const dark = themes.find(t => t.id == os.store.state.device.darkTheme);
const light = themes.find(t => t.id == os.store.state.device.lightTheme);
applyTheme(v ? dark : light);
});
});
os.store.watch(s => {
return s.device.lightTheme;
}, v => {
const themes = os.store.state.device.themes.concat(builtinThemes);
const theme = themes.find(t => t.id == v);
if (!os.store.state.device.darkmode) {
applyTheme(theme);
}
os.getThemes().then(themes => {
const theme = themes.find(t => t.id == v);
if (!os.store.state.device.darkmode) {
applyTheme(theme);
}
});
});
os.store.watch(s => {
return s.device.darkTheme;
}, v => {
const themes = os.store.state.device.themes.concat(builtinThemes);
const theme = themes.find(t => t.id == v);
if (os.store.state.device.darkmode) {
applyTheme(theme);
}
os.getThemes().then(themes => {
const theme = themes.find(t => t.id == v);
if (os.store.state.device.darkmode) {
applyTheme(theme);
}
});
});
//#endregion
@ -447,6 +450,8 @@ export default (callback: (launch: (router: VueRouter) => [Vue, MiOS], os: MiOS)
api: os.api,
getMeta: os.getMeta,
getMetaSync: os.getMetaSync,
getThemes: os.getThemes,
getPresetThemes: os.getPresetThemes,
signout: os.signout,
new(vm, props) {
const x = new vm({

View File

@ -9,6 +9,9 @@ import Progress from './common/scripts/loading';
import Err from './common/views/components/connect-failed.vue';
import Stream from './common/scripts/stream';
import { builtinThemes } from './theme';
import { Theme } from '../../theme';
import { concat } from '../../prelude/array';
//#region api requests
let spinner = null;
@ -478,6 +481,28 @@ export default class MiOS extends EventEmitter {
}
});
}
/**
*
*/
@autobind
public getPresetThemes() {
return new Promise<Theme[]>(async (res, rej) => {
const pluginThemes = await this.api('plugins/themes') as Theme[];
res(concat([builtinThemes, pluginThemes]));
});
}
/**
*
*/
@autobind
public getThemes() {
return new Promise<Theme[]>(async (res, rej) => {
const installedThemes = this.store.state.device.themes as Theme[];
res(concat([await this.getPresetThemes(), installedThemes]));
});
}
}
class WindowSystem extends EventEmitter {

View File

@ -1,45 +1,14 @@
import * as tinycolor from 'tinycolor2';
export type Theme = {
id: string;
name: string;
author: string;
desc?: string;
base?: 'dark' | 'light';
vars: { [key: string]: string };
props: { [key: string]: string };
};
import { Theme } from '../../theme';
export const lightTheme: Theme = require('../themes/light.json5');
export const darkTheme: Theme = require('../themes/dark.json5');
export const lavenderTheme: Theme = require('../themes/lavender.json5');
export const futureTheme: Theme = require('../themes/future.json5');
export const halloweenTheme: Theme = require('../themes/halloween.json5');
export const cafeTheme: Theme = require('../themes/cafe.json5');
export const japaneseSushiSetTheme: Theme = require('../themes/japanese-sushi-set.json5');
export const gruvboxDarkTheme: Theme = require('../themes/gruvbox-dark.json5');
export const monokaiTheme: Theme = require('../themes/monokai.json5');
export const vividTheme: Theme = require('../themes/vivid.json5');
export const rainyTheme: Theme = require('../themes/rainy.json5');
export const mauveTheme: Theme = require('../themes/mauve.json5');
export const grayTheme: Theme = require('../themes/gray.json5');
export const tweetDeckTheme: Theme = require('../themes/tweet-deck.json5');
export const builtinThemes = [
lightTheme,
darkTheme,
lavenderTheme,
futureTheme,
halloweenTheme,
cafeTheme,
japaneseSushiSetTheme,
gruvboxDarkTheme,
monokaiTheme,
vividTheme,
rainyTheme,
mauveTheme,
grayTheme,
tweetDeckTheme,
];
export function applyTheme(theme: Theme, persisted = true) {

11
src/pluginThemes.ts Normal file
View File

@ -0,0 +1,11 @@
import { Theme } from './theme';
const themes: Theme[] = [];
export function registerTheme(theme: Theme) {
themes.push(theme);
}
export function getThemes() {
return themes;
}

View File

@ -0,0 +1,13 @@
require('json5/lib/register');
import * as fs from 'fs';
import { Theme } from '../../theme';
export function onActivate(service: any) {
const fileNames = fs.readdirSync(`${__dirname}/themes`)
.filter(f => fs.statSync(`${__dirname}/themes/${f}`).isFile());
for (const fileName of fileNames) {
const theme = require(`${__dirname}/themes/${fileName}`) as Theme;
service.registerTheme(theme);
}
}

View File

@ -0,0 +1,5 @@
{
"misskeyVersion": "11",
"name": "featured-themes",
"version": "1.0.0"
}

View File

@ -0,0 +1,14 @@
import define from '../../define';
import { getThemes } from '../../../../pluginThemes';
export const meta = {
desc: {
'ja-JP': 'プラグインによって登録されたテーマを取得します。'
},
tags: ['themes']
};
export default define(meta, async (ps, me) => {
return getThemes();
});

9
src/theme.ts Normal file
View File

@ -0,0 +1,9 @@
export type Theme = {
id: string;
name: string;
author: string;
desc?: string;
base?: 'dark' | 'light';
vars: { [key: string]: string };
props: { [key: string]: string };
};