2017-10-27 01:00:52 +08:00
var onUninstall , onInstall ;
2019-01-09 22:54:35 +08:00
const Plugins = {
2017-10-27 01:00:52 +08:00
Vue : [ ] , //Vue Object
installed : [ ] , //Simple List of Names
json : undefined , //Json from website
2021-08-27 21:48:32 +08:00
download _stats : { } ,
2019-02-04 04:09:35 +08:00
all : [ ] , //Vue Object Data
2019-07-18 00:02:07 +08:00
registered : { } ,
devReload ( ) {
2018-03-29 02:48:11 +08:00
var reloads = 0 ;
2019-02-04 04:09:35 +08:00
for ( var i = Plugins . all . length - 1 ; i >= 0 ; i -- ) {
2020-04-26 02:25:07 +08:00
if ( Plugins . all [ i ] . source == 'file' ) {
2019-02-04 04:09:35 +08:00
Plugins . all [ i ] . reload ( )
2018-03-29 02:48:11 +08:00
reloads ++ ;
2017-10-27 01:00:52 +08:00
}
2019-02-04 04:09:35 +08:00
}
Blockbench . showQuickMessage ( tl ( 'message.plugin_reload' , [ reloads ] ) )
2018-03-29 02:48:11 +08:00
console . log ( 'Reloaded ' + reloads + ' plugin' + pluralS ( reloads ) )
2019-07-18 00:02:07 +08:00
} ,
sort ( ) {
2022-02-11 02:21:50 +08:00
Plugins . all . sort ( ( a , b ) => {
if ( a . tags . find ( tag => tag . match ( /deprecated/i ) ) ) return 1 ;
if ( b . tags . find ( tag => tag . match ( /deprecated/i ) ) ) return - 1 ;
2021-08-27 21:48:32 +08:00
let download _difference = ( Plugins . download _stats [ b . id ] || 0 ) - ( Plugins . download _stats [ a . id ] || 0 ) ;
if ( download _difference ) {
return download _difference
} else {
return sort _collator . compare ( a . title , b . title ) ;
}
2019-07-18 00:02:07 +08:00
} ) ;
2017-10-27 01:00:52 +08:00
}
}
2020-04-26 02:25:07 +08:00
StateMemory . init ( 'installed_plugins' , 'array' )
2023-03-28 04:54:07 +08:00
Plugins . installed = StateMemory . installed _plugins = StateMemory . installed _plugins . filter ( p => p && typeof p == 'object' ) ;
2017-10-27 01:00:52 +08:00
2019-02-04 04:09:35 +08:00
class Plugin {
constructor ( id , data ) {
2019-07-18 00:02:07 +08:00
this . id = id || 'unknown' ;
2019-02-04 04:09:35 +08:00
this . installed = false ;
this . title = '' ;
this . author = '' ;
this . description = '' ;
this . about = '' ;
this . icon = '' ;
2021-05-25 01:45:40 +08:00
this . tags = [ ] ;
2023-07-03 06:31:36 +08:00
this . dependencies = [ ] ;
2021-08-07 21:13:20 +08:00
this . version = '0.0.1' ;
2020-11-20 05:39:00 +08:00
this . variant = 'both' ;
2019-02-04 04:09:35 +08:00
this . min _version = '' ;
2020-10-12 01:53:22 +08:00
this . max _version = '' ;
2023-06-23 23:55:12 +08:00
this . source = 'store' ;
this . creation _date = 0 ;
2021-06-11 22:31:30 +08:00
this . await _loading = false ;
2023-06-23 16:20:29 +08:00
this . about _fetched = false ;
2023-06-30 03:53:10 +08:00
this . disabled = false ;
2019-07-18 00:02:07 +08:00
this . extend ( data )
Plugins . all . safePush ( this ) ;
2019-02-04 04:09:35 +08:00
}
extend ( data ) {
2019-07-18 00:02:07 +08:00
if ( ! ( data instanceof Object ) ) return this ;
2019-02-04 04:09:35 +08:00
Merge . boolean ( this , data , 'installed' )
Merge . string ( this , data , 'title' )
Merge . string ( this , data , 'author' )
Merge . string ( this , data , 'description' )
Merge . string ( this , data , 'about' )
Merge . string ( this , data , 'icon' )
2021-08-07 21:13:20 +08:00
Merge . string ( this , data , 'version' )
2019-02-04 04:09:35 +08:00
Merge . string ( this , data , 'variant' )
Merge . string ( this , data , 'min_version' )
2021-06-11 22:31:30 +08:00
Merge . boolean ( this , data , 'await_loading' ) ;
2023-06-30 03:53:10 +08:00
Merge . boolean ( this , data , 'disabled' ) ;
2023-06-23 23:55:12 +08:00
if ( data . creation _date ) this . creation _date = Date . parse ( data . creation _date ) ;
2021-06-16 02:24:01 +08:00
if ( data . tags instanceof Array ) this . tags . safePush ( ... data . tags . slice ( 0 , 3 ) ) ;
2023-07-03 06:31:36 +08:00
if ( data . dependencies instanceof Array ) this . dependencies . safePush ( ... data . dependencies ) ;
2019-07-18 00:02:07 +08:00
2023-06-30 01:57:41 +08:00
this . new _repo _format = this . min _version != '' && ! compareVersions ( '4.8.0' , this . min _version ) ;
2019-07-18 00:02:07 +08:00
Merge . function ( this , data , 'onload' )
Merge . function ( this , data , 'onunload' )
Merge . function ( this , data , 'oninstall' )
Merge . function ( this , data , 'onuninstall' )
2019-02-04 04:09:35 +08:00
return this ;
}
2021-09-08 00:53:11 +08:00
get name ( ) {
return this . title ;
}
2023-02-17 05:36:14 +08:00
async install ( ) {
2023-07-03 06:31:36 +08:00
let required _dependencies = this . dependencies
. map ( id => ( Plugins . all . find ( p => p . id == id ) || id ) )
. filter ( p => ( p instanceof Plugin == false || p . installed == false ) ) ;
if ( required _dependencies . length ) {
let failed _dependency = required _dependencies . find ( p => ( ! p . isInstallable || p . isInstallable ( ) != true ) ) ;
if ( failed _dependency ) {
let error _message = failed _dependency ;
if ( failed _dependency instanceof Plugin ) {
error _message = ` ** ${ failed _dependency . title } **: ${ failed _dependency . isInstallable ( ) } ` ;
}
Blockbench . showMessageBox ( {
title : 'message.plugin_dependencies.title' ,
message : ` ${ tl ( 'message.plugin_dependencies.invalid' ) } \n \n ${ error _message } ` ,
} ) ;
return ;
}
let list = required _dependencies . map ( p => ` ** ${ p . title } ** ${ tl ( 'dialog.plugins.author' , [ p . author ] ) } ` ) ;
let response = await new Promise ( resolve => {
Blockbench . showMessageBox ( {
title : 'message.plugin_dependencies.title' ,
message : ` ${ tl ( 'message.plugin_dependencies.message1' ) } \n \n * ${ list . join ( '\n* ' ) } \n \n ${ tl ( 'message.plugin_dependencies.message2' ) } ` ,
buttons : [ 'dialog.continue' , 'dialog.cancel' ] ,
width : 512 ,
} , button => {
resolve ( button == 0 ) ;
} )
} )
if ( ! response ) return ;
for ( let dependency of required _dependencies ) {
await dependency . install ( ) ;
}
}
2023-02-17 05:36:14 +08:00
return await this . download ( true ) ;
}
async load ( first , cb ) {
2019-02-04 04:09:35 +08:00
var scope = this ;
2019-07-18 00:02:07 +08:00
Plugins . registered [ this . id ] = this ;
2020-08-14 04:50:54 +08:00
return await new Promise ( ( resolve , reject ) => {
2021-02-28 07:42:01 +08:00
$ . getScript ( Plugins . path + scope . id + '.js' , ( ) => {
2020-08-14 04:50:54 +08:00
if ( cb ) cb . bind ( scope ) ( )
scope . bindGlobalData ( first )
if ( first && scope . oninstall ) {
scope . oninstall ( )
}
2021-02-24 04:56:36 +08:00
if ( first ) Blockbench . showQuickMessage ( tl ( 'message.installed_plugin' , [ this . title ] ) ) ;
2020-08-14 04:50:54 +08:00
resolve ( )
2021-02-28 07:42:01 +08:00
} ) . fail ( ( ) => {
2020-08-14 04:50:54 +08:00
if ( isApp ) {
console . log ( 'Could not find file of plugin "' + scope . id + '". Uninstalling it instead.' )
scope . uninstall ( )
}
2021-02-24 04:56:36 +08:00
if ( first ) Blockbench . showQuickMessage ( tl ( 'message.installed_plugin_fail' , [ this . title ] ) ) ;
reject ( )
2020-08-14 04:50:54 +08:00
} )
this . remember ( )
scope . installed = true ;
2019-02-04 04:09:35 +08:00
} )
}
2019-07-18 00:02:07 +08:00
bindGlobalData ( ) {
2019-02-04 04:09:35 +08:00
var scope = this ;
if ( onUninstall ) {
2019-07-18 00:02:07 +08:00
scope . onuninstall = onUninstall
2019-02-04 04:09:35 +08:00
}
2019-07-18 00:02:07 +08:00
if ( onUninstall ) {
scope . onuninstall = onUninstall
2019-02-04 04:09:35 +08:00
}
2021-03-04 00:50:30 +08:00
if ( window . plugin _data ) {
console . warn ( ` plugin_data is deprecated. Please use Plugin.register instead. ( ${ plugin _data . id || 'unknown plugin' } ) ` )
}
2019-02-04 04:09:35 +08:00
window . onInstall = window . onUninstall = window . plugin _data = undefined
return this ;
}
2020-08-14 04:50:54 +08:00
async download ( first ) {
2019-02-04 04:09:35 +08:00
var scope = this ;
2021-10-22 21:07:01 +08:00
function register ( ) {
jQuery . ajax ( {
url : 'https://blckbn.ch/api/event/install_plugin' ,
type : 'POST' ,
data : {
plugin : scope . id
}
} )
}
2019-02-04 04:09:35 +08:00
if ( ! isApp ) {
2021-10-22 21:07:01 +08:00
if ( first ) register ( ) ;
2023-02-17 05:36:14 +08:00
return await scope . load ( first )
2019-02-04 04:09:35 +08:00
}
2023-06-30 01:57:41 +08:00
// Download files
async function copyFileToDrive ( origin _filename , target _filename , callback ) {
var file = originalFs . createWriteStream ( PathModule . join ( Plugins . path , target _filename ) ) ;
https . get ( 'https://cdn.jsdelivr.net/gh/JannisX11/blockbench-plugins/plugins/' + origin _filename , function ( response ) {
2020-08-14 04:50:54 +08:00
response . pipe ( file ) ;
2023-06-30 01:57:41 +08:00
if ( callback ) response . on ( 'end' , callback ) ;
} ) ;
}
return await new Promise ( async ( resolve , reject ) => {
// New system
if ( this . new _repo _format ) {
copyFileToDrive ( ` ${ this . id } / ${ this . id } .js ` , ` ${ this . id } .js ` , ( ) => {
if ( first ) register ( ) ;
2020-08-14 04:50:54 +08:00
setTimeout ( async function ( ) {
2023-02-17 05:36:14 +08:00
await scope . load ( first ) ;
2020-08-14 04:50:54 +08:00
resolve ( )
2021-06-11 22:31:30 +08:00
} , 20 )
2023-06-30 01:57:41 +08:00
} ) ;
if ( this . hasImageIcon ( ) ) {
copyFileToDrive ( ` ${ this . id } / ${ this . icon } ` , this . id + '.' + this . icon ) ;
}
await this . fetchAbout ( ) ;
if ( this . about ) {
fs . writeFileSync ( PathModule . join ( Plugins . path , this . id + '.about.md' ) , this . about , 'utf-8' ) ;
}
} else {
// Legacy system
copyFileToDrive ( ` ${ this . id } .js ` , ` ${ this . id } .js ` , ( ) => {
2021-10-22 21:07:01 +08:00
if ( first ) register ( ) ;
2023-06-30 01:57:41 +08:00
setTimeout ( async function ( ) {
await scope . load ( first ) ;
resolve ( )
} , 20 )
} ) ;
}
2019-02-04 04:09:35 +08:00
} ) ;
}
2020-08-14 04:50:54 +08:00
async loadFromFile ( file , first ) {
2019-02-04 04:09:35 +08:00
var scope = this ;
2019-07-19 23:31:22 +08:00
if ( ! isApp && ! first ) return this ;
2019-07-18 00:02:07 +08:00
if ( first ) {
2019-02-04 04:09:35 +08:00
if ( isApp ) {
if ( ! confirm ( tl ( 'message.load_plugin_app' ) ) ) return ;
} else {
if ( ! confirm ( tl ( 'message.load_plugin_web' ) ) ) return ;
}
}
2019-07-19 23:31:22 +08:00
2021-05-25 01:45:40 +08:00
this . id = pathToName ( file . path ) ;
2019-07-19 23:31:22 +08:00
Plugins . registered [ this . id ] = this ;
2021-05-25 01:45:40 +08:00
Plugins . all . safePush ( this ) ;
this . source = 'file' ;
2021-06-16 02:24:01 +08:00
this . tags . safePush ( 'Local' ) ;
2020-08-14 04:50:54 +08:00
return await new Promise ( ( resolve , reject ) => {
if ( isApp ) {
$ . getScript ( file . path , ( ) => {
if ( window . plugin _data ) {
scope . id = ( plugin _data && plugin _data . id ) || pathToName ( file . path )
scope . extend ( plugin _data )
scope . bindGlobalData ( )
}
2020-11-20 05:39:00 +08:00
if ( first && scope . oninstall ) {
scope . oninstall ( )
}
scope . installed = true ;
scope . path = file . path ;
this . remember ( ) ;
Plugins . sort ( ) ;
2020-08-14 04:50:54 +08:00
resolve ( )
} ) . fail ( reject )
} else {
try {
2022-10-29 23:25:33 +08:00
new Function ( file . content ) ( ) ;
2020-08-14 04:50:54 +08:00
} catch ( err ) {
reject ( err )
}
if ( ! Plugins . registered && window . plugin _data ) {
scope . id = ( plugin _data && plugin _data . id ) || scope . id
2019-07-19 23:31:22 +08:00
scope . extend ( plugin _data )
scope . bindGlobalData ( )
}
2022-03-10 04:52:10 +08:00
if ( first && scope . oninstall ) {
scope . oninstall ( )
}
2019-07-19 23:31:22 +08:00
scope . installed = true
2020-04-26 02:25:07 +08:00
this . remember ( )
2019-07-19 23:31:22 +08:00
Plugins . sort ( )
2020-08-14 04:50:54 +08:00
resolve ( )
2019-07-19 23:31:22 +08:00
}
2020-08-14 04:50:54 +08:00
} )
2019-02-04 04:09:35 +08:00
}
2020-12-07 05:44:55 +08:00
async loadFromURL ( url , first ) {
2020-04-26 02:25:07 +08:00
if ( first ) {
if ( isApp ) {
if ( ! confirm ( tl ( 'message.load_plugin_app' ) ) ) return ;
} else {
if ( ! confirm ( tl ( 'message.load_plugin_web' ) ) ) return ;
}
}
this . id = pathToName ( url )
Plugins . registered [ this . id ] = this ;
Plugins . all . safePush ( this )
2021-06-16 02:24:01 +08:00
this . tags . safePush ( 'Remote' ) ;
2020-04-26 02:25:07 +08:00
this . source = 'url' ;
2020-12-07 05:44:55 +08:00
await new Promise ( ( resolve , reject ) => {
$ . getScript ( url , ( ) => {
if ( window . plugin _data ) {
this . id = ( plugin _data && plugin _data . id ) || pathToName ( url )
this . extend ( plugin _data )
this . bindGlobalData ( )
}
2022-04-03 20:08:34 +08:00
if ( first && this . oninstall ) {
this . oninstall ( )
2022-03-10 04:52:10 +08:00
}
2020-12-07 05:44:55 +08:00
this . installed = true
this . path = url
this . remember ( )
Plugins . sort ( )
// Save
if ( isApp ) {
var file = originalFs . createWriteStream ( Plugins . path + this . id + '.js' )
https . get ( url , ( response ) => {
response . pipe ( file ) ;
response . on ( 'end' , resolve )
} ) . on ( 'error' , reject ) ;
} else {
resolve ( )
}
} ) . fail ( ( ) => {
if ( isApp ) {
2023-02-17 05:36:14 +08:00
this . load ( ) . then ( resolve ) . catch ( resolve )
2020-12-07 05:44:55 +08:00
}
} )
2020-04-26 02:25:07 +08:00
} )
return this ;
}
remember ( id = this . id , path = this . path ) {
2023-02-17 05:36:14 +08:00
let entry = Plugins . installed . find ( plugin => plugin . id == this . id ) ;
let already _exists = ! ! entry ;
if ( ! entry ) entry = { } ;
entry . id = id ;
entry . version = this . version ;
entry . path = path ;
entry . source = this . source ;
2023-06-30 03:53:10 +08:00
entry . disabled = this . disabled ? true : undefined ;
2023-02-17 05:36:14 +08:00
if ( ! already _exists ) Plugins . installed . push ( entry ) ;
2020-04-26 02:25:07 +08:00
StateMemory . save ( 'installed_plugins' )
return this ;
}
2019-02-04 04:09:35 +08:00
uninstall ( ) {
2019-12-18 00:44:27 +08:00
try {
this . unload ( ) ;
if ( this . onuninstall ) {
this . onuninstall ( ) ;
}
} catch ( err ) {
console . log ( 'Error in unload or uninstall method: ' , err ) ;
2019-07-18 00:02:07 +08:00
}
delete Plugins . registered [ this . id ] ;
2020-04-26 02:25:07 +08:00
let in _installed = Plugins . installed . find ( plugin => plugin . id == this . id ) ;
Plugins . installed . remove ( in _installed ) ;
StateMemory . save ( 'installed_plugins' )
2019-07-18 00:02:07 +08:00
this . installed = false ;
2020-04-26 02:25:07 +08:00
if ( isApp && this . source !== 'store' ) {
2019-02-04 04:09:35 +08:00
Plugins . all . remove ( this )
2020-12-07 05:44:55 +08:00
}
if ( isApp && this . source != 'file' ) {
2023-06-30 01:57:41 +08:00
function removeCachedFile ( filepath ) {
if ( fs . existsSync ( filepath ) ) {
fs . unlink ( filepath , ( err ) => {
if ( err ) console . log ( err ) ;
} ) ;
}
2019-02-04 04:09:35 +08:00
}
2023-06-30 01:57:41 +08:00
removeCachedFile ( Plugins . path + this . id + '.js' ) ;
removeCachedFile ( Plugins . path + this . id + '.' + this . icon ) ;
removeCachedFile ( Plugins . path + this . id + '.about.md' ) ;
2019-02-04 04:09:35 +08:00
}
2020-04-26 02:25:07 +08:00
StateMemory . save ( 'installed_plugins' )
2019-02-04 04:09:35 +08:00
return this ;
}
2019-07-18 00:02:07 +08:00
unload ( ) {
if ( this . onunload ) {
this . onunload ( )
}
return this ;
}
2019-02-04 04:09:35 +08:00
reload ( ) {
2020-04-26 02:25:07 +08:00
if ( ! isApp && this . source == 'file' ) return this ;
2019-07-18 00:02:07 +08:00
this . unload ( )
2021-08-09 04:01:53 +08:00
this . tags . empty ( ) ;
2023-07-03 06:31:36 +08:00
this . dependencies . empty ( ) ;
2019-07-18 00:02:07 +08:00
Plugins . all . remove ( this )
2020-04-26 02:25:07 +08:00
if ( this . source == 'file' ) {
this . loadFromFile ( { path : this . path } , false )
} else if ( this . source == 'url' ) {
this . loadFromURL ( this . path , false )
}
2019-02-04 04:09:35 +08:00
return this ;
}
2023-06-30 03:53:10 +08:00
toggleDisabled ( ) {
if ( ! this . disabled ) {
this . disabled = true ;
this . unload ( )
} else {
if ( this . onload ) {
this . onload ( )
}
this . disabled = false ;
}
this . remember ( ) ;
}
2020-04-26 02:25:07 +08:00
isReloadable ( ) {
2023-06-30 03:53:10 +08:00
return this . installed && ! this . disabled && ( ( this . source == 'file' && isApp ) || ( this . source == 'url' ) ) ;
2020-04-26 02:25:07 +08:00
}
2019-02-04 04:09:35 +08:00
isInstallable ( ) {
var scope = this ;
var result =
scope . variant === 'both' ||
(
isApp === ( scope . variant === 'desktop' ) &&
isApp !== ( scope . variant === 'web' )
) ;
if ( result && scope . min _version ) {
2020-10-12 01:53:22 +08:00
result = Blockbench . isOlderThan ( scope . min _version ) ? 'outdated_client' : true ;
}
if ( result && scope . max _version ) {
result = Blockbench . isNewerThan ( scope . max _version ) ? 'outdated_plugin' : true
}
if ( result === false ) {
2019-02-04 04:09:35 +08:00
result = ( scope . variant === 'web' ) ? 'web_only' : 'app_only'
}
return ( result === true ) ? true : tl ( 'dialog.plugins.' + result ) ;
}
2023-06-23 23:55:12 +08:00
hasImageIcon ( ) {
2023-06-29 06:13:46 +08:00
return this . icon . endsWith ( '.png' ) || this . icon . endsWith ( '.svg' ) ;
2023-06-23 23:55:12 +08:00
}
2023-06-23 16:20:29 +08:00
getIcon ( ) {
2023-06-23 23:55:12 +08:00
if ( this . hasImageIcon ( ) ) {
2023-06-30 01:57:41 +08:00
if ( isApp ) {
if ( this . installed && this . source == 'store' ) {
return Plugins . path + this . id + '.' + this . icon ;
}
if ( this . source != 'store' )
return this . path . replace ( /\w+\.js$/ , this . icon ) ;
}
return ` https://cdn.jsdelivr.net/gh/JannisX11/blockbench-plugins/plugins/ ${ this . id } / ${ this . icon } ` ;
2023-06-23 23:55:12 +08:00
}
2023-06-23 16:20:29 +08:00
return this . icon ;
2019-02-04 04:09:35 +08:00
}
2023-06-23 23:55:12 +08:00
async fetchAbout ( ) {
2023-06-30 01:57:41 +08:00
if ( ! this . about _fetched && ! this . about && this . new _repo _format ) {
if ( isApp && this . installed ) {
try {
let content = fs . readFileSync ( PathModule . join ( Plugins . path , this . id + '.about.md' ) , { encoding : 'utf-8' } ) ;
this . about = content ;
this . about _fetched = true ;
return ;
} catch ( err ) {
console . error ( 'failed to get about for plugin ' + this . id ) ;
}
}
2023-06-23 23:55:12 +08:00
let url = ` https://cdn.jsdelivr.net/gh/JannisX11/blockbench-plugins/plugins/ ${ this . id } /about.md ` ;
let result = await fetch ( url ) . catch ( ( ) => {
console . error ( 'about.md missing for plugin ' + this . id ) ;
} ) ;
if ( result . ok ) {
this . about = await result . text ( ) ;
}
2023-06-23 16:20:29 +08:00
this . about _fetched = true ;
}
2019-02-04 04:09:35 +08:00
}
}
2021-04-25 19:50:31 +08:00
// Alias for typescript
const BBPlugin = Plugin ;
2019-07-18 00:02:07 +08:00
Plugin . register = function ( id , data ) {
if ( typeof id !== 'string' || typeof data !== 'object' ) {
console . warn ( 'Plugin.register: not enough arguments, string and object required.' )
return ;
}
var plugin = Plugins . registered [ id ] ;
if ( ! plugin ) {
plugin = Plugins . registered . unknown ;
if ( plugin ) {
delete Plugins . registered . unknown ;
plugin . id = id ;
Plugins . registered [ id ] = plugin ;
}
}
2020-12-12 21:44:30 +08:00
if ( ! plugin ) {
Blockbench . showMessageBox ( {
translateKey : 'load_plugin_failed' ,
message : tl ( 'message.load_plugin_failed.message' , [ id ] )
} )
} ;
2019-07-18 00:02:07 +08:00
plugin . extend ( data )
2023-06-30 03:53:10 +08:00
if ( plugin . isInstallable ( ) == true && plugin . disabled == false ) {
2020-10-12 01:53:22 +08:00
if ( plugin . onload instanceof Function ) {
plugin . onload ( )
}
2019-07-18 00:02:07 +08:00
}
return plugin ;
}
2019-02-04 04:09:35 +08:00
2017-10-27 01:00:52 +08:00
if ( isApp ) {
2019-01-09 22:54:35 +08:00
Plugins . path = app . getPath ( 'userData' ) + osfs + 'plugins' + osfs
2017-10-27 01:00:52 +08:00
fs . readdir ( Plugins . path , function ( err ) {
2018-10-18 01:50:25 +08:00
if ( err ) {
fs . mkdir ( Plugins . path , function ( a ) { } )
}
2017-10-27 01:00:52 +08:00
} )
2019-02-04 04:09:35 +08:00
} else {
Plugins . path = 'https://cdn.jsdelivr.net/gh/JannisX11/blockbench-plugins/plugins/' ;
2017-10-27 01:00:52 +08:00
}
2020-09-17 22:50:29 +08:00
Plugins . loading _promise = new Promise ( ( resolve , reject ) => {
2022-01-08 21:22:22 +08:00
$ . ajax ( {
cache : false ,
url : 'https://cdn.jsdelivr.net/gh/JannisX11/blockbench-plugins/plugins.json' ,
dataType : 'json' ,
success ( data ) {
Plugins . json = data ;
2023-06-23 23:55:12 +08:00
2022-01-08 21:22:22 +08:00
resolve ( ) ;
Plugins . loading _promise . resolved = true ;
} ,
error ( ) {
console . log ( 'Could not connect to plugin server' )
$ ( '#plugin_available_empty' ) . text ( 'Could not connect to plugin server' )
resolve ( ) ;
Plugins . loading _promise . resolved = true ;
}
} ) ;
2021-08-27 21:48:32 +08:00
} )
$ . getJSON ( 'https://blckbn.ch/api/stats/plugins?weeks=2' , data => {
Plugins . download _stats = data ;
if ( Plugins . json ) {
Plugins . sort ( ) ;
}
2017-10-27 01:00:52 +08:00
} )
2020-08-14 04:50:54 +08:00
async function loadInstalledPlugins ( ) {
2020-09-17 22:50:29 +08:00
if ( ! Plugins . loading _promise . resolved ) {
await Plugins . loading _promise ;
2019-02-04 04:09:35 +08:00
}
2020-09-17 22:50:29 +08:00
const install _promises = [ ] ;
2023-02-17 05:36:14 +08:00
2021-12-15 05:51:21 +08:00
if ( Plugins . json instanceof Object && navigator . onLine ) {
2019-02-04 04:09:35 +08:00
//From Store
2017-10-27 01:00:52 +08:00
for ( var id in Plugins . json ) {
2023-02-17 05:36:14 +08:00
var plugin = new Plugin ( id , Plugins . json [ id ] ) ;
let installed _match = Plugins . installed . find ( p => {
2020-04-30 05:35:47 +08:00
return p && p . id == id && p . source == 'store'
2023-02-17 05:36:14 +08:00
} ) ;
if ( installed _match ) {
2023-06-30 01:57:41 +08:00
if ( isApp && (
( installed _match . version && plugin . version && ! compareVersions ( plugin . version , installed _match . version ) ) ||
Blockbench . isOlderThan ( plugin . min _version )
) ) {
2023-02-17 05:36:14 +08:00
// Get from file
let promise = plugin . load ( false ) ;
2021-06-11 22:31:30 +08:00
install _promises . push ( promise ) ;
2023-02-17 05:36:14 +08:00
} else {
// Update
let promise = plugin . download ( ) ;
if ( plugin . await _loading ) {
install _promises . push ( promise ) ;
}
2021-06-11 22:31:30 +08:00
}
2017-10-27 01:00:52 +08:00
}
}
2019-07-18 00:02:07 +08:00
Plugins . sort ( ) ;
2019-02-04 04:09:35 +08:00
} else if ( Plugins . installed . length > 0 && isApp ) {
2019-07-18 00:02:07 +08:00
//Offline
2023-06-30 01:57:41 +08:00
Plugins . installed . forEach ( function ( plugin _data ) {
2019-02-04 04:09:35 +08:00
2023-06-30 01:57:41 +08:00
if ( plugin _data . source == 'store' ) {
let instance = new Plugin ( plugin _data . id ) ;
let promise = instance . load ( false , function ( ) {
2023-02-17 05:36:14 +08:00
Plugins . sort ( ) ;
2019-02-04 04:09:35 +08:00
} )
2020-08-14 04:50:54 +08:00
install _promises . push ( promise ) ;
2019-02-04 04:09:35 +08:00
}
2017-10-27 01:00:52 +08:00
} )
}
if ( Plugins . installed . length > 0 ) {
2023-02-05 03:22:54 +08:00
var load _counter = 0 ;
2020-04-26 02:25:07 +08:00
Plugins . installed . forEachReverse ( function ( plugin ) {
2019-02-04 04:09:35 +08:00
2020-04-26 02:25:07 +08:00
if ( plugin . source == 'file' ) {
2019-02-04 04:09:35 +08:00
//Dev Plugins
2020-04-26 02:25:07 +08:00
if ( isApp && fs . existsSync ( plugin . path ) ) {
2023-06-30 03:53:10 +08:00
var instance = new Plugin ( plugin . id , { disabled : plugin . disabled } ) ;
2020-08-14 04:50:54 +08:00
install _promises . push ( instance . loadFromFile ( { path : plugin . path } , false ) ) ;
2023-02-05 03:22:54 +08:00
load _counter ++ ;
console . log ( ` 🧩📁 Loaded plugin " ${ plugin . id || plugin . path } " from file ` ) ;
2019-07-18 00:02:07 +08:00
} else {
2023-05-13 04:04:21 +08:00
Plugins . installed . remove ( plugin ) ;
2019-07-18 00:02:07 +08:00
}
2020-04-26 02:25:07 +08:00
} else if ( plugin . source == 'url' ) {
2023-06-30 03:53:10 +08:00
var instance = new Plugin ( plugin . id , { disabled : plugin . disabled } ) ;
2020-12-07 05:44:55 +08:00
install _promises . push ( instance . loadFromURL ( plugin . path , false ) ) ;
2023-02-05 03:22:54 +08:00
load _counter ++ ;
console . log ( ` 🧩🌐 Loaded plugin " ${ plugin . id || plugin . path } " from URL ` ) ;
2020-04-26 02:25:07 +08:00
} else {
2023-05-13 04:04:21 +08:00
if ( Plugins . all . find ( p => p . id == plugin . id ) ) {
load _counter ++ ;
console . log ( ` 🧩🛒 Loaded plugin " ${ plugin . id } " from store ` ) ;
} else {
Plugins . installed . remove ( plugin ) ;
}
2019-02-04 04:09:35 +08:00
}
} )
2023-02-05 03:22:54 +08:00
console . log ( ` Loaded ${ load _counter } plugin ${ pluralS ( load _counter ) } ` )
2017-10-27 01:00:52 +08:00
}
2020-04-26 02:25:07 +08:00
StateMemory . save ( 'installed_plugins' )
2017-10-27 01:00:52 +08:00
2020-08-14 04:50:54 +08:00
install _promises . forEach ( promise => {
promise . catch ( console . error ) ;
} )
return await Promise . allSettled ( install _promises ) ;
2017-10-27 01:00:52 +08:00
}
2018-11-12 04:19:08 +08:00
BARS . defineActions ( function ( ) {
2021-02-20 05:10:47 +08:00
Plugins . dialog = new Dialog ( {
id : 'plugins' ,
title : 'dialog.plugins.title' ,
2023-06-23 16:20:29 +08:00
buttons : [ ] ,
2023-06-29 06:13:46 +08:00
width : 1200 ,
2021-02-20 05:10:47 +08:00
component : {
data : {
2021-02-24 04:56:36 +08:00
tab : 'installed' ,
2021-02-20 05:10:47 +08:00
search _term : '' ,
2023-06-23 16:20:29 +08:00
items : Plugins . all ,
selected _plugin : null ,
2023-06-29 06:13:46 +08:00
page : 0 ,
per _page : 25 ,
2023-06-23 16:20:29 +08:00
isMobile : Blockbench . isMobile ,
2021-02-20 05:10:47 +08:00
} ,
computed : {
plugin _search ( ) {
var name = this . search _term . toUpperCase ( )
return this . items . filter ( item => {
2021-02-24 04:56:36 +08:00
if ( ( this . tab == 'installed' ) == item . installed ) {
2021-02-20 05:10:47 +08:00
if ( name . length > 0 ) {
return (
item . id . toUpperCase ( ) . includes ( name ) ||
item . title . toUpperCase ( ) . includes ( name ) ||
item . description . toUpperCase ( ) . includes ( name ) ||
2022-01-26 22:51:08 +08:00
item . author . toUpperCase ( ) . includes ( name ) ||
item . tags . find ( tag => tag . toUpperCase ( ) . includes ( name ) )
2021-02-20 05:10:47 +08:00
)
}
return true ;
}
return false ;
} )
2023-06-23 23:55:12 +08:00
} ,
suggested _rows ( ) {
let tags = [ "Animation" ] ;
this . items . forEach ( plugin => {
if ( ! plugin . installed ) return ;
tags . safePush ( ... plugin . tags )
} )
let rows = tags . map ( tag => {
2023-06-29 06:13:46 +08:00
let plugins = this . items . filter ( plugin => ! plugin . installed && plugin . tags . includes ( tag ) && ! plugin . tags . includes ( 'Deprecated' ) ) . slice ( 0 , 12 ) ;
2023-06-23 23:55:12 +08:00
return {
title : tag ,
plugins ,
}
2023-06-29 06:13:46 +08:00
} ) . filter ( row => row . plugins . length > 2 ) ;
//rows.sort((a, b) => a.plugins.length - b.plugins.length);
rows . sort ( ( ) => Math . random ( ) - 0.5 ) ;
let cutoff = Date . now ( ) - ( 3_600_000 * 24 * 28 ) ;
let new _plugins = this . items . filter ( plugin => ! plugin . installed && plugin . creation _date > cutoff && ! plugin . tags . includes ( 'Deprecated' ) )
new _plugins . sort ( ( a , b ) => a . creation _date - b . creation _date ) ;
let new _row = {
title : 'New' ,
plugins : new _plugins . slice ( 0 , 12 )
}
rows . splice ( 0 , 0 , new _row ) ;
return rows . slice ( 0 , 3 ) ;
} ,
viewed _plugins ( ) {
return this . plugin _search . slice ( this . page * this . per _page , ( this . page + 1 ) * this . per _page ) ;
} ,
pages ( ) {
let pages = [ ] ;
let length = this . plugin _search . length ;
for ( let i = 0 ; i * this . per _page < length ; i ++ ) {
pages . push ( i ) ;
}
return pages ;
2021-02-20 05:10:47 +08:00
}
} ,
2021-05-25 01:45:40 +08:00
methods : {
2023-06-29 06:13:46 +08:00
setTab ( tab ) {
this . tab = tab ;
this . setPage ( 0 ) ;
} ,
setPage ( number ) {
this . page = number ;
} ,
2023-06-23 16:20:29 +08:00
selectPlugin ( plugin ) {
plugin . fetchAbout ( ) ;
this . selected _plugin = plugin ;
} ,
2023-07-03 06:31:36 +08:00
showDependency ( dependency ) {
let plugin = Plugins . all . find ( p => p . id == dependency ) ;
if ( plugin ) {
this . selectPlugin ( plugin ) ;
}
} ,
getDependencyName ( dependency ) {
let plugin = Plugins . all . find ( p => p . id == dependency ) ;
return plugin ? ( plugin . title + ( plugin . installed ? ' ✓' : '' ) ) : ( dependency + ' ⚠' ) ;
} ,
isDependencyInstalled ( dependency ) {
let plugin = Plugins . all . find ( p => p . id == dependency ) ;
return plugin && plugin . installed ;
} ,
2021-10-02 00:43:10 +08:00
getTagClass ( tag ) {
2022-02-11 02:21:50 +08:00
if ( tag . match ( /^(local|remote)$/i ) ) {
2021-10-02 00:43:10 +08:00
return 'plugin_tag_source'
2022-02-11 02:21:50 +08:00
} else if ( tag . match ( /^minecraft/i ) ) {
2021-10-02 00:43:10 +08:00
return 'plugin_tag_mc'
2022-02-11 02:21:50 +08:00
} else if ( tag . match ( /^deprecated/i ) ) {
return 'plugin_tag_deprecated'
2021-05-25 01:45:40 +08:00
}
2021-09-21 17:20:10 +08:00
} ,
2023-06-23 16:20:29 +08:00
formatAbout ( about ) {
return pureMarked ( about ) ;
} ,
2021-09-21 17:20:10 +08:00
getIconNode : Blockbench . getIconNode ,
2023-01-25 02:05:25 +08:00
pureMarked ,
2021-09-21 17:20:10 +08:00
tl
2021-05-25 01:45:40 +08:00
} ,
2023-06-23 16:20:29 +08:00
mount _directly : true ,
2021-02-20 05:10:47 +08:00
template : `
2023-06-23 16:20:29 +08:00
< content style = "display: flex;" class = "dialog_content" >
2023-06-23 23:55:12 +08:00
< div id = "plugin_browser_sidebar" v - show = "!isMobile || !selected_plugin" >
2023-06-23 16:20:29 +08:00
< div class = "bar flex" id = "plugins_list_main_bar" >
< div class = "tool" v - if = "!isMobile" @ click = "selected_plugin = null" > < i class = "material-icons icon" > home < / i > < / d i v >
2023-06-29 06:13:46 +08:00
< search - bar id = "plugin_search_bar" v - model = "search_term" @ input = "setPage(0)" > < / s e a r c h - b a r >
2023-06-23 16:20:29 +08:00
< / d i v >
2021-02-20 05:10:47 +08:00
< div class = "tab_bar" >
2023-06-29 06:13:46 +08:00
< div : class = "{open: tab == 'installed'}" @ click = "setTab('installed')" > $ { tl ( 'dialog.plugins.installed' ) } < / d i v >
< div : class = "{open: tab == 'available'}" @ click = "setTab('available')" > $ { tl ( 'dialog.plugins.available' ) } < / d i v >
2021-02-20 05:10:47 +08:00
< / d i v >
2023-06-23 16:20:29 +08:00
< ul class = "list" id = "plugin_list" >
2023-06-30 03:53:10 +08:00
< li v - for = "plugin in viewed_plugins" : plugin = "plugin.id" : class = "{plugin: true, testing: plugin.fromFile, selected: plugin == selected_plugin, disabled_plugin: plugin.disabled, incompatible: plugin.isInstallable() !== true}" @ click = "selectPlugin(plugin)" >
2023-06-23 16:20:29 +08:00
< div >
< div class = "plugin_icon_area" >
2023-06-23 23:55:12 +08:00
< img v - if = "plugin.hasImageIcon()" : src = "plugin.getIcon()" width = "48" height = "48px" / >
< dynamic - icon v - else : icon = "plugin.icon" / >
2023-06-23 16:20:29 +08:00
< / d i v >
< div >
< div class = "title" > { { plugin . title || plugin . id } } < / d i v >
< div class = "author" > { { tl ( 'dialog.plugins.author' , [ plugin . author ] ) } } < / d i v >
< / d i v >
< / d i v >
< div class = "description" > { { plugin . description } } < / d i v >
< ul class = "plugin_tag_list" >
< li v - for = "tag in plugin.tags" : class = "getTagClass(tag)" : key = "tag" @ click = "search_term = tag;" > { { tag } } < / l i >
< / u l >
< / l i >
< div class = "no_plugin_message tl" v - if = "plugin_search.length < 1 && tab === 'installed'" > $ { tl ( 'dialog.plugins.none_installed' ) } < / d i v >
< div class = "no_plugin_message tl" v - if = "plugin_search.length < 1 && tab === 'available'" id = "plugin_available_empty" > { { tl ( navigator . onLine ? 'dialog.plugins.none_available' : 'dialog.plugins.offline' ) } } < / d i v >
< / u l >
2023-06-29 06:13:46 +08:00
< ol class = "pagination_numbers" v - if = "pages.length > 1" >
< li v - for = "number in pages" : class = "{selected: page == number}" @ click = "setPage(number)" > { { number + 1 } } < / l i >
< / o l >
2021-02-20 05:10:47 +08:00
< / d i v >
2023-06-23 16:20:29 +08:00
< div id = "plugin_browser_page" v - if = "selected_plugin" >
< div v - if = "isMobile" @ click = "selected_plugin = null;" > Back to Overview todo < / d i v >
2023-06-30 03:53:10 +08:00
< div class = "plugin_browser_page_header" : class = "{disabled_plugin: selected_plugin.disabled}" >
2023-06-23 16:20:29 +08:00
< div class = "plugin_icon_area" >
2023-06-23 23:55:12 +08:00
< img v - if = "selected_plugin.hasImageIcon()" : src = "selected_plugin.getIcon()" width = "48" height = "48px" / >
< dynamic - icon v - else : icon = "selected_plugin.icon" / >
2021-02-20 05:10:47 +08:00
< / d i v >
2023-06-23 16:20:29 +08:00
< div >
2023-06-23 23:55:12 +08:00
< h1 >
{ { selected _plugin . title || selected _plugin . id } }
< div class = "version" > v { { selected _plugin . version } } < / d i v >
< / h 1 >
< div class = "author" >
{ { tl ( 'dialog.plugins.author' , [ selected _plugin . author ] ) } }
2023-06-30 03:53:10 +08:00
< div v - if = "selected_plugin.disabled" class = "plugin_disabled_tag" > 🌙 $ { tl ( 'dialog.plugins.is_disabled' ) } < / d i v >
< div v - else - if = "selected_plugin.installed" class = "plugin_installed_tag" > ✓ $ { tl ( 'dialog.plugins.is_installed' ) } < / d i v >
2023-06-23 23:55:12 +08:00
< / d i v >
2021-02-20 05:10:47 +08:00
< / d i v >
2023-06-23 16:20:29 +08:00
< / d i v >
2023-06-24 05:14:04 +08:00
< div class = "button_bar" v - if = "selected_plugin.installed || selected_plugin.isInstallable() == true" >
2023-06-30 03:53:10 +08:00
< button type = "button" v - if = "selected_plugin.installed && selected_plugin.source != 'store'" @ click = "selected_plugin.toggleDisabled()" >
< i class = "material-icons icon" > bedtime < / i >
< span > { { selected _plugin . disabled ? '${tl(' dialog . plugins . enable ')}' : '${tl(' dialog . plugins . disable ')}' } } < / s p a n >
< / b u t t o n >
2023-06-30 01:57:41 +08:00
< button type = "button" @ click = "selected_plugin.reload()" v - if = "selected_plugin.installed && selected_plugin.isReloadable()" >
< i class = "material-icons icon" > refresh < / i >
< span > $ { tl ( 'dialog.plugins.reload' ) } < / s p a n >
< / b u t t o n >
< button type = "button" class = "" @ click = "selected_plugin.uninstall()" v - if = "selected_plugin.installed" >
< i class = "material-icons icon" > delete < / i >
< span > $ { tl ( 'dialog.plugins.uninstall' ) } < / s p a n >
< / b u t t o n >
< button type = "button" class = "" @ click = "selected_plugin.install()" v - else >
< i class = "material-icons icon" > add < / i >
< span > $ { tl ( 'dialog.plugins.install' ) } < / s p a n >
< / b u t t o n >
2023-06-24 05:14:04 +08:00
< / d i v >
2023-06-23 16:20:29 +08:00
< ul class = "plugin_tag_list" >
< li v - for = "tag in selected_plugin.tags" : class = "getTagClass(tag)" : key = "tag" @ click = "search_term = tag;" > { { tag } } < / l i >
< / u l >
2023-06-30 03:53:10 +08:00
< div class = "description" : class = "{disabled_plugin: selected_plugin.disabled}" > { { selected _plugin . description } } < / d i v >
2023-06-23 16:20:29 +08:00
2023-07-03 06:31:36 +08:00
< div class = "plugin_dependencies" v - if = "selected_plugin.dependencies.length" >
$ { tl ( 'dialog.plugins.dependencies' ) }
< a v - for = "dep in selected_plugin.dependencies" @ click = "showDependency(dep)" : class = "{installed: isDependencyInstalled(dep)}" > { { getDependencyName ( dep ) } } < / a >
< / d i v >
< div class = "tiny plugin_compatibility_issue" v - if = "selected_plugin.isInstallable() != true" >
2023-06-23 23:55:12 +08:00
< i class = "material-icons icon" > error < / i >
{ { selected _plugin . isInstallable ( ) } }
< / d i v >
2023-06-23 16:20:29 +08:00
2023-06-23 23:55:12 +08:00
< h2 v - if = "selected_plugin.about" style = "margin-top: 36px;" > About < / h 2 >
< dynamic - icon v - else - if = "!selected_plugin.hasImageIcon()" : icon = "selected_plugin.icon" id = "plugin_page_background_decoration" / >
2023-06-30 01:57:41 +08:00
< div class = "about markdown" v - if = "selected_plugin.about" v - html = "formatAbout(selected_plugin.about)" > < / d i v >
2023-06-23 16:20:29 +08:00
< / d i v >
< div id = "plugin_browser_start_page" v - if = "!selected_plugin && !isMobile" >
2023-06-23 23:55:12 +08:00
< h1 > Blockbench Plugins < / h 1 >
< img src = "./assets/plugins.png" / >
< p > Plugins allow you to configure Blockbench beyond the default capabilities . Select from a list of 100 community created plugins . < / p >
< p > Want to write your own plugin ? Check out the < a href = "https://www.blockbench.net/wiki/docs/plugin" target = "_blank" > Plugin Documentation < / a > . < / p >
2023-06-23 16:20:29 +08:00
2023-06-23 23:55:12 +08:00
< div v - for = "row in suggested_rows" class = "plugins_suggested_row" >
< h3 > { { row . title } } < / h 3 >
< ul >
< li v - for = "plugin in row.plugins" @ click = "selectPlugin(plugin)" >
< div class = "plugin_icon_area" >
< img v - if = "plugin.hasImageIcon()" : src = "plugin.getIcon()" width = "48" height = "48px" / >
< dynamic - icon v - else : icon = "plugin.icon" / >
< / d i v >
< div class = "title" > < span > { { plugin . title || plugin . id } } < / s p a n > < / d i v >
< div class = "author" > { { plugin . author } } < / d i v >
< / l i >
< / u l >
< / d i v >
2023-06-23 16:20:29 +08:00
< / d i v >
< / c o n t e n t >
2021-02-20 05:10:47 +08:00
`
}
} )
2023-06-23 16:20:29 +08:00
let actions _setup = false ;
2019-08-18 00:26:14 +08:00
new Action ( 'plugins_window' , {
2018-12-27 21:03:04 +08:00
icon : 'extension' ,
category : 'blockbench' ,
2022-10-14 04:52:04 +08:00
side _menu : new Menu ( 'plugins_window' , [
'load_plugin' ,
'load_plugin_from_url'
] ) ,
click ( e ) {
2021-02-20 05:10:47 +08:00
Plugins . dialog . show ( ) ;
2021-02-24 04:56:36 +08:00
let none _installed = ! Plugins . all . find ( plugin => plugin . installed ) ;
if ( none _installed ) Plugins . dialog . content _vue . tab = 'available' ;
2023-06-23 16:20:29 +08:00
if ( ! actions _setup ) {
BarItems . load _plugin . toElement ( '#plugins_list_main_bar' ) ;
BarItems . load _plugin _from _url . toElement ( '#plugins_list_main_bar' ) ;
actions _setup = true ;
2021-02-20 05:10:47 +08:00
}
2021-02-20 20:21:11 +08:00
$ ( 'dialog#plugins #plugin_search_bar input' ) . trigger ( 'focus' )
2018-12-27 21:03:04 +08:00
}
} )
2019-08-18 00:26:14 +08:00
new Action ( 'reload_plugins' , {
2019-02-04 04:09:35 +08:00
icon : 'sync' ,
category : 'blockbench' ,
2022-10-14 04:52:04 +08:00
click ( ) {
2019-02-04 04:09:35 +08:00
Plugins . devReload ( )
}
} )
2019-08-18 00:26:14 +08:00
new Action ( 'load_plugin' , {
2019-07-18 00:02:07 +08:00
icon : 'fa-file-code' ,
2018-11-12 04:19:08 +08:00
category : 'blockbench' ,
2022-10-14 04:52:04 +08:00
click ( ) {
2018-11-12 04:19:08 +08:00
Blockbench . import ( {
2020-04-26 02:25:07 +08:00
resource _id : 'dev_plugin' ,
2019-02-04 04:09:35 +08:00
extensions : [ 'js' ] ,
2018-11-12 04:19:08 +08:00
type : 'Blockbench Plugin' ,
} , function ( files ) {
2020-04-26 02:25:07 +08:00
new Plugin ( ) . loadFromFile ( files [ 0 ] , true )
} )
}
} )
new Action ( 'load_plugin_from_url' , {
icon : 'cloud_download' ,
category : 'blockbench' ,
2022-10-14 04:52:04 +08:00
click ( ) {
2020-04-26 02:25:07 +08:00
Blockbench . textPrompt ( 'URL' , '' , url => {
new Plugin ( ) . loadFromURL ( url , true )
2018-11-12 04:19:08 +08:00
} )
}
} )
2021-09-08 00:53:11 +08:00
new Action ( 'add_plugin' , {
icon : 'add' ,
category : 'blockbench' ,
2022-10-14 04:52:04 +08:00
click ( ) {
2021-09-08 00:53:11 +08:00
setTimeout ( _ => ActionControl . select ( '+plugin: ' ) , 1 ) ;
}
} )
new Action ( 'remove_plugin' , {
icon : 'remove' ,
category : 'blockbench' ,
2022-10-14 04:52:04 +08:00
click ( ) {
2021-09-08 00:53:11 +08:00
setTimeout ( _ => ActionControl . select ( '-plugin: ' ) , 1 ) ;
}
} )
2018-11-12 04:19:08 +08:00
} )