From 2d8e6240c61dc6301f49cbdcd1c3b04736f9ca93 Mon Sep 17 00:00:00 2001 From: KernelDeimos Date: Fri, 31 May 2024 20:36:14 -0400 Subject: [PATCH] feat(backend): add tip of day --- packages/backend/src/CoreModule.js | 3 + .../backend/src/services/DevConsoleService.js | 36 ++++++++ .../backend/src/services/DevTODService.js | 86 +++++++++++++++++++ 3 files changed, 125 insertions(+) create mode 100644 packages/backend/src/services/DevTODService.js diff --git a/packages/backend/src/CoreModule.js b/packages/backend/src/CoreModule.js index 3de3ff73..d28ffe88 100644 --- a/packages/backend/src/CoreModule.js +++ b/packages/backend/src/CoreModule.js @@ -222,6 +222,9 @@ const install = async ({ services, app }) => { const { DetailProviderService } = require('./services/DetailProviderService'); services.registerService('whoami', DetailProviderService); + + const { DevTODService } = require('./services/DevTODService'); + services.registerService('__dev-tod', DevTODService); } const install_legacy = async ({ services }) => { diff --git a/packages/backend/src/services/DevConsoleService.js b/packages/backend/src/services/DevConsoleService.js index 9e9d870c..be0afada 100644 --- a/packages/backend/src/services/DevConsoleService.js +++ b/packages/backend/src/services/DevConsoleService.js @@ -57,6 +57,7 @@ class DevConsoleService extends BaseService { // if a widget throws an error we MUST remove it; // it's probably a stack overflow because it's printing. const to_remove = []; + let positions = []; for ( const w of this.widgets ) { let output; try { output = w(); @@ -66,8 +67,43 @@ class DevConsoleService extends BaseService { continue; } output = Array.isArray(output) ? output : [output]; + positions.push([this.static_lines.length, output.length]); this.static_lines.push(...output); } + + const DESIRED_MIN_OUT = 10; + const size_ok = () => + process.stdout.rows - DESIRED_MIN_OUT > this.static_lines.length; + let n_hidden = 0; + for ( let i = this.widgets.length-1 ; i >= 0 ; i-- ) { + if ( size_ok() ) break; + const w = this.widgets[i]; + if ( ! w.unimportant ) continue; + n_hidden++; + const [start, length] = positions[i]; + this.static_lines.splice(start, length); + // update positions + for ( let j = i ; j < positions.length ; j++ ) { + positions[j][0] -= length; + } + } + for ( let i = this.widgets.length-1 ; i >= 0 ; i-- ) { + if ( size_ok() ) break; + n_hidden++; + const w = this.widgets[i]; + const [start, length] = positions[i]; + this.static_lines.splice(start, length); + } + if ( n_hidden && size_ok() ) { + this.static_lines.push( + `\x1B[33m` + + this.generateEnd( + `[ ${n_hidden} widget${n_hidden === 1 ? '' : 's'} hidden ]` + ) + + `\x1B[0m` + ); + } + if (!this.arrays_equal(initialOutput, this.static_lines)) { this.mark_updated(); // Update only if outputs have changed } diff --git a/packages/backend/src/services/DevTODService.js b/packages/backend/src/services/DevTODService.js new file mode 100644 index 00000000..ed3a84c5 --- /dev/null +++ b/packages/backend/src/services/DevTODService.js @@ -0,0 +1,86 @@ +const { surrounding_box } = require("../fun/dev-console-ui-utils"); +const BaseService = require("./BaseService"); + +const SOURCE_CODE_TIPS = ` + Most services are registered in CoreModule.js + Boot sequence events are different from service events + ExpectationService exists to ensure Puter doesn't miss a step + Services are composable; StrategyService is a good example + API endpoints should be on a separate origin in production + There is some limited query-building in packages/backend/src/om +`; + +const tips = ( + // CLI tips + ` + Type \`help\` to see a list of commands + \`logs:show\` toggles log output; useful when typing long commands + \`logs:indent \` toggles indentation for some logs + \`lock:locks \` will list any active mutex locks + `, + // Source code tips + ` + Most services are registered in CoreModule.js + Boot sequence events are different from service events + ExpectationService exists to ensure Puter doesn't miss a step + Services are composable; StrategyService is a good example + API endpoints should be on a separate origin in production + These messages come from DevTODService.js + ` +).split('\n').map((line) => line.trim()) + .filter((line) => line.length) + .map(tip => { + const lines = []; + const WRAP = process.stdout.columns || 50; + while ( tip.length ) { + lines.push(tip.substring(0, WRAP)); + tip = tip.substring(WRAP); + } + return lines; + }) + +class DevTODService extends BaseService { + async _init () { + const svc_commands = this.services.get('commands'); + this._register_commands(svc_commands); + } + async ['__on_boot.consolidation'] () { + const random_tip = tips[Math.floor(Math.random() * tips.length)]; + this.tod_widget = () => { + const lines = [ + "", + "\x1B[1mTip of the Day\x1B[0m", + ...random_tip, + "Type tod:dismiss to un-stick this message", + "", + ]; + surrounding_box('33;1', lines); + return lines; + } + + this.tod_widget.unimportant = true; + + const svc_devConsole = this.services.get('dev-console', { optional: true }); + if ( ! svc_devConsole ) return; + svc_devConsole.add_widget(this.tod_widget); + } + + _register_commands (commands) { + commands.registerCommands('tod', [ + { + id: 'dismiss', + description: 'Dismiss the startup message', + handler: async (_, log) => { + const svc_devConsole = this.services.get('dev-console', { optional: true }); + if ( ! svc_devConsole ) return; + svc_devConsole.remove_widget(this.tod_widget); + const lines = this.tod_widget(); + for ( const line of lines ) log.log(line); + this.tod_widget = null; + } + } + ]); + } +} + +module.exports = { DevTODService }; \ No newline at end of file