MCPcopy Index your code
hub / github.com/1j01/os-gui

github.com/1j01/os-gui @v0.7.3

Chat with this repo
repository ↗ · DeepWiki ↗ · release v0.7.3 ↗ · + Follow
269 symbols 660 edges 19 files 92 documented · 34% 1 cross-repo links
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

os-gui.js

A library for imitating operating system graphical user interfaces on the web

Specifically, Windows 98 — for now at least; it could be expanded in the future.

This library powers 98.js.org, a web-based version of Windows 98, including Paint, Notepad, Sound Recorder, and more.

See the homepage for more information.

Features

  • Menu bars, with support for checkbox and radio items, disabled states, submenus, keyboard shortcuts, and more

  • App windows which you can drag around, maximize, minimize, close, and resize

  • Dialog and tool window variants

  • Flying titlebar animation that guides your eyes during maximize/minimize/restore

  • Focus containment: if you Tab or Shift+Tab within a window, it wraps around to the first/last control.

  • Button styles, including lightweight buttons, disabled buttons, and default action buttons

  • Scrollbar styles, webkit-specific (in the future there could be a custom scrollbar based on a nonintrusive scrollbar library, or styles supporting a library, where you're expected to use the library directly)

  • Procedurally rendered arrows, allowing for different scrollbar sizes
  • Inversion effect when clicking on scrollbar track

  • Themeable with Windows .theme & .themepack files at runtime!

Demo

See demos online here

See also

Requirements

This library currently requires jQuery for the windowing implementation. Menu bars do not require jQuery.

(Eventually I want to have no dependencies. So far I've removed jQuery from the menu code...)

Setup

The library is not (yet) provided as a single convenient file.

You can either 1. download the repository as a ZIP file, 2. clone the repository, or 3. install the library as an npm package.

You have to include scripts for the components you want to use (MenuBar.js or $Window.js), along with stylesheets for layout, a theme, and a color scheme.

Make sure to use the compiled CSS files, not the source files.

In <head>:

<link href="https://github.com/1j01/os-gui/raw/v0.7.3/os-gui/layout.css" rel="stylesheet" type="text/css">
<link href="https://github.com/1j01/os-gui/raw/v0.7.3/os-gui/windows-98.css" rel="stylesheet" type="text/css">
<link href="https://github.com/1j01/os-gui/raw/v0.7.3/os-gui/windows-default.css" rel="stylesheet" type="text/css">

In <head> or <body>:

<script src="https://github.com/1j01/os-gui/raw/v0.7.3/os-gui/MenuBar.js"></script>

<script src="https://github.com/1j01/os-gui/raw/v0.7.3/lib/jquery.js"></script> 
<script src="https://github.com/1j01/os-gui/raw/v0.7.3/os-gui/$Window.js"></script>

API

Note: The API will likely change a lot, but I maintain a Changelog.

Panel & Inset Styles

  • .inset-deep creates a 2px inset border
  • .outset-deep creates a 2px inset border (like a button or window or menu popup)
  • .inset-shallow creates a 1px inset border
  • .outset-shallow creates a 1px outset border

Button styles

Button styles are applied to button elements globally. (And if you ever want to reset it, note that you have to get rid of the pseudo element ::after as well. @TODO: scope CSS)

Toggle Buttons

To make a toggle button, add the .toggle class to the button. Make it show as pressed with the .selected class. (@TODO: rename this .pressed)

You should use the styles together with semantic aria-pressed, aria-haspopup, and/or aria-expanded attributes as appropriate.

Default Buttons

You can show button is the default action by adding .default to the button. Note that in Windows 98, this style moves from button to button depending on the focus. A rule of thumb is that it should be on the button that will trigger with Enter.

Lightweight Buttons

You can make a lightweight button by adding .lightweight to the button. Lightweight buttons are subtle and have no border until hover.

Disabled Button States

You can disable a button by adding the standard disabled attribute to the button.

Pressed Button States

You can show a button as being pressed by adding the .pressing class to the button.
This is useful for buttons that are triggered by a keystroke.

Scrollbar styles

Scrollbar styles are applied globally, but they have a -webkit- prefix, so they'll only work in "webkit-based" browsers, generally, like Chrome, Safari, and Opera.

(Can be overridden with ::-webkit-scrollbar and related selectors (but not easily reset to the browser default, unless -webkit-appearance: scrollbar works... @TODO: scope CSS)

Selection styles

Selection styles are applied globally.

(Can be overridden with ::selection (but not easily reset to the browser default... unless with unset? @TODO: scope CSS)

MenuBar(menus)

Creates a menu bar component.

menus should be an object holding arrays of menu item specifications, keyed by menu button name.

Returns an object with property element, which you should then append to the DOM where you want it.

See examples in the demo code.

element

The DOM element that represents the menu bar.

closeMenus()

Closes any menus that are open.

setKeyboardScope(...eventTargets)

Hotkeys like Alt will be handled at the level of the given element(s) or event target(s).

By default, the scope is window (global), for the case of a single-page application where the menu bar is at the top. If you are putting the menu bar in a window, you should call this with the window's element:

menu_bar.setKeyboardScope($window[0]);

or better yet,

$window.setMenuBar(menu_bar);

which takes care of the keyboard scope for you, while attaching the menu bar to the window.

Note that some keyboard behavior is always handled if the menu bar has focus.

Note also for iframes, you may need to call this with $window[0], iframe.contentWindow currently, but this should be changed in the future (keyboard events should be proxied).

Event: info

Can be used to implement a status bar. A description is provided as event.detail.description when rolling over menu items that specify a description. For example:

menubar.element.addEventListener("info", (event)=> {
    statusBar.textContent = event.detail?.description || "";
});

Event: default-info

Signals that a status bar should be reset to blank or a default message.

menubar.element.addEventListener("default-info", (event)=> {
    statusBar.textContent = "";

    // or:
    statusBar.textContent = "For Help, click Help Topics on the Help Menu.";
    // like in MS Paint (and JS Paint)

    // or:
    statusBar.textContent = "For Help, press F1.";
    // like WordPad

    // or perhaps even:
    statusBar.innerHTML = "For Help, <a href='docs'>click here</a>";
    // Note that a link is not a common pattern, and it could only work for the default text;
    // for menu item descriptions the message in the status bar is transient, so
    // you wouldn't be able to reach it to click on it.
});

Menu item specification

Menu item specifications are either MENU_DIVIDER (a constant indicating a horizontal rule), or a radio group specification, or an object with the following properties:

  • label: a label for the item; ampersands define access keys (to use a literal ampersand, use &&)
  • shortcutLabel (optional): a keyboard shortcut to show for the item, like "Ctrl+A" (Note: you need to listen for the shortcut yourself, unlike access keys)
  • ariaKeyShortcuts (optional): aria-keyshortcuts for the item, like "Control+A Meta+A", for screen readers. "Ctrl" is not valid (you must spell it out), and it's best to provide an alternative for macOS, usually with the equivalent Command key, using "Meta" (and event.metaKey).
  • action (optional): a function to execute when the item is clicked (can only specify either action or checkbox)
  • checkbox (optional): an object specifying that this item should behave as a checkbox.
    • Property check of this object should be a function that checks if the item should be checked or not, returning true for checked and false for unchecked. What a cutesy name.
    • Property toggle should be a function that toggles the state of the option, however you're storing it; called when clicked.
  • enabled (optional): can be false to unconditionally disable the item, or a function that determines whether the item should be enabled, returning true to enable the item, false to disable.
  • submenu (optional): an array of menu item specifications to create a submenu
  • description (optional): for implementing a status bar; an info event is emitted when rolling over the item with this description
  • value (optional): for radio items, the value of the item; can be any type, but === is used to determine whether the item is checked.

A radio group specification is an object with the following properties:

  • radioItems: an array of menu item specifications to create a radio button group. Unlike submenu, the items are included directly in this menu. It is recommended to separate the radio group from other menu items with a MENU_DIVIDER.
  • getValue: a function that should return the value of the selected radio item.
  • setValue: a function that should change the state to the given value, in an application-specific way.
  • ariaLabel (optional): a string to use as the aria-label for the radio group (for screen reader accessibility)

Access Keys

Menus can be navigated with contextual hotkeys known as access keys.

Place an ampersand before a letter in a menu button or menu item's label to make it an access key. Microsoft has documentation on access keys, including guidelines for choosing access keys. Generally the first letter is a good choice.

If a menu item doesn't define an access key, the first letter of the label can be used to access it.

For menu buttons, you need to hold Alt when pressing the button's access key, but for menu items in menu popups you must press the key directly, as Alt will close the menus.

If there are multiple menu items with the same access key, it will cycle between them without activating them. You should try to make the access keys unique, including between defined access keys and the first letters of menu items without defined access keys. (This behavior is observed in Windows 98, in Explorer's Favorites menu, where you can make bookmarks with the first letter matching the access keys of other menu items.)

There is an AccessKeys object exported by MenuBar.js, with functions for dealing with access keys:

AccessKeys.escape(label)

Escapes ampersands in the given label, so that they are not interpreted as access keys.

This is useful for dynamic menus, like a history menu that uses page titles as labels. You don't want ampersands to be spuriously interpreted as access keys, or double ampersands to be interpreted as a single ampersand.

AccessKeys.unescape(label)

Un-escapes all double ampersands in the label.

For rendering, use toHTML or toFragment instead.

AccessKeys.has(label)

Returns true if the label has an access key.

AccessKeys.get(label)

Returns the access key for the given label, or null if none.

MenuBar handles access keys automatically, but if you're including access keys for other UI elements, you need to handle them yourself, and you can use this rather than hard-coding the access key, so it doesn't need to be changed in two places.

AccessKeys.remove(label)

Removes the access key indicator (&) from the label, and un-escapes any double ampersands. Also removes a parenthetical access key indicator, like " (&N)", as a special case.

AccessKeys.toText(label)

Removes the access key indicator (&) from the label, and un-escapes any double ampersands. This is like toHTML but for plain text.

Note: while often access keys are part of a word, like "&New", in translations they are often indicated separately, like "새로 만들기 (&N)", since the access key stays the same, but the letter is no longer part of the word (or even the alphabet). This function doesn't remove strings like " (&N)", it will remove only the "&" and leave "새로 만들기 (N)". If you want that behavior, use AccessKeys.remove(label).

AccessKeys.toHTML(label)

Returns HTML (with proper escaping) with the access key as a <span class='menu-hotkey'> element.

Security note: It is safe to use the result of this function in HTML element content, as it escapes the label. It is not safe to use in an attribute value, but this is not the intended usage.

Layout note: you may want to surround the result with <span style="white-space: pre"> to prevent whitespace from collapsing if the access key is next to a space.

AccessKeys.toFragment(label)

Returns a DocumentFragment with the access key as a <span class='menu-hotkey'> element.

Layout note: you may want to surround the re

Extension points exported contracts — how you extend this code

OSGUIFormWindow (Interface)
* A window with a form, for some kinds of dialogs.
os-gui.d.ts
$WindowConstructor (Interface)
* Creates a new window.
os-gui.d.ts
OSGUIWindow (Interface)
(no doc)
os-gui.d.ts
OSGUIWindowOptions (Interface)
(no doc)
os-gui.d.ts
OSGUICheckbox (Interface)
(no doc)
os-gui.d.ts

Core symbols most depended-on inside this repo

get
called by 281
os-gui.d.ts
jQuery
called by 41
demo/lib/jquery-3.3.1.js
isFunction
called by 40
demo/lib/jquery-3.3.1.js
focus
called by 37
os-gui.d.ts
remove
called by 33
os-gui.d.ts
getProp
called by 30
parse-theme.js
indexOf
called by 29
os-gui.d.ts
E
called by 27
MenuBar.js

Shape

Function 217
Method 40
Interface 12

Languages

TypeScript100%

Modules by API surface

demo/lib/jquery-3.3.1.js109 symbols
os-gui.d.ts52 symbols
$Window.js37 symbols
MenuBar.js23 symbols
demo/test.js20 symbols
parse-theme.js10 symbols
demo/physics-teaser.js9 symbols
demo/demo.js4 symbols
graphic-glitch.js2 symbols
demo/animation.js1 symbols
cypress.config.ts1 symbols
$MenuBar.js1 symbols

Used by 1 indexed graphs manifest dependencies, hub-wide

For agents

$ claude mcp add os-gui \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact

Ask about this repo answers extend the page