Skip to content
Snippets Groups Projects
Commit 819135b7 authored by echicken's avatar echicken :chicken:
Browse files

Initial commit of new shell

parent c8f8233c
Branches
No related tags found
No related merge requests found
Pipeline #6518 failed
{
"extends": "./node_modules/@swag/ts4s/.babelrc.json"
}
\ No newline at end of file
.env
build
node_modules
\ No newline at end of file
This diff is collapsed.
{
"name": "active-shell",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "ts4s build -s src -b build",
"deploy": "npm run build && eval $(cat .env) && scp -P $SSH_PORT build/*.js $SSH_USER@$SSH_HOST:$DEPLOY_PATH"
},
"author": "echicken",
"license": "MIT",
"dependencies": {
"@swag/ts4s": "git+ssh://git@gitlab.synchro.net:swag/ts4s.git#newload",
"swindows": "git+ssh://git@gitlab.synchro.net:echicken/swindows.git"
}
}
import type { ICgaDefs, INodeDefs, ISbbsDefs } from '@swag/ts4s';
import { load } from '@swag/ts4s';
import * as swindows from 'swindows';
import ActivityWindow from './components/ActivityWindow';
import MainMenu from './components/MainMenu';
import MessageMenu from './components/MessageMenu';
import GamesMenu from './components/GamesMenu';
import { IShellWindow } from './lib/types';
const sbbsdefs: ISbbsDefs = load('sbbsdefs.js');
const cgadefs: ICgaDefs = load('cga_defs.js');
const nodedefs: INodeDefs = load('nodedefs.js');
const { bbs, console, mswait, system, time, skipsp, truncsp, user } = js.global;
const queue = js.global.load(true, `${js.exec_dir}background.js`, `${user.number}`);
const windowManager = new swindows.WindowManager();
const windows: IShellWindow[] = [
ActivityWindow(windowManager),
MainMenu(windowManager),
MessageMenu(windowManager),
GamesMenu(windowManager),
];
let activeWindow: number = 1;
function focusWindow(idx: number): void {
idx = idx % windows.length;
windows[activeWindow].window.title = {
text: windows[activeWindow].window.title?.text ?? '',
attr: (cgadefs.BG_BLACK|cgadefs.WHITE) as swindows.types.attr,
alignment: swindows.defs.ALIGNMENT.LEFT,
};
windows[idx].window.title = {
text: windows[idx].window.title?.text ?? '',
attr: (cgadefs.BG_CYAN|cgadefs.LIGHTCYAN) as swindows.types.attr,
alignment: swindows.defs.ALIGNMENT.LEFT,
};
activeWindow = idx;
}
function getInput(): void {
let input = console.inkey(sbbsdefs.K_NONE);
if (input === '') return;
if (input === 'q') {
js.global.exit(0);
} else if (input === '\t') {
focusWindow(activeWindow + 1);
} else {
windows[activeWindow].menu.getCmd(input);
}
}
function chomp(str: string): string {
return skipsp(truncsp(str));
}
function getNotifications(): void {
const msg = [];
while (!js.terminated && queue.data_waiting) {
msg.push(queue.read());
}
if (system.node_list[bbs.node_num - 1].misc&nodedefs.NODE_NMSG) {
const nm = chomp(system.get_node_message(bbs.node_num)).split('\r\n');
if (nm.length > 0) msg.push(...nm);
}
if (system.node_list[bbs.node_num - 1].misc&nodedefs.NODE_MSGW) {
const tg = chomp(system.get_telegram(user.number) ?? '').split('\r\n');
if (tg.length > 0) msg.push(...tg);
}
if (msg.length < 1) return;
if (windows[0].window.cursor.y > 0) windows[0].window.write('\r\n');
windows[0].window.write(`\x01h\x01w${system.timestr(time())}\x01n\x01w\r\n`);
windows[0].window.write(msg.join('\r\n'));
}
function init(): void {
js.on_exit(`bbs.sys_status = ${bbs.sys_status};`);
js.on_exit(`console.attributes = ${console.attributes};`);
js.on_exit(`console.ctrlkey_passthru = ${console.ctrlkey_passthru};`);
js.on_exit(`windowManager.showCursor();`);
js.on_exit(`console.clear();`);
js.time_limit = 0;
bbs.sys_status |= sbbsdefs.SS_MOFF;
console.ctrlkey_passthru = "+UPKTG";
console.clear(cgadefs.BG_BLACK|cgadefs.LIGHTGRAY);
windowManager.hideCursor();
}
function main(): void {
while (!js.terminated) {
getInput();
getNotifications();
windowManager.refresh();
mswait(5);
}
}
init();
main();
import type { ISbbsDefs, ISmbDefs } from '@swag/ts4s';
import { load } from '@swag/ts4s';
const sbbsdefs: ISbbsDefs = load('sbbsdefs.js');
const smbdefs: ISmbDefs = load('smbdefs.js');
const { argv, format, msg_area, mswait, parent_queue, system, time, File, MsgBase, Queue } = js.global;
const queue = parent_queue ?? new Queue('never');
const msgPointers: Record<string, number> = {};
function scanMessages(): void {
const sf = new File(`${system.data_dir}user/${format('%04d', argv[0])}.subs`);
if (!sf.open('r')) return;
const subs = sf.iniGetAllObjects();
sf.close();
for (const sub of subs) {
if (sub === null) continue;
const cfg = sub.cfg as number;
if ((cfg&sbbsdefs.SCAN_CFG_NEW) < 1) continue;
const name = sub.name as string;
const ptr = sub.ptr as number;
if (msgPointers[name] === undefined) msgPointers[name] = ptr;
const mb = new MsgBase(name);
if (!mb.open()) continue;
let unread: number = 0;
for (let n = msgPointers[name] + 1; n <= mb.last_msg; n++) {
const h = mb.get_msg_header(false, n);
if (h === null) continue;
if ((h.attr&smbdefs.MSG_DELETE) > 0 || (h.attr&smbdefs.MSG_NODISP) > 0) continue;
unread++;
}
msgPointers[name] = mb.last_msg;
mb.close();
if (unread > 0) queue.write(`\x01h\x01w${unread} \x01cnew ${unread > 1 ? 'messages': 'message'} in ${msg_area.sub[name].name}\x01n\x01w`);
}
}
js.time_limit = 0;
let now = time();
let mscan = now - 30;
while (!js.terminated && !queue.orphan) {
now = time();
if (now - mscan >= 30) {
mscan = now;
scanMessages();
}
mswait(1000);
}
import type { IKeyDefs } from '@swag/ts4s';
import { load } from '@swag/ts4s';
import { defs, types, ControlledWindow } from 'swindows';
import { IShellWindow } from '../lib/types';
import getWindowOptions from '../lib/window-options';
const keydefs: IKeyDefs = load('key_defs.js');
export default function(wm: types.IWindowManager): IShellWindow {
const options = getWindowOptions('Activity', { x: 0, y: 0 }, { width: wm.size.width, height: Math.ceil(wm.size.height / 2) }, wm);
const window = new ControlledWindow(options);
window.wrap = defs.WRAP.NONE;
function getCmd(cmd: string): void {
switch (cmd) {
case keydefs.KEY_UP:
window.scroll(0, -1);
break;
case keydefs.KEY_DOWN:
window.scroll(0, 1);
break;
}
}
return { window, menu: { getCmd } };
}
\ No newline at end of file
import { types, ControlledWindow, LightBar } from 'swindows';
import { IShellWindow } from '../lib/types';
import getWindowOptions from '../lib/window-options';
import { loadItems } from '../lib/menu';
export default function(wm: types.IWindowManager): IShellWindow {
const options = getWindowOptions('Games', { x: Math.floor(wm.size.width / 3) + (wm.size.width - (Math.floor(wm.size.width / 3) * 2)), y: Math.floor(wm.size.height / 2) }, { width: Math.floor(wm.size.width / 3), height: Math.floor(wm.size.height / 2) }, wm);
const window = new ControlledWindow(options);
const items = loadItems(`${js.exec_dir}games.ini`, wm);
const menu = new LightBar({
items,
name: 'Games Menu',
window: window,
});
menu.draw();
return { window, menu };
}
\ No newline at end of file
import type { ICgaDefs } from '@swag/ts4s';
import { load } from '@swag/ts4s';
import { types, ControlledWindow, LightBar } from 'swindows';
import { IShellWindow } from '../lib/types';
import getWindowOptions from '../lib/window-options';
import { loadItems } from '../lib/menu';
const cgadefs: ICgaDefs = load('cga_defs.js');
export default function(wm: types.IWindowManager): IShellWindow {
const options = getWindowOptions('Main', { x: 0, y: Math.floor(wm.size.height / 2) }, { width: Math.floor(wm.size.width / 3), height: Math.floor(wm.size.height / 2) }, wm);
if (options.title !== undefined) options.title.attr = ((cgadefs.BG_CYAN|cgadefs.LIGHTCYAN) as types.attr);
const window = new ControlledWindow(options);
const items = loadItems(`${js.exec_dir}main.ini`, wm);
const menu = new LightBar({
items,
name: 'Main Menu',
window,
});
menu.draw();
return { window, menu };
}
import { types, ControlledWindow, LightBar } from 'swindows';
import { IShellWindow } from '../lib/types';
import getWindowOptions from '../lib/window-options';
import { loadItems } from '../lib/menu';
export default function(wm: types.IWindowManager): IShellWindow {
const options = getWindowOptions('Messages', { x: Math.floor(wm.size.width / 3), y: Math.floor(wm.size.height / 2) }, { width: wm.size.width - (Math.floor(wm.size.width / 3) * 2), height: Math.floor(wm.size.height / 2) }, wm);
const window = new ControlledWindow(options);
const items = loadItems(`${js.exec_dir}messages.ini`, wm);
const menu = new LightBar({
items,
name: 'Messages Menu',
window: window,
});
menu.draw();
return { window, menu };
}
\ No newline at end of file
import type { ICgaDefs } from '@swag/ts4s';
import { load } from '@swag/ts4s';
import { WindowManager } from "swindows";
import { ILightBarItem } from "swindows/src/LightBar";
const cgadefs: ICgaDefs = load('cga_defs.js');
const { bbs, console, user, system, time, File } = js.global;
function exec(wm: WindowManager, fn: (...args: any[]) => any, ...args: any[]): void {
console.clear(cgadefs.BG_BLACK|cgadefs.LIGHTGRAY);
try {
fn(...args);
} catch (err: unknown) {
console.putmsg(err as string);
}
console.clear(cgadefs.BG_BLACK|cgadefs.LIGHTGRAY);
wm.draw();
}
export function loadItems(filename: string, wm: WindowManager): ILightBarItem[] {
const ret: ILightBarItem[] = [];
const f = new File(filename);
if (!f.open('r')) throw new Error(`Failed to open ${filename} for reading`);
const ini = f.iniGetAllObjects();
f.close();
for (const section of ini) {
if (section === null) continue;
if (section.ars !== undefined && !user.compare_ars(section.ars as string)) continue;
const name = section.name as string;
let onSelect = () => {};
if (section.xtrn !== undefined) {
onSelect = () => exec(wm, bbs.exec_xtrn.bind(bbs), section.xtrn as string)
} else if (section.exec !== undefined) {
onSelect = () => exec(wm, bbs.exec.bind(bbs), section.exec as string)
} else if (section.eval !== undefined) {
onSelect = () => {
// TypeScript disallows eval from modules (ts4s) so we'll just fake it.
const tf = `${system.temp_path}${user.number}-${bbs.node_num}-${time()}-${name.replace(/\s/g, '_')}.js`;
const jsf = new File(tf);
if (!jsf.open('w')) throw new Error(`Failed to open ${tf} for writing`);
jsf.write(section.eval as string);
jsf.close();
exec(wm, bbs.exec.bind(bbs), `?${tf}`);
jsf.remove();
}
}
ret.push({
text: name,
onSelect,
});
}
return ret;
}
\ No newline at end of file
import { IControlledWindow } from "swindows/src/types";
export interface IInputHandler {
getCmd: (str: string) => void;
};
export interface IShellWindow {
window: IControlledWindow,
menu: IInputHandler,
};
\ No newline at end of file
import type { ICgaDefs } from '@swag/ts4s';
import { load } from '@swag/ts4s';
import * as swindows from 'swindows';
const cgadefs: ICgaDefs = load('cga_defs.js');
const border: swindows.types.IBorderOptions = {
style: swindows.defs.BORDER_STYLE.SINGLE,
pattern: swindows.defs.BORDER_PATTERN.DIAGONAL,
attr: [
((cgadefs.BG_BLACK|cgadefs.WHITE) as swindows.types.attr),
((cgadefs.BG_BLACK|cgadefs.LIGHTCYAN) as swindows.types.attr),
((cgadefs.BG_BLACK|cgadefs.CYAN) as swindows.types.attr),
((cgadefs.BG_BLACK|cgadefs.LIGHTBLUE) as swindows.types.attr),
],
};
const title: swindows.types.IBorderText = {
text: '',
attr: ((cgadefs.BG_BLACK|cgadefs.WHITE) as swindows.types.attr)
}
export default function getWindowOptions(name: string, position: swindows.types.IPosition, size: swindows.types.ISize, windowManager: swindows.types.IWindowManager): swindows.types.IControlledWindowOptions {
const options = {
border,
title: { ...title },
position,
size,
scrollBar: {
vertical: {
enabled: true,
}
},
windowManager,
name: `${name} window`,
}
options.title.text = name;
return options;
}
{
"extends": "./node_modules/@swag/ts4s/tsconfig-synchronet.json",
"compilerOptions": {
"rootDir": ".",
"outDir": "build",
"target": "ESNext",
"lib": ["ESNext"]
},
"include": [
"src/active-shell.ts",
],
"exclude": [
"node_modules"
]
}
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment