Compare commits

...

6 Commits

Author SHA1 Message Date
modeco80
41a70632b8 eijah you fucker gogole. 2024-03-25 20:19:18 -04:00
modeco80
331b5efd87 add color styling to vote buttons 2024-03-25 20:08:36 -04:00
modeco80
239df1f510 whoops 2024-03-25 10:13:20 -04:00
modeco80
e3f0ec56a2 Port modals to HTML dialog
Places that used alert() now use a custom dialog. Also the change username modal has been remade.

Also styles some elements. These aren't final, more just to actually style them.

I would like to use jsx or osmething instead of stuffing these in the html, especially since it would allow us to single-source-of-truth i18n (removing our second-source-of-truth in the i18n module that statically replace stuff) in a lot more places. and make other code a lot less iffy.

we do NOT need react or some shit if we do that. just nano-jsx, or something even simpler is more than good enough, and should keep us in our "100 kb smaller" track
2024-03-25 10:12:01 -04:00
modeco80
b5256da3a5 WIP: Rip out bootstrap
Things that have been done:
- Remove bootstrap
- Switch to a version of the computernewb css ported to scss
- (ONLY FOR NOW!!) rip out modals from html and typescript
- Change the vm hash system so we use the "hashchange" listener, meaning we don't need a event on the list item anymore. Additionally this means that you can just switch VMs just by changing the hash if you know the ID. This is essentially how the cvm3 webapp works.

There are still a ton of things that need to be done:

- Fixing general styling
- Fix card styling, it's a bit broken at the moment
- Adding styling for the navbar so it looks correct
- Adding styling for HTML elements the original computernewb homepage CSS didn't have
- Reimplementing modals using <dialog> (better yet, implementing prompt and confirmation dialogs AS modals)
- Fixing dropdowns
2024-03-24 08:32:32 -04:00
modeco80
ed46f171f5 meh (we aren't a library) 2024-03-24 05:08:40 -04:00
16 changed files with 973 additions and 557 deletions

1
.npmrc
View File

@ -1 +0,0 @@
package-lock=false

View File

@ -1,4 +1,4 @@
dist
*.md
*.html
#*.html
*.css

View File

@ -4,7 +4,7 @@
"description": "kill me",
"private": true,
"scripts": {
"build": "parcel build --no-source-maps --dist-dir dist --public-url '.' src/html/index.html",
"build": "parcel build --dist-dir dist --public-url '.' src/html/index.html",
"serve": "parcel src/html/index.html",
"test": "jest",
"clean": "run-script-os",
@ -16,7 +16,6 @@
"dependencies": {
"@hcaptcha/types": "^1.0.3",
"@popperjs/core": "^2.11.8",
"bootstrap": "^5.3.2",
"nanoevents": "^7.0.1",
"simple-keyboard": "^3.7.53"
},
@ -24,6 +23,7 @@
"@parcel/watcher": "~2.1.0"
},
"devDependencies": {
"@parcel/transformer-sass": "2.12.0",
"@types/bootstrap": "^5.2.10",
"@types/jest": "^29.5.12",
"jest": "^29.7.0",

31
src/css/_colors-dark.scss Normal file
View File

@ -0,0 +1,31 @@
// TODO: Maybe we *should* use CSS variables, so the user can pick any theme
// and maybe put in hooks for that.. but for now this is fine!
// (this really should be called _style-dark @ this point..)
$root-bg-color: rgb(32, 32, 32);
$root-fg-color: rgb(180, 180, 180);
// Elements
$input-bg-color: rgb(51, 51, 51);
// Resist the urge to make these radioactive. I made them kinda grungy
// on purpose, and it seems to be okay.
$button-bg-color: rgb(51, 107, 145);
$button-red-bg-color: rgb(159, 51, 51);
$button-green-bg-color: rgb(61, 151, 43);
// Cards
$card-background-color: rgb(48, 48, 48);
$card-border-color: rgb(58, 58, 58);
$card-box-shadow-color: rgb(0, 0, 0);
/* I have to do different syntax here because you can only do divisors with rgb() ahahah GRRRR */
$card-image-background-color: #ffffff10;
$link-color: rgb(100, 140, 200);
$link-visited-color: rgb(160, 140, 200);
// Root mixin
.root {
background-color: $root-bg-color;
color: $root-fg-color;
font-family: 'Segoe UI', Tahoma, Cantarell, sans-serif;
}

62
src/css/cards.scss Normal file
View File

@ -0,0 +1,62 @@
// module for cards
@import 'colors-dark';
.cards {
display: grid;
list-style: none;
padding: unset;
gap: 1em;
grid-template-columns: repeat(auto-fit, minmax(0, 20em));
/* I wish I could do this in certain situations.
* Card sections that need to wrap should be centered, but ones that fit on one line could be left-aligned. */
/* aah i keep enabling and disabling this i don't know what to dooooo */
/* justify-content: center; */
font-size: 0.875rem;
}
.cards > li > * {
height: 100%;
}
.cards .card {
display: block;
line-height: 1.5;
overflow: auto;
text-decoration: inherit;
color: inherit;
background-color: $root-fg-color $card-background-color;
border: 4px solid $root-fg-color $card-border-color;
border-radius: 4px;
box-shadow: 0px 0px 1px 0px $root-fg-color $card-box-shadow-color;
}
.cards .card:hover {
box-shadow: 0px 0px 4px 0px $root-fg-color $card-box-shadow-color;
/* hmm, maybe get a better indication because this isn't the most evident or the best looking */
outline: 2px solid $root-fg-color $card-border-color;
}
.cards .card .card-image {
display: block;
width: 100%;
background-color: $root-fg-color $card-image-background-color;
object-fit: contain;
box-sizing: border-box;
margin: auto;
/* this'd be cool if this perfectly fit a square, but then the cards are a bit bigger than i'd like them to be. gah */
max-height: 15em;
}
.cards .card .card-body {
padding: 1em;
}
.cards .card .card-body > * {
margin: unset;
}
.cards .card .card-heading {
font-size: 1.1em;
font-weight: bold;
margin-bottom: 0.25em;
}

44
src/css/dialog.scss Normal file
View File

@ -0,0 +1,44 @@
// for our new <dialog> based modals
@import 'colors-dark';
/* a bit of a "lazy" curve. should probably be in a shared file */
@keyframes dialogFadeIn {
0% {
opacity: 0;
}
35% {
opacity: 0.75;
}
100% {
opacity: 1;
}
}
// don't really know how i can animate "out",
// at least without hacks, but oh well
//
// this will also probably be snappier than the mess that is
// the bootstrap modals..
dialog::backdrop {
animation: dialogFadeIn 0.3s;
background-color: rgba(0, 0, 0, 0.65);
}
dialog {
@extend .root;
color: inherit;
background-color: $root-fg-color $card-background-color;
border: none;
border-radius: 4px;
box-shadow: 0px 0px 8px 0px $root-fg-color $card-box-shadow-color;
}
:modal {
animation: dialogFadeIn 0.2s;
}
.dialog-alignright {
float: right;
}

58
src/css/elements.scss Normal file
View File

@ -0,0 +1,58 @@
// Module for element styling
// THIS IS NOT DONE!
@import 'colors-dark';
.button-default {
@extend .root;
background-color: $button-bg-color !important;
border-radius: 6px;
border-width: 0;
color: #ffffff !important;
cursor: pointer;
display: inline-block;
font-size: 14px;
font-weight: 500;
line-height: 20px;
list-style: none;
margin: 0;
padding: 10px 12px;
text-align: center;
transition: all 200ms;
vertical-align: baseline;
white-space: nowrap;
user-select: none;
-webkit-user-select: none;
touch-action: manipulation;
}
button {
@extend .button-default;
}
.button-red {
background-color: $button-red-bg-color !important;
}
.button-green {
background-color: $button-green-bg-color !important;
}
input[type='text'] {
padding: 0.1em;
height: 1.75em;
//position: relative;
outline: none;
border: 1px solid rgba(0, 0, 0, 0.15);
background-color: $input-bg-color;
font-size: 16px;
color: #ffffff;
}

339
src/css/main.scss Normal file
View File

@ -0,0 +1,339 @@
// TODO!!!!!!
@import 'colors-dark';
// modules
@import 'elements';
@import 'cards';
@import 'dialog';
html,
body {
@extend .root;
margin: unset;
}
.main {
margin: auto;
overflow-wrap: break-word;
}
/** Prettier links **/
a {
color: $link-color;
text-decoration: none;
}
a:visited {
color: $link-visited-color;
text-decoration: none;
}
// ze webapp
#vmview {
display: none;
}
/*.vmtile {
text-decoration: none;
color: #FFFFFF;
font-size: 16pt;
border: 2px solid #575757;
border-radius: 15px;
height: fit-content;
width: fit-content;
display: block;
padding: 4px;
}*/
#vmDisplay,
#btns {
margin-left: auto;
margin-right: auto;
text-align: center;
display: block;
margin-bottom: 10px;
}
#vmlist > div.row > div {
padding-bottom: 10px;
}
#vmlist div.col-md-3 > div.card:hover {
cursor: pointer;
border-color: rgb(8, 121, 250);
}
.vmtile > img {
margin-bottom: 2px;
}
.chat-table,
.username-table {
overflow-y: auto;
border: 1px solid #575757;
}
.chat-table {
height: 30vh;
}
.username-table {
max-height: 30vh;
}
.username-table > table > thead {
position: sticky;
top: 0;
}
#turnstatus {
text-align: center;
}
#voteResetPanel {
text-align: center;
}
.focused {
box-shadow: 0 0 9px 0 rgba(45, 213, 255, 0.75);
-moz-box-shadow: 0 0 9px 0 rgba(45, 213, 255, 0.75);
-webkit-box-shadow: 0 0 9px 0 rgba(45, 213, 255, 0.75);
}
.waiting {
box-shadow: 0 0 9px 0 rgba(242, 255, 63, 0.75);
-moz-box-shadow: 0 0 9px 0 rgba(242, 255, 63, 0.75);
-webkit-box-shadow: 0 0 9px 0 rgba(242, 255, 63, 0.75);
}
#staffbtns {
display: none;
}
#staffbtns > button {
display: none;
}
#qemuMonitorOutput {
height: 180px;
}
#xssCheckboxContainer {
display: none;
}
#forceVotePanel {
display: none;
}
tr.user-admin > td,
.chat-username-admin,
.username-admin {
color: #ff0000 !important;
}
tr.user-moderator > td,
.chat-username-moderator,
.username-moderator {
color: #00ff00 !important;
}
tr.user-turn > td {
background-color: #cfe2ff !important;
--bs-table-bg-state: #cfe2ff !important;
color: #000000;
--bs-table-color: #000000;
}
tr.user-turn:hover,
tr.user-turn > td:hover {
background-color: #bacbe6 !important;
--bs-table-bg-state: #bacbe6 !important;
}
tr.user-waiting > td {
background-color: #fff3cd !important;
--bs-table-bg-state: #fff3cd !important;
color: #000000;
--bs-table-color: #000000;
}
.tr.user-waiting:hover,
tr.user-waiting > td:hover {
background-color: #ece1be !important;
--bs-table-bg-state: #ece1be !important;
}
.user-current {
font-style: italic;
}
/* Start OSK */
.osk-container {
display: flex;
justify-content: center;
width: 1024px;
margin: 0 auto;
margin-bottom: 10px;
border-radius: 5px;
}
.simple-keyboard.hg-theme-default {
display: inline-block;
}
.osk-main.simple-keyboard {
width: 640px;
min-width: 640px;
background: none;
}
.osk-main.simple-keyboard .hg-row:first-child {
margin-bottom: 8.51px;
}
.osk-arrows.simple-keyboard {
align-self: flex-end;
background: none;
}
.simple-keyboard .hg-button.selectedButton {
background: rgba(5, 25, 70, 0.53);
color: white;
}
.simple-keyboard .hg-button.emptySpace {
pointer-events: none;
background: none;
border: none;
box-shadow: none;
}
.osk-arrows .hg-row {
justify-content: center;
}
.osk-arrows .hg-button {
width: 50px;
flex-grow: 0 !important;
justify-content: center !important;
display: flex !important;
align-items: center !important;
}
.controlArrows {
display: flex;
align-items: center;
justify-content: space-between;
flex-flow: column;
}
.osk-control.simple-keyboard {
background: none;
}
.osk-control.simple-keyboard .hg-row:first-child {
margin-bottom: 8.51px;
}
.osk-control .hg-button {
width: 50px;
flex-grow: 0 !important;
justify-content: center !important;
display: flex !important;
align-items: center !important;
}
.numPad {
display: flex;
align-items: flex-end;
}
.osk-numpad.simple-keyboard {
background: none;
}
.osk-numpad.simple-keyboard {
width: 160px;
}
.osk-numpad.simple-keyboard .hg-button {
width: 50px;
justify-content: center;
display: flex;
align-items: center;
}
.osk-numpadEnd.simple-keyboard {
width: 50px;
background: none;
margin: 0;
padding: 5px 5px 5px 0;
}
.osk-numpadEnd.simple-keyboard .hg-button {
align-items: center;
justify-content: center;
display: flex;
}
.osk-numpadEnd .hg-button.hg-standardBtn.hg-button-plus {
height: 85px;
}
.osk-numpadEnd.simple-keyboard .hg-button.hg-button-enter {
height: 85px;
}
.simple-keyboard.hg-theme-default .hg-button.hg-selectedButton {
background: rgba(5, 25, 70, 0.53);
color: white;
}
.hg-button.hg-functionBtn.hg-button-space {
width: 350px;
}
/*
Theme: cvmDark
*/
.simple-keyboard.cvmDark .hg-button {
border-bottom: none;
background: rgba(0, 0, 0, 0.5);
color: white;
}
.simple-keyboard.cvmDark .hg-button:active {
background: #1c4995;
color: white;
}
#root .simple-keyboard.cvmDark + .simple-keyboard-preview {
background: #1c4995;
}
/*
Theme: cvmDisabled
*/
.simple-keyboard.cvmDisabled .hg-button {
border-bottom: none;
pointer-events: none;
background: gray;
color: white;
}
/* End OSK */
#badPasswordAlert {
display: none;
}
/* VM0 Blur */
a[data-cvm-node='vm0b0t'] {
img {
filter: blur(40px) !important;
}
}
// shitty bootstrap polyfill
.d-none {
display: none;
}

View File

@ -1,292 +0,0 @@
#vmview {
display: none;
}
/*.vmtile {
text-decoration: none;
color: #FFFFFF;
font-size: 16pt;
border: 2px solid #575757;
border-radius: 15px;
height: fit-content;
width: fit-content;
display: block;
padding: 4px;
}*/
#vmDisplay, #btns {
margin-left: auto;
margin-right: auto;
text-align: center;
display: block;
margin-bottom: 10px;
}
#vmlist > div.row > div {
padding-bottom: 10px;
}
#vmlist div.col-md-3 > div.card:hover {
cursor: pointer;
border-color: rgb(8, 121, 250);
}
.vmtile > img {
margin-bottom: 2px;
}
.chat-table, .username-table {
overflow-y: auto;
border: 1px solid #575757;
}
.chat-table {
height: 30vh;
}
.username-table {
max-height: 30vh;
}
.username-table > table > thead {
position: sticky;
top: 0;
}
#turnstatus {
text-align: center;
}
#voteResetPanel {
text-align: center;
}
.focused {
box-shadow: 0 0 9px 0 rgba(45,213,255,.75);
-moz-box-shadow: 0 0 9px 0 rgba(45,213,255,.75);
-webkit-box-shadow: 0 0 9px 0 rgba(45,213,255,.75)
}
.waiting {
box-shadow: 0 0 9px 0 rgba(242,255,63,.75);
-moz-box-shadow: 0 0 9px 0 rgba(242,255,63,.75);
-webkit-box-shadow: 0 0 9px 0 rgba(242,255,63,.75)
}
#staffbtns {
display: none;
}
#staffbtns > button {
display: none;
}
#qemuMonitorOutput {
height: 180px;
}
#xssCheckboxContainer {
display: none;
}
#forceVotePanel {
display: none;
}
tr.user-admin > td, .chat-username-admin, .username-admin {
color: #FF0000 !important;
}
tr.user-moderator > td, .chat-username-moderator, .username-moderator {
color: #00FF00 !important;
}
tr.user-turn > td {
background-color: #cfe2ff !important;
--bs-table-bg-state: #cfe2ff !important;
color: #000000;
--bs-table-color: #000000;
}
tr.user-turn:hover, tr.user-turn > td:hover {
background-color: #bacbe6 !important;
--bs-table-bg-state: #bacbe6 !important;
}
tr.user-waiting > td {
background-color: #fff3cd !important;
--bs-table-bg-state: #fff3cd !important;
color: #000000;
--bs-table-color: #000000;
}
.tr.user-waiting:hover, tr.user-waiting > td:hover {
background-color: #ece1be !important;
--bs-table-bg-state: #ece1be !important;
}
.user-current {
font-style: italic;
}
/* Start OSK */
.osk-container {
display: flex;
justify-content: center;
width: 1024px;
margin: 0 auto;
margin-bottom: 10px;
border-radius: 5px;
}
.simple-keyboard.hg-theme-default {
display: inline-block;
}
.osk-main.simple-keyboard {
width: 640px;
min-width: 640px;
background: none;
}
.osk-main.simple-keyboard .hg-row:first-child {
margin-bottom: 8.51px;
}
.osk-arrows.simple-keyboard {
align-self: flex-end;
background: none;
}
.simple-keyboard .hg-button.selectedButton {
background: rgba(5, 25, 70, 0.53);
color: white;
}
.simple-keyboard .hg-button.emptySpace {
pointer-events: none;
background: none;
border: none;
box-shadow: none;
}
.osk-arrows .hg-row {
justify-content: center;
}
.osk-arrows .hg-button {
width: 50px;
flex-grow: 0 !important;
justify-content: center !important;
display: flex !important;
align-items: center !important;
}
.controlArrows {
display: flex;
align-items: center;
justify-content: space-between;
flex-flow: column;
}
.osk-control.simple-keyboard {
background: none;
}
.osk-control.simple-keyboard .hg-row:first-child {
margin-bottom: 8.51px;
}
.osk-control .hg-button {
width: 50px;
flex-grow: 0 !important;
justify-content: center !important;
display: flex !important;
align-items: center !important;
}
.numPad {
display: flex;
align-items: flex-end;
}
.osk-numpad.simple-keyboard {
background: none;
}
.osk-numpad.simple-keyboard {
width: 160px;
}
.osk-numpad.simple-keyboard .hg-button {
width: 50px;
justify-content: center;
display: flex;
align-items: center;
}
.osk-numpadEnd.simple-keyboard {
width: 50px;
background: none;
margin: 0;
padding: 5px 5px 5px 0;
}
.osk-numpadEnd.simple-keyboard .hg-button {
align-items: center;
justify-content: center;
display: flex;
}
.osk-numpadEnd .hg-button.hg-standardBtn.hg-button-plus {
height: 85px;
}
.osk-numpadEnd.simple-keyboard .hg-button.hg-button-enter {
height: 85px;
}
.simple-keyboard.hg-theme-default .hg-button.hg-selectedButton {
background: rgba(5, 25, 70, 0.53);
color: white;
}
.hg-button.hg-functionBtn.hg-button-space {
width: 350px;
}
/*
Theme: cvmDark
*/
.simple-keyboard.cvmDark .hg-button {
border-bottom: none;
background: rgba(0, 0, 0, 0.5);
color: white;
}
.simple-keyboard.cvmDark .hg-button:active {
background: #1c4995;
color: white;
}
#root .simple-keyboard.cvmDark + .simple-keyboard-preview {
background: #1c4995;
}
/*
Theme: cvmDisabled
*/
.simple-keyboard.cvmDisabled .hg-button {
border-bottom: none;
pointer-events: none;
background: gray;
color: white;
}
/* End OSK */
#badPasswordAlert {
display: none;
}
/* VM0 Blur */
div[data-cvm-node=vm0b0t] {
img {
filter:blur(40px)!important;
}
}

View File

