Răsfoiți Sursa

feat(backend): add tip of day

KernelDeimos 11 luni în urmă
părinte
comite
2d8e6240c6

+ 3 - 0
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 }) => {

+ 36 - 0
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
         }

+ 86 - 0
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 };