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
This commit is contained in:
modeco80 2024-03-24 08:32:32 -04:00
parent ed46f171f5
commit b5256da3a5
10 changed files with 658 additions and 427 deletions

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",

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

@ -0,0 +1,15 @@
// 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!
$root-bg-color: rgb(32, 32, 32);
$root-fg-color: rgb(180, 180, 180);
/** 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);

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

@ -0,0 +1,61 @@
@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;
}

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

@ -0,0 +1,362 @@
// TODO!!!!!!
@import 'colors-dark';
// modules
@import 'cards';
html,
body {
background-color: $root-bg-color;
color: $root-fg-color;
font-family: 'Segoe UI', Tahoma, Cantarell, sans-serif;
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;
}
}
// for our new <dialog> based modals
// This should probably be put into a more uniform file
/* a bit of a "lazy" curve */
@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.7s;
}
:modal {
animation: dialogFadeIn 0.4s;
}

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

@ -4,8 +4,7 @@
<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"/>
<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!"/>
@ -17,95 +16,12 @@
<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"/>
<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">
</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>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container-fluid">
<a class="navbar-brand" href="#"><span id="siteNameText"></span></a>
@ -139,9 +55,8 @@
</div>
</div>
</nav>
<div class="container-fluid" id="vmlist">
<div class="row"></div>
</div>
<ul class="cards" id="vmlist">
</ul>
<div class="container-fluid" id="vmview">
<div id="vmDisplay"></div>
<p id="turnstatus" class="text-light"></p>

View File

@ -8,9 +8,7 @@ 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';
@ -246,7 +244,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 +280,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 +310,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);
@ -360,7 +354,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 +362,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 +382,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,13 +409,7 @@ function closeVM() {
elements.username.classList.add('text-light');
}
async function loadList() {
await Promise.all(
Config.ServerAddresses.map((url) => {
return multicollab(url);
})
);
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 {
@ -431,12 +419,22 @@ async function loadList() {
}
}
async function loadList() {
await Promise.all(
Config.ServerAddresses.map((url) => {
return multicollab(url);
})
);
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() {
@ -667,6 +665,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 +687,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 +696,8 @@ function doLogin() {
});
}
*/
function onLogin(_rank: Rank, _perms: Permissions) {
rank = _rank;
perms = _perms;
@ -775,6 +777,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 +789,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 +838,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 +852,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 +867,5 @@ document.addEventListener('DOMContentLoaded', async () => {
welcomeModalDismissBtn.disabled = false;
}, 5000);
}
*/
});

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"