@ -1,221 +1,158 @@
<!DOCTYPE HTML>
<!doctype html>
<html prefix="og: https://ogp.me/ns#">
<head>
<title>CollabVM</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta charset="utf-8"/>
<link href="../css/style.css" rel="stylesheet" type="text/css"/>
<link href="../../node_modules/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet" type="text/css"/>
<script src="https://kit.fontawesome.com/7add23c1ae.js" crossorigin="anonymous"></script>
<link rel="icon" href="../assets/favicon.ico">
<meta name="description" content="A website that lets you take turns controlling online virtual machines with complete strangers!"/>
<!-- Opengraph shit -->
<meta property="og:type" content="website"/>
<meta property="og:title" content="CollabVM"/>
<meta property="og:url" content="https://computernewb.com/collab-vm/"/>
<meta property="og:description" content="A website that lets you take turns controlling online virtual machines with complete strangers!"/>
<meta property="og:site_name" content="Computernewb"/>
<meta property="og:image" content="https://computernewb.com/collab-vm/desktop.png"/>
</head>
<body class="bg-dark">
<div class="modal fade" id="qemuMonitorModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content bg-dark text-light">
<div class="modal-header">
<h5 class="modal-title">QEMU Monitor</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<textarea id="qemuMonitorOutput" readonly="" class="form-control bg-dark text-light"></textarea>
<div class="input-group">
<input type="text" id="qemuMonitorInput" class="form-control bg-dark text-light" placeholder="Command"/>
<button class="btn btn-outline-secondary btn-primary text-light" type="button" id="qemuMonitorSendBtn">Send</button>
</div>
</div>
</div>
</div>
</div>
<div class="modal fade" id="welcomeModal" tabindex="-1" data-bs-backdrop="static" data-bs-keyboard="false" aria-labelledby="welcomeModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content bg-dark text-light">
<div class="modal-header">
<h1>Welcome to CollabVM</h1>
</div>
<div class="modal-body">
<p>Before continuing, please familiarize yourself with our rules:</p>
<h3>R1. Don't break the law.</h3>
Do not use CollabVM or CollabVM's network to violate United States federal law, New York state law, or international law. If CollabVM becomes aware a crime has been committed through its service, you will be immediately banned, and your activities may be reported to the authorities if necessary.<br><br>CollabVM is required by law to notify law enforcement agencies if it becomes aware of the presence of child pornography on, or being transmitted through its network.<br><br>COPPA is also enforced, please do not use CollabVM if you are under the age of 13 years old.
<h3>R2. No running DoS/DDoS tools.</h3>
Do not use CollabVM to DoS/DDoS an indivdiual, business, company, or anyone else.
<h3>R3. No spam distribution.</h3>
Do not spam any emails using this service or push spam in general.
<h3>R4. Do not abuse any exploits.</h3>
Do not abuse any exploits, additionally if you see someone abusing exploits or you need to report one, please contact me at: computernewbab@gmail.com
<h3>R5. Don't impersonate other users.</h3>
Do not impersonate other members of CollabVM. If caught, you'll be temporarily disconnected, and banned if necessary.
<h3>R6. One vote per person.</h3>
Do not use any methods or tools to bypass the vote restriction. Only one vote per person is allowed, no matter what. Anybody who is caught doing this will be banned.
<h3>R7. No Remote Administration Tools.</h3>
Do not use any remote administration tools (ex: DarkComet, NanoCore, Anydesk, TeamViewer, Orcus, etc.)
<h3>R8. No bypassing CollabNet.</h3>
Do not attempt to bypass the blocking provided by CollabNet, especially if it is being used to break Rule 1, Rule 2, or Rule 7 (or run stupid over-used things).
<h3>R9. No performing destructive actions constantly.</h3>
Any user may not destroy the VM (rendering it unusable constantly), install/reinstall the operating system (except on VM7 or VM8), or run bots that do such. This includes bots that spam massive amounts of keyboard/mouse input ("kitting").
<h3>R10. No Cryptomining</h3>
Attempting to mine cryptocurrency on the VMs will result in a kick, and then a permanent ban if you keep attempting. Besides, it's not like you're gonna make any money off it.
<h3>NSFW Warning</h3>
Please note that NSFW content is allowed on our anarchy VM (VM0b0t), and is viewed regularly. In addition, while we give a good effort to keep NSFW off the main VMs, people will occasionally slip it through.
</div>
<div class="modal-footer">
<button type="button" id="welcomeModalDismiss" class="btn btn-primary" data-bs-dismiss="modal">Understood</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="loginModal" tabindex="-1" aria-labelledby="loginModalLabel" aria-hidden="true">
<div class="modal-dialog modal-md">
<div class="modal-content bg-dark text-light">
<div class="modal-header">
<h5 class="modal-title">Login</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="alert alert-danger alert-dismissible" id="badPasswordAlert" role="alert">
Incorrect password.
<button type="button" class="btn-close" aria-label="Close" id="incorrectPasswordDismissBtn"></button>
</div>
<div class="input-group">
<input type="hidden" name="username" id="adminInputVMID"/>
<span class="input-group-text bg-dark text-light">Password</span>
<input id="adminPassword" type="password" class="form-control bg-dark text-light" placeholder="Password" name="password"/>
</div>
<head>
<title>CollabVM</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta charset="utf-8" />
<link href="../css/main.scss" rel="stylesheet" />
<script src="https://kit.fontawesome.com/7add23c1ae.js" crossorigin="anonymous"></script>
<link rel="icon" href="../assets/favicon.ico" />
<meta name="description" content="A website that lets you take turns controlling online virtual machines with complete strangers!" />
<!-- Opengraph shit -->
<meta property="og:type" content="website" />
<meta property="og:title" content="CollabVM" />
<meta property="og:url" content="https://computernewb.com/collab-vm/" />
<meta property="og:description" content="A website that lets you take turns controlling online virtual machines with complete strangers!" />
<meta property="og:site_name" content="Computernewb" />
<meta property="og:image" content="https://computernewb.com/collab-vm/desktop.png" />
</head>
<body class="main">
<!-- Needed for the new modal API, this is where all the modals mount to
I know I could probably create it dynamically but I just want it to work OK-->
<div id="modalRoot">
<!-- TEMP! these will be created in js or jsx -->
</div>
<div class="modal-footer">
<button type="button" id="loginButton" class="btn btn-primary">Login</button>
<dialog id="changeUsernameDialog">
<form method="dialog">
<label>Enter a new username, or leave blank to assign yourself a guest username.</label><br />
<section>
<input id="usernameInput" type="text" placeholder="A username, or leave blank." />
</section>
<div class="dialog-alignright">
<button id="okButton" value="" class="button-green">OK</button>
<button id="cancelButton" class="button-red">Cancel</button>
</div>
</form>
</dialog>
<dialog id="alertDialog">
<form method="dialog">
<label id="alertMessage"></label>
<div class="dialog-alignright">
<button id="okButton" value="" class="button-green">OK</button>
</div>
</form>
</dialog>
</div>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container-fluid">
<a class="navbar-brand" href="#"><span id="siteNameText"></span></a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item">
<a id="homeBtn" href="#" class="nav-link active" aria-current="page"><i class="fa-solid fa-house"></i> <span id="homeBtnText"></span></a>
</li>
<li class="nav-item">
<a href="https://computernewb.com/collab-vm/faq/" class="nav-link"><i class="fa-solid fa-circle-question"></i> <span id="faqBtnText"></span></a>
</li>
<li class="nav-item">
<a href="https://computernewb.com/collab-vm/rules" class="nav-link"><i class="fa-solid fa-clipboard-check"></i> <span id="rulesBtnText"></span></a>
</li>
<li class="nav-item">
<a href="https://discord.gg/a4kqb4mGyX" class="nav-link"><i class="fa-brands fa-discord"></i> Discord</a>
</li>
<li class="nav-item">
<a href="https://reddit.com/r/collabvm" class="nav-link"><i class="fa-brands fa-reddit"></i> Subreddit</a>
</li>
<li class="nav-item">
<a rel="me" class="nav-link" href="https://fedi.computernewb.com/@collabvm"><i class="fa-brands fa-mastodon"></i> Mastodon</a>
</li>
<li class="nav-item">
<a href="https://computernewb.com/collab-vm/user-vm" class="nav-link"><i class="fa-solid fa-user"></i> UserVM</a>
</li>
</ul>
</div>
</div>
</nav>
<ul class="cards" id="vmlist"></ul>
<div class="container-fluid" id="vmview">
<div id="vmDisplay"></div>
<p id="turnstatus" class="text-light"></p>
<div id="voteResetPanel" class="bg-dark text-light" style="display: none">
<span id="voteResetHeaderText"></span><br />
<button class="button-green" id="voteYesBtn"><i class="fa-solid fa-check"></i> <span id="voteYesBtnText"></span><span class="badge bg-secondary" id="voteYesLabel"></span></button>
<button class="button-red" id="voteNoBtn"><i class="fa-solid fa-ban"></i> <span id="voteNoBtnText"></span><span class="badge bg-secondary" id="voteNoLabel"></span></button><br />
<span id="voteTimeText"></span>
<div id="forceVotePanel">
<button class="button-green" id="forceVoteYesBtn"><i class="fa-solid fa-check"></i> <span id="passVoteBtnText"></span></button>
<button class="button-red" id="forceVoteNoBtn"><i class="fa-solid fa-ban"></i> <span id="cancelVoteBtnText"></span></button>
</div>
</div>
<div id="btns">
<button class="btn btn-secondary" id="takeTurnBtn"><i class="fa-solid fa-computer-mouse"></i> <span id="turnBtnText"></span></button>
<button class="btn btn-secondary" id="oskBtn"><i class="fa-solid fa-keyboard"></i> Keyboard</button>
<button class="btn btn-secondary" id="changeUsernameBtn"><i class="fa-solid fa-signature"></i> <span id="changeUsernameBtnText"></span></button>
<button class="btn btn-secondary" id="voteResetButton"><i class="fa-solid fa-rotate-left"></i> <span id="voteForResetBtnText"></span></button>
<button class="btn btn-secondary" id="screenshotButton"><i class="fa-solid fa-camera"></i> <span id="screenshotBtnText"></span></button>
<button class="btn btn-secondary" id="ctrlAltDelBtn"><i class="fa-solid fa-gear"></i> Ctrl+Alt+Del</button>
<div id="staffbtns">
<button class="btn btn-secondary" id="restoreBtn"><i class="fa-solid fa-rotate-left"></i> Restore</button>
<button class="btn btn-secondary" id="rebootBtn"><i class="fa-solid fa-power-off"></i> Reboot</button>
<button class="btn btn-secondary" id="clearQueueBtn"><i class="fa-solid fa-eraser"></i> Clear Turn Queue</button>
<button class="btn btn-secondary" id="bypassTurnBtn"><i class="fa-solid fa-forward"></i> Bypass Turn</button>
<button class="btn btn-secondary" id="endTurnBtn"><i class="fa-solid fa-ban"></i> <span id="endTurnBtnText"></span></button>
<button class="btn btn-secondary" id="indefTurnBtn"><i class="fa-solid fa-infinity"></i> Indefinite Turn</button>
<button class="btn btn-secondary" id="qemuMonitorBtn" data-bs-toggle="modal" data-bs-target="#qemuMonitorModal"><i class="fa-solid fa-terminal"></i> QEMU Monitor</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="hcaptchaModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content bg-dark text-light">
<div class="modal-body">
<div id="captcha-box"></div>
</div>
</div>
</div>
</div>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container-fluid">
<a class="navbar-brand" href="#"><span id="siteNameText"></span></a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item">
<a id="homeBtn" href="#" class="nav-link active" aria-current="page"><i class="fa-solid fa-house"></i> <span id="homeBtnText"></span></a>
</li>
<li class="nav-item">
<a href="https://computernewb.com/collab-vm/faq/" class="nav-link"><i class="fa-solid fa-circle-question"></i> <span id="faqBtnText"></span></a>
</li>
<li class="nav-item">
<a href="https://computernewb.com/collab-vm/rules" class="nav-link"><i class="fa-solid fa-clipboard-check"></i> <span id="rulesBtnText"></span></a>
</li>
<li class="nav-item">
<a href="https://discord.gg/a4kqb4mGyX" class="nav-link"><i class="fa-brands fa-discord"></i> Discord</a>
</li>
<li class="nav-item">
<a href="https://reddit.com/r/collabvm" class="nav-link"><i class="fa-brands fa-reddit"></i> Subreddit</a>
</li>
<li class="nav-item">
<a rel="me" class="nav-link" href="https://fedi.computernewb.com/@collabvm"><i class="fa-brands fa-mastodon"></i> Mastodon</a>
</li>
<li class="nav-item">
<a href="https://computernewb.com/collab-vm/user-vm" class="nav-link"><i class="fa-solid fa-user"></i> UserVM</a>
</li>
</ul>
</div>
</div>
</nav>
<div class="container-fluid" id="vmlist">
<div class="row"></div>
</div>
<div class="container-fluid" id="vmview">
<div id="vmDisplay"></div>
<p id="turnstatus" class="text-light"></p>
<div id="voteResetPanel" class="bg-dark text-light" style="display:none;">
<span id="voteResetHeaderText"></span><br/>
<button class="btn btn-success" id="voteYesBtn"><i class="fa-solid fa-check"></i> <span id="voteYesBtnText"></span><span class="badge bg-secondary" id="voteYesLabel"></span></button> <button class="btn btn-danger" id="voteNoBtn"><i class="fa-solid fa-ban"></i> <span id="voteNoBtnText"></span><span class="badge bg-secondary" id="voteNoLabel"></span></button><br/>
<span id="voteTimeText"></span>
<div id="forceVotePanel">
<button class="btn btn-info" id="forceVoteYesBtn"><i class="fa-solid fa-check"></i> <span id="passVoteBtnText"></span></button>
<button class="btn btn-info" id="forceVoteNoBtn"><i class="fa-solid fa-ban"></i> <span id="cancelVoteBtnText"></span></button>
</div>
</div>
<div id="btns">
<button class="btn btn-secondary" id="takeTurnBtn"><i class="fa-solid fa-computer-mouse"></i> <span id="turnBtnText"></span></button>
<button class="btn btn-secondary" id="oskBtn"><i class="fa-solid fa-keyboard"></i> Keyboard</button>
<button class="btn btn-secondary" id="changeUsernameBtn"><i class="fa-solid fa-signature"></i> <span id="changeUsernameBtnText"></span></button>
<button class="btn btn-secondary" id="voteResetButton"><i class="fa-solid fa-rotate-left"></i> <span id="voteForResetBtnText"></span></button>
<button class="btn btn-secondary" id="screenshotButton"><i class="fa-solid fa-camera"></i> <span id="screenshotBtnText"></span></button>
<button class="btn btn-secondary" id="ctrlAltDelBtn"><i class="fa-solid fa-gear"></i> Ctrl+Alt+Del</button>
<div id="staffbtns">
<button class="btn btn-secondary" id="restoreBtn"><i class="fa-solid fa-rotate-left"></i> Restore</button>
<button class="btn btn-secondary" id="rebootBtn"><i class="fa-solid fa-power-off"></i> Reboot</button>
<button class="btn btn-secondary" id="clearQueueBtn"><i class="fa-solid fa-eraser"></i> Clear Turn Queue</button>
<button class="btn btn-secondary" id="bypassTurnBtn"><i class="fa-solid fa-forward"></i> Bypass Turn</button>
<button class="btn btn-secondary" id="endTurnBtn"><i class="fa-solid fa-ban"></i> <span id="endTurnBtnText"></span></button>
<button class="btn btn-secondary" id="indefTurnBtn"><i class="fa-solid fa-infinity"></i> Indefinite Turn</button>
<button class="btn btn-secondary" id="qemuMonitorBtn" data-bs-toggle="modal" data-bs-target="#qemuMonitorModal"><i class="fa-solid fa-terminal"></i> QEMU Monitor</button>
</div>
</div>
<div class="osk-container d-none" id="osk-container">
<div class="osk-main"></div>
<div class="osk-main"></div>
<div class="controlArrows">
<div class="osk-control"></div>
<div class="osk-arrows"></div>
</div>
<div class="controlArrows">
<div class="osk-control"></div>
<div class="osk-arrows"></div>
</div>
<div class="numPad">
<div class="osk-numpad"></div>
<div class="osk-numpadEnd"></div>
</div>
<div class="numPad">
<div class="osk-numpad"></div>
<div class="osk-numpadEnd"></div>
</div>
</div>
<div class="row">
<div class="col-md-4">
<div class="table-responsive username-table">
<table class="table table-hover table-dark table-borderless">
<thead>
<th><i class="fa-solid fa-user"></i> <span id="usersOnlineText"></span> (<span id="onlineusercount"></span>)</th>
</thead>
<tbody id="userlist"></tbody>
</table>
</div>
</div>
<div class="col-md-8">
<div class="table-responsive chat-table" id="chatListDiv">
<table class="table table-hover table-dark table-borderless">
<tbody id="chatList">
</tbody>
</table>
</div>
<div class="input-group">
<span class="input-group-text bg-dark text-light" id="username">Username</span>
<input type="text" class="form-control bg-dark text-light" id="chat-input"/>
<div class="input-group-text bg-dark text-light" id="xssCheckboxContainer">
<input class="form-check-input" type="checkbox" value="" id="xssCheckbox"/>
<label class="form-check-label" for="xssCheckbox">XSS</label>
</div>
<button class="btn btn-primary text-light" type="button" id="sendChatBtn"><i class="fa-solid fa-paper-plane"></i></button>
</div>
</div>
</div>
</div>
<script src="https://js.hcaptcha.com/1/api.js" async defer></script>
<script type="module" src="../ts/main.ts" type="application/javascript"></script>
</body>
<div class="row">
<div class="col-md-4">
<div class="table-responsive username-table">
<table class="table table-hover table-dark table-borderless">
<thead>
<th><i class="fa-solid fa-user"></i> <span id="usersOnlineText"></span> (<span id="onlineusercount"></span>)</th>
</thead>
<tbody id="userlist"></tbody>
</table>
</div>
</div>
<div class="col-md-8">
<div class="table-responsive chat-table" id="chatListDiv">
<table class="table table-hover table-dark table-borderless">
<tbody id="chatList"></tbody>
</table>
</div>
<div class="input-group">
<span class="input-group-text bg-dark text-light" id="username">Username</span>
<input type="text" class="form-control bg-dark text-light" id="chat-input" />
<div class="input-group-text bg-dark text-light" id="xssCheckboxContainer">
<input class="form-check-input" type="checkbox" value="" id="xssCheckbox" />
<label class="form-check-label" for="xssCheckbox">XSS</label>
</div>
<button class="btn btn-primary text-light" type="button" id="sendChatBtn"><i class="fa-solid fa-paper-plane"></i></button>
</div>
</div>
</div>
</div>
<script src="https://js.hcaptcha.com/1/api.js" async defer></script>
<script type="module" src="../ts/main.ts" type="application/javascript"></script>
</body>
</html>

