mirror of
https://github.com/captbaritone/webamp.git
synced 2026-01-23 02:15:01 +00:00
Merge 0820b4a281 into 2fe3235d51
This commit is contained in:
commit
d4bfe4a11e
13 changed files with 2980 additions and 0 deletions
358
experiments/webcramp/classic.css
Normal file
358
experiments/webcramp/classic.css
Normal 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;
|
||||
}
|
||||
|
||||
BIN
experiments/webcramp/favicon.ico
Normal file
BIN
experiments/webcramp/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 766 B |
30
experiments/webcramp/index.html
Normal file
30
experiments/webcramp/index.html
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Webcramp • 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 • 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>
|
||||
265
experiments/webcramp/layout.css
Normal file
265
experiments/webcramp/layout.css
Normal 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;
|
||||
}
|
||||
4
experiments/webcramp/lib/jquery.min.js
vendored
Normal file
4
experiments/webcramp/lib/jquery.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
309
experiments/webcramp/lib/os-gui/$MenuBar.js
Normal file
309
experiments/webcramp/lib/os-gui/$MenuBar.js
Normal 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;
|
||||
|
||||
}
|
||||
233
experiments/webcramp/lib/os-gui/$Window.js
Normal file
233
experiments/webcramp/lib/os-gui/$Window.js
Normal 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;
|
||||
};
|
||||
105
experiments/webcramp/lib/os-gui/layout.css
Normal file
105
experiments/webcramp/lib/os-gui/layout.css
Normal 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;
|
||||
}
|
||||
BIN
experiments/webcramp/lib/os-gui/theme/images/arrows.png
Normal file
BIN
experiments/webcramp/lib/os-gui/theme/images/arrows.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 341 B |
232
experiments/webcramp/lib/os-gui/theme/windows-98.css
Normal file
232
experiments/webcramp/lib/os-gui/theme/windows-98.css
Normal 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;
|
||||
}
|
||||
1330
experiments/webcramp/lib/pep.js
Normal file
1330
experiments/webcramp/lib/pep.js
Normal file
File diff suppressed because it is too large
Load diff
114
experiments/webcramp/src/app.js
Normal file
114
experiments/webcramp/src/app.js
Normal 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();
|
||||
BIN
experiments/webcramp/winamp-0.2.png
Normal file
BIN
experiments/webcramp/winamp-0.2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 427 B |
Loading…
Add table
Add a link
Reference in a new issue