This commit is contained in:
Isaiah Odhner 2026-01-21 22:37:11 +00:00 committed by GitHub
commit d4bfe4a11e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 2980 additions and 0 deletions

View file

@ -0,0 +1,358 @@
.desktop {
background: #008080;
}
.taskbar {
outline: 1px solid #c0c0c0;
border-top: 1px solid white;
background: #c0c0c0;
}
.taskbar-divider {
border-left: 1px solid #707070; /* TODO: is this the correct color? */
border-right: 1px solid white;
}
.tray {
/* border: inset 1px; */
box-shadow: 1px 1px 0 #707070 inset, -1px -1px 0 white inset;
}
.taskbar-time {
font-family: sans-serif;
font-family: "MS Sans Serif", "Segoe UI", sans-serif;
font-size: 12px;
}
.start-menu-titlebar {
/* background-image: url("images/start-menu-side.png"); */
/* background-image: url("images/start-menu-side-98-js.png"); */
background-image: url("images/start-menu-side-98-js-alt.png");
background-repeat: no-repeat;
background-position: bottom left;
background-color: #000080;
}
.start-menu li a,
.start-menu .back-link {
display: block;
padding: 5px 0;
color: blue !important; /* regardless of :visited */
}
.start-menu li a:before,
.start-menu .back-link:before,
.the-thing-heading:before {
content: "»";
text-decoration: none;
display: inline-block;
margin-right: 5px;
}
.start-menu .back-link:before {
content: "«"
}
.the-thing-heading {
/* font-size: 1em; */
}
.windows-cant-do-it-you-can {
margin-top: 2em;
/* background: white;
padding: 5px;
border: inset 1px; */
}
.task.selected {
font-weight: bold;
}
button {
outline: 0; /* sorry (breaks some accessibility) */
/* TODO: have a focus ring for start button at least */
}
.desktop-icon .title {
margin-top: 5px;
font-family: sans-serif;
font-family: "MS Sans Serif", "Segoe UI", sans-serif;
/*font-size: 10px;*/
font-size: 8pt;
background: #008080;
color: white;
position: relative;
}
.folder-view.focused .desktop-icon.selected .title {
background: #000080;
color: white;
}
.folder-view.focused .desktop-icon.focused .title::after {
content: "";
position: absolute;
left: 0;
top: 0;
bottom: 0;
right: 0;
border: 1px dotted #ffffff;
mix-blend-mode: exclusion;
}
.desktop-icon .selection-effect {
display: none;
}
.folder-view.focused .desktop-icon.selected .selection-effect {
display: block;
position: absolute;
left: 0;
top: 0;
filter: drop-shadow(32px 0 0 #000080);
transform: translateX(-32px);
clip-path: polygon(100% 0, 200% 0, 200% 100%, 100% 100%);
opacity: 0.5;
}
.desktop-icon.shortcut .icon-wrapper {
position: relative;
}
.desktop-icon.shortcut .icon-wrapper:after {
content: url("images/icons/shortcut-32x32.png");
position: absolute;
left: 0;
vertical-align: bottom;
}
.folder-view .marquee {
border: 1px dotted #ffffff;
mix-blend-mode: exclusion;
/* TODO: fallback for browsers that don't support mix-blend-mode (something like #FF7F7F)... Edge doesn't support @supports either :/ */
z-index: 1;
}
.menus *,
.menu-popup * {
font-family: 'Segoe UI', sans-serif;
font-size: 12px;
cursor: default;
}
.menu-container {
margin-bottom: 1px;
}
.menu-button {
padding: 2px 5px;
position: relative;
outline: 0;
}
.menu-button:hover {
box-shadow: 1px 1px 0 white inset, -1px -1px 0 #707070 inset;
}
.menu-button:active,
.menu-button.active {
box-shadow: 1px 1px 0 #707070 inset, -1px -1px 0 white inset;
top: 1px;
left: 1px;
}
.menu-popup:not(.something-else) { /* CSS specifity hack */
display: block;
padding: 2px;
background: #c0c0c0;
border-top: 1px solid #c0c0c0;
border-left: 1px solid #c0c0c0;
border-right: 1px solid #000;
border-bottom: 1px solid #000;
box-shadow: 1px 1px 0 white inset, -1px -1px 0 #707070 inset;
}
.menu-item {
padding: 1px 3px;
margin: 2px;
}
.menu-item[disabled] {
text-shadow: 0.8px 0.8px 0px #fff;
}
.menu-item:focus,
.menu-item.active {
background: #007;
color: #fff;
text-shadow: none;
outline: 0;
}
.menu-item[disabled] {
color: #808080;
}
.menu-hr {
border: 0;
border-top: 1px solid #808080;
border-bottom: 1px solid #ffffff;
margin: 0px 2px;
}
.menu-hotkey {
text-decoration: underline;
}
button.selected {
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACAQMAAABIeJ9nAAAABlBMVEW9vb3///8EwsWUAAAADElEQVQI12NoYHAAAAHEAMFJRSpJAAAAAElFTkSuQmCC) repeat;
}
/* XXX: using .draggable as "a class that Webamp uses on its windows",
because :not() doesn't support compound selectors like :not(.webamp-container .window) or :not(#webamp *) */
.window:not(.draggable) {
background: #c0c0c0;
border-top: 1px solid #dfdfdf;
border-left: 1px solid #dfdfdf;
border-right: 1px solid #000;
border-bottom: 1px solid #000;
padding: 2px;
}
.window:not(.draggable):after {
content: '';
pointer-events: none;
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
border-top: 1px solid #fff;
border-left: 1px solid #fff;
border-right: 1px solid #7b7b7b;
border-bottom: 1px solid #7b7b7b;
}
.window:not(.draggable):after > * {
margin: 1px;
}
.window-titlebar {
background: #000080; /* fallback to standard color */
background: -moz-linear-gradient(left, #000080 0%, #1084d0 100%); /* FF3.6+ */
background: -webkit-gradient(linear, left top, right top, color-stop(0%, #000080), color-stop(100%, #1084d0)); /* Chrome, Safari4+ */
background: -webkit-linear-gradient(left, #000080 0%, #1084d0 100%); /* Chrome10+, Safari5.1+ */
background: -o-linear-gradient(left, #000080 0%, #1084d0 100%); /* Opera 11.10+ */
background: -ms-linear-gradient(left, #000080 0%, #1084d0 100%); /* IE10+ */
background: linear-gradient(to right, #000080 0%, #1084d0 100%); /* W3C */
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#000080', endColorstr='#1084d0', GradientType=1 ); /* IE6-9 */
/* TODO: gradient shouldn't actually extend to the window controls */
color: white;
font-family: 'Segoe UI', sans-serif;
font-size: 12px;
}
.window-title {
padding-left: 2px;
}
.window-button {
margin: 2px;
}
.window-content {
font-family: Arial, sans-serif;
font-size: 16px;
}
/* TODO: different kind of button effect (at least for taskbar buttons) where there's only a single 1px solid white on top and left borders (not depressed) */
.start-menu,
.ui-slider-handle,
button {
padding: 0;
vertical-align: middle;
font-family: Arial, sans-serif;
font-size: 12px;
background: #c0c0c0;
border-top: 1px solid #fff;
border-left: 1px solid #fff;
border-right: 1px solid #000;
border-bottom: 1px solid #000;
position: relative;
}
.start-menu:after,
.ui-slider-handle:after,
button:after {
content: '';
pointer-events: none;
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
border-top: 1px solid #dfdfdf;
border-left: 1px solid #dfdfdf;
border-right: 1px solid #808080;
border-bottom: 1px solid #808080;
}
button:not([disabled]):hover:active,
button.selected {
border-top: 1px solid #000;
border-left: 1px solid #000;
border-right: 1px solid #fff;
border-bottom: 1px solid #fff;
}
button:not([disabled]):hover:active:after,
button.selected:after {
border-top: 1px solid #808080;
border-left: 1px solid #808080;
border-right: 1px solid #dfdfdf;
border-bottom: 1px solid #dfdfdf;
}
button:before,
.start-menu:before {
right: 0px;
top: -3px;
}
button:hover:active:before {
right: -1px;
top: -2px;
}
* {
image-rendering: crisp-edges;
image-rendering: pixelated;
/* only works on Mac, and not on a standards track: */
font-smooth: none;
-webkit-font-smoothing: none;
}
::selection {
background-color: #000080;
color: white;
}
::-webkit-scrollbar,
::-webkit-scrollbar-thumb,
::-webkit-scrollbar-button {
width: 16px;
height: 16px;
}
::-webkit-scrollbar-thumb,
::-webkit-scrollbar-button {
background: #c0c0c0;
border-top: 1px solid #c0c0c0;
border-left: 1px solid #c0c0c0;
border-right: 1px solid #000;
border-bottom: 1px solid #000;
box-shadow: 1px 1px 0 white inset, -1px -1px 0 #707070 inset;
}
::-webkit-scrollbar-button:hover:active {
border: 1px solid #7b7b7b;
box-shadow: none;
}
::-webkit-scrollbar-corner {
background: #c0c0c0;
}
::-webkit-scrollbar-button {
background-position: center;
background-repeat: no-repeat;
}
::-webkit-scrollbar-button {
background-image: url("images/arrows.png");
}
::-webkit-scrollbar-button:horizontal:decrement:disabled {
background-position: calc(-24px + 1px) 1px; /* left */
}
::-webkit-scrollbar-button:horizontal:increment:disabled {
background-position: calc(-36px + 1px) 1px; /* right */
}
::-webkit-scrollbar-button:vertical:decrement:disabled {
background-position: calc(-12px + 1px) 1px; /* up */
}
::-webkit-scrollbar-button:vertical:increment:disabled {
background-position: calc(-0px + 1px) 1px; /* down */
}
::-webkit-scrollbar-button:horizontal:decrement {
background-position: calc(-24px + 1px) calc(-12px + 1px); /* left */
}
::-webkit-scrollbar-button:horizontal:increment {
background-position: calc(-36px + 1px) calc(-12px + 1px); /* right */
}
::-webkit-scrollbar-button:vertical:decrement {
background-position: calc(-12px + 1px) calc(-12px + 1px); /* up */
}
::-webkit-scrollbar-button:vertical:increment {
background-position: calc(-0px + 1px) calc(-12px + 1px); /* down */
}
::-webkit-scrollbar {
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACAQMAAABIeJ9nAAAABlBMVEW9vb3///8EwsWUAAAADElEQVQI12NoYHAAAAHEAMFJRSpJAAAAAElFTkSuQmCC) repeat;
}
::-webkit-scrollbar-track-piece:active {
background: black;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 766 B

View file

@ -0,0 +1,30 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>Webcramp &bull; Winamp 0.2a in your browser</title>
<link href="lib/os-gui/layout.css" rel="stylesheet" type="text/css" />
<link href="lib/os-gui/theme/windows-98.css" rel="stylesheet" type="text/css" />
<meta name="description" content="Winamp 0.2a reimplemented in HTML5 and JavaScript" />
<meta property="og:title" content="Webcramp &bull; Winamp in your browser" />
<meta property="og:description" content="Winamp 0.2a reimplemented in HTML5 and JavaScript" />
<meta property="og:type" content="website" />
<meta property="og:url" content="https://0.2a.webamp.org" />
<meta property="og:image" content="https://getwacup.com/images/Winamp_0.2a.jpg" />
<link rel="shortcut icon" href="winamp-0.2.png" type="image/png" />
<link rel="shortcut icon" href="favicon.ico" type="image/x-icon" />
<meta name="theme-color" content="#008080" />
<meta name="viewport" content="width=device-width, user-scalable=no" />
</head>
<body>
<div class="desktop"></div>
<script src="lib/pep.js"></script>
<script src="lib/jquery.min.js"></script>
<script src="lib/os-gui/$Window.js"></script>
<script src="lib/os-gui/$MenuBar.js"></script>
<script src="src/app.js"></script>
</body>
</html>

View file

@ -0,0 +1,265 @@
html, body {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
border: 0;
overflow: hidden;
}
.start-menu {
position: absolute !important; /* XXX overriding relative; could instead of using :after use border-image */
left: 2px;
bottom: 25px;
width: calc(186px + 21px); /* 186px isn't the proper width measured out, just what I had it before, (for the content, on chrome on windows 10; text wrapping is not cross-platform-consistent) */
padding-left: 21px; /* 21px = .start-menu-titlebar */
height: 340px;
max-height: calc(100vh - 26px); /* based on taskbar height TODO: do this differently, probably in JS, since the taskbar will be resizable (or could be) */
box-sizing: border-box;
display: flex;
flex-direction: row;
}
.start-menu-titlebar {
flex: 0 0 auto;
width: 21px;
}
.start-menu-content {
overflow: auto;
padding: 15px;
}
.start-menu ul {
list-style: none;
margin: 1em 0;
padding: 0;
}
.desktop {
height: 100%;
display: flex;
}
.folder-view {
position: relative;
flex: 1;
}
.taskbar {
position: absolute;
z-index: 2;
bottom: 0;
left: 0;
right: 0;
height: 27px; /* not including outline part of border */
display: flex;
flex: 1;
}
/* .taskbar-button is generally .task or .start-button for now */
.taskbar button,
.taskbar-divider,
.tray {
margin: 2px;
}
.taskbar-divider {
margin-right: 0px;
margin-left: 0px;
/* TODO: maybe make that 2px rule padding on the taskbar instead and avoid reseting this */
}
.taskbar button {
line-height: 14px;
padding: 2px;
overflow: hidden; /* TODO: If tons and tons of tasks don't show icons? (smaller than width of an icon?) */
display: flex;
flex-direction: row;
align-items: center;
/* (justify-content: flex-start;) */
}
.taskbar button .title {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.taskbar .start-button {
flex-shrink: 0;
padding: 2px 4px;
}
.tray {
display: flex;
flex-direction: row;
/* align-items: center; */
/* align-content: center; */
/* justify-content: center; */
line-height: 22px;
box-sizing: border-box;
vertical-align: middle;
}
.tray-icons {
display: flex;
align-items: center;
margin-left: 2px; /* XXX basically 1px, but 2px because of the way the border is done */
}
.tray-icon {
margin-left: 1px;
}
.taskbar-time {
width: 60px;
text-align: center;
}
/* TODO: offset task button contents down a px when depressed... actually most buttons */
.tasks {
display: flex;
flex: 1;
height: 100%;
}
.task {
max-width: 200px;
width: 50%;
text-align: left;
}
.task img {
padding-right: 4px;
flex: 0 0 auto;
}
.desktop,
.folder-view,
.taskbar,
.start-button,
.tasks,
.task,
.window,
.window-titlebar,
.window-title {
cursor: default;
user-select: none;
-o-user-select: none;
-ms-user-select: none;
-moz-user-select: none;
-webkit-user-select: none;
}
.window-titlebar .icon {
vertical-align: bottom;
}
.window-content {
margin: 0;
padding: 0;
}
.desktop-icon {
display: inline-block;
vertical-align: bottom; /* with inline-block, always */
margin: 5px;
text-align: center;
}
.desktop-icon .icon-wrapper {
display: inline-block;
vertical-align: bottom; /* with inline-block, always */
position: relative;
}
.desktop-icon .title {
line-height: 13px;
font-size: 9px;
overflow: hidden;
text-overflow: ellipsis; /* TODO: make this work with multiline text! This is apparently a limitation of this CSS property and ther are various crazy CSS hacks and JS libs to solve this. */
word-wrap: break-word; /* All browsers since IE 5.5+ */
overflow-wrap: break-word; /* Renamed property in CSS3 draft spec */
}
.desktop-icon:not(.focused) .title {
/* max-height: 2em would only work with line-height: 1 */
max-height: calc(13px * 2);
}
.desktop-icon.focused {
z-index: 1;
}
/* Fix dragging things (like windows) over iframes */
.drag iframe {
pointer-events: none;
}
.marquee {
pointer-events: none;
}
.menus {
display: flex;
flex: 0 0 auto;
}
.menu-container {
position: relative;
}
.menu-popup {
position: absolute;
top: 100%;
left: 0;
z-index: 400000; /* so sub-menu-popups can be visible over the window */
box-sizing: border-box;
}
.menu-popup-table {
border-collapse: collapse;
}
.menu-item {
white-space: nowrap;
}
.menu-hr {
display: block !important;
height: 0;
width: auto;
}
.menu-hotkey {
display: inline !important;
}
.menu-item-checkbox-area,
.menu-item-submenu-area {
min-width: 16px;
}
.menu-item-checkbox-area,
.menu-item-submenu-area {
text-align: center;
}
.window-titlebar {
display: flex;
flex-direction: row;
align-items: center;
}
.window-title-area {
position: relative;
flex: 1;
height: 16px; /* 100% doesn't work */
}
.window-title {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
display: inline-block !important;
margin: 0;
padding: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.window-close-button {
display: block !important;
float: right;
width: 13px;
height: 11px;
}
.window-close-button:before {
content: '×';
position: relative;
}
.window-content .button-group {
width: 85px;
}
.window-content .button-group > button {
width: 95%;
padding: 3px 5px;
}
::before, ::after {
pointer-events: none;
}
.cursor-bully * {
cursor: inherit !important;
}
.loading-program * {
cursor: progress;
}

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,309 @@
// TODO: E\("([a-z]+)"\) -> "<$1>" or get rid of jQuery as a dependency
function E(t){
return document.createElement(t);
}
$MenuBar.DIVIDER = "DIVIDER";
// TODO: maybe {hr: true} instead of $MenuBar.DIVIDER
function $MenuBar(menus){
var $ = jQuery;
var $G = $(self);
var $menus = $(E("div")).addClass("menus");
$menus.attr("touch-action", "none");
var selecting_menus = false;
var _html = function(menus_key){
return menus_key.replace(/&(.)/, function(m){
return "<span class='menu-hotkey'>" + m[1] + "</span>";
});
};
var _hotkey = function(menus_key){
return menus_key[menus_key.indexOf("&")+1].toUpperCase();
};
var close_menus = function(){
$menus.find(".menu-button").trigger("release");
// Close any rogue floating submenus
$(".menu-popup").hide();
};
var is_disabled = function(item){
if(typeof item.enabled === "function"){
return !item.enabled();
}else if(typeof item.enabled === "boolean"){
return !item.enabled;
}else{
return false;
}
};
// TODO: API for context menus (i.e. floating menu popups)
function $MenuPopup(menu_items){
var $menu_popup = $(E("div")).addClass("menu-popup");
var $menu_popup_table = $(E("table")).addClass("menu-popup-table").appendTo($menu_popup);
$.map(menu_items, function(item){
var $row = $(E("tr")).addClass("menu-row").appendTo($menu_popup_table)
if(item === $MenuBar.DIVIDER){
var $td = $(E("td")).attr({colspan: 4}).appendTo($row);
var $hr = $(E("hr")).addClass("menu-hr").appendTo($td);
}else{
var $item = $row.addClass("menu-item");
var $checkbox_area = $(E("td")).addClass("menu-item-checkbox-area");
var $label = $(E("td")).addClass("menu-item-label");
var $shortcut = $(E("td")).addClass("menu-item-shortcut");
var $submenu_area = $(E("td")).addClass("menu-item-submenu-area");
$item.append($checkbox_area, $label, $shortcut, $submenu_area);
$item.attr("tabIndex", -1);
$label.html(_html(item.item));
$shortcut.text(item.shortcut);
$menu_popup.on("update", function(){
$item.attr("disabled", is_disabled(item));
if(item.checkbox && item.checkbox.check){
$checkbox_area.text(item.checkbox.check() ? "✓" : "");
}
});
$item.on("pointerover", function(){
$menu_popup.triggerHandler("update");
$item.focus();
});
if(item.checkbox){
$checkbox_area.text("✓");
}
if(item.submenu){
$submenu_area.html('<svg xmlns="http://www.w3.org/2000/svg" width="10" height="11" viewBox="0 0 10 11" style="fill:currentColor;display:inline-block;vertical-align:middle"><path d="M7.5 4.33L0 8.66L0 0z"/></svg>');
var $submenu_popup = $MenuPopup(item.submenu).appendTo("body");
$submenu_popup.hide();
var open_submenu = function(){
$submenu_popup.show();
$submenu_popup.triggerHandler("update");
var rect = $submenu_area[0].getBoundingClientRect();
$submenu_popup.css({
position: "absolute",
left: rect.right,
top: rect.top,
});
};
var open_tid, close_tid;
$item.add($submenu_popup).on("pointerover", function(e){
if(open_tid){clearTimeout(open_tid);}
if(close_tid){clearTimeout(close_tid);}
});
$item.on("pointerover", function(e){
if(open_tid){clearTimeout(open_tid);}
if(close_tid){clearTimeout(close_tid);}
open_tid = setTimeout(open_submenu, 200);
});
$item.add($submenu_popup).on("pointerout", function(){
$menu_popup.closest(".menu-container").find(".menu-button").focus();
if(open_tid){clearTimeout(open_tid);}
if(close_tid){clearTimeout(close_tid);}
close_tid = setTimeout(function(){
$submenu_popup.hide();
}, 200);
});
$item.on("click pointerdown", open_submenu);
}
var item_action = function(){
if(item.checkbox){
if(item.checkbox.toggle){
item.checkbox.toggle();
}
$menu_popup.triggerHandler("update");
}else if(item.action){
close_menus();
item.action();
}
};
$item.on("pointerup", function(e){
if(e.pointerType === "mouse" && e.button !== 0){
return;
}
item_action();
});
$item.on("pointerover", function(){
if(item.submenu){
$menus.triggerHandler("info", "");
}else{
$menus.triggerHandler("info", item.description || "");
}
});
$item.on("pointerout", function(){
if($item.is(":visible")){
$menus.triggerHandler("info", "");
$menu_popup.closest(".menu-container").find(".menu-button").focus();
}
});
$item.on("keydown", function(e){
if(e.ctrlKey || e.shiftKey || e.altKey){
return;
}
if(e.keyCode === 13){ // Enter
e.preventDefault();
item_action();
}
});
$menu_popup.on("keydown", function(e){
// TODO: finish implementing this
// * make it work when menu opened with mouse
// * make it focus the item
if(e.ctrlKey || e.shiftKey || e.altKey){
return;
}
if(String.fromCharCode(e.keyCode) === _hotkey(item.item)){
e.preventDefault();
$item.click();
}
});
}
});
return $menu_popup;
}
var this_click_opened_the_menu = false;
$.each(menus, function(menus_key, menu_items){
var $menu_container = $(E("div")).addClass("menu-container").appendTo($menus);
var $menu_button = $(E("div")).addClass("menu-button").appendTo($menu_container);
var $menu_popup = $MenuPopup(menu_items).appendTo($menu_container);
var menu_id = menus_key.replace("&", "").replace(/ /g, "-").toLowerCase();
$menu_button.addClass("" + menu_id + "-menu-button");
if(menu_id == "extras"){
// TODO: refactor shared key string, move to function
if(localStorage["jspaint extras menu visible"] != "true"){
$menu_button.hide();
}
}
$menu_popup.hide();
$menu_button.html(_html(menus_key));
$menu_button.attr("tabIndex", -1)
$menu_container.on("keydown", function(e){
var $focused_item = $menu_popup.find(".menu-item:focus");
switch(e.keyCode){
case 37: // Left
$menu_container.prev().find(".menu-button").trigger("pointerdown");
break;
case 39: // Right
if($focused_item.find(".menu-item-submenu-area:not(:empty)").length){
$focused_item.click();
$(".menu-popup .menu-item").first().focus();
e.preventDefault();
}else{
$menu_container.next().find(".menu-button").trigger("pointerdown");
}
break;
case 40: // Down
if($menu_popup.is(":visible") && $focused_item.length){
var $next = $focused_item.next();
while($next.length && !$next.is(".menu-item")){
$next = $next.next();
}
$next.focus();
}else{
$menu_button.trigger("pointerdown");
$menu_popup.find(".menu-item").first().focus();
}
break;
case 38: // Up
if($menu_popup.is(":visible") && $focused_item.length){
var $prev = $focused_item.prev();
while($prev.length && !$prev.is(".menu-item")){
$prev = $prev.prev();
}
$prev.focus();
}else{
$menu_button.trigger("pointerdown"); // or maybe do nothing?
$menu_popup.find(".menu-item").last().focus();
}
break;
}
});
$G.on("keydown", function(e){
if(e.ctrlKey){ // Ctrl+...
if(e.keyCode !== 17){ // anything but Ctrl
close_menus();
}
return;
}
if(e.altKey){
if(String.fromCharCode(e.keyCode) === _hotkey(menus_key)){
e.preventDefault();
$menu_button.trigger("pointerdown");
}
}
});
$menu_button.on("pointerdown pointerover", function(e){
if(e.type === "pointerover" && !selecting_menus){
return;
}
if(e.type !== "pointerover"){
if(!$menu_button.hasClass("active")){
this_click_opened_the_menu = true;
}
}
close_menus();
$menu_button.focus();
$menu_button.addClass("active");
$menu_popup.show();
$menu_popup.triggerHandler("update");
selecting_menus = true;
$menus.triggerHandler("info", "");
});
$menu_button.on("pointerup", function(e){
if(this_click_opened_the_menu){
this_click_opened_the_menu = false;
return;
}
if($menu_button.hasClass("active")){
close_menus();
}
});
$menu_button.on("release", function(e){
selecting_menus = false;
$menu_button.removeClass("active");
$menu_popup.hide();
$menus.triggerHandler("default-info");
});
});
$G.on("keypress", function(e){
if(e.keyCode === 27){ // Esc
close_menus();
}
});
$G.on("blur", function(e){
// console.log("blur", e.target, document.activeElement);
close_menus();
});
$G.on("pointerdown pointerup", function(e){
if($(e.target).closest(".menus, .menu-popup").length === 0){
// console.log(e.type, "occurred outside of menus (on ", e.target, ") so...");
close_menus();
}
});
return $menus;
}

View file

@ -0,0 +1,233 @@
// TODO: E\("([a-z]+)"\) -> "<$1>" or get rid of jQuery as a dependency
function E(t){
return document.createElement(t);
}
// TODO: remove!
var $G = $(window);
$Window.Z_INDEX = 5;
function $Window(options){
options = options || {};
var $w = $(E("div")).addClass("window").appendTo("body");
$w.$titlebar = $(E("div")).addClass("window-titlebar").appendTo($w);
$w.$title = $(E("span")).addClass("window-title").appendTo($w.$titlebar);
$w.$x = $(E("button")).addClass("window-close-button window-button button").appendTo($w.$titlebar);
$w.$content = $(E("div")).addClass("window-content").appendTo($w);
var $component = options.$component;
if(options.icon){
$w.icon_name = options.icon;
$w.$icon = $Icon(options.icon, TITLEBAR_ICON_SIZE).prependTo($w.$titlebar);
}
if($component){
$w.addClass("component-window");
}
$w.attr("touch-action", "none");
$w.$x.on("click", function(){
$w.close();
});
$w.$x.on("mousedown selectstart", function(e){
e.preventDefault();
});
$w.css({
position: "absolute",
zIndex: $Window.Z_INDEX++
});
$w.bringToFront = function(){
$w.css({
zIndex: $Window.Z_INDEX++
});
};
$w.on("pointerdown", function(){
$w.bringToFront();
});
$w.on("keydown", function(e){
if(e.ctrlKey || e.altKey || e.shiftKey){
return;
}
var $buttons = $w.$content.find("button.button");
var $focused = $(document.activeElement);
var focused_index = $buttons.index($focused);
// console.log(e.keyCode);
switch(e.keyCode){
case 40: // Down
case 39: // Right
if($focused.is("button")){
if(focused_index < $buttons.length - 1){
$buttons.get(focused_index + 1).focus();
e.preventDefault();
}
}
break;
case 38: // Up
case 37: // Left
if($focused.is("button")){
if(focused_index > 0){
$buttons.get(focused_index - 1).focus();
e.preventDefault();
}
}
break;
case 32: // Space
case 13: // Enter (doesn't actually work in chrome because the button gets clicked immediately)
if($focused.is("button")){
$focused.addClass("pressed");
var release = function(){
$focused.removeClass("pressed");
$focused.off("focusout", release);
$(window).off("keyup", keyup);
};
var keyup = function(e){
if(e.keyCode === 32 || e.keyCode === 13){
release();
}
};
$focused.on("focusout", release);
$(window).on("keyup", keyup);
}
break;
case 9: // Tab
// wrap around when tabbing through controls in a window
var $controls = $w.$content.find("input, textarea, select, button, a");
var focused_control_index = $controls.index($focused);
if(focused_control_index === $controls.length - 1){
e.preventDefault();
$controls[0].focus();
}
break;
case 27: // Esc
$w.close();
break;
}
});
// @TODO: restore last focused controls when clicking/mousing down on the window
$w.applyBounds = function(){
$w.css({
left: Math.max(0, Math.min(innerWidth - $w.width(), $w[0].getBoundingClientRect().left)),
top: Math.max(0, Math.min(innerHeight - $w.height(), $w[0].getBoundingClientRect().top)),
});
};
$w.center = function(){
$w.css({
left: (innerWidth - $w.width()) / 2,
top: (innerHeight - $w.height()) / 2,
});
$w.applyBounds();
};
$G.on("resize", $w.applyBounds);
var drag_offset_x, drag_offset_y;
var drag = function(e){
$w.css({
left: e.clientX - drag_offset_x,
top: e.clientY - drag_offset_y,
});
};
$w.$titlebar.attr("touch-action", "none");
$w.$titlebar.on("mousedown selectstart", function(e){
e.preventDefault();
});
$w.$titlebar.on("pointerdown", function(e){
if($(e.target).is("button")){
return;
}
drag_offset_x = e.clientX - $w[0].getBoundingClientRect().left;
drag_offset_y = e.clientY - $w[0].getBoundingClientRect().top;
$G.on("pointermove", drag);
});
$G.on("pointerup", function(e){
$G.off("pointermove", drag);
});
$w.$titlebar.on("dblclick", function(e){
if($component){
$component.dock();
}
});
$w.$Button = function(text, handler){
var $b = $(E("button"))
.appendTo($w.$content)
.addClass("dialogue-button")
.text(text)
.on("click", function(){
if(handler){
handler();
}
$w.close();
});
return $b;
};
$w.title = function(title){
if(title){ // TODO: !== undefined
$w.$title.text(title);
return $w;
}else{
return $w.$title.text();
}
};
$w.close = function(){
var e = $.Event("close");
$w.trigger(e);
if(e.isDefaultPrevented()){
return;
}
if($component){
$component.detach();
}
$w.remove();
$w.closed = true;
};
$w.closed = false;
if(options.title){
$w.title(options.title);
}
if(!$component){
$w.center();
}
return $w;
}
function $FormWindow(title){
var $w = new $Window();
$w.title(title);
$w.$form = $(E("form")).appendTo($w.$content);
$w.$main = $(E("div")).appendTo($w.$form);
$w.$buttons = $(E("div")).appendTo($w.$form).addClass("button-group");
$w.$Button = function(label, action){
var $b = $(E("button")).appendTo($w.$buttons).text(label);
$b.on("click", function(e){
// prevent the form from submitting
// @TODO: instead, prevent the form's submit event
e.preventDefault();
action();
});
// this should really not be needed @TODO
$b.addClass("button dialogue-button");
$b.on("pointerdown", function(){
$b.focus();
});
return $b;
};
return $w;
};

View file

@ -0,0 +1,105 @@
.menus,
.menu-popup,
.window,
.window-titlebar,
.window-title {
cursor: default;
user-select: none;
-o-user-select: none;
-ms-user-select: none;
-moz-user-select: none;
-webkit-user-select: none;
}
.window-titlebar .icon {
vertical-align: bottom;
}
.window-content {
margin: 0;
padding: 0;
}
.desktop-icon {
display: inline-block;
vertical-align: bottom; /* with inline-block, always */
margin: 5px;
text-align: center;
}
.desktop-icon .icon-wrapper {
display: inline-block;
vertical-align: bottom; /* with inline-block, always */
position: relative;
}
.desktop-icon .title {
line-height: 1;
max-height: 2em;
text-overflow: ellipsis;
}
/* Fix dragging things (like windows) over iframes */
.drag iframe {
pointer-events: none;
}
.menus {
display: flex;
flex: 0 0 auto;
}
.menu-container {
position: relative;
}
.menu-popup {
position: absolute;
top: 100%;
left: 0;
z-index: 400000; /* so sub-menu-popups can be visible over the window */
box-sizing: border-box;
}
.menu-popup-table {
border-collapse: collapse;
}
.menu-item {
white-space: nowrap;
}
.menu-hr {
display: block !important;
height: 0;
width: auto;
}
.menu-hotkey {
display: inline !important;
}
.menu-item-checkbox-area,
.menu-item-submenu-area {
min-width: 16px;
}
.menu-item-checkbox-area,
.menu-item-submenu-area {
text-align: center;
}
.window-title {
display: inline-block !important;
margin: 0;
padding: 0;
}
.window-close-button {
display: inline-block !important;
float: right;
width: 13px;
height: 11px;
}
.window-close-button:before {
content: '×';
position: relative;
}
.window-content .button-group {
width: 85px;
}
.window-content .button-group > button {
width: 95%;
padding: 3px 5px;
}
::before, ::after {
pointer-events: none;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 341 B

View file

@ -0,0 +1,232 @@
button {
outline: 0; /* TODO: FIXME: accessibility */
}
.menus *,
.menu-popup * {
font-family: 'Segoe UI', sans-serif;
font-size: 12px;
cursor: default;
}
.menus {
background: #c0c0c0;
}
.menu-container {
margin-bottom: 1px;
}
.menu-button {
padding: 2px 5px;
position: relative;
outline: 0;
}
.menu-button:hover {
box-shadow: 1px 1px 0 white inset, -1px -1px 0 #707070 inset;
}
.menu-button:active,
.menu-button.active {
box-shadow: 1px 1px 0 #707070 inset, -1px -1px 0 white inset;
top: 1px;
left: 1px;
}
.menu-popup:not(.something-else) { /* CSS specifity hack */
display: block;
padding: 2px;
background: #c0c0c0;
border-top: 1px solid #c0c0c0;
border-left: 1px solid #c0c0c0;
border-right: 1px solid #000;
border-bottom: 1px solid #000;
box-shadow: 1px 1px 0 white inset, -1px -1px 0 #707070 inset;
}
.menu-item {
padding: 1px 3px;
margin: 2px;
}
.menu-item[disabled] {
text-shadow: 0.8px 0.8px 0px #fff;
}
.menu-item:focus,
.menu-item.active {
background: #007;
color: #fff;
text-shadow: none;
outline: 0;
}
.menu-item[disabled] {
color: #808080;
}
.menu-hr {
border: 0;
border-top: 1px solid #808080;
border-bottom: 1px solid #ffffff;
margin: 0px 2px;
}
.menu-hotkey {
text-decoration: underline;
}
button.selected {
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACAQMAAABIeJ9nAAAABlBMVEW9vb3///8EwsWUAAAADElEQVQI12NoYHAAAAHEAMFJRSpJAAAAAElFTkSuQmCC) repeat;
}
.window {
background: #c0c0c0;
border-top: 1px solid #dfdfdf;
border-left: 1px solid #dfdfdf;
border-right: 1px solid #000;
border-bottom: 1px solid #000;
padding: 2px;
}
.window:after {
content: '';
pointer-events: none;
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
border-top: 1px solid #fff;
border-left: 1px solid #fff;
border-right: 1px solid #7b7b7b;
border-bottom: 1px solid #7b7b7b;
}
.window > * {
margin: 1px;
}
.window-titlebar {
background: #000080; /* fallback to standard color */
background: -moz-linear-gradient(left, #000080 0%, #1084d0 100%); /* FF3.6+ */
background: -webkit-gradient(linear, left top, right top, color-stop(0%, #000080), color-stop(100%, #1084d0)); /* Chrome, Safari4+ */
background: -webkit-linear-gradient(left, #000080 0%, #1084d0 100%); /* Chrome10+, Safari5.1+ */
background: -o-linear-gradient(left, #000080 0%, #1084d0 100%); /* Opera 11.10+ */
background: -ms-linear-gradient(left, #000080 0%, #1084d0 100%); /* IE10+ */
background: linear-gradient(to right, #000080 0%, #1084d0 100%); /* W3C */
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#000080', endColorstr='#1084d0', GradientType=1 ); /* IE6-9 */
color: white;
font-family: 'Segoe UI', sans-serif;
font-size: 12px;
}
.window-title {
padding-left: 2px;
}
.window-button {
margin: 2px;
}
.window-content {
font-family: Arial, sans-serif;
font-size: 16px;
}
.button-like-border,
button {
padding: 0;
vertical-align: middle;
font-family: Arial, sans-serif;
font-size: 12px;
background: #c0c0c0;
border-top: 1px solid #fff;
border-left: 1px solid #fff;
border-right: 1px solid #000;
border-bottom: 1px solid #000;
position: relative;
}
.button-like-border:after,
button:after {
content: '';
pointer-events: none;
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
border-top: 1px solid #dfdfdf;
border-left: 1px solid #dfdfdf;
border-right: 1px solid #808080;
border-bottom: 1px solid #808080;
}
button:not([disabled]):hover:active,
button.selected {
border-top: 1px solid #000;
border-left: 1px solid #000;
border-right: 1px solid #fff;
border-bottom: 1px solid #fff;
}
button:not([disabled]):hover:active:after,
button.selected:after {
border-top: 1px solid #808080;
border-left: 1px solid #808080;
border-right: 1px solid #dfdfdf;
border-bottom: 1px solid #dfdfdf;
}
/*
* {
image-rendering: crisp-edges;
image-rendering: pixelated;
}
*/
::selection {
background-color: #000080;
color: white;
}
::-webkit-scrollbar,
::-webkit-scrollbar-thumb,
::-webkit-scrollbar-button {
width: 16px;
height: 16px;
}
::-webkit-scrollbar-thumb,
::-webkit-scrollbar-button {
background: #c0c0c0;
border-top: 1px solid #c0c0c0;
border-left: 1px solid #c0c0c0;
border-right: 1px solid #000;
border-bottom: 1px solid #000;
box-shadow: 1px 1px 0 white inset, -1px -1px 0 #707070 inset;
}
::-webkit-scrollbar-button:hover:active {
border: 1px solid #7b7b7b;
box-shadow: none;
}
::-webkit-scrollbar-corner {
background: #c0c0c0;
}
::-webkit-scrollbar-button {
background-position: center;
background-repeat: no-repeat;
}
::-webkit-scrollbar-button {
background-image: url("images/arrows.png");
}
::-webkit-scrollbar-button:horizontal:decrement:disabled {
background-position: calc(-24px + 1px) 1px; /* left */
}
::-webkit-scrollbar-button:horizontal:increment:disabled {
background-position: calc(-36px + 1px) 1px; /* right */
}
::-webkit-scrollbar-button:vertical:decrement:disabled {
background-position: calc(-12px + 1px) 1px; /* up */
}
::-webkit-scrollbar-button:vertical:increment:disabled {
background-position: calc(-0px + 1px) 1px; /* down */
}
::-webkit-scrollbar-button:horizontal:decrement {
background-position: calc(-24px + 1px) calc(-12px + 1px); /* left */
}
::-webkit-scrollbar-button:horizontal:increment {
background-position: calc(-36px + 1px) calc(-12px + 1px); /* right */
}
::-webkit-scrollbar-button:vertical:decrement {
background-position: calc(-12px + 1px) calc(-12px + 1px); /* up */
}
::-webkit-scrollbar-button:vertical:increment {
background-position: calc(-0px + 1px) calc(-12px + 1px); /* down */
}
::-webkit-scrollbar {
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACAQMAAABIeJ9nAAAABlBMVEW9vb3///8EwsWUAAAADElEQVQI12NoYHAAAAHEAMFJRSpJAAAAAElFTkSuQmCC) repeat;
}
::-webkit-scrollbar-track-piece:active {
background: black;
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,114 @@
var audio = new Audio;
var base_title = "WinAMP v0.2a";
var file_name;
var blob_url;
audio.onended = stop;
function stop() {
URL.revokeObjectURL(blob_url);
audio.pause();
audio.src = "";
audio.load();
audio.currentTime = 0;
$window.title(base_title);
$(".menu-popup").triggerHandler("update");
};
function play_from_file(file) {
blob_url = URL.createObjectURL(file);
audio.src = blob_url;
audio.play();
file_name = file.name;
$window.title(base_title + " - " + file_name);
};
var show_nothingness = true;
var menus = {
"&MP3": [
{
item: "&Play...",
action: () => {
stop();
$("<input type='file' accept='audio/mp3'>").click().change(function (e) {
if (this.files[0]) {
play_from_file(this.files[0]);
}
});
},
},
{
item: "&Stop",
action: stop,
enabled: () => audio.currentTime > 0,
},
{
item: "P&ause",
action: () => {
audio.pause();
$window.title(base_title + " - " + file_name + " - Paused");
},
enabled: () => !audio.paused,
},
{
item: "&Unpause",
action: () => {
audio.play();
$window.title(base_title + " - " + file_name);
},
enabled: () => audio.currentTime > 0 && audio.paused,
},
$MenuBar.DIVIDER,
{
item: "E&xit",
action: () => {
},
enabled: false,
}
],
"&Help": [
{
item: "&About...",
action: () => {
var $about_window = $Window({ title: "About WinAMP" });
$about_window.$content.html(`
WinAMP v0.2a<br>
Compiled on Apr 21 1997 at 22:42:06<br>
Copyright (C) 1997, Nullsoft<br>
This program is freeware -- for more information,<br>
please read the included README.TXT
`);
$about_window.$Button("OK", () => { });
}
},
]
};
var $menubar = new $MenuBar(menus);
TITLEBAR_ICON_SIZE = 16;
function $Icon(path, size) {
var $img = $("<img class='icon'/>");
$img.attr({
draggable: false,
src: path,
width: size,
height: size,
});
return $img;
}
var $window = new $Window({ title: base_title, icon: "winamp-0.2.png" });
$window.$content.append($menubar);
$window.width(220);
$window.on("close", (event) => {
event.preventDefault();
});
$window.center();

Binary file not shown.

After

Width:  |  Height:  |  Size: 427 B