View File

@ -8,11 +8,11 @@ import Keyboard from 'simple-keyboard';
import { OSK_buttonToKeysym } from './keyboard';
import 'simple-keyboard/build/css/index.css';
import VoteStatus from './protocol/VoteStatus.js';
import * as bootstrap from 'bootstrap';
import MuteState from './protocol/MuteState.js';
import { Unsubscribe } from 'nanoevents';
import { I18nStringKey, TheI18n } from './i18n.js';
import { Format } from './format.js';
import { ChangeUsername_Modal } from './modals/change_username_modal.js';
import { Alert_Modal } from './modals/alert_modal.js';
// Elements
const w = window as any;
@ -246,7 +246,7 @@ let expectedClose = false;
let turn = -1;
// Listed VMs
const vms: VM[] = [];
const cards: HTMLDivElement[] = [];
const cards: HTMLLIElement[] = [];
const users: {
user: User;
element: HTMLTableRowElement;
@ -282,22 +282,19 @@ async function multicollab(url: string) {
// Add to the DOM
for (let vm of list) {
let div = document.createElement('div');
div.classList.add('col-sm-5', 'col-md-3');
let card = document.createElement('div');
card.classList.add('card', 'bg-dark', 'text-light');
// can we have jsx please........ please... i hate thisssss
let div = document.createElement('li');
let card = document.createElement('a');
card.classList.add('card');
card.setAttribute('data-cvm-node', vm.id);
card.addEventListener('click', async () => {
try {
await openVM(vm);
} catch (e) {
alert((e as Error).message);
}
});
vm.thumbnail.classList.add('card-img-top');
card.href = `#${vm.id}`;
vm.thumbnail.classList.add('card-image');
let cardBody = document.createElement('div');
cardBody.classList.add('card-body');
let cardTitle = document.createElement('h5');
cardTitle.classList.add('card-heading');
cardTitle.innerHTML = vm.displayName;
let usersOnline = document.createElement('span');
usersOnline.innerHTML = `(<i class="fa-solid fa-users"></i> ${online})`;
@ -315,8 +312,7 @@ async function openVM(vm: VM): Promise<void> {
// If there's an active VM it must be closed before opening another
if (VM !== null) return;
expectedClose = false;
// Set hash
location.hash = vm.id;
// Create the client
VM = new CollabVMClient(vm.url);
@ -331,13 +327,13 @@ async function openVM(vm: VM): Promise<void> {
// TODO: i18n these
switch (status) {
case 'taken':
alert(TheI18n.GetString(I18nStringKey.kError_UsernameTaken));
Alert_Modal(TheI18n.GetString(I18nStringKey.kError_UsernameTaken));
break;
case 'invalid':
alert(TheI18n.GetString(I18nStringKey.kError_UsernameInvalid));
Alert_Modal(TheI18n.GetString(I18nStringKey.kError_UsernameInvalid));
break;
case 'blacklisted':
alert(TheI18n.GetString(I18nStringKey.kError_UsernameBlacklisted));
Alert_Modal(TheI18n.GetString(I18nStringKey.kError_UsernameBlacklisted));
break;
}
});
@ -345,11 +341,11 @@ async function openVM(vm: VM): Promise<void> {
VM!.on('turn', (status) => turnUpdate(status));
VM!.on('vote', (status: VoteStatus) => voteUpdate(status));
VM!.on('voteend', () => voteEnd());
VM!.on('votecd', (voteCooldown) => window.alert(TheI18n.GetString(I18nStringKey.kVM_VoteCooldownTimer, voteCooldown)));
VM!.on('votecd', (voteCooldown) => Alert_Modal(TheI18n.GetString(I18nStringKey.kVM_VoteCooldownTimer, voteCooldown)));
VM!.on('login', (rank: Rank, perms: Permissions) => onLogin(rank, perms));
VM!.on('close', () => {
if (!expectedClose) alert(TheI18n.GetString(I18nStringKey.kError_UnexpectedDisconnection));
if (!expectedClose) Alert_Modal(TheI18n.GetString(I18nStringKey.kError_UnexpectedDisconnection));
closeVM();
});
@ -360,7 +356,7 @@ async function openVM(vm: VM): Promise<void> {
chatMessage('', `<b>${vm.id}</b><hr>`);
let username = localStorage.getItem('username');
let connected = await VM.connect(vm.id, username);
elements.adminInputVMID.value = vm.id;
//elements.adminInputVMID.value = vm.id;
w.VMName = vm.id;
if (!connected) {
// just give up
@ -368,7 +364,7 @@ async function openVM(vm: VM): Promise<void> {
throw new Error('Failed to connect to node');
}
// Set the title
document.title = Format("{0} - {1}", vm.id, TheI18n.GetString(I18nStringKey.kGeneric_CollabVM));
document.title = Format('{0} - {1}', vm.id, TheI18n.GetString(I18nStringKey.kGeneric_CollabVM));
// Append canvas
elements.vmDisplay.appendChild(VM!.canvas);
// Switch to the VM view
@ -388,7 +384,7 @@ function closeVM() {
// Remove the canvas
elements.vmDisplay.innerHTML = '';
// Switch to the VM list
elements.vmlist.style.display = 'block';
elements.vmlist.style.display = '';
elements.vmview.style.display = 'none';
// Clear users
users.splice(0, users.length);
@ -415,6 +411,16 @@ function closeVM() {
elements.username.classList.add('text-light');
}
async function openHash() {
// automatically join the vm that's in the url if it exists in the node list
let v = vms.find((v) => v.id === window.location.hash.substring(1));
try {
if (v !== undefined) await openVM(v);
} catch (e) {
Alert_Modal((e as Error).message);
}
}
async function loadList() {
await Promise.all(
Config.ServerAddresses.map((url) => {
@ -422,21 +428,15 @@ async function loadList() {
})
);
// automatically join the vm that's in the url if it exists in the node list
let v = vms.find((v) => v.id === window.location.hash.substring(1));
try {
if (v !== undefined) await openVM(v);
} catch (e) {
alert((e as Error).message);
}
await openHash();
}
function sortVMList() {
cards.sort((a, b) => {
return a.children[0].getAttribute('data-cvm-node')! > b.children[0].getAttribute('data-cvm-node')! ? 1 : -1;
});
elements.vmlist.children[0].innerHTML = '';
cards.forEach((c) => elements.vmlist.children[0].appendChild(c));
//elements.vmlist.children[0].innerHTML = '';
cards.forEach((c) => elements.vmlist.appendChild(c));
}
function sortUserList() {
@ -633,8 +633,13 @@ elements.sendChatBtn.addEventListener('click', sendChat);
elements.chatinput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') sendChat();
});
elements.changeUsernameBtn.addEventListener('click', () => {
let newname = prompt(TheI18n.GetString(I18nStringKey.kVMPrompts_EnterNewUsernamePrompt), w.username);
elements.changeUsernameBtn.addEventListener('click', async () => {
let newname = await ChangeUsername_Modal(w.username);
// cancelled
if(newname == null)
return;
if (newname === w.username) return;
VM?.rename(newname);
});
@ -667,6 +672,8 @@ elements.voteYesBtn.addEventListener('click', () => VM?.vote(true));
elements.voteNoBtn.addEventListener('click', () => VM?.vote(false));
// Login
let usernameClick = false;
/*
const loginModal = new bootstrap.Modal(elements.loginModal);
elements.loginModal.addEventListener('shown.bs.modal', () => elements.adminPassword.focus());
elements.username.addEventListener('click', () => {
@ -687,7 +694,7 @@ function doLogin() {
elements.adminPassword.value = '';
let u = VM?.on('login', () => {
u!();
loginModal.hide();
//loginModal.hide();
elements.badPasswordAlert.style.display = 'none';
});
let _u = VM?.on('badpw', () => {
@ -696,6 +703,8 @@ function doLogin() {
});
}
*/
function onLogin(_rank: Rank, _perms: Permissions) {
rank = _rank;
perms = _perms;
@ -746,7 +755,7 @@ function userModOptions(user: { user: User; element: HTMLTableRowElement }) {
if (perms.grabip)
addUserDropdownItem(ul, 'Get IP', async () => {
let ip = await VM!.getip(user.user.username);
alert(ip);
Alert_Modal(ip);
});
tr.appendChild(ul);
}
@ -775,6 +784,7 @@ elements.forceVoteNoBtn.addEventListener('click', () => VM?.forceVote(false));
elements.forceVoteYesBtn.addEventListener('click', () => VM?.forceVote(true));
elements.indefTurnBtn.addEventListener('click', () => VM?.indefiniteTurn());
/*
async function sendQEMUCommand() {
if (!elements.qemuMonitorInput.value) return;
let cmd = elements.qemuMonitorInput.value;
@ -786,6 +796,7 @@ async function sendQEMUCommand() {
}
elements.qemuMonitorSendBtn.addEventListener('click', () => sendQEMUCommand());
elements.qemuMonitorInput.addEventListener('keypress', (e) => e.key === 'Enter' && sendQEMUCommand());
*/
elements.osk.addEventListener('click', () => elements.oskContainer.classList.toggle('d-none'));
@ -834,6 +845,11 @@ w.cvmEvents = {
};
w.VMName = null;
// could be a neat feature?
window.addEventListener('hashchange', async () => {
await openHash();
});
document.addEventListener('DOMContentLoaded', async () => {
// Initalize the i18n system
await TheI18n.Init();
@ -843,6 +859,7 @@ document.addEventListener('DOMContentLoaded', async () => {
// Load all VMs
await loadList();
/*
// Welcome modal
let noWelcomeModal = window.localStorage.getItem('no-welcome-modal');
if (noWelcomeModal !== '1') {
@ -857,4 +874,5 @@ document.addEventListener('DOMContentLoaded', async () => {
welcomeModalDismissBtn.disabled = false;
}, 5000);
}
*/
});

View File

@ -0,0 +1,9 @@
const elements = {
alertDialog: document.querySelector('#alertDialog') as HTMLDialogElement
};
export function Alert_Modal(text: string) {
const alertMessage = elements.alertDialog.querySelector('#alertMessage') as HTMLLabelElement;
alertMessage.innerText = text;
elements.alertDialog.showModal();
}

View File

@ -0,0 +1,52 @@
const elements = {
changeUsernameDialog: document.querySelector('#changeUsernameDialog') as HTMLDialogElement
};
// returns a promise which will resolve with "null" for canceled,
// and a username for a username
export async function ChangeUsername_Modal(lastUsername: string): Promise<string | null> {
return new Promise((res, rej) => {
const usernameInput = elements.changeUsernameDialog.querySelector('#usernameInput') as HTMLInputElement;
const cancelButton = elements.changeUsernameDialog.querySelector('#cancelButton') as HTMLButtonElement;
const okButton = elements.changeUsernameDialog.querySelector('#okButton') as HTMLButtonElement;
usernameInput.addEventListener('change', (e) => {
okButton.value = usernameInput.value;
});
function handleDialog() {
resetBox();
res(elements.changeUsernameDialog.returnValue);
}
function handleDialogCancel() {
resetBox();
res(null);
}
function resetBox() {
usernameInput.value = '';
// remove event listener s you google..
elements.changeUsernameDialog.removeEventListener('close', handleDialog);
elements.changeUsernameDialog.removeEventListener('close', handleDialogCancel);
}
elements.changeUsernameDialog.addEventListener('cancel', (e) => {
handleDialogCancel();
});
okButton.addEventListener('click', (ev) => {
elements.changeUsernameDialog.addEventListener('close', handleDialog);
});
cancelButton.addEventListener('click', (ev) => {
elements.changeUsernameDialog.addEventListener('close', handleDialogCancel);
});
// show the modal!
usernameInput.value = lastUsername;
okButton.value = lastUsername;
elements.changeUsernameDialog.showModal();
});
}

View File

@ -0,0 +1,90 @@
<!-- These are the original modals from the webapp. I wanna make them in JSX or something
now that we're dropping bootstrap, but if I'm resigned to, I can just blind port these -->
<div class="modal fade" id="qemuMonitorModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content bg-dark text-light">
<div class="modal-header">
<h5 class="modal-title">QEMU Monitor</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<textarea id="qemuMonitorOutput" readonly="" class="form-control bg-dark text-light"></textarea>
<div class="input-group">
<input type="text" id="qemuMonitorInput" class="form-control bg-dark text-light" placeholder="Command"/>
<button class="btn btn-outline-secondary btn-primary text-light" type="button" id="qemuMonitorSendBtn">Send</button>
</div>
</div>
</div>
</div>
</div>
<div class="modal fade" id="welcomeModal" tabindex="-1" data-bs-backdrop="static" data-bs-keyboard="false" aria-labelledby="welcomeModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content bg-dark text-light">
<div class="modal-header">
<h1>Welcome to CollabVM</h1>
</div>
<div class="modal-body">
<p>Before continuing, please familiarize yourself with our rules:</p>
<h3>R1. Don't break the law.</h3>
Do not use CollabVM or CollabVM's network to violate United States federal law, New York state law, or international law. If CollabVM becomes aware a crime has been committed through its service, you will be immediately banned, and your activities may be reported to the authorities if necessary.<br><br>CollabVM is required by law to notify law enforcement agencies if it becomes aware of the presence of child pornography on, or being transmitted through its network.<br><br>COPPA is also enforced, please do not use CollabVM if you are under the age of 13 years old.
<h3>R2. No running DoS/DDoS tools.</h3>
Do not use CollabVM to DoS/DDoS an indivdiual, business, company, or anyone else.
<h3>R3. No spam distribution.</h3>
Do not spam any emails using this service or push spam in general.
<h3>R4. Do not abuse any exploits.</h3>
Do not abuse any exploits, additionally if you see someone abusing exploits or you need to report one, please contact me at: computernewbab@gmail.com
<h3>R5. Don't impersonate other users.</h3>
Do not impersonate other members of CollabVM. If caught, you'll be temporarily disconnected, and banned if necessary.
<h3>R6. One vote per person.</h3>
Do not use any methods or tools to bypass the vote restriction. Only one vote per person is allowed, no matter what. Anybody who is caught doing this will be banned.
<h3>R7. No Remote Administration Tools.</h3>
Do not use any remote administration tools (ex: DarkComet, NanoCore, Anydesk, TeamViewer, Orcus, etc.)
<h3>R8. No bypassing CollabNet.</h3>
Do not attempt to bypass the blocking provided by CollabNet, especially if it is being used to break Rule 1, Rule 2, or Rule 7 (or run stupid over-used things).
<h3>R9. No performing destructive actions constantly.</h3>
Any user may not destroy the VM (rendering it unusable constantly), install/reinstall the operating system (except on VM7 or VM8), or run bots that do such. This includes bots that spam massive amounts of keyboard/mouse input ("kitting").
<h3>R10. No Cryptomining</h3>
Attempting to mine cryptocurrency on the VMs will result in a kick, and then a permanent ban if you keep attempting. Besides, it's not like you're gonna make any money off it.
<h3>NSFW Warning</h3>
Please note that NSFW content is allowed on our anarchy VM (VM0b0t), and is viewed regularly. In addition, while we give a good effort to keep NSFW off the main VMs, people will occasionally slip it through.
</div>
<div class="modal-footer">
<button type="button" id="welcomeModalDismiss" class="btn btn-primary" data-bs-dismiss="modal">Understood</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="loginModal" tabindex="-1" aria-labelledby="loginModalLabel" aria-hidden="true">
<div class="modal-dialog modal-md">
<div class="modal-content bg-dark text-light">
<div class="modal-header">
<h5 class="modal-title">Login</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="alert alert-danger alert-dismissible" id="badPasswordAlert" role="alert">
Incorrect password.
<button type="button" class="btn-close" aria-label="Close" id="incorrectPasswordDismissBtn"></button>
</div>
<div class="input-group">
<input type="hidden" name="username" id="adminInputVMID"/>
<span class="input-group-text bg-dark text-light">Password</span>
<input id="adminPassword" type="password" class="form-control bg-dark text-light" placeholder="Password" name="password"/>
</div>
</div>
<div class="modal-footer">
<button type="button" id="loginButton" class="btn btn-primary">Login</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="hcaptchaModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content bg-dark text-light">
<div class="modal-body">
<div id="captcha-box"></div>
</div>
</div>
</div>
</div>

View File

@ -14,6 +14,11 @@
"target": "es2022", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
// "jsx": "preserve", /* Specify what JSX code is generated. */
// for later:tm:
//"jsx": "react-jsx",
//"jsxImportSource": "nano-jsx/esm",
// "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */

View File

@ -1150,6 +1150,15 @@
"@parcel/utils" "2.12.0"
react-refresh "^0.9.0"
"@parcel/transformer-sass@2.12.0":
version "2.12.0"
resolved "https://registry.yarnpkg.com/@parcel/transformer-sass/-/transformer-sass-2.12.0.tgz#9132ee78197db04baf51d3024a1bf3c35f1df5ef"
integrity sha512-xLLoSLPST+2AHJwFRLl4foArDjjy6P1RChP3TxMU2MVS1sbKGJnfFhFpHAacH8ASjuGtu5rbpfpHRZePlvoZxw==
dependencies:
"@parcel/plugin" "2.12.0"
"@parcel/source-map" "^2.1.1"
sass "^1.38.0"
"@parcel/transformer-svg@2.12.0":
version "2.12.0"
resolved "https://registry.yarnpkg.com/@parcel/transformer-svg/-/transformer-svg-2.12.0.tgz#0281e89bf0f438ec161c19b59a8a8978434a3621"
@ -1535,7 +1544,7 @@ ansi-styles@^5.0.0:
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b"
integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==
anymatch@^3.0.3:
anymatch@^3.0.3, anymatch@~3.1.2:
version "3.1.3"
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e"
integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==
@ -1627,16 +1636,16 @@ base-x@^3.0.8:
dependencies:
safe-buffer "^5.0.1"
binary-extensions@^2.0.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522"
integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==
boolbase@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e"
integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==
bootstrap@^5.3.2:
version "5.3.3"
resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-5.3.3.tgz#de35e1a765c897ac940021900fcbb831602bac38"
integrity sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==
brace-expansion@^1.1.7:
version "1.1.11"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
@ -1645,7 +1654,7 @@ brace-expansion@^1.1.7:
balanced-match "^1.0.0"
concat-map "0.0.1"
braces@^3.0.2:
braces@^3.0.2, braces@~3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
@ -1723,6 +1732,21 @@ char-regex@^1.0.2:
resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf"
integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==
"chokidar@>=3.0.0 <4.0.0":
version "3.6.0"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b"
integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==
dependencies:
anymatch "~3.1.2"
braces "~3.0.2"
glob-parent "~5.1.2"
is-binary-path "~2.1.0"
is-glob "~4.0.1"
normalize-path "~3.0.0"
readdirp "~3.6.0"
optionalDependencies:
fsevents "~2.3.2"
chrome-trace-event@^1.0.2, chrome-trace-event@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac"
@ -2056,7 +2080,7 @@ fs.realpath@^1.0.0:
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==
fsevents@^2.3.2:
fsevents@^2.3.2, fsevents@~2.3.2:
version "2.3.3"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
@ -2091,6 +2115,13 @@ get-stream@^6.0.0:
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7"
integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==
glob-parent@~5.1.2:
version "5.1.2"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
dependencies:
is-glob "^4.0.1"
glob@^7.1.3, glob@^7.1.4:
version "7.2.3"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b"
@ -2166,6 +2197,11 @@ human-signals@^2.1.0:
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0"
integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==
immutable@^4.0.0:
version "4.3.5"
resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.3.5.tgz#f8b436e66d59f99760dc577f5c99a4fd2a5cc5a0"
integrity sha512-8eabxkth9gZatlwl5TBuJnCsoTADlL6ftEr7A4qgdaTsPyreilDSnUk57SO+jfKcNtxPa22U5KK6DSeAYhpBJw==
import-fresh@^3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b"
@ -2205,6 +2241,13 @@ is-arrayish@^0.2.1:
resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==
is-binary-path@~2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==
dependencies:
binary-extensions "^2.0.0"
is-core-module@^2.13.0:
version "2.13.1"
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.1.tgz#ad0d7532c6fea9da1ebdc82742d74525c6273384"
@ -2227,7 +2270,7 @@ is-generator-fn@^2.0.0:
resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118"
integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==
is-glob@^4.0.3:
is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1:
version "4.0.3"
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
@ -2938,7 +2981,7 @@ node-releases@^2.0.14:
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b"
integrity sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==
normalize-path@^3.0.0:
normalize-path@^3.0.0, normalize-path@~3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
@ -3081,7 +3124,7 @@ picocolors@^1.0.0:
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
picomatch@^2.0.4, picomatch@^2.2.3, picomatch@^2.3.1:
picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
@ -3174,6 +3217,13 @@ react-refresh@^0.9.0:
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.9.0.tgz#71863337adc3e5c2f8a6bfddd12ae3bfe32aafbf"
integrity sha512-Gvzk7OZpiqKSkxsQvO/mbTN1poglhmAV7gR/DdIrRrSMXraRQQlfikRJOr3Nb9GTMPC5kof948Zy6jJZIFtDvQ==
readdirp@~3.6.0:
version "3.6.0"
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7"
integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==
dependencies:
picomatch "^2.2.1"
regenerator-runtime@^0.13.7:
version "0.13.11"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9"
@ -3225,6 +3275,15 @@ safe-buffer@^5.0.1:
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
sass@^1.38.0:
version "1.72.0"
resolved "https://registry.yarnpkg.com/sass/-/sass-1.72.0.tgz#5b9978943fcfb32b25a6a5acb102fc9dabbbf41c"
integrity sha512-Gpczt3WA56Ly0Mn8Sl21Vj94s1axi9hDIzDFn9Ph9x3C3p4nNyvsqJoQyVXKou6cBlfFWEgRW4rT8Tb4i3XnVA==
dependencies:
chokidar ">=3.0.0 <4.0.0"
immutable "^4.0.0"
source-map-js ">=0.6.2 <2.0.0"
semver@^6.3.0, semver@^6.3.1:
version "6.3.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
@ -3269,6 +3328,11 @@ slash@^3.0.0:
resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==
"source-map-js@>=0.6.2 <2.0.0":
version "1.2.0"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.0.tgz#16b809c162517b5b8c3e7dcd315a2a5c2612b2af"
integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==
source-map-support@0.5.13:
version "0.5.13"
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932"