Introduce plugin system supporting theme registration
This commit is contained in:
		
							parent
							
								
									83fedcff3b
								
							
						
					
					
						commit
						0fd0b4f466
					
				
					 23 changed files with 141 additions and 48 deletions
				
			
		|  | @ -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/**/*', | ||||
|  |  | |||
|  | @ -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) { | ||||
|  |  | |||
|  | @ -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,7 @@ 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'; | ||||
| 
 | ||||
| const logger = new Logger('core', 'cyan'); | ||||
| const bootLogger = logger.createSubLogger('boot', 'magenta', false); | ||||
|  | @ -75,6 +77,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 +115,24 @@ function showEnvironment(): void { | |||
| 	logger.info(`You ${isRoot() ? '' : 'do not '}have root privileges`); | ||||
| } | ||||
| 
 | ||||
| const pluginService = { | ||||
| 	registerTheme(theme: any) { | ||||
| 		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 | ||||
|  */ | ||||
|  |  | |||
|  | @ -1,5 +1,9 @@ | |||
| import * as cluster from 'cluster'; | ||||
| import { initDb } from '../db/postgre'; | ||||
| import Xev from 'xev'; | ||||
| import { registerTheme } from '../pluginThemes'; | ||||
| 
 | ||||
| const ev = new Xev(); | ||||
| 
 | ||||
| /** | ||||
|  * Init worker process | ||||
|  | @ -16,5 +20,9 @@ export async function workerMain() { | |||
| 	if (cluster.isWorker) { | ||||
| 		// Send a 'ready' message to parent process
 | ||||
| 		process.send!('ready'); | ||||
| 
 | ||||
| 		ev.on('registerPluginTheme', theme => { | ||||
| 			registerTheme(theme); | ||||
| 		}); | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -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,7 @@ | |||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import i18n from '../../../../i18n'; | ||||
| import { lightTheme, darkTheme, builtinThemes, applyTheme, Theme } from '../../../../theme'; | ||||
| import { lightTheme, darkTheme, applyTheme, Theme } from '../../../../theme'; | ||||
| import { Chrome } from 'vue-color'; | ||||
| import * as uuid from 'uuid'; | ||||
| import * as tinycolor from 'tinycolor2'; | ||||
|  | @ -138,7 +138,8 @@ export default Vue.extend({ | |||
| 
 | ||||
| 	data() { | ||||
| 		return { | ||||
| 			builtinThemes: builtinThemes, | ||||
| 			themes: [], | ||||
| 			presetThemes: [], | ||||
| 			installThemeCode: null, | ||||
| 			selectedThemeId: null, | ||||
| 			myThemeBase: 'light', | ||||
|  | @ -151,11 +152,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'); | ||||
| 		}, | ||||
|  |  | |||
|  | @ -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,29 +364,32 @@ 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); | ||||
| 				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); | ||||
| 				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); | ||||
| 				os.getThemes().then(themes => { | ||||
| 					const theme = themes.find(t => t.id == v); | ||||
| 					if (os.store.state.device.darkmode) { | ||||
| 						applyTheme(theme); | ||||
| 					} | ||||
| 				}); | ||||
| 			}); | ||||
| 			//#endregion
 | ||||
| 
 | ||||
| 			/*// Reapply current theme | ||||
|  | @ -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({ | ||||
|  |  | |||
|  | @ -9,6 +9,8 @@ import Progress from './common/scripts/loading'; | |||
| 
 | ||||
| import Err from './common/views/components/connect-failed.vue'; | ||||
| import Stream from './common/scripts/stream'; | ||||
| import { Theme, builtinThemes } from './theme'; | ||||
| import { concat } from '../../prelude/array'; | ||||
| 
 | ||||
| //#region api requests
 | ||||
| let spinner = null; | ||||
|  | @ -478,6 +480,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 { | ||||
|  |  | |||
|  | @ -12,34 +12,12 @@ export type 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) { | ||||
|  |  | |||
							
								
								
									
										9
									
								
								src/pluginThemes.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/pluginThemes.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,9 @@ | |||
| const themes: any[] = []; | ||||
| 
 | ||||
| export function registerTheme(theme: any) { | ||||
| 	themes.push(theme); | ||||
| } | ||||
| 
 | ||||
| export function getThemes() { | ||||
| 	return themes; | ||||
| } | ||||
							
								
								
									
										12
									
								
								src/plugins/featured-themes/main.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/plugins/featured-themes/main.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,12 @@ | |||
| require('json5/lib/register'); | ||||
| import * as fs from 'fs'; | ||||
| 
 | ||||
| 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}`); | ||||
| 		service.registerTheme(theme); | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										5
									
								
								src/plugins/featured-themes/plugin-meta.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								src/plugins/featured-themes/plugin-meta.json
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,5 @@ | |||
| { | ||||
| 	"misskeyVersion": "11", | ||||
| 	"name": "featured-themes", | ||||
| 	"version": "1.0.0" | ||||
| } | ||||
							
								
								
									
										14
									
								
								src/server/api/endpoints/plugins/themes.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/server/api/endpoints/plugins/themes.ts
									
										
									
									
									
										Normal 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(); | ||||
| }); | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue