mirror of
				https://github.com/SikongJueluo/cc-utils.git
				synced 2025-11-04 19:27:50 +08:00 
			
		
		
		
	try to wordwrap, but failed
This commit is contained in:
		@@ -222,6 +222,7 @@ function main(args: string[]) {
 | 
				
			|||||||
      return;
 | 
					      return;
 | 
				
			||||||
    } else if (args[0] == "config") {
 | 
					    } else if (args[0] == "config") {
 | 
				
			||||||
      log.info("Launching Access Control TUI...");
 | 
					      log.info("Launching Access Control TUI...");
 | 
				
			||||||
 | 
					      log.setInTerminal(false);
 | 
				
			||||||
      try {
 | 
					      try {
 | 
				
			||||||
        launchAccessControlTUI();
 | 
					        launchAccessControlTUI();
 | 
				
			||||||
      } catch (error) {
 | 
					      } catch (error) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,6 +16,7 @@ import {
 | 
				
			|||||||
  For,
 | 
					  For,
 | 
				
			||||||
  Switch,
 | 
					  Switch,
 | 
				
			||||||
  Match,
 | 
					  Match,
 | 
				
			||||||
 | 
					  ScrollContainer,
 | 
				
			||||||
} from "../lib/ccTUI";
 | 
					} from "../lib/ccTUI";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  AccessConfig,
 | 
					  AccessConfig,
 | 
				
			||||||
@@ -65,7 +66,7 @@ const AccessControlTUI = () => {
 | 
				
			|||||||
  setConfig(() => loadedConfig);
 | 
					  setConfig(() => loadedConfig);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Tab navigation functions
 | 
					  // Tab navigation functions
 | 
				
			||||||
  const tabNames = ["Basic", "Groups", "Welcome", "Warn", "Notice Toast"];
 | 
					  const tabNames = ["Basic", "Groups", "Welcome", "Warn", "Notice"];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const showError = (message: string) => {
 | 
					  const showError = (message: string) => {
 | 
				
			||||||
    setErrorState("show", true);
 | 
					    setErrorState("show", true);
 | 
				
			||||||
@@ -403,18 +404,23 @@ const AccessControlTUI = () => {
 | 
				
			|||||||
        ),
 | 
					        ),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Users list
 | 
					        // Users list
 | 
				
			||||||
        For({ each: () => getSelectedGroup().groupUsers ?? [] }, (user) =>
 | 
					        For(
 | 
				
			||||||
          div(
 | 
					          {
 | 
				
			||||||
            { class: "flex flex-row items-center" },
 | 
					            class: "flex flex-col",
 | 
				
			||||||
            label({}, user),
 | 
					            each: () => getSelectedGroup().groupUsers ?? [],
 | 
				
			||||||
            button(
 | 
					          },
 | 
				
			||||||
              {
 | 
					          (user) =>
 | 
				
			||||||
                class: "ml-1 bg-red text-white",
 | 
					            div(
 | 
				
			||||||
                onClick: () => removeUser(user),
 | 
					              { class: "flex flex-row items-center" },
 | 
				
			||||||
              },
 | 
					              label({}, user),
 | 
				
			||||||
              "X",
 | 
					              button(
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                  class: "ml-1 bg-red text-white",
 | 
				
			||||||
 | 
					                  onClick: () => removeUser(user),
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "X",
 | 
				
			||||||
 | 
					              ),
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
          ),
 | 
					 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
      ),
 | 
					      ),
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
@@ -532,20 +538,17 @@ const AccessControlTUI = () => {
 | 
				
			|||||||
    return Show(
 | 
					    return Show(
 | 
				
			||||||
      { when: () => errorState().show },
 | 
					      { when: () => errorState().show },
 | 
				
			||||||
      div(
 | 
					      div(
 | 
				
			||||||
        {
 | 
					        { class: "flex flex-col bg-red " },
 | 
				
			||||||
          class:
 | 
					        label(
 | 
				
			||||||
            "fixed top-1/4 left-1/4 right-1/4 bottom-1/4 bg-red text-white border",
 | 
					          { class: "w-25 text-white", wordWrap: true },
 | 
				
			||||||
        },
 | 
					          () => errorState().message,
 | 
				
			||||||
        div(
 | 
					        ),
 | 
				
			||||||
          { class: "flex flex-col p-2" },
 | 
					        button(
 | 
				
			||||||
          label({}, () => errorState().message),
 | 
					          {
 | 
				
			||||||
          button(
 | 
					            class: "bg-white text-black",
 | 
				
			||||||
            {
 | 
					            onClick: hideError,
 | 
				
			||||||
              class: "mt-2 bg-white text-black",
 | 
					          },
 | 
				
			||||||
              onClick: hideError,
 | 
					          "OK",
 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            "OK",
 | 
					 | 
				
			||||||
          ),
 | 
					 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
      ),
 | 
					      ),
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
@@ -597,27 +600,36 @@ const AccessControlTUI = () => {
 | 
				
			|||||||
    ),
 | 
					    ),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Content area
 | 
					    // Content area
 | 
				
			||||||
    div({ class: "flex-1 p-2 w-screen" }, TabContent()),
 | 
					    Show(
 | 
				
			||||||
 | 
					      { when: () => !errorState().show },
 | 
				
			||||||
 | 
					      div(
 | 
				
			||||||
 | 
					        { class: "flex flex-col" },
 | 
				
			||||||
 | 
					        ScrollContainer(
 | 
				
			||||||
 | 
					          { class: "flex-1 p-2", width: 50, showScrollbar: true },
 | 
				
			||||||
 | 
					          TabContent(),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Action buttons
 | 
					        // Action buttons
 | 
				
			||||||
    div(
 | 
					        div(
 | 
				
			||||||
      { class: "flex flex-row justify-center p-2" },
 | 
					          { class: "flex flex-row justify-center p-2" },
 | 
				
			||||||
      button(
 | 
					          button(
 | 
				
			||||||
        {
 | 
					            {
 | 
				
			||||||
          class: "bg-green text-white mr-2",
 | 
					              class: "bg-green text-white mr-2",
 | 
				
			||||||
          onClick: handleSave,
 | 
					              onClick: handleSave,
 | 
				
			||||||
        },
 | 
					            },
 | 
				
			||||||
        "Save",
 | 
					            "Save",
 | 
				
			||||||
      ),
 | 
					          ),
 | 
				
			||||||
      button(
 | 
					          button(
 | 
				
			||||||
        {
 | 
					            {
 | 
				
			||||||
          class: "bg-gray text-white",
 | 
					              class: "bg-gray text-white",
 | 
				
			||||||
          onClick: () => {
 | 
					              onClick: () => {
 | 
				
			||||||
            // Close TUI - this will be handled by the application framework
 | 
					                // Close TUI - this will be handled by the application framework
 | 
				
			||||||
            error("TUI_CLOSE");
 | 
					                error("TUI_CLOSE");
 | 
				
			||||||
          },
 | 
					              },
 | 
				
			||||||
        },
 | 
					            },
 | 
				
			||||||
        "Close",
 | 
					            "Close",
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
      ),
 | 
					      ),
 | 
				
			||||||
    ),
 | 
					    ),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -49,31 +49,30 @@ export class CCLog {
 | 
				
			|||||||
   * For SECOND interval: YYYY-MM-DD-HH-MM-SS
 | 
					   * For SECOND interval: YYYY-MM-DD-HH-MM-SS
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  private getTimePeriodString(time: number): string {
 | 
					  private getTimePeriodString(time: number): string {
 | 
				
			||||||
    // Calculate which time period this timestamp falls into
 | 
					 | 
				
			||||||
    const periodStart = Math.floor(time / this.interval) * this.interval;
 | 
					    const periodStart = Math.floor(time / this.interval) * this.interval;
 | 
				
			||||||
    const periodDate = os.date("*t", periodStart);
 | 
					    const d = os.date("*t", periodStart);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (this.interval >= DAY) {
 | 
					    if (this.interval >= DAY) {
 | 
				
			||||||
      return `${periodDate.year}-${String(periodDate.month).padStart(2, "0")}-${String(periodDate.day).padStart(2, "0")}`;
 | 
					      return `${d.year}-${string.format("%02d", d.month)}-${string.format("%02d", d.day)}`;
 | 
				
			||||||
    } else {
 | 
					    } else if (this.interval >= HOUR) {
 | 
				
			||||||
      return `[${periodDate.year}-${String(periodDate.month).padStart(2, "0")}-${String(periodDate.day).padStart(2, "0")}] - [${String(periodDate.hour).padStart(2, "0")}-${String(periodDate.min).padStart(2, "0")}-${String(periodDate.sec).padStart(2, "0")}]`;
 | 
					      return `${d.year}-${string.format("%02d", d.month)}-${string.format("%02d", d.day)}_${string.format("%02d", d.hour)}`;
 | 
				
			||||||
 | 
					    } else if (this.interval >= MINUTE) {
 | 
				
			||||||
 | 
					      return `${d.year}-${string.format("%02d", d.month)}-${string.format("%02d", d.day)}_${string.format("%02d", d.hour)}-${string.format("%02d", d.min)}`;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    return `${d.year}-${string.format("%02d", d.month)}-${string.format("%02d", d.day)}_${string.format("%02d", d.hour)}-${string.format("%02d", d.min)}-${string.format("%02d", d.sec)}`;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private generateFilePath(baseFilename: string, timePeriod: string): string {
 | 
					  private generateFilePath(baseFilename: string, timePeriod: string): string {
 | 
				
			||||||
    // Extract file extension if present
 | 
					    const scriptDir = shell.dir() ?? "";
 | 
				
			||||||
    const fileNameSubStrings = baseFilename.split(".");
 | 
					 | 
				
			||||||
    let filenameWithoutExt: string;
 | 
					 | 
				
			||||||
    let extension = "";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (fileNameSubStrings.length > 1) {
 | 
					    const [filenameWithoutExt, extension] = baseFilename.includes(".")
 | 
				
			||||||
      filenameWithoutExt = fileNameSubStrings[0];
 | 
					      ? baseFilename.split(".")
 | 
				
			||||||
      extension = fileNameSubStrings[1];
 | 
					      : [baseFilename, "log"];
 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      filenameWithoutExt = baseFilename;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return `${shell.dir()}/${filenameWithoutExt}[${timePeriod}].${extension}`;
 | 
					    return fs.combine(
 | 
				
			||||||
 | 
					      scriptDir,
 | 
				
			||||||
 | 
					      `${filenameWithoutExt}_${timePeriod}.${extension}`,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private checkAndRotateLogFile() {
 | 
					  private checkAndRotateLogFile() {
 | 
				
			||||||
@@ -154,6 +153,10 @@ export class CCLog {
 | 
				
			|||||||
    this.writeLine(this.getFormatMsg(msg, LogLevel.Error), colors.red);
 | 
					    this.writeLine(this.getFormatMsg(msg, LogLevel.Error), colors.red);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public setInTerminal(value: boolean) {
 | 
				
			||||||
 | 
					    this.inTerm = value;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  public close() {
 | 
					  public close() {
 | 
				
			||||||
    if (this.fp !== undefined) {
 | 
					    if (this.fp !== undefined) {
 | 
				
			||||||
      this.fp.close();
 | 
					      this.fp.close();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,8 +5,8 @@
 | 
				
			|||||||
import { UIObject } from "./UIObject";
 | 
					import { UIObject } from "./UIObject";
 | 
				
			||||||
import { calculateLayout } from "./layout";
 | 
					import { calculateLayout } from "./layout";
 | 
				
			||||||
import { render as renderTree, clearScreen } from "./renderer";
 | 
					import { render as renderTree, clearScreen } from "./renderer";
 | 
				
			||||||
import { CCLog } from "../ccLog";
 | 
					import { CCLog, HOUR } from "../ccLog";
 | 
				
			||||||
import { findScrollContainer } from "./scrollContainer";
 | 
					import { setLogger } from "./context";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Main application class
 | 
					 * Main application class
 | 
				
			||||||
@@ -28,7 +28,8 @@ export class Application {
 | 
				
			|||||||
    const [width, height] = term.getSize();
 | 
					    const [width, height] = term.getSize();
 | 
				
			||||||
    this.termWidth = width;
 | 
					    this.termWidth = width;
 | 
				
			||||||
    this.termHeight = height;
 | 
					    this.termHeight = height;
 | 
				
			||||||
    this.logger = new CCLog("tui_debug.log", false);
 | 
					    this.logger = new CCLog("tui_debug.log", false, HOUR);
 | 
				
			||||||
 | 
					    setLogger(this.logger);
 | 
				
			||||||
    this.logger.debug("Application constructed.");
 | 
					    this.logger.debug("Application constructed.");
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,7 +4,16 @@
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { UIObject, BaseProps, createTextNode } from "./UIObject";
 | 
					import { UIObject, BaseProps, createTextNode } from "./UIObject";
 | 
				
			||||||
import { Accessor, Setter, Signal } from "./reactivity";
 | 
					import {
 | 
				
			||||||
 | 
					  Accessor,
 | 
				
			||||||
 | 
					  createEffect,
 | 
				
			||||||
 | 
					  createMemo,
 | 
				
			||||||
 | 
					  createSignal,
 | 
				
			||||||
 | 
					  Setter,
 | 
				
			||||||
 | 
					  Signal,
 | 
				
			||||||
 | 
					} from "./reactivity";
 | 
				
			||||||
 | 
					import { For } from "./controlFlow";
 | 
				
			||||||
 | 
					import { logger } from "./context";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Props for div component
 | 
					 * Props for div component
 | 
				
			||||||
@@ -14,7 +23,10 @@ export type DivProps = BaseProps & Record<string, unknown>;
 | 
				
			|||||||
/**
 | 
					/**
 | 
				
			||||||
 * Props for label component
 | 
					 * Props for label component
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export type LabelProps = BaseProps & Record<string, unknown>;
 | 
					export type LabelProps = BaseProps & {
 | 
				
			||||||
 | 
					  /** Whether to automatically wrap long text. Defaults to false. */
 | 
				
			||||||
 | 
					  wordWrap?: boolean;
 | 
				
			||||||
 | 
					} & Record<string, unknown>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Props for button component
 | 
					 * Props for button component
 | 
				
			||||||
@@ -95,10 +107,78 @@ export function div(
 | 
				
			|||||||
 * label({}, () => `Hello, ${name()}!`)
 | 
					 * label({}, () => `Hello, ${name()}!`)
 | 
				
			||||||
 * ```
 | 
					 * ```
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Splits a string by whitespace, keeping the whitespace as separate elements.
 | 
				
			||||||
 | 
					 * This is a TSTL-compatible replacement for `text.split(/(\s+)/)`.
 | 
				
			||||||
 | 
					 * @param text The text to split.
 | 
				
			||||||
 | 
					 * @returns An array of words and whitespace.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					function splitByWhitespace(text: string): string[] {
 | 
				
			||||||
 | 
					  const parts: string[] = [];
 | 
				
			||||||
 | 
					  let currentWord = "";
 | 
				
			||||||
 | 
					  let currentWhitespace = "";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  for (const char of text) {
 | 
				
			||||||
 | 
					    if (char === " " || char === "\t" || char === "\n" || char === "\r") {
 | 
				
			||||||
 | 
					      if (currentWord.length > 0) {
 | 
				
			||||||
 | 
					        parts.push(currentWord);
 | 
				
			||||||
 | 
					        currentWord = "";
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      currentWhitespace += char;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      if (currentWhitespace.length > 0) {
 | 
				
			||||||
 | 
					        parts.push(currentWhitespace);
 | 
				
			||||||
 | 
					        currentWhitespace = "";
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      currentWord += char;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (currentWord.length > 0) {
 | 
				
			||||||
 | 
					    parts.push(currentWord);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  if (currentWhitespace.length > 0) {
 | 
				
			||||||
 | 
					    parts.push(currentWhitespace);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return parts;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function label(
 | 
					export function label(
 | 
				
			||||||
  props: LabelProps,
 | 
					  props: LabelProps,
 | 
				
			||||||
  text: string | Accessor<string>,
 | 
					  text: string | Accessor<string>,
 | 
				
			||||||
): UIObject {
 | 
					): UIObject {
 | 
				
			||||||
 | 
					  if (props.wordWrap === true) {
 | 
				
			||||||
 | 
					    logger?.debug(`label : ${textutils.serialiseJSON(props)}`);
 | 
				
			||||||
 | 
					    const p = { ...props };
 | 
				
			||||||
 | 
					    delete p.wordWrap;
 | 
				
			||||||
 | 
					    const containerProps: DivProps = {
 | 
				
			||||||
 | 
					      ...p,
 | 
				
			||||||
 | 
					      class: `${p.class ?? ""} flex flex-row flex-wrap`,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (typeof text === "string") {
 | 
				
			||||||
 | 
					      // Handle static strings
 | 
				
			||||||
 | 
					      const words = splitByWhitespace(text);
 | 
				
			||||||
 | 
					      const children = words.map((word) => createTextNode(word));
 | 
				
			||||||
 | 
					      const node = new UIObject("div", containerProps, children);
 | 
				
			||||||
 | 
					      children.forEach((child) => (child.parent = node));
 | 
				
			||||||
 | 
					      return node;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      // Handle reactive strings (Accessor<string>)
 | 
				
			||||||
 | 
					      const words = createMemo(() => splitByWhitespace(text()));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const forNode = For(
 | 
				
			||||||
 | 
					        { class: `${p.class ?? ""} flex flex-row flex-wrap`, each: words },
 | 
				
			||||||
 | 
					        (word) => createTextNode(word),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const node = new UIObject("div", containerProps, [forNode]);
 | 
				
			||||||
 | 
					      forNode.parent = node;
 | 
				
			||||||
 | 
					      return node;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const textNode = createTextNode(text);
 | 
					  const textNode = createTextNode(text);
 | 
				
			||||||
  const node = new UIObject("label", props, [textNode]);
 | 
					  const node = new UIObject("label", props, [textNode]);
 | 
				
			||||||
  textNode.parent = node;
 | 
					  textNode.parent = node;
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										21
									
								
								src/lib/ccTUI/context.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/lib/ccTUI/context.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
				
			|||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Global context for the TUI application.
 | 
				
			||||||
 | 
					 * This is a simple way to provide global instances like a logger
 | 
				
			||||||
 | 
					 * to all components without prop drilling.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import type { CCLog } from "../ccLog";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * The global logger instance for the TUI application.
 | 
				
			||||||
 | 
					 * This will be set by the Application instance on creation.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export let logger: CCLog | undefined;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Sets the global logger instance.
 | 
				
			||||||
 | 
					 * @param l The logger instance.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export function setLogger(l: CCLog): void {
 | 
				
			||||||
 | 
					  logger = l;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -332,8 +332,9 @@ function drawNode(
 | 
				
			|||||||
              ? node.textContent()
 | 
					              ? node.textContent()
 | 
				
			||||||
              : node.textContent;
 | 
					              : node.textContent;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          term.setTextColor(textColor ?? colors.white);
 | 
					          if (bgColor !== undefined) {
 | 
				
			||||||
          term.setBackgroundColor(bgColor ?? colors.black);
 | 
					            term.setBackgroundColor(bgColor);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
          term.setCursorPos(x, y);
 | 
					          term.setCursorPos(x, y);
 | 
				
			||||||
          term.write(text.substring(0, width));
 | 
					          term.write(text.substring(0, width));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user