• Custom Scrollabe Text File Viewer

    From Codefenix@VERT/CONCHAOS to All on Tuesday, November 05, 2024 18:38:49
    As discussed in Synchronet Discussion, I wanted a viewer for text files with a scrollable interface.

    This is the one that I came up with. Some parts I lifted from other modules to try to understand how frames and scrollbars work. It works, but it's far from perfect. I plan to use it in conjunction with some other mods that I tinker with (news readers, inter-bbs one-liner history, etc) as well as general text files.

    Here's a demo of me using it in my modified General Text Files section:
    https://conchaos.synchro.net/img/viewfile_demo.gif

    It uses a frame object to display the text, but I'm starting to think that's not the best idea. Some files take a long time to load, and files no larger than 117 KB can lead to "out of memory" errors. Getting the impression frames weren't meant for "large" objects, so maybe there's a better way.

    Anyone who wants to try it is more than welcome. Just be aware it may not give optimal results for now.

    // viewfile.js ============================================= start //

    load("sbbsdefs.js");
    load("frame.js");
    load("scrollbar.js");

    const OPTIONS = " " + ascii(24) + " / " + ascii(25) +
    " / pg-up / pg-dn / home / end / q: quit";

    function init_display() {
    const w = console.screen_columns;
    const h = console.screen_rows;
    const f = { top: new Frame(1, 1, w, h, BG_BLACK|LIGHTGRAY) };
    f.topbar = new Frame(1, 1, w, 1, BG_LIGHTGRAY|BLACK, f.top);
    f.output = new Frame(1, 2, w, h - 2, BG_BLACK|LIGHTGRAY, f.top);
    f.bottombar = new Frame(1, h, w, 1, BG_LIGHTGRAY|BLACK, f.top);
    f.output_scroll = new ScrollBar(f.output, { autohide: true });
    f.output.word_wrap = true;
    f.bottombar.putmsg(OPTIONS);
    f.top.open();
    return f;
    }

    function main(file_name, file_title, scroll_to_top) {
    var title = file_title ? file_title : file_getname(file_name);
    var exit_viewer = false;
    var key_pressed;
    const frames = init_display();
    if (file_exists(file_name)) {
    log(LOG_INFO, "Loading " + title);
    frames.topbar.putmsg("Loading " + title.substr(0, 65) + "...");
    frames.top.cycle();
    //frames.output.load(file_name); /* Issues with frame load():
    // Seems slow; eg: 4 secs to load 49 KB.
    // "out of memory" error loading 219 KB.
    // "out of memory" error loading 117 KB.
    // Not all extension types supported.

    var txt = new File(file_name); // Trying this as an alternative..
    if (txt.open("r")) { // ... but still slow. :(
    frames.output.putmsg(strip_ansi(txt.read()).replace(/\f/g, ""));
    txt.close();
    }

    if (scroll_to_top) {
    frames.output.scrollTo(0, 0);
    }
    frames.output_scroll.cycle();
    log(LOG_INFO, "Done loading: " + title);
    frames.top.cycle();
    frames.topbar.clear();
    frames.topbar.putmsg("Viewing: " + title.substr(0, 70));
    frames.top.cycle();
    frames.output_scroll.cycle();
    while (!js.terminated && !exit_viewer) {
    frames.output_scroll.cycle();
    key_pressed = console.inkey(K_NONE, 5).toUpperCase();
    if (key_pressed == KEY_UP && frames.output.offset.y > 0) {
    frames.output.scroll(0, -1);
    } else if (key_pressed == KEY_DOWN && (frames.output.offset.y +
    frames.output.height) < frames.output.data_height) {
    frames.output.scroll(0, 1);
    } else if (key_pressed == KEY_PAGEUP &&
    frames.output.offset.y > 0 ) {
    frames.output.scroll(0, -1 * frames.output.height);
    } else if (key_pressed == KEY_PAGEDN && (frames.output.offset.y +
    frames.output.height) < frames.output.data_height) {
    frames.output.scroll(0, frames.output.height);
    } else if (key_pressed == KEY_HOME) {
    frames.output.scrollTo(0, 0);
    } else if (key_pressed == KEY_END) {
    frames.output.scrollTo(0, frames.output.data_height -
    frames.output.height - 1);
    } else if (key_pressed == "Q") {
    exit_viewer = true;
    }
    frames.output_scroll.cycle();
    frames.top.cycle();
    }
    frames.top.close();
    print("\x01q\x01l\x01n");
    } else {
    log(LOG_ERROR, "File not found: " + file_name);
    }
    }

    // 1: file path (string)
    // 2: title (string)
    // 3: start from top (boolean; default is false)
    main(argv[0], argv[1], argv[2]);

    // viewfile.js ============================================= end //

    |15 þ ù ú codefenix ú ù ú ConstructiveChaos BBS ú ú ù þ þ
    |08 þ þ ù (https/telnet/ssh)://conchaos.synchro.net ú ù þ
    |07

    ...A fool must now and then be right by chance.
    ---
    þ Synchronet þ -=[ ConstructiveChaos BBS | conchaos.synchro.net ]=-
  • From Digital Man@VERT to Codefenix on Tuesday, November 05, 2024 16:16:39
    Re: Custom Scrollabe Text File Viewer
    By: Codefenix to All on Tue Nov 05 2024 06:38 pm

    Here's a demo of me using it in my modified General Text Files section:
    https://conchaos.synchro.net/img/viewfile_demo.gif

    Nice!

    I've definitely contemplated and even started similar projects once or twice but never finished.

    I agree: large files can be problematic. Modern log files can be gigabytes in size these days (!). You could maybe have some file size threshold beyond which you just punt and use printfile() or have your script load the file into memory in chunks (how printfile() works by default).
    --
    digital man (rob)

    This Is Spinal Tap quote #14:
    The Boston gig has been cancelled. [Don't] worry, it's not a big college town. Norco, CA WX: 68.7øF, 37.0% humidity, 7 mph W wind, 0.00 inches rain/24hrs
    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Nightfox@VERT/DIGDIST to Codefenix on Tuesday, November 05, 2024 18:53:09
    Re: Custom Scrollabe Text File Viewer
    By: Codefenix to All on Tue Nov 05 2024 06:38 pm

    As discussed in Synchronet Discussion, I wanted a viewer for text files with a scrollable interface.

    This is the one that I came up with. Some parts I lifted from other modules to try to understand how frames and scrollbars work. It works, but it's far from perfect. I plan to use it in conjunction with some other mods that I tinker with (news readers, inter-bbs one-liner history, etc) as well as general text files.

    It uses a frame object to display the text, but I'm starting to think that's not the best idea. Some files take a long time to load, and files no larger than 117 KB can lead to "out of memory" errors. Getting the impression frames weren't meant for "large" objects, so maybe there's a better way.

    Looks cool :)

    Nightfox

    ---
    þ Synchronet þ Digital Distortion: digitaldistortionbbs.com
  • From Codefenix@VERT/CONCHAOS to Digital Man on Wednesday, November 06, 2024 09:38:38
    Re: Custom Scrollabe Text File Viewer
    By: Digital Man to Codefenix on Tue Nov 05 2024 04:16 pm

    I agree: large files can be problematic. Modern log files can be gigabytes in size these days (!). You could maybe have some file size threshold beyond which you just punt and use printfile() or have your script load the file into memory in chunks (how printfile() works by default).

    That's interesting. Like just use the frame object as a kind of viewport for the lines I want to display, and use the up/down/etc to control which range of lines come into view. Seems very doable. Thanks for the thought.

    Gigabyte log files... (shivers)...

    |15 þ ù ú codefenix ú ù ú ConstructiveChaos BBS ú ú ù þ þ
    |08 þ þ ù (https/telnet/ssh)://conchaos.synchro.net ú ù þ
    |07

    ...When I die, I'm leaving my body to science fiction.
    ---
    þ Synchronet þ -=[ ConstructiveChaos BBS | conchaos.synchro.net ]=-
  • From Codefenix@VERT/CONCHAOS to Digital Man on Wednesday, November 06, 2024 18:05:13
    Re: Custom Scrollabe Text File Viewer
    By: Codefenix to Digital Man on Wed Nov 06 2024 09:38 am

    script load the file into memory in chunks (how printfile() works by
    default).

    That's interesting. Like just use the frame object as a kind of viewport for the lines I want to display, and use the up/down/etc to control which range of lines come into view. Seems very doable. Thanks for the thought.

    Ok, this version does exactly that. It still reads the entire file into memory, but it only writes a screen's worth of text to the frame at a time. Now it's much faster and has handled everything I've thrown at it. The largest file I tried was 1,599 KB, and it handled it with ease. I don't personally plan on displaying files higher than the megabyte teritory, at least not currently. I also ditched the scrollbar in favor of a simple percentage indicator in the bottom right. Other than that, it works basically like the one I demo'd earlier.

    If I keep working on this, I might try to add a CTRL+F option for finding text, but probably not much else. For now it suits my needs just fine.

    // viewfile.js ============================================= start //

    load("sbbsdefs.js");
    load("frame.js");
    load("attr_conv.js");

    const OPTIONS = " " + ascii(24) + " / " + ascii(25) +
    " / pg-up / pg-dn / home / end / q: quit";

    function init_display() {
    const w = console.screen_columns;
    const h = console.screen_rows;
    const f = { top: new Frame(1, 1, w, h, BG_BLACK|LIGHTGRAY) };
    f.topbar = new Frame(1, 1, w, 1, BG_LIGHTGRAY|BLACK, f.top);
    f.output = new Frame(1, 2, w, h - 2, BG_BLACK|LIGHTGRAY, f.top);
    f.bottombar = new Frame(1, h, w, 1, BG_LIGHTGRAY|BLACK, f.top);
    f.output.word_wrap = true;
    f.bottombar.putmsg(OPTIONS);
    f.top.open();
    return f;
    }

    function get_lines_for_screen (lines, start, end) {
    var viewport = [];
    for (var i = start; i < end; i++) {
    viewport.push(lines[i]);
    }
    return "\n" + viewport.join("\n") + "\n";
    }

    function viewfile(file_name, file_title, start_at_top) {
    var title = file_title ? file_title : file_getname(file_name);
    var exit_viewer = false;
    var key_pressed;
    var line_index;
    var file_lines = [];
    var max_lines;
    var move_viewport = false;
    const frames = init_display();

    if (file_exists(file_name)) {
    log(LOG_INFO, "Loading " + title);
    frames.topbar.putmsg("Loading " + title.substr(0, 65) + "...");
    frames.top.cycle();

    var txt = new File(file_name);
    if (txt.open("r")) {
    file_lines = convertAttrsToSyncPerSysCfg(txt.read()
    ).replace(/\f/g, "").split(/\r?\n/);
    txt.close();
    }
    max_lines = file_lines.length - frames.output.height;
    line_index = start_at_top ? 0 : max_lines;
    frames.output.putmsg( get_lines_for_screen(file_lines, line_index,
    line_index + frames.output.height) );
    frames.output.scrollTo(0, 1); // WHY???
    frames.bottombar.gotoxy( frames.bottombar.width-10, 1);
    frames.bottombar.putmsg( format( "[%.3d%%]",
    Math.round((line_index / max_lines) * 100)) );
    log(LOG_INFO, "Done loading: " + title);
    frames.topbar.clear();
    frames.topbar.putmsg("Viewing: " + title.substr(0, 70));
    frames.top.cycle();
    while (!js.terminated && !exit_viewer) {
    switch (console.inkey(K_NONE, 5).toUpperCase()) {
    case KEY_UP:
    line_index = line_index - 1;
    move_viewport = true;
    break;
    case KEY_DOWN:
    line_index = line_index + 1;
    move_viewport = true;
    break;
    case KEY_PAGEUP:
    line_index = line_index - frames.output.height;
    move_viewport = true;
    break;
    case KEY_PAGEDN:
    line_index = line_index + frames.output.height;
    move_viewport = true;
    break;
    case KEY_HOME:
    line_index = 0;
    move_viewport = true;
    break;
    case KEY_END:
    line_index = max_lines;
    move_viewport = true;
    break;
    case KEY_ESC:
    case "Q":
    exit_viewer = true;
    break;
    }
    if (move_viewport) {
    if (line_index < 0) {
    line_index = 0;
    }
    if (line_index > max_lines) {
    line_index = max_lines;
    }
    frames.output.clear();
    frames.output.putmsg( get_lines_for_screen(file_lines,
    line_index, line_index + frames.output.height) );
    frames.output.scrollTo(0, 1); // WHY???
    frames.output.cycle();
    frames.bottombar.gotoxy( frames.bottombar.width-10, 1);
    frames.bottombar.putmsg( format( "[%.3d%%]",
    Math.round((line_index / max_lines) * 100)) );
    frames.bottombar.cycle();
    move_viewport = false;
    }
    }
    frames.top.close();
    print("\x01q\x01l\x01n"); // resets the screen
    } else {
    log(LOG_ERROR, "File not found: " + file_name);
    }
    }

    function main(file_name, file_title, scroll_to_top) {
    viewfile(file_name, file_title, scroll_to_top);
    }

    // 1: file path (string)
    // 2: title (string; defaults to filename)
    // 3: start at top (boolean; default is false)
    main(argv[0], argv[1], argv[2]);

    // viewfile.js ============================================= end //

    |15 þ ù ú codefenix ú ù ú ConstructiveChaos BBS ú ú ù þ þ
    |08 þ þ ù (https/telnet/ssh)://conchaos.synchro.net ú ù þ
    |07

    ...Everybody should believe in something: I believe I'll have another drink. ---
    þ Synchronet þ -=[ ConstructiveChaos BBS | conchaos.synchro.net ]=-
  • From Nightfox@VERT/DIGDIST to Codefenix on Wednesday, November 06, 2024 18:42:16
    Re: Custom Scrollabe Text File Viewer
    By: Codefenix to Digital Man on Wed Nov 06 2024 06:05 pm

    file_lines = convertAttrsToSyncPerSysCfg(txt.read() ).replace(/\f/g,

    ConvertAttrsToSyncPerSysCfg() uses the configuration optoins from SCFG > Message Options > Extra Attribute Codes, which I think are specifically intended for messages posted in the message sub-boards. DM might correct me on this, but if you're making a general text file viewer, I'm wondering if it might make more sense to ignore those settings and just convert all attribute codes, or have your reader have its own settings for which attributes to convert.

    Nightfox

    ---
    þ Synchronet þ Digital Distortion: digitaldistortionbbs.com
  • From Digital Man@VERT to Nightfox on Wednesday, November 06, 2024 20:12:20
    Re: Custom Scrollabe Text File Viewer
    By: Nightfox to Codefenix on Wed Nov 06 2024 06:42 pm

    Re: Custom Scrollabe Text File Viewer
    By: Codefenix to Digital Man on Wed Nov 06 2024 06:05 pm

    file_lines = convertAttrsToSyncPerSysCfg(txt.read() ).replace(/\f/g,

    ConvertAttrsToSyncPerSysCfg() uses the configuration optoins from SCFG > Message Options > Extra Attribute Codes, which I think are specifically intended for messages posted in the message sub-boards. DM might correct me on this, but if you're making a general text file viewer, I'm wondering if it might make more sense to ignore those settings and just convert all attribute codes, or have your reader have its own settings for which attributes to convert.

    The settings in SCFG->Message Options->Extra Attribute Codes actually impact the function of sbbs_t::putmsg() routine, used by printfile, menu and friends (and their JS equivalents), so not just message text.
    --
    digital man (rob)

    Breaking Bad quote #35:
    You ever smoke anything else, Wendy? Sausages don't count - ha ha - Hank Norco, CA WX: 60.6øF, 15.0% humidity, 3 mph NW wind, 0.00 inches rain/24hrs
    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Nightfox@VERT/DIGDIST to Digital Man on Thursday, November 07, 2024 09:37:25
    Re: Custom Scrollabe Text File Viewer
    By: Digital Man to Nightfox on Wed Nov 06 2024 08:12 pm

    The settings in SCFG->Message Options->Extra Attribute Codes actually impact the function of sbbs_t::putmsg() routine, used by printfile, menu and friends (and their JS equivalents), so not just message text.

    Ah, good to know.

    Nightfox

    ---
    þ Synchronet þ Digital Distortion: digitaldistortionbbs.com