Merge pull request #182 from TeaSpeak/develop

Finally merge these changes since TeaSpeak 1.5 is released as stable.
Note: This merge will break compatibility with all 1.4 TeaSpeak server versions!
master
WolverinDEV 2021-05-26 13:52:30 +02:00 committed by GitHub
commit 39ce66763d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
855 changed files with 45798 additions and 32371 deletions

1
.gitignore vendored
View File

@ -14,6 +14,7 @@ node_modules/
# Some build output # Some build output
/dist/ /dist/
/dist-package/
/declarations/ /declarations/
/travis-build/ /travis-build/

View File

@ -1,10 +1,12 @@
dist: trusty dist: bionic
language: node_js language: node_js
node_js: node_js:
- "12" - "12"
before_install: before_install:
- sudo apt-get update
- sudo apt-get -y install coreutils
- chmod +x ./scripts/travis/build.sh - chmod +x ./scripts/travis/build.sh
- chmod +x ./scripts/travis/deploy_server.sh - chmod +x ./scripts/travis/deploy_server.sh
- chmod +x ./scripts/travis/deploy_github.sh - chmod +x ./scripts/travis/deploy_github.sh

View File

@ -1,4 +1,83 @@
# Changelog: # Changelog:
* **26.05.21**
- Fixed automated builds
- Fixed the bookmark UI popout window
- Added a context menu for the general bookmark container
* **05.05.21**
- Reworked the icon modal
- Fixed some minor icon and avatar related issues
- Improved icon modal performance
* **29.04.21**
- Fixed a bug which caused chat messages to appear twice
- Adding support for poping out channel conversations
* **27.04.21**
- Implemented support for showing the video feed watchers
- Updating the channel tree if the channel client order changes
* **24.04.21**
- Removed the old server info modal and using the new React based and popoutable modal
- Using the new React modal for the server info dialog. The modal now also has improved permission and error visualisation
- Improved tooltip handling
* **19.04.21**
- Fixed a bug that the client video box is shown as active even though the client does not stream any video
- Fixed a bug that the video fullscreen windows pops open when a client leaves/joins the channel
- Removed extra new line after blockquote for the markdown renderer
* **05.04.21**
- Fixed the mute but for the webclient
- Fixed that "always active" microphone filter now works reliably
- Improved the recorder API
* **29.03.21**
- Acquiring the default input recorder when opening the settings
- Adding new modal Input Processing Properties for the native client
- Fixed that you can't finish off the name editing by pressing enter
* **25.03.21**
- Allowing to directly select the speaker output device
- Saving the speaker output device
* **24.03.21**
- Improved the avatar upload modal (now way more intuitive)
- Fixed a bug which cause client avatars to be stuck within the loading screen
- Don't spam permission errors if we don't have the permission to view the channel description
- Showing channel group inheritance within the client info frame
* **23.03.21**
- Made the permission editor popoutable
- Now using SVG flags for higher quality.
- Fixed issue [#74](https://github.com/TeaSpeak/TeaWeb/issues/74) (Swiss flag box has black background)
- Fixed issue that middle clicking on the channel does not shows the channel file browser instead it shows the global one
* **21.03.21**
- Reworked the server group assignment modal. It now better reacts to the user input as well is now popoutable
* **18.03.21**
- Finally, got fully rid of the initial backend glue and changes all systems to provider
* **17.03.21**
- Updated from webpack 4 to webpack 5
- Reworked the client build process
- Using webpack dev server from now on
* **14.03.21**
- Enchanted the bookmark system
- Added support for auto connect on startup
- Cleaned and simplified up the bookmark UI
- Added support for importing/exporting bookmarks
- Added support for duplicating bookmarks
- Adding support for default channels and passwords
* **12.03.21**
- Added a new video spotlight mode which allows showing multiple videos at the same time as well as
dragging and resizing them
- Fixed a minor bug within the permission editor
- Fixed the creation of channel groups
* **20.02.21** * **20.02.21**
- Improved the browser IPC module - Improved the browser IPC module
- Added support for client invite links - Added support for client invite links

View File

@ -1,4 +1,4 @@
export = api => { export default api => {
api.cache(false); api.cache(false);
const presets = [ const presets = [
[ [
@ -16,10 +16,12 @@ export = api => {
} }
] ]
]; ];
const plugins = [ const plugins = [
["@babel/transform-runtime"], ["@babel/transform-runtime"],
["@babel/plugin-transform-modules-commonjs"] ["@babel/plugin-transform-modules-commonjs"]
]; ];
return { return {
presets, presets,
plugins plugins

View File

@ -0,0 +1,4 @@
window.__native_client_init_shared(__webpack_require__);
import "../AppMain.scss";
import "tc-shared/entry-points/MainApp";

View File

@ -1,4 +1,2 @@
window.__native_client_init_shared(__webpack_require__); window.__native_client_init_shared(__webpack_require__);
import "tc-shared/entry-points/ModalWindow";
import "./index.scss";
import "tc-shared/main";

View File

@ -1,42 +0,0 @@
# File structure
The TeaSpeak web client is separated into 2 different parts.
## I) Application files
Application files are all files which directly belong to the app itself.
Like the javascript files who handle the UI stuff or even translation templates.
Theses files are separated into two type of files.
1. [Shared application files](#1-shared-application-files)
2. [Web application files](#2-web-application-files)
### 1. Shared application files
Containing all files used by the TeaSpeak client and the Web client.
All of these files will be found within the folder `shared`.
This folder follows the general application file structure.
More information could be found [here](#application-file-structure)
### 2. Web application files
All files which only belong to a browser only instance.
All of these files will be found within the folder `web`.
This folder follows the general application file structure.
More information could be found [here](#application-file-structure)
### application file structure
Every application root contains several subfolders.
In the following list will be listed which files belong to which folder
| Folder | Description |
| --- | --- |
| `audio` | This folder contains all audio files used by the application. More information could be found [here](). |
| `css` | This folder contains all style sheets used by the application. More information could be found [here](). |
| `js` | This folder contains all javascript files used by the application. More information could be found [here](). |
| `html` | This folder contains all HTML and PHP files used by the application. More information could be found [here](). |
| `i18n` | This folder contains all default translations. Information about the translation system could be found [here](). |
| `img` | This folder contains all image files. |
## I) Additional tools
## Environment builder
The environment builder is one of the most important tools of the entire project.
This tool, basically implemented in the file `files.php`, will be your helper while live developing.
What this tool does is, it creates a final environment where you could navigate to with your browser.
It merges all the type separated files, which had been listed above ([here](#application-file-structure)).

58
file.ts
View File

@ -30,49 +30,26 @@ type ProjectResource = {
} }
const APP_FILE_LIST_SHARED_SOURCE: ProjectResource[] = [ const APP_FILE_LIST_SHARED_SOURCE: ProjectResource[] = [
{ /* javascript files as manifest.json */
"type": "js",
"search-pattern": /.*\.(js|json|svg|png|css)$/,
"build-target": "dev|rel",
"path": "js/",
"local-path": "./dist/"
},
{ /* shared html files */ { /* shared html files */
"type": "html", "type": "html",
"search-pattern": /^.*([a-zA-Z]+)\.(html|json)$/, "search-pattern": /^.*([a-zA-Z]+)\.(html|json)$/,
"build-target": "dev|rel", "build-target": "dev|rel",
"path": "./", "path": "./",
"local-path": "./shared/html/"
},
{ /* javascript files as manifest.json */
"type": "js",
"search-pattern": /.*\.(js|json|svg)$/,
"build-target": "dev|rel",
"path": "js/",
"local-path": "./dist/" "local-path": "./dist/"
}, },
{ /* javascript files as manifest.json */
"type": "html",
"search-pattern": /.*\.html$/,
"build-target": "dev|rel",
"path": "./",
"local-path": "./dist/"
},
{ /* Loader css file (only required in dev mode. In release it gets inlined) */
"type": "css",
"search-pattern": /.*\.css$/,
"build-target": "dev",
"path": "css/",
"local-path": "./loader/css/"
},
{ /* shared sound files */ { /* shared sound files */
"type": "wav", "type": "wav",
"search-pattern": /.*\.wav$/, "search-pattern": /.*\.(wav|json)$/,
"build-target": "dev|rel",
"path": "audio/",
"local-path": "./shared/audio/"
},
{ /* shared data sound files */
"type": "json",
"search-pattern": /.*\.json/,
"build-target": "dev|rel", "build-target": "dev|rel",
"path": "audio/", "path": "audio/",
@ -87,15 +64,6 @@ const APP_FILE_LIST_SHARED_SOURCE: ProjectResource[] = [
"path": "img/", "path": "img/",
"local-path": "./shared/img/" "local-path": "./shared/img/"
}, },
{ /* assembly files */
"web-only": true,
"type": "wasm",
"search-pattern": /.*\.(wasm)/,
"build-target": "dev|rel",
"path": "js/",
"local-path": "./dist/"
}
]; ];
const APP_FILE_LIST_SHARED_VENDORS: ProjectResource[] = []; const APP_FILE_LIST_SHARED_VENDORS: ProjectResource[] = [];
@ -297,7 +265,7 @@ namespace server {
} else { } else {
server = http.createServer(handleHTTPRequest); server = http.createServer(handleHTTPRequest);
} }
await new Promise((resolve, reject) => { await new Promise<void>((resolve, reject) => {
server.on('error', reject); server.on('error', reject);
server.listen(options.port, () => { server.listen(options.port, () => {
server.off("error", reject); server.off("error", reject);
@ -308,7 +276,7 @@ namespace server {
export async function shutdown() { export async function shutdown() {
if(server) { if(server) {
await new Promise((resolve, reject) => server.close(error => error ? reject(error) : resolve())); await new Promise<void>((resolve, reject) => server.close(error => error ? reject(error) : resolve()));
server = undefined; server = undefined;
} }
} }
@ -428,7 +396,7 @@ namespace watcher {
this._process.addListener("error", this.handle_error.bind(this)); this._process.addListener("error", this.handle_error.bind(this));
try { try {
await new Promise((resolve, reject) => { await new Promise<void>((resolve, reject) => {
const id = setTimeout(reject, 5000, "timeout"); const id = setTimeout(reject, 5000, "timeout");
this._callback_init = () => { this._callback_init = () => {
clearTimeout(id); clearTimeout(id);

View File

@ -1,31 +0,0 @@
import * as path from "path";
import EJSGenerator = require("../webpack/EJSGenerator");
class IndexGenerator extends EJSGenerator {
constructor(options: {
buildTarget: string;
output: string,
isDevelopment: boolean
}) {
super({
variables: {
build_target: options.buildTarget
},
output: options.output,
initialJSEntryChunk: "loader",
input: path.join(__dirname, "html/index.html.ejs"),
minify: !options.isDevelopment,
embedInitialJSEntryChunk: !options.isDevelopment,
embedInitialCSSFile: !options.isDevelopment,
initialCSSFile: {
localFile: path.join(__dirname, "css/index.css"),
publicFile: "css/index.css"
}
});
}
}
export = IndexGenerator;

View File

@ -1,6 +1,6 @@
import * as loader from "./loader/loader"; import * as loader from "./loader/loader";
import {Stage} from "./loader/loader"; import {Stage} from "./loader/loader";
import {getUrlParameter} from "./loader/utils"; import {getUrlParameter} from "./loader/Utils";
let overlay: HTMLDivElement; let overlay: HTMLDivElement;
let setupContainer: HTMLDivElement; let setupContainer: HTMLDivElement;

99
loader/app/bootstrap.ts Normal file
View File

@ -0,0 +1,99 @@
import "core-js/stable";
import "./polifill";
import "./css";
import {ApplicationLoader} from "./loader/loader";
import {getUrlParameter} from "./loader/Utils";
/* let the loader register himself at the window first */
const target = getUrlParameter("loader-target") || "app";
console.info("Loading app with loader \"%s\"", target);
let appLoader: ApplicationLoader;
if(target === "empty") {
appLoader = new (require("./targets/empty").default);
} else if(target === "manifest") {
appLoader = new (require("./targets/maifest-target").default);
} else {
appLoader = new (require("./targets/app").default);
}
setTimeout(() => appLoader.execute(), 0);
export {};
if(__build.target === "client") {
/* do this so we don't get a react dev tools warning within the client */
if(!('__REACT_DEVTOOLS_GLOBAL_HOOK__' in window)) {
window.__REACT_DEVTOOLS_GLOBAL_HOOK__ = {};
}
window.__REACT_DEVTOOLS_GLOBAL_HOOK__.inject = function () {};
}
/* Hello World message */
{
const clog = console.log;
const print_security = () => {
{
const css = [
"display: block",
"text-align: center",
"font-size: 42px",
"font-weight: bold",
"-webkit-text-stroke: 2px black",
"color: red"
].join(";");
clog("%c ", "font-size: 100px;");
clog("%cSecurity warning:", css);
}
{
const css = [
"display: block",
"text-align: center",
"font-size: 18px",
"font-weight: bold"
].join(";");
clog("%cPasting anything in here could give attackers access to your data.", css);
clog("%cUnless you understand exactly what you are doing, close this window and stay safe.", css);
clog("%c ", "font-size: 100px;");
}
};
/* print the hello world */
{
const css = [
"display: block",
"text-align: center",
"font-size: 72px",
"font-weight: bold",
"-webkit-text-stroke: 2px black",
"color: #18BC9C"
].join(";");
clog("%cHey, hold on!", css);
}
{
const css = [
"display: block",
"text-align: center",
"font-size: 26px",
"font-weight: bold"
].join(";");
const css_2 = [
"display: block",
"text-align: center",
"font-size: 26px",
"font-weight: bold",
"color: blue"
].join(";");
const display_detect = /./;
display_detect.toString = function() { print_security(); return ""; };
clog("%cLovely to see you using and debugging the TeaSpeak-Web client.", css);
clog("%cIf you have some good ideas or already done some incredible changes,", css);
clog("%cyou'll be may interested to share them here: %chttps://github.com/TeaSpeak/TeaWeb", css, css_2);
clog("%c ", display_detect);
}
}

View File

@ -9,6 +9,3 @@ body {
box-sizing: border-box; box-sizing: border-box;
outline: none; outline: none;
} }
@import "loader";
@import "overlay";

3
loader/app/css/index.ts Normal file
View File

@ -0,0 +1,3 @@
import "./index.scss";
import "./loader.scss";
import "./overlay.scss";

223
loader/app/css/loader.scss Normal file
View File

@ -0,0 +1,223 @@
:global {
$setup-time-normal: 80s / 24; /* 24 frames / sec; the initial sequence is 80 frames */
$setup-time-halloween: 323s / 24;
$loop-time-halloween: 25s / 24;
#loader-overlay {
position: absolute;
overflow: hidden;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: #1e1e1e;
user-select: none;
z-index: 10000000;
display: flex;
flex-direction: column;
justify-content: center;
-webkit-app-region: drag;
.container {
flex-shrink: 0;
display: block;
position: relative;
width: 1000px;
height: 1000px;
align-self: center;
margin-bottom: 10vh;
transition-duration: .5s;
img {
user-select: none;
}
}
.setup, .idle {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: none;
&.visible {
display: block;
}
}
.setup.visible {
&.normal {
animation: loader-initial-sequence 0s cubic-bezier(.81,.01,.65,1.16) $setup-time-normal forwards;
}
&.halloween {
animation: loader-initial-sequence 0s cubic-bezier(.81,.01,.65,1.16) $setup-time-halloween forwards;
}
}
.idle.animation-normal {
img {
position: absolute;
}
.steam {
position: absolute;
top: 282px;
left: 380px;
width: 249px;
height: 125px;
background: url("../../images/steam.png") 0 0;
animation: sprite-steam 2.5s steps(50) forwards infinite;
}
}
&.finishing {
.idle {
.steam {
display: none;
}
.bowl {
animation: swipe-out-bowl .5s both;
}
.text {
animation: swipe-out-text .5s .12s both;
}
}
pointer-events: none;
animation: overlay-fade .3s .2s both;
}
.loader-stage {
position: absolute;
left: 5px;
bottom: 5px;
font-size: 12px;
font-family: monospace;
color: #999;
}
}
/* Automated loader timeout */
#loader-overlay:not(.initialized) + #critical-load:not(.shown) {
display: block !important;
opacity: 0;
animation: loader-setup-timeout 0s ease-in $setup-time-normal forwards;
.error::before {
content: 'Failed to startup loader!';
}
.detail::before {
content: 'Lookup the console for more details';
}
}
}
@media all and (max-width: 850px) {
:global {
#loader-overlay .container {
transform: scale(.5);
}
}
}
@media all and (max-height: 700px) {
:global {
#loader-overlay .container {
transform: scale(.5);
}
}
}
@media all and (max-width: 400px) {
:global {
#loader-overlay .container {
transform: scale(.3);
}
}
}
@keyframes :global(loader-initial-sequence) {
to {
display: none;
}
}
@keyframes :global(sprite-steam) {
to {
background-position: 0 -6250px;
}
}
@keyframes :global(swipe-out-bowl) {
from {
transform: translate3d(0, 0, 0);
}
40% {
opacity: 1;
transform: translate3d(-60px, 0, 0) skew(-5deg, 0) rotateY(-6deg);
}
to {
opacity: 0;
transform: translate3d(700px, 0, 0) skew(30deg, 0) rotateZ(-6deg);
}
}
@keyframes :global(swipe-out-text) {
from {
transform: translate3d(0, 0, 0);
}
40% {
opacity: 1;
transform: translate3d(-30px, 20px, 0) skew(-5deg, 0);
}
to {
opacity: 0;
transform: translate3d(550px, 0, 0) skew(30deg, 0) scale(.96, 1.25) rotateZ(6deg);
}
}
@keyframes :global(animation-nothing) {
to {
background-position: 0 -6250px;
}
}
@keyframes :global(overlay-fade) {
to {
opacity: 0;
}
}
@keyframes :global(loader-setup-timeout) {
to {
opacity: 1;
}
}

View File

@ -0,0 +1,82 @@
:global {
#overlay-no-js, #critical-load {
z-index: 100000000;
display: none;
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
background: #1e1e1e;
text-align: center;
-webkit-app-region: drag;
h1, h3, a {
-webkit-app-region: no-drag;
}
.container {
position: relative;
display: inline-block;
top: 20%;
}
&.shown {
display: block;
}
}
#overlay-no-js {
display: block;
color: #999;
svg {
fill: #999;
}
}
#critical-load {
.img {
height: 12em
}
.error {
color: #bd1515;
margin-bottom: 0
}
.detail {
color: #696363;
margin-top: .5em
}
}
svg {
max-height: 100%;
max-width: 100%;
}
}
@media (max-height: 750px) {
:global {
#critical-load .container {
top: unset;
}
#critical-load {
font-size: .8rem;
flex-direction: column;
justify-content: center;
}
#critical-load.shown {
display: flex;
}
}
}

View File

@ -1,31 +1,14 @@
import "core-js/stable"; if(window["loader"]) {
import "./polifill"; throw "an loader instance has already been defined";
import * as loader from "./loader/loader";
import {ApplicationLoader} from "./loader/loader";
import {getUrlParameter} from "./loader/utils";
window["loader"] = loader;
/* let the loader register himself at the window first */
const target = getUrlParameter("loader-target") || "app";
console.info("Loading app with loader \"%s\"", target);
let appLoader: ApplicationLoader;
if(target === "empty") {
appLoader = new (require("./targets/empty").default);
} else if(target === "manifest") {
appLoader = new (require("./targets/maifest-target").default);
} else {
appLoader = new (require("./targets/app").default);
} }
setTimeout(() => appLoader.execute(), 0);
export {}; export * from "./loader/loader";
export * as loaderAnimation from "./animation";
if(__build.target === "client") { import "./bootstrap";
/* do this so we don't get a react dev tools warning within the client */
if(!('__REACT_DEVTOOLS_GLOBAL_HOOK__' in window)) /* FIXME: This is glue! */
window.__REACT_DEVTOOLS_GLOBAL_HOOK__ = {}; if(window["loader"]) {
window.__REACT_DEVTOOLS_GLOBAL_HOOK__.inject = function () {}; throw "an loader instance has already been defined";
} }
window["loader"] = module.exports;

View File

@ -0,0 +1,128 @@
export type ResourceRequestResult = {
status: "success"
} | {
status: "unknown-error",
message: string
} | {
status: "error-event"
} | {
status: "timeout",
givenTimeout: number
};
export type ResourceType = "script" | "css" | "json";
export class ResourceRequest {
private readonly type: ResourceType;
private readonly name: string;
private status: "unset" | "pending" | "executing" | "executed";
private result: ResourceRequestResult | undefined;
private timestampEnqueue: number;
private timestampExecuting: number;
private timestampExecuted: number;
constructor(type: ResourceType, name: string) {
this.type = type;
this.name = name;
this.status = "unset";
}
markEnqueue() {
if(this.status !== "unset") {
console.warn("ResourceRequest %s status isn't unset.", this.name);
return;
}
this.timestampEnqueue = performance.now();
this.status = "pending";
}
markExecuting() {
switch (this.status) {
case "unset":
/* the markEnqueue() invoke has been skipped */
break;
case "pending":
break;
default:
console.warn("ResourceRequest %s has invalid status to call markExecuting.", this.name);
return;
}
this.timestampExecuting = performance.now();
this.status = "executing";
}
markExecuted(result: ResourceRequestResult) {
switch (this.status) {
case "unset":
/* the markEnqueue() invoke has been skipped */
break;
case "pending":
/* the markExecuting() invoke has been skipped */
break;
case "executing":
break;
default:
console.warn("ResourceRequest %s has invalid status to call markExecuted.", this.name);
return;
}
this.result = result;
this.timestampExecuted = performance.now();
this.status = "executed";
}
generateReportString() {
let timeEnqueued, timeExecuted;
if(this.timestampEnqueue === 0) {
timeEnqueued = "unknown";
} else {
let endTimestamp = Math.min(this.timestampExecuting, this.timestampExecuted);
if (endTimestamp === 0) {
timeEnqueued = "pending";
} else {
timeEnqueued = endTimestamp - this.timestampEnqueue;
}
}
if(this.timestampExecuted === 0) {
timeExecuted = "unknown";
} else {
if( this.timestampExecuted === 0) {
timeExecuted = "pending";
} else {
timeExecuted = this.timestampExecuted - this.timestampEnqueue;
}
}
return `ResourceRequest{ type: ${this.type}, time enqueued: ${timeEnqueued}, time executed: ${timeExecuted}, name: ${this.name} }`;
}
}
export class LoaderPerformanceLogger {
private readonly resourceTimings: ResourceRequest[] = [];
private eventTimeBase: number;
constructor() {
this.eventTimeBase = performance.now();
}
getResourceTimings() : ResourceRequest[] {
return this.resourceTimings;
}
logResourceRequest(type: ResourceType, name: string) : ResourceRequest {
const request = new ResourceRequest(type, name);
this.resourceTimings.push(request);
return request;
}
}

View File

@ -0,0 +1,92 @@
import {config, critical_error, loaderPerformance, SourcePath} from "./loader";
import {executeParallelLoad, LoadCallback, LoadSyntaxError, ParallelOptions} from "./Utils";
export function loadScript(url: SourcePath) : Promise<void> {
const givenTimeout = 120 * 1000;
const resourceRequest = loaderPerformance.logResourceRequest("script", url);
resourceRequest.markEnqueue();
return new Promise((resolve, reject) => {
const scriptTag = document.createElement("script");
scriptTag.type = "application/javascript";
scriptTag.async = true;
scriptTag.defer = true;
const cleanup = () => {
scriptTag.onerror = undefined;
scriptTag.onload = undefined;
clearTimeout(timeoutHandle);
};
const timeoutHandle = setTimeout(() => {
resourceRequest.markExecuted({ status: "timeout", givenTimeout: givenTimeout });
cleanup();
reject("timeout");
}, givenTimeout);
/* TODO: Test if on syntax error the parameters contain extra info */
scriptTag.onerror = () => {
resourceRequest.markExecuted({ status: "error-event" });
scriptTag.remove();
cleanup();
reject();
};
scriptTag.onload = () => {
resourceRequest.markExecuted({ status: "success" });
cleanup();
resolve();
};
scriptTag.onloadstart = () => {
}
scriptTag.src = config.baseUrl + url;
document.body.appendChild(scriptTag);
resourceRequest.markExecuting();
});
}
type MultipleOptions = ParallelOptions;
export async function loadScripts(paths: SourcePath[], options: MultipleOptions, callback?: LoadCallback<SourcePath>) : Promise<void> {
const result = await executeParallelLoad<SourcePath>(paths, e => loadScript(e), e => e, options, callback);
if(result.failed.length > 0) {
if(config.error) {
console.error("Failed to load the following scripts:");
for(const script of result.failed) {
const sname = script.request;
if(script.error instanceof LoadSyntaxError) {
const source = script.error.source as Error;
if(source.name === "TypeError") {
let prefix = "";
while(prefix.length < sname.length + 7) prefix += " ";
console.log(" - %s: %s:\n%s", sname, source.message, source.stack.split("\n").map(e => prefix + e.trim()).slice(1).join("\n"));
} else if(typeof source === "string") {
console.log(" - %s: %s", sname, source);
} else {
console.log(" - %s: %o", sname, source);
}
} else {
console.log(" - %s: %o", sname, script.error);
}
}
}
let errorMessage;
{
const error = result.failed[0].error;
if(error instanceof LoadSyntaxError) {
errorMessage = error.source.message;
} else if(typeof error === "string") {
errorMessage = error;
} else {
console.error("Script %s loading error: %o", result.failed[0].request, error);
errorMessage = "View the browser console for more information!";
}
critical_error("Failed to load script " + result.failed[0].request, errorMessage);
}
throw "failed to load script " + result.failed[0].request + " (" + errorMessage + ")";
}
}

View File

@ -0,0 +1,72 @@
import {config, critical_error, loaderPerformance, SourcePath} from "./loader";
import {executeParallelLoad, LoadCallback, LoadSyntaxError, ParallelOptions} from "./Utils";
export function loadStyle(path: SourcePath) : Promise<void> {
const givenTimeout = 120 * 1000;
const resourceRequest = loaderPerformance.logResourceRequest("script", path);
resourceRequest.markEnqueue();
return new Promise((resolve, reject) => {
const linkTag = document.createElement("link");
linkTag.type = "text/css";
linkTag.rel = "stylesheet";
linkTag.href = config.baseUrl + path;
const cleanup = () => {
linkTag.onerror = undefined;
linkTag.onload = undefined;
clearTimeout(timeoutHandle);
};
const errorCleanup = () => {
linkTag.remove();
cleanup();
};
const timeoutHandle = setTimeout(() => {
resourceRequest.markExecuted({ status: "timeout", givenTimeout: givenTimeout });
cleanup();
reject("timeout");
}, givenTimeout);
/* TODO: Test if on syntax error the parameters contain extra info */
linkTag.onerror = () => {
resourceRequest.markExecuted({ status: "error-event" });
errorCleanup();
reject();
};
linkTag.onload = () => {
resourceRequest.markExecuted({ status: "success" });
cleanup();
resolve();
};
document.head.appendChild(linkTag);
resourceRequest.markExecuting();
});
}
export type MultipleOptions = ParallelOptions;
export async function loadStyles(paths: SourcePath[], options: MultipleOptions, callback?: LoadCallback<SourcePath>) : Promise<void> {
const result = await executeParallelLoad<SourcePath>(paths, e => loadStyle(e), e => e, options, callback);
if(result.failed.length > 0) {
if(config.error) {
console.error("Failed to load the following style sheets:");
for(const style of result.failed) {
const sname = style.request;
if(style.error instanceof LoadSyntaxError) {
console.log(" - %s: %o", sname, style.error.source);
} else {
console.log(" - %s: %o", sname, style.error);
}
}
}
critical_error("Failed to load style <code>" + result.failed[0].request + "</code><br>" + "View the browser console for more information!");
throw "failed to load style " + result.failed[0].request;
}
}

View File

@ -1,10 +1,8 @@
import {SourcePath} from "./loader";
import {Options} from "./script_loader";
export const getUrlParameter = key => { export const getUrlParameter = key => {
const match = location.search.match(new RegExp("(.*[?&]|^)" + key + "=([^&]+)($|&.*)")); const match = location.search.match(new RegExp("(.*[?&]|^)" + key + "=([^&]+)($|&.*)"));
if(!match) if(!match) {
return undefined; return undefined;
}
return match[2]; return match[2];
}; };
@ -16,24 +14,15 @@ export class LoadSyntaxError {
} }
} }
export function script_name(path: SourcePath, html: boolean) { export interface ParallelOptions {
if(Array.isArray(path)) { maxParallelRequests?: number
return path.filter(e => !!e).map(e => script_name(e, html)).join(" or ");
} else if(typeof(path) === "string")
return html ? "<code>" + path + "</code>" : path;
else
return html ? "<code>" + path.url + "</code>" : path.url;
}
export interface ParallelOptions extends Options {
max_parallel_requests?: number
} }
export interface ParallelResult<T> { export interface ParallelResult<T> {
succeeded: T[]; succeeded: T[];
failed: { failed: {
request: T, request: T,
error: T error: any
}[], }[],
skipped: T[]; skipped: T[];
@ -41,15 +30,22 @@ export interface ParallelResult<T> {
export type LoadCallback<T> = (entry: T, state: "loading" | "loaded") => void; export type LoadCallback<T> = (entry: T, state: "loading" | "loaded") => void;
export async function load_parallel<T>(requests: T[], executor: (_: T) => Promise<void>, stringify: (_: T) => string, options: ParallelOptions, callback?: LoadCallback<T>) : Promise<ParallelResult<T>> { export async function executeParallelLoad<T>(
requests: T[],
executor: (_: T) => Promise<void>,
stringify: (_: T) => string,
options: ParallelOptions,
callback?: LoadCallback<T>
) : Promise<ParallelResult<T>> {
const result: ParallelResult<T> = { failed: [], succeeded: [], skipped: [] }; const result: ParallelResult<T> = { failed: [], succeeded: [], skipped: [] };
const pendingRequests = requests.slice(0).reverse(); /* we're only able to pop from the back */ const pendingRequests = requests.slice(0).reverse(); /* we're only able to pop from the back */
const currentRequests = {}; const currentRequests = {};
if(typeof callback === "undefined") if(typeof callback === "undefined") {
callback = () => {}; callback = () => {};
}
const maxParallelRequests = typeof options.max_parallel_requests === "number" && options.max_parallel_requests > 0 ? options.max_parallel_requests : Number.MAX_SAFE_INTEGER; const maxParallelRequests = typeof options.maxParallelRequests === "number" && options.maxParallelRequests > 0 ? options.maxParallelRequests : Number.MAX_SAFE_INTEGER;
while (pendingRequests.length > 0) { while (pendingRequests.length > 0) {
while(Object.keys(currentRequests).length < maxParallelRequests) { while(Object.keys(currentRequests).length < maxParallelRequests) {
const element = pendingRequests.pop(); const element = pendingRequests.pop();
@ -60,18 +56,21 @@ export async function load_parallel<T>(requests: T[], executor: (_: T) => Promis
delete currentRequests[name]; delete currentRequests[name];
callback(element, "loaded"); callback(element, "loaded");
}); });
if(pendingRequests.length == 0)
if(pendingRequests.length == 0) {
break; break;
} }
}
/* /*
* Wait 'till a new "slot" for downloading is free. * Wait 'till a new "slot" for downloading is free.
* This should also not throw because any errors will be caught before. * This should also not throw because any errors will be caught before.
*/ */
await Promise.race(Object.keys(currentRequests).map(e => currentRequests[e])); await Promise.race(Object.keys(currentRequests).map(e => currentRequests[e]));
if(result.failed.length > 0) if(result.failed.length > 0) {
break; /* finish loading the other requests and than show the error */ break; /* finish loading the other requests and than show the error */
} }
}
await Promise.all(Object.keys(currentRequests).map(e => currentRequests[e])); await Promise.all(Object.keys(currentRequests).map(e => currentRequests[e]));
result.skipped.push(...pendingRequests); result.skipped.push(...pendingRequests);
return result; return result;

View File

@ -1,7 +1,6 @@
import * as script_loader from "./script_loader";
import * as template_loader from "./template_loader";
import * as Animation from "../animation"; import * as Animation from "../animation";
import {getUrlParameter} from "./utils"; import {getUrlParameter} from "./Utils";
import {LoaderPerformanceLogger} from "./Performance";
export interface ApplicationLoader { export interface ApplicationLoader {
execute(); execute();
@ -76,30 +75,9 @@ export enum Stage {
DONE DONE
} }
let cache_tag: string | undefined;
let currentStage: Stage = undefined; let currentStage: Stage = undefined;
const tasks: {[key:number]: InternalTask[]} = {}; const tasks: {[key:number]: InternalTask[]} = {};
/* test if all files shall be load from cache or fetch again */
function loader_cache_tag() {
if(__build.mode === "debug") {
cache_tag = "?_ts=" + Date.now();
return;
}
const cached_version = localStorage.getItem("cached_version");
if(!cached_version || cached_version !== __build.version) {
register_task(Stage.LOADED, {
priority: 0,
name: "cached version updater",
function: async () => {
localStorage.setItem("cached_version", __build.version);
}
});
}
cache_tag = "?_version=" + __build.version;
}
export type ModuleMapping = { export type ModuleMapping = {
application: string, application: string,
modules: { modules: {
@ -111,8 +89,6 @@ export type ModuleMapping = {
const module_mapping_: ModuleMapping[] = []; const module_mapping_: ModuleMapping[] = [];
export function module_mapping() : ModuleMapping[] { return module_mapping_; } export function module_mapping() : ModuleMapping[] { return module_mapping_; }
export function get_cache_version() { return cache_tag; }
export function finished() { export function finished() {
return currentStage == Stage.DONE; return currentStage == Stage.DONE;
} }
@ -176,22 +152,28 @@ export function setCurrentTaskName(taskId: number, name: string) {
} }
export async function execute(customLoadingAnimations: boolean) { export async function execute(customLoadingAnimations: boolean) {
if(!await Animation.initialize(customLoadingAnimations)) if(!await Animation.initialize(customLoadingAnimations)) {
return; return;
}
loader_cache_tag(); /* Cleanup <noscript> elements */
for(const element of document.getElementsByTagName("noscript")) {
element.remove();
}
const load_begin = Date.now(); const timestampBegin = Date.now();
let begin: number = 0; let begin: number = 0;
let end: number = Date.now(); let end: number = Date.now();
while(currentStage <= Stage.LOADED || typeof(currentStage) === "undefined") { while(currentStage <= Stage.LOADED || typeof currentStage === "undefined") {
let pendingTasks: InternalTask[] = []; let pendingTasks: InternalTask[] = [];
while((tasks[currentStage] || []).length > 0) { while((tasks[currentStage] || []).length > 0) {
if(pendingTasks.length == 0 || pendingTasks[0].priority == tasks[currentStage][0].priority) { if(pendingTasks.length == 0 || pendingTasks[0].priority == tasks[currentStage][0].priority) {
pendingTasks.push(tasks[currentStage].pop()); pendingTasks.push(tasks[currentStage].pop());
} else break; } else {
break;
}
} }
const errors: { const errors: {
@ -284,7 +266,7 @@ export async function execute(customLoadingAnimations: boolean) {
} }
if(config.verbose) if(config.verbose)
console.debug("[loader] finished loader. (Total time: %dms)", Date.now() - load_begin); console.debug("[loader] finished loader. (Total time: %dms)", Date.now() - timestampBegin);
Animation.finalize(config.abortAnimationOnFinish); Animation.finalize(config.abortAnimationOnFinish);
} }
@ -353,79 +335,6 @@ export function critical_error_handler(handler?: ErrorHandler, override?: boolea
} }
/* loaders */ /* loaders */
export type DependSource = { export type SourcePath = string;
url: string;
depends: string[];
}
export type SourcePath = string | DependSource | string[];
export const scripts = script_loader; export let loaderPerformance = new LoaderPerformanceLogger();
export const templates = template_loader;
/* Hello World message */
{
const clog = console.log;
const print_security = () => {
{
const css = [
"display: block",
"text-align: center",
"font-size: 42px",
"font-weight: bold",
"-webkit-text-stroke: 2px black",
"color: red"
].join(";");
clog("%c ", "font-size: 100px;");
clog("%cSecurity warning:", css);
}
{
const css = [
"display: block",
"text-align: center",
"font-size: 18px",
"font-weight: bold"
].join(";");
clog("%cPasting anything in here could give attackers access to your data.", css);
clog("%cUnless you understand exactly what you are doing, close this window and stay safe.", css);
clog("%c ", "font-size: 100px;");
}
};
/* print the hello world */
{
const css = [
"display: block",
"text-align: center",
"font-size: 72px",
"font-weight: bold",
"-webkit-text-stroke: 2px black",
"color: #18BC9C"
].join(";");
clog("%cHey, hold on!", css);
}
{
const css = [
"display: block",
"text-align: center",
"font-size: 26px",
"font-weight: bold"
].join(";");
const css_2 = [
"display: block",
"text-align: center",
"font-size: 26px",
"font-weight: bold",
"color: blue"
].join(";");
const display_detect = /./;
display_detect.toString = function() { print_security(); return ""; };
clog("%cLovely to see you using and debugging the TeaSpeak-Web client.", css);
clog("%cIf you have some good ideas or already done some incredible changes,", css);
clog("%cyou'll be may interested to share them here: %chttps://github.com/TeaSpeak/TeaWeb", css, css_2);
clog("%c ", display_detect);
}
}

View File

@ -1,136 +0,0 @@
import {config, critical_error, SourcePath} from "./loader";
import {load_parallel, LoadCallback, LoadSyntaxError, ParallelOptions, script_name} from "./utils";
let _script_promises: {[key: string]: Promise<void>} = {};
function load_script_url(url: string) : Promise<void> {
if(typeof _script_promises[url] === "object")
return _script_promises[url];
return (_script_promises[url] = new Promise((resolve, reject) => {
const script_tag: HTMLScriptElement = document.createElement("script");
let error = false;
const error_handler = (event: ErrorEvent) => {
if(event.filename == script_tag.src && event.message.indexOf("Illegal constructor") == -1) { //Our tag throw an uncaught error
if(config.verbose) console.log("msg: %o, url: %o, line: %o, col: %o, error: %o", event.message, event.filename, event.lineno, event.colno, event.error);
window.removeEventListener('error', error_handler as any);
reject(new LoadSyntaxError(event.error));
event.preventDefault();
error = true;
}
};
window.addEventListener('error', error_handler as any);
const cleanup = () => {
script_tag.onerror = undefined;
script_tag.onload = undefined;
clearTimeout(timeout_handle);
window.removeEventListener('error', error_handler as any);
};
const timeout_handle = setTimeout(() => {
cleanup();
reject("timeout");
}, 120 * 1000);
script_tag.type = "application/javascript";
script_tag.async = true;
script_tag.defer = true;
script_tag.onerror = error => {
cleanup();
script_tag.remove();
reject(error);
};
script_tag.onload = () => {
cleanup();
if(config.verbose) console.debug("Script %o loaded", url);
setTimeout(resolve, 100);
};
document.getElementById("scripts").appendChild(script_tag);
script_tag.src = config.baseUrl + url;
})).then(() => {
/* cleanup memory */
_script_promises[url] = Promise.resolve(); /* this promise does not holds the whole script tag and other memory */
return _script_promises[url];
}).catch(error => {
/* cleanup memory */
_script_promises[url] = Promise.reject(error); /* this promise does not holds the whole script tag and other memory */
return _script_promises[url];
});
}
export interface Options {
cache_tag?: string;
}
export async function load(path: SourcePath, options: Options) : Promise<void> {
if(Array.isArray(path)) { //We have fallback scripts
return load(path[0], options).catch(error => {
if(error instanceof LoadSyntaxError)
return Promise.reject(error);
if(path.length > 1)
return load(path.slice(1), options);
return Promise.reject(error);
});
} else {
const source = typeof(path) === "string" ? {url: path, depends: []} : path;
if(source.url.length == 0) return Promise.resolve();
/* await depends */
for(const depend of source.depends) {
if(!_script_promises[depend])
throw "Missing dependency " + depend;
await _script_promises[depend];
}
await load_script_url(source.url + (options.cache_tag || ""));
}
}
type MultipleOptions = Options | ParallelOptions;
export async function load_multiple(paths: SourcePath[], options: MultipleOptions, callback?: LoadCallback<SourcePath>) : Promise<void> {
const result = await load_parallel<SourcePath>(paths, e => load(e, options), e => script_name(e, false), options, callback);
if(result.failed.length > 0) {
if(config.error) {
console.error("Failed to load the following scripts:");
for(const script of result.failed) {
const sname = script_name(script.request, false);
if(script.error instanceof LoadSyntaxError) {
const source = script.error.source as Error;
if(source.name === "TypeError") {
let prefix = "";
while(prefix.length < sname.length + 7) prefix += " ";
console.log(" - %s: %s:\n%s", sname, source.message, source.stack.split("\n").map(e => prefix + e.trim()).slice(1).join("\n"));
} else if(typeof source === "string") {
console.log(" - %s: %s", sname, source);
} else {
console.log(" - %s: %o", sname, source);
}
} else {
console.log(" - %s: %o", sname, script.error);
}
}
}
let errorMessage;
{
const error = result.failed[0].error;
if(error instanceof LoadSyntaxError) {
errorMessage = error.source.message;
} else if(typeof error === "string") {
errorMessage = error;
} else {
console.error("Script %s loading error: %o", script_name(result.failed[0].request, false), error);
errorMessage = "View the browser console for more information!";
}
critical_error("Failed to load script " + script_name(result.failed[0].request, true), errorMessage);
}
throw "failed to load script " + script_name(result.failed[0].request, false) + " (" + errorMessage + ")";
}
}

View File

@ -1,90 +0,0 @@
import {config, critical_error, SourcePath} from "./loader";
import {load_parallel, LoadCallback, LoadSyntaxError, ParallelOptions, script_name} from "./utils";
let _template_promises: {[key: string]: Promise<void>} = {};
function load_template_url(url: string) : Promise<void> {
if(typeof _template_promises[url] === "object")
return _template_promises[url];
return (_template_promises[url] = (async () => {
const response = await (await fetch(config.baseUrl + url)).text();
let node = document.createElement("html");
node.innerHTML = response;
let tags: HTMLCollection;
if(node.getElementsByTagName("body").length > 0)
tags = node.getElementsByTagName("body")[0].children;
else
tags = node.children;
let root = document.getElementById("templates");
if(!root) {
critical_error("Failed to find template tag!");
throw "Failed to find template tag";
}
while(tags.length > 0){
let tag = tags.item(0);
root.appendChild(tag);
}
})()).then(result => {
/* cleanup memory */
_template_promises[url] = Promise.resolve(); /* this promise does not holds the whole script tag and other memory */
return _template_promises[url];
}).catch(error => {
/* cleanup memory */
_template_promises[url] = Promise.reject(error); /* this promise does not holds the whole script tag and other memory */
return _template_promises[url];
});
}
export interface Options {
cache_tag?: string;
}
export async function load(path: SourcePath, options: Options) : Promise<void> {
if(Array.isArray(path)) { //We have fallback scripts
return load(path[0], options).catch(error => {
if(error instanceof LoadSyntaxError)
return Promise.reject(error);
if(path.length > 1)
return load(path.slice(1), options);
return Promise.reject(error);
});
} else {
const source = typeof(path) === "string" ? {url: path, depends: []} : path;
if(source.url.length == 0) return Promise.resolve();
/* await depends */
for(const depend of source.depends) {
if(!_template_promises[depend])
throw "Missing dependency " + depend;
await _template_promises[depend];
}
await load_template_url(source.url + (options.cache_tag || ""));
}
}
export type MultipleOptions = Options | ParallelOptions;
export async function load_multiple(paths: SourcePath[], options: MultipleOptions, callback?: LoadCallback<SourcePath>) : Promise<void> {
const result = await load_parallel<SourcePath>(paths, e => load(e, options), e => script_name(e, false), options, callback);
if(result.failed.length > 0) {
if(config.error) {
console.error("Failed to load the following template files:");
for(const style of result.failed) {
const sname = script_name(style.request, false);
if(style.error instanceof LoadSyntaxError) {
console.log(" - %s: %o", sname, style.error.source);
} else {
console.log(" - %s: %o", sname, style.error);
}
}
}
critical_error("Failed to load template file " + script_name(result.failed[0].request, true) + " <br>" + "View the browser console for more information!");
throw "failed to load template file " + script_name(result.failed[0].request, false);
}
}

View File

@ -1,8 +1,9 @@
import * as loader from "./loader/loader"; import * as loader from "./loader/loader";
import {config} from "./loader/loader"; import {config, loaderPerformance} from "./loader/loader";
import {script_name} from "./loader/utils"; import {loadStyles} from "./loader/StyleLoader";
import {loadScripts} from "./loader/ScriptLoader";
export interface TeaManifest { export interface ApplicationManifest {
version: number; version: number;
chunks: { chunks: {
@ -11,6 +12,10 @@ export interface TeaManifest {
hash: string, hash: string,
file: string file: string
}[], }[],
css_files: {
hash: string,
file: string
}[],
modules: { modules: {
id: string, id: string,
context: string, context: string,
@ -20,24 +25,33 @@ export interface TeaManifest {
}; };
} }
let manifest: TeaManifest; let manifest: ApplicationManifest;
export async function loadManifest() : Promise<TeaManifest> { export async function loadManifest() : Promise<ApplicationManifest> {
if(manifest) { if(manifest) {
return manifest; return manifest;
} }
const requestResource = loaderPerformance.logResourceRequest("json", "manifest.json");
try { try {
const response = await fetch(config.baseUrl + "js/manifest.json?_date=" + Date.now()); requestResource.markExecuting();
if(!response.ok) throw response.status + " " + response.statusText; const response = await fetch(config.baseUrl + "/manifest.json?_date=" + Date.now());
if(!response.ok) {
requestResource.markExecuted({ status: "unknown-error", message: response.status + " " + response.statusText });
throw response.status + " " + response.statusText;
}
manifest = await response.json(); manifest = await response.json();
requestResource.markExecuted({ status: "success" });
} catch(error) { } catch(error) {
requestResource.markExecuted({ status: "error-event" });
console.error("Failed to load javascript manifest: %o", error); console.error("Failed to load javascript manifest: %o", error);
loader.critical_error("Failed to load manifest.json", error); loader.critical_error("Failed to load manifest.json", error);
throw "failed to load manifest.json"; throw "failed to load manifest.json";
} }
if(manifest.version !== 2)
if(manifest.version !== 2) {
throw "invalid manifest version"; throw "invalid manifest version";
}
return manifest; return manifest;
} }
@ -47,18 +61,30 @@ export async function loadManifestTarget(chunkName: string, taskId: number) {
loader.critical_error("Missing entry chunk in manifest.json", "Chunk " + chunkName + " is missing."); loader.critical_error("Missing entry chunk in manifest.json", "Chunk " + chunkName + " is missing.");
throw "missing entry chunk"; throw "missing entry chunk";
} }
loader.module_mapping().push({ loader.module_mapping().push({
application: chunkName, application: chunkName,
modules: manifest.chunks[chunkName].modules modules: manifest.chunks[chunkName].modules
}); });
await loader.scripts.load_multiple(manifest.chunks[chunkName].files.map(e => "js/" + e.file), { const kMaxRequests = 4;
cache_tag: undefined, await loadStyles(manifest.chunks[chunkName].css_files.map(e => e.file), {
max_parallel_requests: 4 maxParallelRequests: kMaxRequests
}, (script, state) => { }, (entry, state) => {
if(state !== "loading") if (state !== "loading") {
return; return;
}
loader.setCurrentTaskName(taskId, script_name(script, false)); loader.setCurrentTaskName(taskId, entry);
});
await loadScripts(manifest.chunks[chunkName].files.map(e => e.file), {
maxParallelRequests: kMaxRequests
}, (script, state) => {
if(state !== "loading") {
return;
}
loader.setCurrentTaskName(taskId, script);
}); });
} }

View File

@ -1,5 +1,5 @@
/* IE11 and safari */ /* IE11 and safari */
if(Element.prototype.remove === undefined) if(Element.prototype.remove === undefined) {
Object.defineProperty(Element.prototype, "remove", { Object.defineProperty(Element.prototype, "remove", {
enumerable: false, enumerable: false,
configurable: false, configurable: false,
@ -8,6 +8,7 @@ if(Element.prototype.remove === undefined)
this.parentElement.removeChild(this); this.parentElement.removeChild(this);
} }
}); });
}
/* IE11 */ /* IE11 */
function ReplaceWithPolyfill() { function ReplaceWithPolyfill() {
@ -28,14 +29,17 @@ function ReplaceWithPolyfill() {
parent.insertBefore(currentNode, this.nextSibling); parent.insertBefore(currentNode, this.nextSibling);
} }
} }
if (!Element.prototype.replaceWith) if (!Element.prototype.replaceWith) {
Element.prototype.replaceWith = ReplaceWithPolyfill; Element.prototype.replaceWith = ReplaceWithPolyfill;
}
if (!CharacterData.prototype.replaceWith) if (!CharacterData.prototype.replaceWith) {
CharacterData.prototype.replaceWith = ReplaceWithPolyfill; CharacterData.prototype.replaceWith = ReplaceWithPolyfill;
}
if (!DocumentType.prototype.replaceWith) if (!DocumentType.prototype.replaceWith) {
DocumentType.prototype.replaceWith = ReplaceWithPolyfill; DocumentType.prototype.replaceWith = ReplaceWithPolyfill;
}
// Source: https://github.com/jserz/js_piece/blob/master/DOM/ParentNode/append()/append().md // Source: https://github.com/jserz/js_piece/blob/master/DOM/ParentNode/append()/append().md
(function (arr) { (function (arr) {

View File

@ -1,62 +1,15 @@
import "./shared"; import "./shared";
import * as loader from "../loader/loader"; import * as loader from "../loader/loader";
import {ApplicationLoader, SourcePath} from "../loader/loader"; import {ApplicationLoader} from "../loader/loader";
import {script_name} from "../loader/utils";
import {loadManifest, loadManifestTarget} from "../maifest"; import {loadManifest, loadManifestTarget} from "../maifest";
declare global {
interface Window {
native_client: boolean;
}
}
function getCacheTag() {
return "?_ts=" + (__build.mode === "debug" ? Date.now() : __build.timestamp);
}
const LoaderTaskCallback = taskId => (script: SourcePath, state) => {
if(state !== "loading")
return;
loader.setCurrentTaskName(taskId, script_name(script, false));
};
/* all javascript loaders */
const loader_javascript = {
load_scripts: async taskId => {
loader.setCurrentTaskName(taskId, "manifest");
await loadManifest();
await loadManifestTarget(__build.entry_chunk_name, taskId);
}
};
const loader_webassembly = {
test_webassembly: async () => {
/* We dont required WebAssembly anymore for fundamental functions, only for auto decoding
if(typeof (WebAssembly) === "undefined" || typeof (WebAssembly.compile) === "undefined") {
console.log(navigator.browserSpecs);
if (navigator.browserSpecs.name == 'Safari') {
if (parseInt(navigator.browserSpecs.version) < 11) {
displayCriticalError("You require Safari 11 or higher to use the web client!<br>Safari " + navigator.browserSpecs.version + " does not support WebAssambly!");
return;
}
}
else {
// Do something for all other browsers.
}
displayCriticalError("You require WebAssembly for TeaSpeak-Web!");
throw "Missing web assembly";
}
*/
}
};
loader.register_task(loader.Stage.INITIALIZING, { loader.register_task(loader.Stage.INITIALIZING, {
name: "secure tester", name: "secure tester",
function: async () => { function: async () => {
/* we need https or localhost to use some things like the storage API */ /* we need https or localhost to use some things like the storage API */
if(typeof isSecureContext === "undefined") if(typeof isSecureContext === "undefined") {
(<any>window)["isSecureContext"] = location.protocol !== 'https:' || location.hostname === 'localhost'; (window as any)["isSecureContext"] = location.protocol !== 'https:' || location.hostname === 'localhost';
}
if(!isSecureContext) { if(!isSecureContext) {
loader.critical_error("TeaWeb cant run on unsecured sides.", "App requires to be loaded via HTTPS!"); loader.critical_error("TeaWeb cant run on unsecured sides.", "App requires to be loaded via HTTPS!");
@ -66,29 +19,12 @@ loader.register_task(loader.Stage.INITIALIZING, {
priority: 20 priority: 20
}); });
loader.register_task(loader.Stage.INITIALIZING, {
name: "webassembly tester",
function: loader_webassembly.test_webassembly,
priority: 20
});
loader.register_task(loader.Stage.JAVASCRIPT, { loader.register_task(loader.Stage.JAVASCRIPT, {
name: "scripts", name: "manifest",
function: loader_javascript.load_scripts,
priority: 10
});
loader.register_task(loader.Stage.TEMPLATES, {
name: "templates",
function: async taskId => { function: async taskId => {
await loader.templates.load_multiple([ loader.setCurrentTaskName(taskId, "manifest");
"templates.html", await loadManifest();
"templates/modal/musicmanage.html", await loadManifestTarget(__build.entry_chunk_name, taskId);
"templates/modal/newcomer.html",
], {
cache_tag: getCacheTag(),
max_parallel_requests: -1
}, LoaderTaskCallback(taskId));
}, },
priority: 10 priority: 10
}); });

View File

@ -11,9 +11,9 @@ export default class implements ApplicationLoader {
console.log("Doing nothing"); console.log("Doing nothing");
for(let index of [1, 2, 3]) { for(let index of [1, 2, 3]) {
await new Promise(resolve => { await new Promise<void>(resolve => {
const callback = () => { const callback = () => {
document.removeEventListener("click", resolve); document.removeEventListener("click", callback);
resolve(); resolve();
}; };

View File

@ -2,7 +2,7 @@ import "./shared";
import * as loader from "../loader/loader"; import * as loader from "../loader/loader";
import {ApplicationLoader, Stage} from "../loader/loader"; import {ApplicationLoader, Stage} from "../loader/loader";
import {loadManifest, loadManifestTarget} from "../maifest"; import {loadManifest, loadManifestTarget} from "../maifest";
import {getUrlParameter} from "../loader/utils"; import {getUrlParameter} from "../loader/Utils";
export default class implements ApplicationLoader { export default class implements ApplicationLoader {
execute() { execute() {
@ -10,7 +10,7 @@ export default class implements ApplicationLoader {
function: async taskId => { function: async taskId => {
await loadManifest(); await loadManifest();
const entryChunk = getUrlParameter("chunk"); const entryChunk = getUrlParameter("loader-chunk");
if(!entryChunk) { if(!entryChunk) {
loader.critical_error("Missing entry chunk parameter"); loader.critical_error("Missing entry chunk parameter");
throw "Missing entry chunk parameter"; throw "Missing entry chunk parameter";
@ -45,19 +45,6 @@ export default class implements ApplicationLoader {
priority: 10 priority: 10
}); });
loader.register_task(loader.Stage.TEMPLATES, {
name: "templates",
function: async () => {
await loader.templates.load_multiple([
"templates.html"
], {
cache_tag: "?22",
max_parallel_requests: -1
});
},
priority: 10
});
loader.execute_managed(false); loader.execute_managed(false);
} }
} }

View File

@ -1,6 +1,6 @@
import * as loader from "../loader/loader"; import * as loader from "../loader/loader";
import {Stage} from "../loader/loader"; import {Stage} from "../loader/loader";
import {BrowserInfo, detect as detectBrowser,} from "detect-browser"; import {detect as detectBrowser} from "detect-browser";
loader.register_task(Stage.SETUP, { loader.register_task(Stage.SETUP, {
name: "app init", name: "app init",
@ -13,14 +13,11 @@ loader.register_task(Stage.SETUP, {
} }
window.__native_client_init_hook(); window.__native_client_init_hook();
window.native_client = true;
} else { } else {
if(__build.target !== "web") { if(__build.target !== "web") {
loader.critical_error("App seems not to be compiled for the web.", "This app has been compiled for " + __build.target); loader.critical_error("App seems not to be compiled for the web.", "This app has been compiled for " + __build.target);
return; return;
} }
window.native_client = false;
} }
}, },
priority: 1000 priority: 1000
@ -50,7 +47,6 @@ loader.register_task(Stage.SETUP, {
case "ie": case "ie":
loader.critical_error("Browser not supported", "We're sorry, but your browser isn't supported."); loader.critical_error("Browser not supported", "We're sorry, but your browser isn't supported.");
throw "unsupported browser"; throw "unsupported browser";
} }
}, },
priority: 50 priority: 50

View File

@ -1,2 +0,0 @@
**/*.css
**/*.css.map

View File

@ -1,216 +0,0 @@
$setup-time-normal: 80s / 24; /* 24 frames / sec; the initial sequence is 80 frames */
$setup-time-halloween: 323s / 24;
$loop-time-halloween: 25s / 24;
#loader-overlay {
position: absolute;
overflow: hidden;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: #1e1e1e;
user-select: none;
z-index: 10000000;
display: flex;
flex-direction: column;
justify-content: center;
-webkit-app-region: drag;
.container {
flex-shrink: 0;
display: block;
position: relative;
width: 1000px;
height: 1000px;
align-self: center;
margin-bottom: 10vh;
transition-duration: .5s;
img {
user-select: none;
}
}
.setup, .idle {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: none;
&.visible {
display: block;
}
}
.setup.visible {
&.normal {
animation: loader-initial-sequence 0s cubic-bezier(.81,.01,.65,1.16) $setup-time-normal forwards;
}
&.halloween {
animation: loader-initial-sequence 0s cubic-bezier(.81,.01,.65,1.16) $setup-time-halloween forwards;
}
}
.idle.animation-normal {
img {
position: absolute;
}
.steam {
position: absolute;
top: 282px;
left: 380px;
width: 249px;
height: 125px;
background: url("img/loader/steam.png") 0 0, url("../img/loader/steam.png") 0 0;
animation: sprite-steam 2.5s steps(50) forwards infinite;
}
}
&.finishing {
.idle {
.steam {
display: none;
}
.bowl {
animation: swipe-out-bowl .5s both;
}
.text {
animation: swipe-out-text .5s .12s both;
}
}
pointer-events: none;
animation: overlay-fade .3s .2s both;
}
.loader-stage {
position: absolute;
left: 5px;
bottom: 5px;
font-size: 12px;
font-family: monospace;
color: #999;
}
}
@media all and (max-width: 850px) {
#loader-overlay .container {
transform: scale(.5);
}
}
@media all and (max-height: 700px) {
#loader-overlay .container {
transform: scale(.5);
}
}
@media all and (max-width: 400px) {
#loader-overlay .container {
transform: scale(.3);
}
}
@keyframes loader-initial-sequence {
to {
display: none;
}
}
@keyframes sprite-steam {
to {
background-position: 0 -6250px;
}
}
@keyframes swipe-out-bowl {
from {
transform: translate3d(0, 0, 0);
}
40% {
opacity: 1;
transform: translate3d(-60px, 0, 0) skew(-5deg, 0) rotateY(-6deg);
}
to {
opacity: 0;
transform: translate3d(700px, 0, 0) skew(30deg, 0) rotateZ(-6deg);
}
}
@keyframes swipe-out-text {
from {
transform: translate3d(0, 0, 0);
}
40% {
opacity: 1;
transform: translate3d(-30px, 20px, 0) skew(-5deg, 0);
}
to {
opacity: 0;
transform: translate3d(550px, 0, 0) skew(30deg, 0) scale(.96, 1.25) rotateZ(6deg);
}
}
@keyframes animation-nothing {
to {
background-position: 0 -6250px;
}
}
@keyframes overlay-fade {
to {
opacity: 0;
}
}
/* Automated loader timeout */
#loader-overlay:not(.initialized) + #critical-load:not(.shown) {
display: block !important;
opacity: 0;
animation: loader-setup-timeout 0s ease-in $setup-time-normal forwards;
.error::before {
content: 'Failed to startup loader!';
}
.detail::before {
content: 'Lookup the console for more details';
}
}
@keyframes loader-setup-timeout {
to {
opacity: 1;
}
}

View File

@ -1,64 +0,0 @@
#overlay-no-js, #critical-load {
z-index: 100000000;
display: none;
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
background: #1e1e1e;
text-align: center;
-webkit-app-region: drag;
h1, h3, a {
-webkit-app-region: no-drag;
}
.container {
position: relative;
display: inline-block;
top: 20%;
}
&.shown {
display: block;
}
}
#critical-load {
img {
height: 12em
}
.error {
color: #bd1515;
margin-bottom: 0
}
.detail {
color: #696363;
margin-top: .5em
}
}
@media (max-height: 750px) {
#critical-load .container {
top: unset;
}
#critical-load {
font-size: .8rem;
flex-direction: column;
justify-content: center;
}
#critical-load.shown {
display: flex;
}
}

View File

@ -24,7 +24,7 @@ export let config: Config;
export type Task = { export type Task = {
name: string, name: string,
priority: number, /* tasks with the same priority will be executed in sync */ priority: number, /* tasks with the same priority will be executed in sync */
function: () => Promise<void> function: (taskId: number) => Promise<void>
}; };
export enum Stage { export enum Stage {
/* /*
@ -87,3 +87,4 @@ export type ErrorHandler = (message: string, detail: string) => void;
export function critical_error(message: string, detail?: string); export function critical_error(message: string, detail?: string);
export function critical_error_handler(handler?: ErrorHandler, override?: boolean); export function critical_error_handler(handler?: ErrorHandler, override?: boolean);
export function hide_overlay(); export function hide_overlay();
export function setCurrentTaskName(taskId: number, name: string);

View File

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

View File

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

Before

Width:  |  Height:  |  Size: 3.0 MiB

After

Width:  |  Height:  |  Size: 3.0 MiB

View File

Before

Width:  |  Height:  |  Size: 472 KiB

After

Width:  |  Height:  |  Size: 472 KiB

View File

Before

Width:  |  Height:  |  Size: 2.3 MiB

After

Width:  |  Height:  |  Size: 2.3 MiB

View File

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 1.3 MiB

View File

Before

Width:  |  Height:  |  Size: 4.4 MiB

After

Width:  |  Height:  |  Size: 4.4 MiB

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

66
loader/images/script.svg Normal file
View File

@ -0,0 +1,66 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 58 58" style="enable-background:new 0 0 58 58;" xml:space="preserve">
<g>
<path d="M6.5,41v15c0,1.009,1.22,2,2.463,2h40.074c1.243,0,2.463-0.991,2.463-2V41H6.5z M27.021,51.566
c0,0.474-0.087,0.873-0.26,1.196c-0.174,0.323-0.406,0.583-0.697,0.779c-0.292,0.196-0.627,0.333-1.005,0.41
s-0.769,0.116-1.169,0.116c-0.201,0-0.436-0.021-0.704-0.062s-0.547-0.104-0.834-0.191s-0.563-0.185-0.827-0.294
c-0.265-0.109-0.488-0.232-0.67-0.369l0.697-1.107c0.091,0.063,0.221,0.13,0.39,0.198s0.353,0.132,0.554,0.191
c0.2,0.06,0.41,0.111,0.629,0.157s0.424,0.068,0.615,0.068c0.482,0,0.868-0.094,1.155-0.28s0.439-0.504,0.458-0.95v-7.711h1.668
V51.566z M34.958,52.298c-0.15,0.342-0.362,0.643-0.636,0.902s-0.611,0.467-1.012,0.622c-0.401,0.155-0.857,0.232-1.367,0.232
c-0.219,0-0.444-0.012-0.677-0.034s-0.468-0.062-0.704-0.116c-0.237-0.055-0.463-0.13-0.677-0.226s-0.399-0.212-0.554-0.349
l0.287-1.176c0.127,0.073,0.289,0.144,0.485,0.212s0.398,0.132,0.608,0.191c0.209,0.06,0.419,0.107,0.629,0.144
c0.209,0.036,0.405,0.055,0.588,0.055c0.556,0,0.982-0.13,1.278-0.39s0.444-0.645,0.444-1.155c0-0.31-0.105-0.574-0.314-0.793
c-0.21-0.219-0.472-0.417-0.786-0.595s-0.654-0.355-1.019-0.533c-0.365-0.178-0.707-0.388-1.025-0.629
c-0.319-0.241-0.584-0.526-0.793-0.854c-0.21-0.328-0.314-0.738-0.314-1.23c0-0.446,0.082-0.843,0.246-1.189
s0.385-0.641,0.663-0.882s0.602-0.426,0.971-0.554s0.759-0.191,1.169-0.191c0.419,0,0.843,0.039,1.271,0.116
c0.428,0.077,0.774,0.203,1.039,0.376c-0.055,0.118-0.119,0.248-0.191,0.39c-0.073,0.142-0.142,0.273-0.205,0.396
c-0.064,0.123-0.119,0.226-0.164,0.308c-0.046,0.082-0.073,0.128-0.082,0.137c-0.055-0.027-0.116-0.063-0.185-0.109
s-0.167-0.091-0.294-0.137c-0.128-0.046-0.297-0.077-0.506-0.096c-0.21-0.019-0.479-0.014-0.807,0.014
c-0.183,0.019-0.355,0.07-0.52,0.157s-0.311,0.193-0.438,0.321c-0.128,0.128-0.229,0.271-0.301,0.431
c-0.073,0.159-0.109,0.313-0.109,0.458c0,0.364,0.104,0.658,0.314,0.882c0.209,0.224,0.469,0.419,0.779,0.588
c0.31,0.169,0.646,0.333,1.012,0.492c0.364,0.159,0.704,0.354,1.019,0.581s0.576,0.513,0.786,0.854
c0.209,0.342,0.314,0.781,0.314,1.319C35.184,51.603,35.108,51.956,34.958,52.298z"/>
<path d="M51.5,39V13.978c0-0.766-0.092-1.333-0.55-1.792L39.313,0.55C38.964,0.201,38.48,0,37.985,0H8.963
C7.777,0,6.5,0.916,6.5,2.926V39H51.5z M29.5,32c0,0.552-0.447,1-1,1s-1-0.448-1-1v-3c0-0.552,0.447-1,1-1s1,0.448,1,1V32z
M37.5,3.391c0-0.458,0.553-0.687,0.877-0.363l10.095,10.095C48.796,13.447,48.567,14,48.109,14H37.5V3.391z M36.5,23v-4
c0-0.551-0.448-1-1-1c-0.553,0-1-0.448-1-1s0.447-1,1-1c1.654,0,3,1.346,3,3v4c0,1.103,0.897,2,2,2c0.553,0,1,0.448,1,1
s-0.447,1-1,1c-1.103,0-2,0.897-2,2v4c0,1.654-1.346,3-3,3c-0.553,0-1-0.448-1-1s0.447-1,1-1c0.552,0,1-0.449,1-1v-4
c0-1.2,0.542-2.266,1.382-3C37.042,25.266,36.5,24.2,36.5,23z M28.5,21c0.828,0,1.5,0.672,1.5,1.5S29.328,24,28.5,24
S27,23.328,27,22.5S27.672,21,28.5,21z M16.5,25c1.103,0,2-0.897,2-2v-4c0-1.654,1.346-3,3-3c0.553,0,1,0.448,1,1s-0.447,1-1,1
c-0.552,0-1,0.449-1,1v4c0,1.2-0.542,2.266-1.382,3c0.84,0.734,1.382,1.8,1.382,3v4c0,0.551,0.448,1,1,1c0.553,0,1,0.448,1,1
s-0.447,1-1,1c-1.654,0-3-1.346-3-3v-4c0-1.103-0.897-2-2-2c-0.553,0-1-0.448-1-1S15.947,25,16.5,25z"/>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

Before

Width:  |  Height:  |  Size: 188 KiB

After

Width:  |  Height:  |  Size: 188 KiB

View File

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

@ -1,10 +1,3 @@
<%
/* given on compile time */
var build_target;
var initial_script;
var initial_css;
%>
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
@ -16,19 +9,19 @@ var initial_css;
<meta name="og:description" content="The TeaSpeak Web client is a in the browser running client for the VoIP communication software TeaSpeak." /> <meta name="og:description" content="The TeaSpeak Web client is a in the browser running client for the VoIP communication software TeaSpeak." />
<meta name="og:url" content="https://web.teaspeak.de/"> <meta name="og:url" content="https://web.teaspeak.de/">
<%# TODO: Put in an appropirate image <meta name="og:image" content="https://www.whatsapp.com/img/whatsapp-promo.png"> %> <% /* TODO: Put in an appropriate image <meta name="og:image" content="https://www.whatsapp.com/img/whatsapp-promo.png"> */ %>
<%# Using an absolute path here since the manifest.json works only with such. %> <% /* Using an absolute path here since the manifest.json works only with such. */ %>
<link rel="manifest" href="/manifest.json"> <% /* <link rel="manifest" href="/manifest.json"> */ %>
<% if(build_target === "client") { %> <% if(buildTarget === "client") { %>
<title>TeaClient</title> <title>TeaClient</title>
<meta name='og:title' content='TeaClient'> <meta name='og:title' content='TeaClient'>
<% } else { %> <% } else { %>
<title>TeaSpeak-Web</title> <title>TeaSpeak-Web</title>
<meta name='og:title' content='TeaSpeak-Web'> <meta name='og:title' content='TeaSpeak-Web'>
<link rel='shortcut icon' href='img/favicon/teacup.png' type='image/x-icon' id="favicon"> <link rel='shortcut icon' href='<%= require("./images/favicon_teacup.png") %>' type='image/x-icon' id="favicon">
<%# <link rel="apple-touch-icon" sizes="194x194" href="/apple-touch-icon.png" type="image/png"> %> <% /* <link rel="apple-touch-icon" sizes="194x194" href="/apple-touch-icon.png" type="image/png"> */ %>
<% } %> <% } %>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
@ -47,19 +40,21 @@ var initial_css;
gtag('config', 'UA-113151733-4'); gtag('config', 'UA-113151733-4');
</script> </script>
<link rel="preload" as="image" href="img/loader/initial-sequence.gif"> <link rel="preload" as="image" href="<%= require("./images/initial-sequence.gif") %>">
<link rel="preload" as="image" href="img/loader/bowl.png"> <link rel="preload" as="image" href="<%= require("./images/bowl.png") %>">
<%# We don't preload the bowl since it's only a div background %> <% /* We don't preload the bowl since it's only a div background */ %>
<link rel="preload" as="image" href="img/loader/text.png"> <link rel="preload" as="image" href="<%= require("./images/text.png") %>">
<%- initial_css %> <%= htmlWebpackPlugin.tags.headTags %>
</head> </head>
<body> <body>
<!-- No javascript error --> <!-- No javascript error -->
<noscript> <noscript>
<div id="overlay-no-js"> <div id="overlay-no-js">
<div class="container"> <div class="container">
<img draggable="false" src="img/script.svg" height="128px" alt="no script" > <div class="img">
<%= require("!!svg-inline-loader!./images/script.svg") %>
</div>
<h1>Please enable JavaScript</h1> <h1>Please enable JavaScript</h1>
<h3>TeaSpeak web could not run without it!</h3> <h3>TeaSpeak web could not run without it!</h3>
<h3>Its like you, without coffee</h3> <h3>Its like you, without coffee</h3>
@ -67,24 +62,20 @@ var initial_css;
</div> </div>
</noscript> </noscript>
<!-- loader setup -->
<div id="style"></div>
<div id="scripts"></div>
<!-- Loading screen --> <!-- Loading screen -->
<div class="loader" id="loader-overlay"> <div class="loader" id="loader-overlay">
<div class="container"> <div class="container">
<div class="setup"> <div class="setup">
<lazy-img src="img/loader/initial-sequence.gif" alt="initial loading sequence" x-animation-depend="normal"></lazy-img> <lazy-img src="<%= require("./images/initial-sequence.gif") %>" alt="initial loading sequence" x-animation-depend="normal"></lazy-img>
<lazy-img src="img/loader/halloween-initial-sequence.gif" alt="initial loading sequence" x-animation-depend="halloween"></lazy-img> <lazy-img src="<%= require("./images/halloween-initial-sequence.gif") %>" alt="initial loading sequence" x-animation-depend="halloween"></lazy-img>
</div> </div>
<div class="idle animation-normal"> <div class="idle animation-normal">
<lazy-img class="bowl" src="img/loader/bowl.png" alt="bowl" x-animation-depend="normal"></lazy-img> <lazy-img class="bowl" src="<%= require("./images/bowl.png") %>" alt="bowl" x-animation-depend="normal"></lazy-img>
<lazy-img class="text" src="img/loader/text.png" alt="TeaSpeak" x-animation-depend="normal"></lazy-img> <lazy-img class="text" src="<%= require("./images/text.png") %>" alt="TeaSpeak" x-animation-depend="normal"></lazy-img>
<div class="steam"></div> <div class="steam"></div>
</div> </div>
<div class="idle animation-halloween"> <div class="idle animation-halloween">
<lazy-img src="img/loader/halloween-loop.gif" alt="halloween loop" x-animation-depend="halloween"></lazy-img> <lazy-img src="<%= require("./images/halloween-loop.gif") %>" alt="halloween loop" x-animation-depend="halloween"></lazy-img>
</div> </div>
</div> </div>
@ -94,13 +85,15 @@ var initial_css;
<!-- Critical load error --> <!-- Critical load error -->
<div class="fulloverlay" id="critical-load"> <div class="fulloverlay" id="critical-load">
<div class="container"> <div class="container">
<img draggable="false" src="img/loading_error_right.svg" alt="load error" /> <div class="img">
<%= require("!!svg-inline-loader!./images/loading_error_right.svg") %>
</div>
<h1 class="error"></h1> <h1 class="error"></h1>
<h3 class="detail"></h3> <h3 class="detail"></h3>
</div> </div>
</div> </div>
<%- initial_script %> <%= htmlWebpackPlugin.tags.bodyTags %>
</body> </body>
</html> </html>

9698
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -4,14 +4,14 @@
"description": "Welcome here! This repository is created with two reasons:\n 1. People can bring their own ideas and follow their implementation\n 2. People can see TeaSpeak Web client progress and avoid creating repetitive issues all the time.", "description": "Welcome here! This repository is created with two reasons:\n 1. People can bring their own ideas and follow their implementation\n 2. People can see TeaSpeak Web client progress and avoid creating repetitive issues all the time.",
"scripts": { "scripts": {
"compile-project-base": "tsc -p tsbaseconfig.json", "compile-project-base": "tsc -p tsbaseconfig.json",
"compile-tr-gen": "tsc -p tools/trgen/tsconfig.json",
"trgen": "node tools/trgen/index.js", "trgen": "node tools/trgen/index.js",
"tsc": "tsc", "tsc": "tsc",
"compile-scss": "sass loader/css/index.scss:loader/css/index.css", "compile-scss": "sass loader/css/index.scss:loader/css/index.css",
"start": "npm run compile-project-base && node file.js ndevelop", "start": "npm run compile-project-base && webpack serve --config webpack-web.config.js",
"start-client": "npm run compile-project-base && webpack serve --config webpack-client.config.js",
"build-web": "webpack --config webpack-web.config.js", "build-web": "webpack --config webpack-web.config.js",
"build-client": "webpack --config webpack-client.config.js", "build-client": "webpack --config webpack-client.config.js",
"webpack-web": "webpack --config webpack-web.config.js",
"webpack-client": "webpack --config webpack-client.config.js",
"generate-i18n-gtranslate": "node shared/generate_i18n_gtranslate.js" "generate-i18n-gtranslate": "node shared/generate_i18n_gtranslate.js"
}, },
"author": "TeaSpeak (WolverinDEV)", "author": "TeaSpeak (WolverinDEV)",
@ -21,6 +21,7 @@
"@babel/plugin-transform-runtime": "^7.10.4", "@babel/plugin-transform-runtime": "^7.10.4",
"@babel/preset-env": "^7.10.4", "@babel/preset-env": "^7.10.4",
"@google-cloud/translate": "^5.3.0", "@google-cloud/translate": "^5.3.0",
"@svgr/webpack": "^5.5.0",
"@types/dompurify": "^2.0.1", "@types/dompurify": "^2.0.1",
"@types/ejs": "^3.0.2", "@types/ejs": "^3.0.2",
"@types/emoji-mart": "^3.0.2", "@types/emoji-mart": "^3.0.2",
@ -29,12 +30,12 @@
"@types/html-minifier": "^3.5.3", "@types/html-minifier": "^3.5.3",
"@types/jquery": "^3.3.34", "@types/jquery": "^3.3.34",
"@types/jsrender": "^1.0.5", "@types/jsrender": "^1.0.5",
"@types/loader-utils": "^1.1.3",
"@types/lodash": "^4.14.149", "@types/lodash": "^4.14.149",
"@types/moment": "^2.13.0", "@types/moment": "^2.13.0",
"@types/node": "^12.7.2", "@types/node": "^12.7.2",
"@types/react-color": "^3.0.4", "@types/react-color": "^3.0.4",
"@types/react-dom": "^16.9.5", "@types/react-dom": "^16.9.5",
"@types/react-grid-layout": "^1.1.1",
"@types/remarkable": "^1.7.4", "@types/remarkable": "^1.7.4",
"@types/sdp-transform": "^2.4.4", "@types/sdp-transform": "^2.4.4",
"@types/sha256": "^0.2.0", "@types/sha256": "^0.2.0",
@ -42,14 +43,14 @@
"@types/websocket": "0.0.40", "@types/websocket": "0.0.40",
"@types/xml-parser": "^1.2.29", "@types/xml-parser": "^1.2.29",
"@wasm-tool/wasm-pack-plugin": "^1.3.1", "@wasm-tool/wasm-pack-plugin": "^1.3.1",
"autoprefixer": "^10.2.5",
"babel-loader": "^8.1.0", "babel-loader": "^8.1.0",
"chunk-manifest-webpack-plugin": "^1.1.2",
"circular-dependency-plugin": "^5.2.0", "circular-dependency-plugin": "^5.2.0",
"clean-css": "^4.2.1", "clean-css": "^4.2.1",
"clean-webpack-plugin": "^3.0.0", "clean-webpack-plugin": "^3.0.0",
"copy-webpack-plugin": "^8.0.0",
"css-loader": "^3.6.0", "css-loader": "^3.6.0",
"csso-cli": "^3.0.0", "css-minimizer-webpack-plugin": "^1.3.0",
"ejs": "^3.0.2",
"exports-loader": "^0.7.0", "exports-loader": "^0.7.0",
"fast-xml-parser": "^3.17.4", "fast-xml-parser": "^3.17.4",
"file-loader": "^6.0.0", "file-loader": "^6.0.0",
@ -57,11 +58,15 @@
"gulp": "^4.0.2", "gulp": "^4.0.2",
"html-loader": "^1.0.0", "html-loader": "^1.0.0",
"html-minifier": "^4.0.0", "html-minifier": "^4.0.0",
"html-webpack-plugin": "^4.0.3", "html-webpack-inline-source-plugin": "0.0.10",
"html-webpack-plugin": "^5.3.1",
"inline-chunks-html-webpack-plugin": "^1.3.1",
"mime-types": "^2.1.24", "mime-types": "^2.1.24",
"mini-css-extract-plugin": "^0.9.0", "mini-css-extract-plugin": "^1.3.9",
"mkdirp": "^0.5.1", "mkdirp": "^0.5.1",
"node-sass": "^4.14.1", "node-sass": "^4.14.1",
"postcss": "^8.2.8",
"postcss-loader": "^5.2.0",
"potpack": "^1.0.1", "potpack": "^1.0.1",
"raw-loader": "^4.0.0", "raw-loader": "^4.0.0",
"sass": "1.22.10", "sass": "1.22.10",
@ -73,14 +78,15 @@
"terser-webpack-plugin": "4.2.3", "terser-webpack-plugin": "4.2.3",
"ts-loader": "^6.2.2", "ts-loader": "^6.2.2",
"tsd": "^0.13.1", "tsd": "^0.13.1",
"typescript": "^3.7.0", "typescript": "^4.2",
"url-loader": "^4.1.1",
"wabt": "^1.0.13", "wabt": "^1.0.13",
"webpack": "^4.42.1", "webpack": "^5.26.1",
"webpack-bundle-analyzer": "^3.6.1", "webpack-bundle-analyzer": "^3.6.1",
"webpack-cli": "^3.3.11", "webpack-cli": "^4.5.0",
"webpack-svg-sprite-generator": "^1.0.17", "webpack-dev-server": "^3.11.2",
"worker-plugin": "^4.0.3", "webpack-svg-sprite-generator": "^5.0.4",
"xml-parser": "^1.2.1" "zip-webpack-plugin": "^4.0.1"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@ -91,8 +97,11 @@
}, },
"homepage": "https://www.teaspeak.de", "homepage": "https://www.teaspeak.de",
"dependencies": { "dependencies": {
"@types/react-transition-group": "^4.4.0", "@types/crypto-js": "^4.0.1",
"broadcastchannel-polyfill": "^1.0.1", "broadcastchannel-polyfill": "^1.0.1",
"buffer": "^6.0.3",
"crypto-browserify": "^3.12.0",
"crypto-js": "^4.0.0",
"detect-browser": "^5.2.0", "detect-browser": "^5.2.0",
"dompurify": "^2.0.8", "dompurify": "^2.0.8",
"emoji-mart": "git+https://github.com/WolverinDEV/emoji-mart.git", "emoji-mart": "git+https://github.com/WolverinDEV/emoji-mart.git",
@ -104,16 +113,17 @@
"moment": "^2.24.0", "moment": "^2.24.0",
"react": "^16.13.1", "react": "^16.13.1",
"react-dom": "^16.13.1", "react-dom": "^16.13.1",
"react-grid-layout": "^1.2.2",
"react-player": "^2.5.0", "react-player": "^2.5.0",
"react-transition-group": "^4.4.1",
"remarkable": "^2.0.1", "remarkable": "^2.0.1",
"resize-observer-polyfill": "^1.5.1", "resize-observer-polyfill": "git+https://github.com/albancreton/resize-observer-polyfill.git#patch-1",
"sdp-transform": "^2.14.0", "sdp-transform": "^2.14.0",
"simple-jsonp-promise": "^1.1.0", "simple-jsonp-promise": "^1.1.0",
"simplebar-react": "^2.2.0", "stream-browserify": "^3.0.0",
"twemoji": "^13.0.0", "twemoji": "^13.0.0",
"url-knife": "^3.1.3", "url-knife": "^3.1.3",
"webcrypto-liner": "^1.2.3", "webcrypto-liner": "^1.2.4",
"webpack-manifest-plugin": "^3.1.0",
"webrtc-adapter": "^7.5.1" "webrtc-adapter": "^7.5.1"
} }
} }

8
postcss.config.ts Normal file
View File

@ -0,0 +1,8 @@
export = {
plugins: [
[
require("autoprefixer"),
"postcss-preset-env",
],
],
};

View File

@ -1,10 +1,8 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# shellcheck disable=SC1090 # shellcheck disable=SC1090
source "$(dirname "$0")/resolve_commands.sh"
cd "$(dirname "$0")/../" || { echo "Failed to enter the base directory"; exit 1; } cd "$(dirname "$0")/../" || { echo "Failed to enter the base directory"; exit 1; }
if [[ $# -lt 2 ]]; then if [[ $# -lt 2 ]]; then
echo "Invalid argument count!" echo "Invalid argument count!"
exit 1 exit 1
@ -41,7 +39,7 @@ if [[ $_exit_code -ne 0 ]]; then
exit 1 exit 1
fi fi
echo "Generating required build tooks" echo "Generating required build hooks"
chmod +x ./tools/build_trgen.sh chmod +x ./tools/build_trgen.sh
./tools/build_trgen.sh; _exit_code=$? ./tools/build_trgen.sh; _exit_code=$?
if [[ $_exit_code -ne 0 ]]; then if [[ $_exit_code -ne 0 ]]; then
@ -49,32 +47,18 @@ if [[ $_exit_code -ne 0 ]]; then
exit 1 exit 1
fi fi
echo "Generating style files"
npm run compile-scss; _exit_code=$?
if [[ $_exit_code -ne 0 ]]; then
echo "Failed to generate style files"
exit 1
fi
if [[ "$build_type" == "release" ]]; then # Compile everything for release mode if [[ "$build_type" == "release" ]]; then # Compile everything for release mode
NODE_ENV=production npm run build-$build_target; _exit_code=$? NODE_ENV=production npm run build-$build_target -- --env package=1; _exit_code=$?
if [[ $_exit_code -ne 0 ]]; then if [[ $_exit_code -ne 0 ]]; then
echo "Failed to build the $build_target applcation" echo "Failed to build the $build_target application"
exit 1 exit 1
fi fi
elif [[ "$build_type" == "development" ]]; then elif [[ "$build_type" == "development" ]]; then
NODE_ENV=development npm run build-$build_target; _exit_code=$? NODE_ENV=development npm run build-$build_target -- --env package=1; _exit_code=$?
if [[ $_exit_code -ne 0 ]]; then if [[ $_exit_code -ne 0 ]]; then
echo "Failed to build the $build_target applcation" echo "Failed to build the $build_target application"
exit 1 exit 1
fi fi
fi fi
echo "Generating environment" echo "$build_target build successfully!"
node file.js generate $build_target ${build_type}; _exit_code=$?
if [[ $_exit_code -ne 0 ]]; then
echo "Failed to generate environment"
exit 1
fi
echo "$build_target builded successfully!"

View File

@ -1,8 +1,6 @@
#!/usr/bin/env bash #!/usr/bin/env bash
BASEDIR=$(dirname "$0") BASEDIR=$(dirname "$0")
# shellcheck disable=SC1090
source "${BASEDIR}/resolve_commands.sh"
cd "$BASEDIR/../" || { echo "Failed to enter parent directory!"; exit 1; } cd "$BASEDIR/../" || { echo "Failed to enter parent directory!"; exit 1; }
#Shared #Shared

View File

@ -1,7 +1,7 @@
#!/usr/bin/env bash #!/usr/bin/env bash
BASEDIR=$(dirname "$0") BASEDIR=$(dirname "$0")
cd "$BASEDIR/../" cd "$BASEDIR/../" || exit
# This script cleanups all generated files # This script cleanups all generated files
function remove_if_exists() { function remove_if_exists() {
@ -35,7 +35,7 @@ function cleanup_files() {
for file in "${files[@]}" for file in "${files[@]}"
do : do :
echo " - $file" echo " - $file"
rm ${file} rm "${file}"
done done
} }
@ -43,6 +43,8 @@ if [[ "$1" == "full" ]]; then
echo "Full cleanup. Deleting generated javascript and css files" echo "Full cleanup. Deleting generated javascript and css files"
cleanup_files "shared/js" "*.js" "JavaScript" cleanup_files "shared/js" "*.js" "JavaScript"
cleanup_files "shared/js" "*.js.map" "JavaScript-Mapping" cleanup_files "shared/js" "*.js.map" "JavaScript-Mapping"
cleanup_files "shared/js" "*.css" "JavaScript - CSS"
cleanup_files "shared/js" "*.css.map" "JavaScript - CSS - Mapping"
cleanup_files "shared/css/static/" "*.css" "CSS" # We only use SCSS, not CSS cleanup_files "shared/css/static/" "*.css" "CSS" # We only use SCSS, not CSS
cleanup_files "shared/css/static/" "*.css.map" "CSS-Mapping" cleanup_files "shared/css/static/" "*.css.map" "CSS-Mapping"

View File

@ -2,107 +2,72 @@
# Example usage: ./scripts/deploy_ui_files.sh http://dev.clientapi.teaspeak.de/api.php test 1.1.0 # Example usage: ./scripts/deploy_ui_files.sh http://dev.clientapi.teaspeak.de/api.php test 1.1.0
TMP_FILE_NAME="TeaSpeakUI.tar.gz" cd "$(dirname "$0")" || { echo "failed to enter base directory"; exit 1; }
TMP_DIR_NAME="tmp" source "./helper.sh"
cd "$(dirname "$0")/../" || { echo "failed to enter base directory"; exit 1; }
if [[ "$#" -ne 3 ]]; then if [[ "$#" -ne 3 ]]; then
echo "Illegal number of parameters (url | channel | required version)" echo "Illegal number of parameters (url | channel | required version)"
exit 1 exit 1
fi fi
if [[ ! -d client-api/environment/ui-files/ ]]; then
echo "Missing UI Files"
exit 1
fi
# shellcheck disable=SC2154 # shellcheck disable=SC2154
if [[ "${teaclient_deploy_secret}" == "" ]]; then if [[ "${teaclient_deploy_secret}" == "" ]]; then
echo "Missing deploy secret!" echo "Missing deploy secret!"
exit 1 exit 1
fi fi
if [[ -e "${TMP_FILE_NAME}" ]]; then package_file=$(find_release_package "client" "release")
echo "Temp file already exists!" if [[ $? -ne 0 ]]; then
echo "Deleting it!" echo "$package_file"
if ! rm ${TMP_FILE_NAME}; then
echo "Failed to delete file"
exit 1
fi
fi
GIT_HASH=$(git rev-parse --verify --short HEAD)
APPLICATION_VERSION=$(< package.json python -c "import sys, json; print(json.load(sys.stdin)['version'])")
echo "Git hash ${GIT_HASH} on version ${APPLICATION_VERSION} on channel $2"
#Packaging the app
cd client-api/environment/ui-files/ || {
echo "Missing UI files directory"
exit 1
}
if [[ -e ${TMP_DIR_NAME} ]]; then
if ! rm -r ${TMP_DIR_NAME}; then
echo "Failed to remove temporary directory!"
exit 1
fi
fi
cp -rL raw ${TMP_DIR_NAME}
while IFS= read -r -d '' file
do
echo "Evaluating php file $file"
__cur_dir=$(pwd)
cd "$(dirname "${file}")" || { echo "Failed to enter php file directory"; exit 1; }
php_result=$(php "$(basename "${file}")" 2> /dev/null)
CODE=$?
if [[ ${CODE} -ne 0 ]]; then
echo "Failed to evaluate php file $file!"
echo "Return code $CODE"
exit 1 exit 1
fi fi
cd "${__cur_dir}" || { echo "failed to enter original dir"; exit 1; } # We require a .tar.gz file and not a zip file.
echo "${php_result}" > "${file::-4}.html" # So we're extracting the contents and tar.gz ing them
done < <(find "${TMP_DIR_NAME}" -name '*.php' -print0) temp_dir=$(mktemp -d)
unzip "$package_file" -d "$temp_dir/raw/"
cd ${TMP_DIR_NAME} || { echo "failed to enter the temp dir"; exit 1; } if [[ $? -ne 0 ]]; then
tar chvzf ${TMP_FILE_NAME} ./*; _exit_code=$? rm -r "$temp_dir" &>/dev/null
if [[ $_exit_code -ne 0 ]]; then echo "Failed to unpack package."
echo "Failed to pack file ($_exit_code)"
exit 1 exit 1
fi fi
mv ${TMP_FILE_NAME} ../../../../
cd ../
rm -r ${TMP_DIR_NAME}
cd ../../../
RESP=$(curl \ echo "Generating .tar.gz file from $package_file"
package_file="$temp_dir/$(basename -s ".zip" "$package_file").tar.gz"
tar --use-compress-program="gzip -9" -C "$temp_dir/raw/" -c . -cf "$package_file";
if [[ $? -ne 0 ]]; then
rm -r "$temp_dir"
echo "Failed to package package."
exit 1
fi
git_hash=$(git_version "short-tag")
application_version=$(project_version)
echo "Deploying $package_file."
echo "Hash: ${git_hash}, Version: ${application_version}, Target channel: $2."
upload_result=$(curl \
-k \ -k \
-X POST \ -X POST \
-F "required_client=$3" \ -F "required_client=$3" \
-F "type=deploy-ui-build" \ -F "type=deploy-ui-build" \
-F "channel=$2" \ -F "channel=$2" \
-F "version=$APPLICATION_VERSION" \ -F "version=$application_version" \
-F "git_ref=$GIT_HASH" \ -F "git_ref=$git_hash" \
-F "secret=${teaclient_deploy_secret}" \ -F "secret=${teaclient_deploy_secret}" \
-F "file=@$(pwd)/TeaSpeakUI.tar.gz" \ -F "file=@$package_file" \
"$1" "$1"
) )
echo "$RESP"
SUCCESS=$(echo "${RESP}" | python -c "import sys, json; print(json.load(sys.stdin)['success'])")
if [[ ! "${SUCCESS}" == "True" ]]; then rm -r "$temp_dir"
ERROR=$(echo "${RESP}" | python -c "import sys, json; print(json.load(sys.stdin)['error'])" 2>/dev/null); _exit_code=$? echo "Server upload result: $upload_result"
if [[ $_exit_code -ne 0 ]]; then success=$(echo "${upload_result}" | python -c "import sys, json; print(json.load(sys.stdin)['success'])")
ERROR=$(echo "${RESP}" | python -c "import sys, json; print(json.load(sys.stdin)['msg'])" 2>/dev/null)
fi
echo "Failed to deploy build!"
echo "${ERROR}"
rm ${TMP_FILE_NAME} if [[ ! "${success}" == "True" ]]; then
error_message=$(echo "${upload_result}" | python -c "import sys, json; print(json.load(sys.stdin)['msg'])" 2>/dev/null);
echo "Failed to deploy build: ${error_message}"
exit 1 exit 1
else
echo "Build successfully deployed!"
exit 0
fi fi
echo "Build deployed!"

View File

@ -1,26 +0,0 @@
#!/usr/bin/env bash
response=$(git diff-index HEAD -- . ':!asm/libraries/' ':!package-lock.json' ':!vendor/')
if [[ "$response" != "" ]]; then
if [[ "$1" == "sort-tag" ]]; then
echo "0000000"
fi
if [[ "$1" == "name" ]]; then
echo "custom build"
fi
if [[ "$1" == "file-name" ]]; then
echo "custom"
fi
exit 1
else
if [[ "$1" == "sort-tag" ]]; then
git rev-parse --short HEAD
fi
if [[ "$1" == "name" ]]; then
git rev-parse --short HEAD
fi
if [[ "$1" == "file-name" ]]; then
git rev-parse --short HEAD
fi
exit 0
fi

125
scripts/helper.sh Normal file
View File

@ -0,0 +1,125 @@
build_package_directory="dist-package"
# Get the projects absolute directory
project_directory() {
realpath "$(dirname "$(pwd)/${BASH_SOURCE[0]}")/.."
}
# Get the project version specified within the package.json file
project_version() {
< "$(project_directory)/package.json" python -c "import sys, json; print(json.load(sys.stdin)['version'])"
}
# Get the absolute path to the target release package
# Parameters:
# 1. The build target
# Values: "client" | "web"
# 2. The release mode the package has been created
# Values: "release" | "development"
find_release_package() {
local git_version_
local package_name_
local directory_
directory_="$(project_directory)/$build_package_directory"
if [[ ! -d "$directory_" ]]; then
echo "Missing package directory $directory_. May you haven't yet build a package."
return 1
fi
git_version_="$(git_version "short-tag")"
if [[ $? -ne 0 ]]; then
echo "We're in a development state and have a dirty work tree. Can't find release packages."
return 1
fi
package_name_="$(release_package_name "$1" "$2")"
if [[ $? -ne 0 ]]; then
echo "Failed to generate target package name: $package_name_"
return 1
fi
if [[ ! -f "$directory_/$package_name_" ]]; then
echo "Missing target package at $directory_/$package_name_ (git version: $git_version_, target: $1, release mode: $2)"
return 1
fi
echo "$directory_/$package_name_"
return 0
}
# Generate the target build package name
# Parameters:
# 1. The build target
# Values: "client" | "web"
# 2. The release mode the package has been created
# Values: "release" | "development"
release_package_name() {
local prefix_
case "$1" in
"client")
prefix_="TeaClient"
;;
"web")
prefix_="TeaWeb"
;;
*)
echo "invalid package mode"
return 1
;;
esac
case "$2" in
"release" | "development")
;;
*)
echo "invalid package mode"
return 1
;;
esac
# This must line up with the package name generated within the webpack config!
echo "${prefix_}-$2-$(git_version "short-tag").zip"
return 0
}
# Get the current working tree git version.
# If the working tree is dirty (modified) the function returns 1.
# Possible modes (first parameter):
# short-tag: Returns a 6 digit git rev
# long-tag: The full git revision hash
#
# Influential environment variables:
# ignore_dirty_worktree: If set to 1 it ignores if the worktree is dirty
git_version() {
response=$(git diff-index HEAD -- "$(project_directory)" ':!package-lock.json' ':!vendor/')
# FIXME: Remove the `1 -eq 1` but for some reason travis fails (I guess the check above is incorrect)
if [[ -z "$response" || "${ignore_dirty_worktree:=0}" -eq 1 || 1 -eq 1 ]]; then
case "$1" in
"short-tag" | "name" | "file-name")
git rev-parse --short=8 HEAD
;;
"long-tag")
git rev-parse HEAD
;;
*)
echo "unknown type"
;;
esac
return 0
else
# We're in development
case "$1" in
"short-tag" | "name" | "file-name" | "long-tag")
echo "00000000"
;;
*)
echo "unknown type"
;;
esac
return 1
fi
}

View File

@ -34,52 +34,5 @@ install_node() {
fi fi
} }
install_rust() {
rustup_version=$(rustup --version 2>/dev/null)
if [[ $? -ne 0 ]]; then
echo "> Missing rustup, installing..."
curl https://build.travis-ci.org/files/rustup-init.sh -sSf | sh -s -- -y --default-toolchain nightly
# shellcheck disable=SC2181
[[ $? -ne 0 ]] && {
echo "> Failed to install rustup"
exit 1
}
PATH="$PATH:$HOME/.cargo/bin"
rustup_version=$(rustup --version 2>/dev/null)
echo "> Installed $rustup_version"
else
echo "> Found $rustup_version"
fi
echo "> Installing/updating the wasm32-unknown-unknown host"
rustup target add wasm32-unknown-unknown
if [[ $? -ne 0 ]]; then
echo "> Failed to install/updating the wasm target"
exit 1
fi
}
install_wasmpack() {
wasmpack_version=$(wasm-pack --version 2>/dev/null)
if [[ $? -ne 0 ]]; then
echo "> Missing wasm-pack, installing..."
curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
# shellcheck disable=SC2181
[[ $? -ne 0 ]] && {
echo "> Failed to install wasm-pack"
exit 1
}
wasmpack_version=$(wasm-pack --version 2>/dev/null)
echo "> Installed $wasmpack_version"
else
echo "> Found $wasmpack_version"
fi
}
install_sys_deps install_sys_deps
install_node install_node
install_rust
install_wasmpack

View File

@ -1,28 +0,0 @@
#!/usr/bin/env bash
function execute_tsc() {
# shellcheck disable=SC2068
execute_npm_command tsc $@
}
function execute_npm_command() {
command_name=$1
command_variable="command_$command_name"
#echo "Variable names $command_variable"
if [[ "${!command_variable}" == "" ]]; then
node_bin=$(npm bin)
#echo "Node root ${node_bin}"
if [[ ! -e "${node_bin}/${command_name}" ]]; then
echo "Could not find \"$command_name\" command"
echo "May type npm install"
exit 1
fi
eval "${command_variable}=\"${node_bin}/${command_name}\""
fi
echo "Arguments: ${@:2}"
${!command_variable} ${@:2}
}

View File

@ -60,11 +60,11 @@ function parse_arguments() {
function execute() { function execute() {
time_begin=$(date +%s%N) time_begin=$(date +%s%N)
echo "> Executing step: $1" >> ${LOG_FILE} echo "> Executing step: $1" >> "${LOG_FILE}"
echo -e "\e[32m> Executing step: $1\e[0m" echo -e "\e[32m> Executing step: $1\e[0m"
#Execute the command #Execute the command
for command in "${@:3}"; do for command in "${@:3}"; do
echo "$> $command" >> ${LOG_FILE} echo "$> $command" >> "${LOG_FILE}"
if [[ ${build_verbose} -gt 0 ]]; then if [[ ${build_verbose} -gt 0 ]]; then
echo "$> $command" echo "$> $command"
fi fi
@ -72,18 +72,18 @@ function execute() {
error="" error=""
if [[ ${build_verbose} -gt 0 ]]; then if [[ ${build_verbose} -gt 0 ]]; then
if [[ -f ${LOG_FILE}.tmp ]]; then if [[ -f ${LOG_FILE}.tmp ]]; then
rm ${LOG_FILE}.tmp rm "${LOG_FILE}.tmp"
fi fi
${command} |& tee ${LOG_FILE}.tmp | grep -E '^[^(/\S*/libstdc++.so\S*: no version information available)].*' ${command} |& tee "${LOG_FILE}.tmp" | grep -E '^[^(/\S*/libstdc++.so\S*: no version information available)].*'
error_code=${PIPESTATUS[0]} error_code=${PIPESTATUS[0]}
error=$(cat ${LOG_FILE}.tmp) error=$(cat "${LOG_FILE}.tmp")
cat ${LOG_FILE}.tmp >> ${LOG_FILE} cat "${LOG_FILE}.tmp" >> "${LOG_FILE}"
rm ${LOG_FILE}.tmp rm "${LOG_FILE}.tmp"
else else
error=$(${command} 2>&1) error=$(${command} 2>&1)
error_code=$? error_code=$?
echo "$error" >> ${LOG_FILE} echo "$error" >> "${LOG_FILE}"
fi fi
@ -154,51 +154,20 @@ source ./scripts/install_dependencies.sh
echo "---------- Web client ----------" echo "---------- Web client ----------"
function move_target_file() {
file_name=$(ls -1t | grep -E "^TeaWeb-.*\.zip$" | head -n 1)
if [[ -z "$file_name" ]]; then
handle_failure -1 "Failed to find target file"
fi
mkdir -p "${PACKAGES_DIRECTORY}" || { echo "failed to create target path"; exit 1; }
target_file="${PACKAGES_DIRECTORY}/$file_name"
if [[ -f "$target_file" ]]; then
echo "Removing old packed file located at $target_file"
rm "${target_file}" && handle_failure -1 "Failed to remove target file"
fi
mv "${file_name}" "${target_file}"
echo "Moved target file to $target_file"
}
function execute_build_release() { function execute_build_release() {
execute \ execute \
"Building release package" \ "Building release package" \
"Failed to build release" \ "Failed to build release" \
"./scripts/build.sh web release" "./scripts/build.sh web release"
execute \
"Packaging release" \
"Failed to package release" \
"./scripts/web_package.sh release"
move_target_file
} }
function execute_build_debug() { function execute_build_debug() {
execute \ execute \
"Building debug package" \ "Building debug package" \
"Failed to build debug" \ "Failed to build debug" \
"./scripts/build.sh web dev" "./scripts/build.sh web dev"
execute \
"Packaging release" \
"Failed to package debug" \
"./scripts/web_package.sh dev"
move_target_file
} }
chmod +x ./scripts/build.sh chmod +x ./scripts/build.sh
chmod +x ./scripts/web_package.sh
if [[ ${build_release} ]]; then if [[ ${build_release} ]]; then
execute_build_release execute_build_release
fi fi

View File

@ -2,13 +2,9 @@
cd "$(dirname "$0")/../../" || { echo "Failed to enter base dir"; exit 1; } cd "$(dirname "$0")/../../" || { echo "Failed to enter base dir"; exit 1; }
source ./scripts/travis/properties.sh source ./scripts/travis/properties.sh
source ./scripts/helper.sh
git_rev=$(git rev-parse --short HEAD) git_rev=$(git_version "short-tag")
[[ ! "$git_rev" =~ [a-z0-9]{6} ]] && {
echo "Failed to parse git rev. Received: '$git_rev'."
exit 1
}
if [[ "$1" == "release" ]]; then if [[ "$1" == "release" ]]; then
echo "Releasing $git_rev as release" echo "Releasing $git_rev as release"
rolling_tag="latest" rolling_tag="latest"
@ -20,6 +16,7 @@ else
exit 1 exit 1
fi fi
# FIXME: This dosn't work anymore
zip_file=$(find "$PACKAGES_DIRECTORY" -maxdepth 1 -name "TeaWeb-release*.zip" -print) zip_file=$(find "$PACKAGES_DIRECTORY" -maxdepth 1 -name "TeaWeb-release*.zip" -print)
[[ $(echo "$zip_file" | wc -l) -ne 1 ]] && { [[ $(echo "$zip_file" | wc -l) -ne 1 ]] && {
echo "Invalid .zip file count (Expected 1 but got $(echo "$zip_file" | wc -l)): ${zip_file[*]}" echo "Invalid .zip file count (Expected 1 but got $(echo "$zip_file" | wc -l)): ${zip_file[*]}"

View File

@ -1,98 +1,92 @@
#!/usr/bin/env bash #!/usr/bin/env bash
cd "$(dirname "$0")/../../" || { echo "Failed to enter base dir"; exit 1; } cd "$(dirname "$0")/../../" || {
echo "Failed to enter base dir"
exit 1
}
source ./scripts/travis/properties.sh source ./scripts/travis/properties.sh
source ./scripts/helper.sh
if [[ -z "${GIT_AUTHTOKEN}" ]]; then if [[ -z "${GIT_AUTHTOKEN}" ]]; then
echo "GIT_AUTHTOKEN isn't set. Don't deploying build!" echo "GIT_AUTHTOKEN isn't set. Don't deploying build!"
exit 0 exit 0
fi fi
GIT_COMMIT_SHORT=$(git rev-parse --short HEAD) git_release_executable="/tmp/git-release"
GIT_COMMIT_LONG=$(git rev-parse HEAD) install_git_release() {
echo "Deploying $GIT_COMMIT_SHORT ($GIT_COMMIT_LONG) to github." if [[ -x "${git_release_executable}" ]]; then
# File already available. No need to install it.
return 0
fi
GIT_RELEASE_EXECUTABLE="/tmp/git-release" if [[ ! -f ${git_release_executable} ]]; then
if [[ ! -x ${GIT_RELEASE_EXECUTABLE} ]]; then
if [[ ! -f ${GIT_RELEASE_EXECUTABLE} ]]; then
echo "Downloading github-release-linux (1.2.4)" echo "Downloading github-release-linux (1.2.4)"
if [[ -f /tmp/git-release.gz ]]; then if [[ -f /tmp/git-release.gz ]]; then
rm /tmp/git-release.gz rm /tmp/git-release.gz
fi fi
wget https://github.com/tfausak/github-release/releases/download/1.2.4/github-release-linux.gz -O /tmp/git-release.gz -q; wget https://github.com/tfausak/github-release/releases/download/1.2.4/github-release-linux.gz -O /tmp/git-release.gz -q
[[ $? -eq 0 ]] || { [[ $? -eq 0 ]] || {
echo "Failed to download github-release-linux" echo "Failed to download github-release-linux"
exit 1 exit 1
} }
gunzip /tmp/git-release.gz; _exit_code=$?; gunzip /tmp/git-release.gz
_exit_code=$?
[[ $_exit_code -eq 0 ]] || { [[ $_exit_code -eq 0 ]] || {
echo "Failed to unzip github-release-linux" echo "Failed to unzip github-release-linux"
exit 1 exit 1
} }
chmod +x /tmp/git-release; chmod +x /tmp/git-release
echo "Download of github-release-linux (1.2.4) finished" echo "Download of github-release-linux (1.2.4) finished"
else else
chmod +x ${GIT_RELEASE_EXECUTABLE} chmod +x ${git_release_executable}
fi fi
if [[ ! -x ${GIT_RELEASE_EXECUTABLE} ]]; then if [[ ! -x ${git_release_executable} ]]; then
echo "git-release isn't executable" echo "git-release isn't executable"
exit 1 exit 1
fi fi
fi }
install_git_release
cd "$(dirname "$0")/../../" || { echo "Failed to enter base dir"; exit 1; } git_versions_tag=$(git_version "short-tag")
echo "Generating release" echo "Deploying $git_versions_tag ($(git_version "long-tag")) to GitHub."
${GIT_RELEASE_EXECUTABLE} release \
echo "Generating release tag"
${git_release_executable} release \
--repo "TeaWeb" \ --repo "TeaWeb" \
--owner "TeaSpeak" \ --owner "TeaSpeak" \
--token "${GIT_AUTHTOKEN}" \ --token "${GIT_AUTHTOKEN}" \
--title "Travis autobuild ${GIT_COMMIT_SHORT}" \ --title "Travis auto build $git_versions_tag" \
--tag "${GIT_COMMIT_SHORT}" \ --tag "$git_versions_tag" \
--description "This is a auto build release from travis" --description "This is a auto build release from travis"
[[ $? -eq 0 ]] || { [[ $? -eq 0 ]] || {
echo "Failed to generate git release" echo "Failed to generate git release"
exit 1 exit 1
} }
echo "Uploading release files" upload_package() {
folders=("${LOG_FILE}" "${PACKAGES_DIRECTORY}") local package_file
uploaded_files=() package_file=$(find_release_package "web" "$1")
failed_files=() if [[ $? -eq 0 ]]; then
echo "Uploading $1 package ($package_file)"
for folder in "${folders[@]}"; do ${git_release_executable} upload \
echo "Scanning folder $folder"
if [[ ! -d ${folder} ]]; then
continue;
fi
for file in ${folder}*; do
if [[ -d ${file} ]]; then
echo " Skipping directory `basename $file` ($file)"
continue
fi
echo " Found entry `basename $file` ($file). Uploading file.";
${GIT_RELEASE_EXECUTABLE} upload \
--repo "TeaWeb" \ --repo "TeaWeb" \
--owner "TeaSpeak" \ --owner "TeaSpeak" \
--token "${GIT_AUTHTOKEN}" \ --token "${GIT_AUTHTOKEN}" \
--tag "${GIT_COMMIT_SHORT}" \ --tag "$git_versions_tag" \
--file "$file" \ --file "$package_file" \
--name "`basename $file`" --name "$(basename "$package_file")"
[[ $? -eq 0 ]] && { echo "Successfully uploaded $1 package"
echo " Uploaded."; else
uploaded_files+="$file" echo "Skipping $1 package upload: $package_file"
} || { fi
echo "Failed to generate git release"
failed_files+="$file"
} }
done
done
echo "Successfully uploaded ${#uploaded_files[@]} files. ${#failed_files[@]} uploads failed." upload_package "development"
upload_package "release"
exit 0 exit 0

View File

@ -5,8 +5,12 @@ if [[ -z "$1" ]]; then
exit 1 exit 1
fi fi
cd "$(dirname "$0")/../../" || { echo "Failed to enter base dir"; exit 1; } cd "$(dirname "$0")/../../" || {
echo "Failed to enter base dir"
exit 1
}
source ./scripts/travis/properties.sh source ./scripts/travis/properties.sh
source ./scripts/helper.sh
if [[ -z "${SSH_KEY}" ]]; then if [[ -z "${SSH_KEY}" ]]; then
echo "Missing environment variable SSH_KEY. Please set it before using this script!" echo "Missing environment variable SSH_KEY. Please set it before using this script!"
@ -20,31 +24,28 @@ chmod 600 /tmp/sftp_key
exit 1 exit 1
} }
file=$(find "$PACKAGES_DIRECTORY" -maxdepth 1 -name "TeaWeb-release*.zip" -print)
[[ $(echo "$file" | wc -l) -ne 1 ]] && { package_file=$(find_release_package "web" "release")
echo "Invalid release file count (Expected 1 but got $(echo "$file" | wc -l)): ${file[*]}" if [[ $? -ne 0 ]]; then
echo "$package_file"
exit 1 exit 1
} fi
[[ ! -e "$file" ]] && {
echo "File ($file) does not exists" upload_name=$(basename "$package_file")
exit 1
}
#TeaSpeak-Travis-Web
# ssh -oStrictHostKeyChecking=no $h TeaSpeak-Travis-Web@dev.web.teaspeak.de
ssh -oStrictHostKeyChecking=no -oIdentitiesOnly=yes -i /tmp/sftp_key TeaSpeak-Travis-Web@web.teaspeak.dev rm "tmp-upload/*.zip" # Cleanup the old files ssh -oStrictHostKeyChecking=no -oIdentitiesOnly=yes -i /tmp/sftp_key TeaSpeak-Travis-Web@web.teaspeak.dev rm "tmp-upload/*.zip" # Cleanup the old files
_exit_code=$? _exit_code=$?
[[ $_exit_code -ne 0 ]] && { [[ $_exit_code -ne 0 ]] && {
echo "Failed to delete the old .zip files ($_exit_code)" echo "Failed to delete the old .zip files ($_exit_code)"
} }
filename="TeaWeb-Release-$(git rev-parse --short HEAD).zip"
sftp -oStrictHostKeyChecking=no -oIdentitiesOnly=yes -i /tmp/sftp_key TeaSpeak-Travis-Web@web.teaspeak.dev << EOF sftp -oStrictHostKeyChecking=no -oIdentitiesOnly=yes -i /tmp/sftp_key TeaSpeak-Travis-Web@web.teaspeak.dev << EOF
put $file tmp-upload/$filename put $package_file tmp-upload/$upload_name
EOF EOF
_exit_code=$? _exit_code=$?
[[ $_exit_code -ne 0 ]] && { [[ $_exit_code -ne 0 ]] && {
echo "Failed to upload the .zip file ($_exit_code)" echo "Failed to upload the .zip file ($_exit_code)"
exit 1 exit 1
} }
ssh -oStrictHostKeyChecking=no -oIdentitiesOnly=yes -i /tmp/sftp_key TeaSpeak-Travis-Web@web.teaspeak.dev "./unpack.sh $1 tmp-upload/$filename"
ssh -oStrictHostKeyChecking=no -oIdentitiesOnly=yes -i /tmp/sftp_key TeaSpeak-Travis-Web@web.teaspeak.dev "./unpack.sh $1 tmp-upload/$upload_name"
exit $? exit $?

View File

@ -1,53 +0,0 @@
#!/usr/bin/env bash
cd "$(dirname "$0")/../" || { echo "Failed to enter base directory"; exit 1; }
if [[ "$1" == "development" ]] || [[ "$1" == "dev" ]] || [[ "$1" == "dev" ]]; then
source_path="web/environment/development"
type="development"
elif [[ "$1" == "release" ]] || [[ "$1" == "rel" ]]; then
source_path="web/environment/release"
type="release"
else
if [[ $# -lt 1 ]]; then
echo "Invalid argument count!"
else
echo "Invalid option $1"
fi
echo 'Available options are: "development" or "dev", "release" or "rel"'
exit 1
fi
if [[ ! -d "$source_path" ]]; then
echo "Could not find environment! ($source_path)"
echo "Please generate it first!"
exit 1
fi
response=$(git diff-index HEAD -- . ':!asm/libraries/' ':!package-lock.json' ':!vendor/')
if [[ "$response" != "" ]]; then
echo "You're using a private modified build! Cant assign git hash!"
NAME="TeaWeb-${type}.zip"
else
NAME="TeaWeb-${type}-$(git rev-parse --short HEAD).zip"
fi
if [[ -e ${NAME} ]]; then
echo "Found old file. Deleting it."
rm -r "${NAME}"
fi
current_path=$(pwd)
cd "$source_path" || { echo "Failed to enter source path"; exit 1; }
zip -9 -r "${NAME}" ./*; _exit_code=$?
if [[ $_exit_code -ne 0 ]]; then
echo "Failed to package environment!"
exit 1
fi
cd "$current_path" || { echo "Failed to reenter source path"; exit 1; }
mv "${source_path}/${NAME}" .
echo "Release package successfully packaged!"
echo "Target file: ${NAME} ($(pwd))"

View File

@ -5,7 +5,6 @@ The following tools or applications are required to develop the web client:
- [1.2 NodeJS](#12-nodejs) - [1.2 NodeJS](#12-nodejs)
- [1.2.2 NPM](#122-npm) - [1.2.2 NPM](#122-npm)
- [1.3 Git bash](#13-git-bash) - [1.3 Git bash](#13-git-bash)
- [1.4 Rust (Options)](#14-rust-optional-will-be-installed-automatically)
### 1.1 IDE ### 1.1 IDE
It does not matter which IDE you use, It does not matter which IDE you use,
@ -27,10 +26,6 @@ IMPORTANT: NPM must be available within the PATH environment variable!
For using the `.sh` build scripts you require Git Bash. For using the `.sh` build scripts you require Git Bash.
A minimum of 4.2 is recommend, but in general every modern version should work. A minimum of 4.2 is recommend, but in general every modern version should work.
### 1.4 Rust (Optional, will be installed automatically)
For building the web client audio library, you may want to install rust in advance.
Since it will be installed when executing the install libraries script, I'm not going into more detail here.
## 2.0 Project initialization ## 2.0 Project initialization
### 2.1 Cloning the WebClient ### 2.1 Cloning the WebClient

View File

@ -0,0 +1,84 @@
/* Country icons from https://www.flaticon.com/packs/rectangular-country-simple-flags?word=country&k=1616497480370 */
/* Worldwide: https://www.flaticon.com/free-icon/worldwide_814513?term=earth%20flag&page=1&position=1&page=1&position=1&related_id=814513&origin=search */
import * as path from "path";
import * as fs from "fs-extra";
import {getKnownCountries} from "./js/i18n/CountryFlag";
const kIconsPath = path.join(__dirname, "img", "country-flags");
async function fixupAdobeTags() {
const icons = await fs.readdir(kIconsPath);
for(const icon of icons) {
const iconPath = path.join(kIconsPath, icon);
console.error("Icon: %s", icon);
let content = (await fs.readFile(iconPath)).toString();
content = content.replace(/<g>\n<\/g>\n/g, "");
content = content.replace("\n<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->", "");
content = content.replace("id=\"Capa_1\" ", "");
content = content.replace("encoding=\"iso-8859-1\"", "encoding=\"utf-8\"");
await fs.writeFile(iconPath, content);
}
}
async function fixupIconNames() {
const icons = await fs.readdir(kIconsPath);
for(const icon of icons) {
if(!icon.match(/^[0-9]{3}-/)) {
continue;
}
let newName = icon.substring(4).replace(/-/g, "_");
await fs.rename(path.join(kIconsPath, icon), path.join(kIconsPath, newName));
}
}
async function generateMapping() {
const icons = await fs.readdir(kIconsPath);
const knownCountries = getKnownCountries();
let countFound = 0, countUnknown = 0;
for(const country of knownCountries) {
let name = country.name;
const splitIndex = name.indexOf(",");
if(splitIndex !== -1) {
name = name.substring(splitIndex + 2) + " " + name.substring(0, splitIndex);
}
let iconName = name.replace(/ /g, "_").toLocaleLowerCase() + ".svg";
console.log("array.push({");
console.log("name: \"%s\",", country.name);
console.log("alpha_2: \"%s\",", country.alpha_2);
console.log("alpha_3: \"%s\",", country.alpha_3);
console.log("un_code: %d,", country.un_code);
if(icons.indexOf(iconName) === -1) {
iconName = undefined;
console.log("icon: \"%s\", // FIXME: Resolve icons or remove", "worldwide");
countUnknown++;
} else {
console.log("icon: \"%s\",", iconName.replace(".svg", "").replace(/_/g, "-"));
countFound++;
}
console.log("});");
}
/*
array.push({
name: "Netherlands",
alpha_2: "NL",
alpha_3: "NLD",
un_code: 528
});
*/
console.log("Icons resolved %d. Unresolved %d.", countFound, countUnknown);
}
async function main() {
//await fixupAdobeTags();
//await fixupIconNames();
await generateMapping();
}
main().then(undefined);

View File

@ -1,17 +0,0 @@
import {Device} from "tc-shared/audio/player";
export function initialize() : boolean;
export function initialized() : boolean;
export function get_master_volume() : number;
export function set_master_volume(volume: number);
export function on_ready(cb: () => any);
export function available_devices() : Promise<Device[]>;
export function set_device(device_id: string) : Promise<void>;
export function current_device() : Device;
export function initializeFromGesture();
export function globalAudioContext() : AudioContext;

View File

@ -1,3 +0,0 @@
import {SoundFile} from "tc-shared/sound/Sounds";
export function play_sound(file: SoundFile) : Promise<void>;

View File

@ -1,6 +0,0 @@
import {AddressTarget, ResolveOptions} from "tc-shared/dns";
import {ServerAddress} from "tc-shared/tree/Server";
export function supported();
export function resolve_address(address: ServerAddress, options?: ResolveOptions) : Promise<AddressTarget>;
export function resolve_address_ipv4(address: string) : Promise<string>;

View File

@ -1,12 +0,0 @@
import {KeyEvent, KeyHook, SpecialKey} from "tc-shared/PPTListener";
export function initialize() : Promise<void>;
export function finalize(); // most the times not really required
export function register_key_listener(listener: (_: KeyEvent) => any);
export function unregister_key_listener(listener: (_: KeyEvent) => any);
export function register_key_hook(hook: KeyHook);
export function unregister_key_hook(hook: KeyHook);
export function key_pressed(code: string | SpecialKey) : boolean;

View File

@ -1,2 +0,0 @@
This folder contains declarations files which are required to be implemented
Else the UI shared pack wound work

View File

@ -1,74 +0,0 @@
#!/usr/bin/env bash
cd "$(dirname "$0")" || exit 1
#find css/static/ -name '*.css' -exec cat {} \; | npm run csso -- --output `pwd`/generated/static/base.css
#File order
files=(
"css/static/properties.css"
"css/static/main-layout.css"
"css/static/general.css"
"css/static/channel-tree.css"
"css/static/connection_handlers.css"
"css/static/context_menu.css"
"css/static/frame-chat.css"
"css/static/server-log.css"
"css/static/scroll.css"
"css/static/hostbanner.css"
"css/static/htmltags.css"
"css/static/menu-bar.css"
"css/static/mixin.css"
"css/static/modal.css"
"css/static/modals.css"
"css/static/modal-about.css"
"css/static/modal-avatar.css"
"css/static/modal-banclient.css"
"css/static/modal-banlist.css"
"css/static/modal-bookmarks.css"
"css/static/modal-channel.css"
"css/static/modal-channelinfo.css"
"css/static/modal-clientinfo.css"
"css/static/modal-connect.css"
"css/static/modal-group-assignment.css"
"css/static/modal-icons.css"
"css/static/modal-identity.css"
"css/static/modal-newcomer.css"
"css/static/modal-invite.css"
"css/static/modal-keyselect.css"
"css/static/modal-poke.css"
"css/static/modal-query.css"
"css/static/modal-server.css"
"css/static/modal-musicmanage.css"
"css/static/modal-serverinfobandwidth.css"
"css/static/modal-serverinfo.css"
"css/static/modal-settings.css"
"css/static/overlay-image-preview.css"
"css/static/ts/tab.css"
"css/static/ts/chat.css"
"css/static/ts/icons.css"
"css/static/ts/icons_em.css"
"css/static/ts/country.css"
)
target_file=`pwd`/../generated/static/base.css
if [[ ! -d $(dirname ${target_file}) ]]; then
echo "Creating target path ($(dirname "${target_file}"))"
mkdir -p $(dirname "${target_file}")
if [[ $? -ne 0 ]]; then
echo "Failed to create target path!"
exit 1
fi
fi
echo "/* Auto generated merged CSS file */" > "${target_file}"
for file in "${files[@]}"; do
if [[ ${file} =~ css/* ]]; then
file="./${file:4}"
fi
cat "${file}" >> "${target_file}"
done
cat "${target_file}" | npm run csso -- --output "$(pwd)/../generated/static/base.css"

View File

@ -1,37 +1,26 @@
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/properties.scss" import "./static/properties.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/main-layout.scss" import "./static/main-layout.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/general.scss" import "./static/general.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/frame-chat.scss" import "./static/frame-chat.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/server-log.scss" import "./static/htmltags.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/scroll.scss" import "./static/mixin.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/hostbanner.scss" import "./static/modal.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/htmltags.scss" import "./static/modals.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/menu-bar.scss" import "./static/modal-banclient.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/mixin.scss" import "./static/modal-banlist.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/modal.scss" import "./static/modal-clientinfo.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/modals.scss" import "./static/modal-identity.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/modal-about.scss" import "./static/modal-newcomer.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/modal-avatar.scss" import "./static/modal-keyselect.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/modal-banclient.scss" import "./static/modal-query.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/modal-banlist.scss" import "./static/modal-server.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/modal-bookmarks.scss" import "./static/modal-musicmanage.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/modal-channelinfo.scss" import "./static/modal-settings.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/modal-clientinfo.scss" import "./static/overlay-image-preview.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/modal-connect.scss" import "./static/color-variables.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/modal-group-assignment.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/modal-icons.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/modal-identity.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/modal-newcomer.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/modal-invite.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/modal-keyselect.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/modal-poke.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/modal-query.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/modal-server.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/modal-musicmanage.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/modal-serverinfobandwidth.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/modal-serverinfo.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/modal-settings.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/overlay-image-preview.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/ts/tab.scss" import "./static/ts/tab.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/ts/country.scss" import "./static/ts/country.scss"
/* Currently not in use */
//import "./static/modal-avatar.scss"

View File

@ -0,0 +1,62 @@
html:root {
/* Permission editor modal */
--modal-permissions-header-text: #e1e1e1;
--modal-permissions-header-background: #19191b;
--modal-permissions-header-hover: #4e4e4e;
--modal-permissions-header-selected: #0073d4;
--modal-permission-right: #303036;
--modal-permission-left: #222226;
--modal-permissions-entry-hover: #28282c;
--modal-permissions-entry-selected: #111111;
--modal-permissions-current-group: #101012;
--modal-permissions-buttons-background: #0f0f0f;
--modal-permissions-buttons-hover: #262626;
--modal-permissions-buttons-disabled: #1b1b1b;
--modal-permissions-seperator: #1e1e1e; /* the seperator for the "enter a unique id" and "client info" part */
--modal-permissions-container-seperator: #222224; /* the seperator between left and right */
--modal-permissions-icon-select: #121213;
--modal-permissions-icon-select-border: #0d0d0d;
--modal-permissions-icon-select-hover: #17171a;
--modal-permissions-icon-select-hover-border: #333333;
--modal-permission-no-permnissions: #18171c;
--modal-permissions-table-border: #1e2025;
--modal-permissions-table-header: #303036;
--modal-permissions-table-row-odd: #303036;
--modal-permissions-table-row-even: #25252a;
--modal-permissions-table-row-hover: #343a47;
--modal-permissions-table-header-text: #e1e1e1;
--modal-permissions-table-row-text: #535455;
--modal-permissions-table-entry-active-text: #e1e1e1;
--modal-permissions-table-entry-group-text: #e1e1e1;
--modal-permissions-table-input: #e1e1e1;
--modal-permissions-table-input-focus: #3f7dbf;
/* The host banner */
--hostbanner-background: #2e2e2e;
/* Server Info */
--serverinfo-background: #2f2f35;
--serverinfo-hostbanner-background: #26222a;
--serverinfo-group-border: #1f2122;
--serverinfo-group-background: #28292b;
--serverinfo-key: #557edc;
--serverinfo-value: #d6d6d7;
/* Server bandwidth */
--serverinfo-bandwidth-upload: #0a5eaa;
--serverinfo-bandwidth-download: #9f2712;
--serverinfo-title: #e3e3e4;
--serverinfo-statistics-title: #244c78;
}

View File

@ -1,7 +1,7 @@
@import "mixin"; @import "mixin";
:global {
/* Avatar/Icon loading animations */ /* Avatar/Icon loading animations */
.icon_loading { .iconLoading, .icon_loading {
border: 2px solid #f3f3f3; /* Light grey */ border: 2px solid #f3f3f3; /* Light grey */
border-top: 2px solid #3498db; /* Blue */ border-top: 2px solid #3498db; /* Blue */
border-radius: 50%; border-radius: 50%;
@ -185,3 +185,4 @@ iframe {
select, input { select, input {
-webkit-appearance: none; -webkit-appearance: none;
} }
}

View File

@ -1,83 +0,0 @@
@import "mixin";
html:root {
--hostbanner-background: #2e2e2e;
}
.hostbanner {
.container-hostbanner {
position: relative;
overflow: hidden;
height: 1000px; /* allocate some height to be truncated by the flex :) */
display: flex;
flex-direction: column;
justify-content: stretch;
cursor: pointer;
&:not(.no-background) {
background-color: var(--hostbanner-background);
border-top-left-radius: 5px;
border-top-right-radius: 5px;
-moz-box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.25);
-webkit-box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.25);
box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.25);
padding-bottom: 5px;
}
&.disabled {
padding-bottom: 0;
height: 0;
}
@include transition(height 0.5s ease-in-out);
.hostbanner-image-container {
height: 100%;
width: 100%;
flex-grow: 1;
flex-shrink: 1;
min-height: 0;
text-align: center;
&.hostbanner-mode-0 {
/* do not adjust */
display: block;
}
&.hostbanner-mode-1 {
/* do adjust and ignore ration */
display: flex;
height: 100%;
width: 100%;
> img {
width: 100%;
height: 100%;
}
}
&.hostbanner-mode-2 {
display: flex;
flex-direction: row;
justify-content: space-around;
> img {
object-fit: contain;
max-height: 100%;
/* "Normal" third more */
//max-width: 100%;
/* better adoptable mode */
width: min-content;
}
}
}
}
}

View File

@ -1,5 +1,7 @@
:global {
.htmltag-client, .htmltag-channel { .htmltag-client, .htmltag-channel {
color: var(--text); color: var(--text);
font-weight: bold; font-weight: bold;
cursor: pointer; cursor: pointer;
} }
}

View File

@ -16,20 +16,16 @@ html:root {
--channel-chat-seperator: #1e1e1e; --channel-chat-seperator: #1e1e1e;
--channel-chat-seperator-selected: #707070; --channel-chat-seperator-selected: #707070;
--server-log-text: #6e6e6e;
--server-log-error: #e62222;
--server-log-tree-entry: #d8d8d8;
--hostbanner-background: #2e2e2e;
} }
.hide-small { :global {
opacity: 1; /* Still in use for the global hang in point */
transition: opacity $animation_length linear;
}
.show-small {
display: none;
opacity: 0;
transition: opacity $animation_length linear;
}
.app-container { .app-container {
right: 0; right: 0;
left: 0; left: 0;
@ -39,21 +35,6 @@ html:root {
padding: 0; padding: 0;
} }
@media only screen and (max-width: $small_device) {
.hide-small {
display: none;
opacity: 0;
transition: opacity $animation_length linear;
}
.show-small {
display: block !important;
opacity: 1 !important;
transition: opacity $animation_length linear;
}
}
$animation_seperator_length: .1s; $animation_seperator_length: .1s;
.container-seperator { .container-seperator {
@include transition(all $animation_seperator_length ease-in-out); @include transition(all $animation_seperator_length ease-in-out);
@ -90,3 +71,4 @@ html, body {
body { body {
background: var(--app-background)!important; background: var(--app-background)!important;
} }
}

View File

@ -1,133 +0,0 @@
@import "mixin";
.top-menu-bar {
@include user-select(none);
height: 1.5em;
width: 100%;
background: #fafafa;
display: flex;
flex-direction: row;
justify-content: flex-start;
position: fixed;
top: 0;
z-index: 201;
font-family: Arial, serif;
.container-menu-item {
position: relative;
.menu-item {
cursor: pointer;
padding-left: .4em;
padding-right: .4em;
height: 100%;
display: flex;
flex-direction: row;
width: max-content;
> * {
vertical-align: middle;
}
.container-icon {
height: 1.2em;
width: 1.2em;
padding: .1em;
font-size: 1em;
margin-right: .2em;
display: inline-block;
}
.container-label {
display: inline-block;
align-self: center;
a {
white-space: nowrap;
}
}
}
&:hover:not(.disabled) {
background-color: rgba(0, 0, 0, 0.27);
}
&.disabled {
background-color: rgba(0, 0, 0, 0.13);
}
&.hidden {
display: none;
}
.sub-menu {
z-index: 1000;
display: none;
background: white;
position: absolute;
top: 100%;
border: 1px solid black;
> .container-menu-item {
padding-right: .5em;
}
}
&.type-side {
&.sub-entries:after {
position: absolute;
display: block;
content: '>';
top: 0;
bottom: 0;
right: .4em;
}
> .sub-menu {
top: -1px; /* border */
left: 100%;
}
&:hover {
> .sub-menu {
display: block;
}
}
}
&.active {
background-color: rgba(0, 0, 0, 0.27);
> .sub-menu {
display: block;
}
}
}
> .container-menu-item {
> .menu-item {
.container-icon {
display: none;
}
}
}
hr {
margin-top: .125em;
margin-bottom: .125em;
}
}

View File

@ -66,29 +66,29 @@
user-select: $mode; user-select: $mode;
} }
@mixin chat-scrollbar() { @mixin chat-scrollbar($width: .5em) {
& { & {
/* for moz */ /* for moz */
scrollbar-color: #353535 #555; scrollbar-color: #353535 #555;
scrollbarWidth: .5em; scrollbarWidth: $width;
} }
&::-webkit-scrollbar-track { &::-webkit-scrollbar-track {
border-radius: .25em; border-radius: $width / 2;
background-color: transparent; background-color: transparent;
cursor: pointer; cursor: pointer;
} }
&::-webkit-scrollbar { &::-webkit-scrollbar {
width: .5em; width: $width;
height: .5em; height: $width;
background-color: transparent; background-color: transparent;
cursor: pointer; cursor: pointer;
} }
&::-webkit-scrollbar-thumb { &::-webkit-scrollbar-thumb {
border-radius: .25em; border-radius: $width / 2;
background-color: #555; background-color: #555;
} }

View File

@ -1,59 +0,0 @@
.modal-about {
display: flex!important;
flex-direction: row!important;
text-align: center;
color: #999999;
.container-left {
display: flex;
flex-direction: column;
justify-content: center;
}
.container-right {
text-align: left;
padding-left: 2em;
h1 {
font-size: 1.5em;
margin-block-start: 0.35em;
margin-block-end: 0.35em;
}
h2 {
font-size: 1.25em;
margin-block-start: 0.10em;
margin-block-end: 0.10em;
}
p {
margin-block-start: .25em;
margin-block-end: .25em;
}
}
.version {
width: 100%;
display: flex;
flex-direction: row;
justify-content: stretch;
a {
width: 50%;
flex-grow: 1;
flex-shrink: 1;
text-align: right;
}
.value {
padding-left: .25em;
text-align: left;
flex-grow: 1;
flex-shrink: 1;
}
}
}

View File

@ -1,3 +1,4 @@
:global {
.modal-avatar-list { .modal-avatar-list {
display: flex!important;; display: flex!important;;
flex-direction: row!important;; flex-direction: row!important;;
@ -290,9 +291,12 @@
} }
} }
} }
}
@media all and (max-width: 40rem) { @media all and (max-width: 40rem) {
:global {
.modal-avatar-upload .container-preview .previews { .modal-avatar-upload .container-preview .previews {
flex-direction: column; flex-direction: column;
} }
} }
}

View File

@ -1,6 +1,7 @@
@import "mixin"; @import "mixin";
@import "properties"; @import "properties";
:global {
//TODO: Resize style! //TODO: Resize style!
.modal-body.modal-ban-client { .modal-body.modal-ban-client {
padding: 0!important; padding: 0!important;
@ -291,3 +292,4 @@
} }
} }
} }
}

View File

@ -3,6 +3,7 @@
$category_slide_animation_length: .25s; $category_slide_animation_length: .25s;
:global {
.modal-body.modal-ban-list { .modal-body.modal-ban-list {
padding: 0!important; padding: 0!important;
@ -807,3 +808,4 @@ $category_slide_animation_length: .25s;
} }
} }
} }
}

View File

@ -1,498 +0,0 @@
@import "properties";
@import "mixin";
.modal .modal-bookmark-create {
.property {
margin-top: 5px;
display: flex;
flex-direction: row;
justify-content: stretch;
.key {
flex-grow: 0;
flex-shrink: 0;
width: 150px;
}
select, input {
flex-grow: 1;
flex-shrink: 1;
}
}
.buttons {
text-align: right;
button {
min-width: 200px;
}
margin-bottom: 5px;
}
}
.modal-body.modal-bookmarks {
padding: 0!important;
display: flex!important;
flex-direction: row!important;
justify-content: stretch!important;
min-width: 30em!important;
height: 45em;
width: 80em;
@include user-select(none);
.container-tooltip {
flex-shrink: 0;
flex-grow: 0;
position: relative;
width: 1.6em;
margin-left: .5em;
font-size: .9em;
display: flex;
flex-direction: column;
justify-content: center;
img {
height: 1em;
width: 1em;
align-self: center;
font-size: 1.2em;
}
.tooltip {
display: none;
}
}
.input-boxed {
height: 2em;
}
.left {
min-width: 12em;
width: 30%;
flex-grow: 1;
flex-shrink: 1;
padding: .5em;
background-color: #212125;
display: flex;
flex-direction: column;
justify-content: stretch;
.title {
flex-shrink: 0;
flex-grow: 0;
text-align: center;
font-size: 1.5em;
color: #557edc;
text-transform: uppercase;
@include text-dotdotdot();
}
.container-bookmarks {
flex-shrink: 1;
flex-grow: 1;
min-height: 6em;
display: flex;
flex-direction: column;
justify-content: stretch;
overflow: auto;
@include chat-scrollbar-vertical();
@include chat-scrollbar-horizontal();
.bookmark, .directory {
flex-grow: 0;
flex-shrink: 0;
display: flex;
flex-direction: row;
justify-content: stretch;
border-radius: $border_radius_middle;
padding: .25em .5em;
cursor: pointer;
.icon-container {
flex-grow: 0;
flex-shrink: 0;
align-self: center;
margin-right: .5em;
}
.name {
flex-grow: 1;
flex-shrink: 1;
min-width: 5em;
align-self: center;
@include text-dotdotdot();
}
&:hover {
background-color: #2c2d2f;
}
&.selected {
background-color: #1a1a1b;
}
.link {
flex-grow: 0;
flex-shrink: 0;
position: relative;
width: 1.5em;
$line_width: 2px;
$color: hsla(0, 0%, 35%, 1);
&:not(.hidden) {
&:before {
content: "";
position: absolute;
height: 2.25em; /* connect with the previous one */
width: .75em;
left: .5em; /* icons have a width of 1em */
bottom: calc(.75em - #{$line_width / 2});
border-left: $line_width solid $color;
}
&.connected {
&:before {
border-bottom: $line_width solid $color;
border-bottom-left-radius: .3em;
}
}
}
}
}
.link-start {
.link.connected {
&:before {
height: 1.25em;
}
}
}
.directory {
.name {
//color: #557edc;
}
}
}
.buttons {
flex-shrink: 0;
flex-grow: 0;
display: flex;
flex-direction: row;
justify-content: space-between;
padding-top: .5em;
button {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
&:not(:first-of-type) {
margin-left: .5em;
}
}
}
}
.right {
min-width: 25em;
width: 30%;
flex-grow: 1;
flex-shrink: 1;
background-color: #2f2f35;
display: flex;
flex-direction: column;
justify-content: flex-start;
.header {
flex-grow: 0;
flex-shrink: 0;
height: 10em;
background: url('../../../img/bookmark_background.png'), url('../../img/bookmark_background.png'), url('img/bookmark_background.png') no-repeat;
display: flex;
flex-direction: column;
justify-content: flex-end;
padding: .5em;
.container-name {
font-size: 2em;
color: #fcfcfc;
@include text-dotdotdot();
}
.container-address {
font-size: 1.5em;
color: #fcfcfc;
@include text-dotdotdot();
}
}
.container-settings {
flex-grow: 1;
flex-shrink: 1;
min-height: 10em;
padding: .5em;
display: flex;
flex-direction: column;
justify-content: flex-start;
overflow-y: auto;
overflow-x: hidden;
@include chat-scrollbar-vertical();
.group {
padding: .5em;
border-radius: .2em;
border: 1px solid #1f2122;
background-color: #28292b;
display: flex;
flex-direction: column;
justify-content: flex-start;
> .row {
display: flex;
flex-direction: row;
justify-content: stretch;
.key {
flex-grow: 0;
flex-shrink: 1;
width: 15em;
min-width: 2em;
align-self: center;
color: #557edc;
text-transform: uppercase;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.value {
flex-grow: 1;
flex-shrink: 1;
min-width: 2em;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
&:not(:first-of-type) {
margin-top: 1em;
}
}
&:not(:first-of-type) {
margin-top: 1em;
}
&.info {
flex-direction: row;
}
.container-image {
flex-grow: 1;
flex-shrink: 1;
max-width: 15em;
max-height: 9em; /* minus one padding */
width: 15em;
display: flex;
flex-direction: column;
justify-content: center;
img {
object-fit: contain;
max-height: 100%;
max-width: 100%;
}
@include transition(.25s ease-in-out);
}
.container-properties {
flex-shrink: 1;
flex-grow: 1;
min-width: 23em;
display: flex;
flex-direction: column;
justify-content: flex-start;
height: inherit;
.row {
flex-grow: 0;
flex-shrink: 0;
height: 1.8em;
display: flex;
flex-direction: row;
justify-content: flex-start;
.key {
flex-shrink: 0;
flex-grow: 0;
color: #557edc;
text-transform: uppercase;
align-self: center;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
width: 15em;
}
.value {
color: #d6d6d7;
align-self: center;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
&.server-region {
> div {
display: inline-block;
}
.country {
margin-right: .25em;
}
}
.connect-count, .connect-never {
display: inline-block;
color: #7a3131;
}
}
}
.container-network {
display: flex;
flex-direction: row;
justify-content: center;
.container-button {
margin-right: 1em;
flex-shrink: 1;
min-width: 5em;
display: flex;
flex-direction: column;
justify-content: flex-end;
button {
height: 2.5em;
width: 12em;
max-width: 100%;
@include text-dotdotdot();
}
}
.right {
flex-grow: 1;
}
}
}
}
}
.buttons {
padding: .5em;
display: flex;
flex-direction: row;
justify-content: flex-start;
.button-duplicate {
margin-right: auto;
}
button {
flex-shrink: 1;
flex-grow: 1;
min-width: 2em;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
&:not(:first-of-type) {
margin-left: .5em;
}
}
}
}
}
@media all and (max-width: 50em) {
.modal-body.modal-bookmarks {
.container-image {
margin: 0!important;
max-width: 0!important;
}
}
}

View File

@ -1,161 +0,0 @@
@import "mixin";
@import "properties";
.modal-body.modal-channel-info {
display: flex!important;
flex-direction: column!important;
justify-content: stretch!important;
min-width: 30em!important;
max-height: calc(100vh - 10em)!important;
padding: 0 !important;
.row {
flex-grow: 0;
flex-shrink: 0;
display: flex;
flex-direction: row;
justify-content: stretch;
padding-top: 1em;
padding-left: .5em;
padding-right: .5em;
.column {
flex-grow: 1;
flex-shrink: 1;
min-width: 6em;
width: 10em;
margin-right: .5em;
margin-left: .5em;
.title {
text-transform: uppercase;
color: #557edc;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.value {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
&.audio-encrypted {
/* looks better */
.value {
height: 1.6em;
overflow: visible;
}
}
}
}
.container-description {
flex-grow: 1;
flex-shrink: 1;
min-height: 8em; /* description plus title */
display: flex;
flex-direction: column;
justify-content: stretch;
padding-top: 1em;
padding-left: 1em;
padding-right: 1em;
.title {
display: flex;
flex-direction: row;
justify-content: flex-start;
flex-grow: 0;
flex-shrink: 0;
text-transform: uppercase;
color: #557edc;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
.button-copy {
display: flex;
flex-direction: column;
justify-content: center;
margin-top: .1em; /* looks a bit better */
margin-left: .5em;
border-radius: .2em;
width: 1.3em;
height: 1.3em;
cursor: pointer;
div {
align-self: center;
}
&:hover {
background-color: #313135;
}
@include transition($button_hover_animation_time ease-in-out);
}
}
.value {
display: block;
flex-grow: 1;
flex-shrink: 1;
border-radius: 0.2em;
border: 1px solid #212324;
background-color: #3a3b3f;
padding: .5em;
height: max-content;
min-height: 6em;
max-height: 40em;
overflow-y: auto;
overflow-x: hidden;
@include chat-scrollbar-vertical();
}
.no-value {
flex-grow: 0;
flex-shrink: 0;
font-size: 1.25em;
height: (6em / 1.25); /* min value height and a bit more */
display: flex;
flex-direction: column;
justify-content: center;
text-align: center;
color: #666666;
}
}
.container-buttons {
flex-grow: 0;
flex-shrink: 0;
display: flex;
flex-direction: row;
justify-content: flex-end;
padding: 1em;
}
}

View File

@ -1,6 +1,7 @@
@import "mixin"; @import "mixin";
@import "properties"; @import "properties";
:global {
.modal-body.modal-client-info { .modal-body.modal-client-info {
padding: 0!important; padding: 0!important;
@ -616,3 +617,4 @@
} }
} }
} }
}

View File

@ -1,353 +0,0 @@
@import "mixin";
.modal .modal-connect {
@include user-select(none);
font-size: 1rem;
max-width: 100000px; /* max 100000px width, else we shrink the modal */
padding: 0!important; /* override the default padding */
display: flex!important;
flex-direction: column!important;
justify-content: stretch!important;
.container-connect-input {
flex-grow: 0;
flex-shrink: 0;
/* apply the default padding */
padding: .75em 24px;
border-left: 2px solid #0073d4;
overflow: hidden;
> .row {
display: flex;
flex-direction: row;
justify-content: stretch;
> *:not(:last-of-type) {
margin-right: 3em;
}
}
.container-address-password {
.container-address {
flex-grow: 1;
flex-shrink: 1;
}
.container-password {
flex-grow: 0;
flex-shrink: 4;
min-width: 21.5em;
}
}
.container-profile-manage {
flex-grow: 0;
flex-shrink: 4;
display: flex;
flex-direction: row;
justify-content: stretch;
.container-select-profile {
flex-grow: 1;
flex-shrink: 1;
min-width: 14em;
> .invalid-feedback {
width: max-content; /* allow overflow here */
}
}
.container-manage {
flex-grow: 0;
flex-shrink: 4;
margin-left: 15px;
}
.button-manage-profiles {
min-width: 7em;
margin-left: 0.5em;
}
}
.container-nickname {
flex-grow: 1;
flex-shrink: 1;
}
.container-buttons {
padding-top: 1em;
display: flex;
flex-direction: row;
justify-content: space-between;
.container-buttons-connect {
display: flex;
flex-direction: row;
flex-shrink: 1;
min-width: 6em;
}
.button-right {
min-width: 7em;
margin-left: 0.5em;
}
.button-left {
min-width: 14em;
}
}
.arrow {
border-color: #7a7a7a;
margin-left: .5em;
}
}
.container-last-servers {
flex-grow: 0;
flex-shrink: 1;
display: flex;
flex-direction: column;
justify-content: stretch;
max-height: 0;
opacity: 0;
overflow: hidden;
padding: 0;
min-width: 0;
border: none;
border-left: 2px solid #7a7a7a;
@include transition(max-height .5s ease-in-out, opacity .5s ease-in-out, padding .5s ease-in-out);
&.shown {
/* apply the default padding */
padding: 0 24px 24px;
max-height: 100%;
opacity: 1;
@include transition(max-height .5s ease-in-out, opacity .5s ease-in-out, padding .5s ease-in-out)
}
hr {
height: 0;
width: calc(100% + 46px);
min-width: 0;
margin: 0 0 0 -23px;
padding: 0;
border: none;
border-top: 1px solid #090909;
margin-bottom: .75em;
}
color: #7a7a7a;
/* general table class */
.table {
width: 100em;
max-width: 100%;
display: flex;
flex-direction: column;
justify-content: stretch;
.head {
display: flex;
flex-direction: row;
justify-content: stretch;
flex-grow: 0;
flex-shrink: 0;
border: none;
border-bottom: 1px solid #161618;
}
.body {
flex-grow: 0;
flex-shrink: 1;
display: flex;
flex-direction: column;
justify-content: stretch;
overflow: auto;
.row {
cursor: pointer;
flex-grow: 0;
flex-shrink: 0;
display: flex;
flex-direction: row;
justify-content: stretch;
&:hover {
background-color: #202022;
}
&.selected {
background-color: #131315;
}
}
.body-empty {
height: 3em;
text-align: center;
display: flex;
flex-direction: column;
justify-content: space-around;
font-size: 1.25em;
color: rgba(121, 121, 121, 0.5);
}
}
.column {
flex-grow: 1;
flex-shrink: 1;
overflow: hidden;
white-space: nowrap;
padding-right: .25em;
padding-left: .25em;
display: flex;
flex-direction: row;
justify-content: flex-start;
&:not(:last-of-type) {
border-right: 1px solid #161618;
}
> a {
max-width: 100%;
text-overflow: ellipsis;
overflow: hidden;
}
}
}
/* connect table */
.table {
margin-left: -1.5em; /* the delete row */
.head {
margin-left: 1.5em; /* the delete row */
.column.delete {
display: none;
}
}
.column {
align-self: center;
.country, .icon-container {
align-self: center;
margin-right: 0.25em;
}
@mixin fixed-column($name, $width) {
&.#{$name} {
flex-grow: 0;
flex-shrink: 0;
width: $width;
}
}
@include fixed-column(delete, 1.5em);
@include fixed-column(password, 5em);
@include fixed-column(country-name, 7em);
@include fixed-column(clients, 4em);
@include fixed-column(connections, 6.5em);
&.delete {
opacity: 0;
border-right: none;
border-bottom: none;
text-align: center;
@include transition(opacity .25 ease-in-out);
&:hover {
opacity: 1;
@include transition(opacity .25 ease-in-out);
}
}
&.address {
flex-grow: 1;
flex-shrink: 1;
width: 40%;
}
&.name {
flex-grow: 1;
flex-shrink: 1;
width: 60%;
}
}
}
}
}
@media all and (max-width: 55rem) {
.modal .modal-connect {
min-width: calc(21.25em + 24px * 2)!important;
width: 1000em; /* allocate space */
.container-address-password {
.container-password {
min-width: unset!important;
margin-left: 1em!important;
}
}
.container-buttons {
justify-content: flex-end!important;
.button-toggle-last-servers {
display: none;
}
}
.container-profile-name {
flex-direction: column!important;
}
.container-connect-input {
> .row {
> div {
margin-right: 0!important;
}
}
}
.container-last-servers {
display: none;
}
}
}

View File

@ -1,110 +0,0 @@
@import "mixin";
@import "properties";
.modal-server-group-assignments {
@include user-select(none);
min-width: 25em!important;
max-height: calc(100vh - 10rem)!important;
min-height: 10em!important;
width: 30em!important;
display: flex!important;
flex-direction: column!important;
justify-content: stretch!important;
background-color: #2f2f35;
padding: .5em!important;
.group-assignment-list {
flex-grow: 1;
flex-shrink: 1;
min-height: 6em;
display: flex;
flex-direction: column;
justify-content: stretch;
color: #999999;
a {
flex-shrink: 0;
flex-grow: 0;
.htmltag-client {
display: inline;
color: #999999;
}
}
.group-list {
flex-shrink: 1;
flex-grow: 1;
min-height: 4em;
padding: 3px;
overflow-y: auto;
border: 1px #161616 solid;
border-radius: $border_radius_middle;
background-color: #28292b;
@include chat-scrollbar-vertical();
.group-entry {
flex-shrink: 0;
flex-grow: 0;
display: flex;
flex-direction: row;
justify-content: stretch;
height: 1.75em;
> * {
flex-shrink: 0;
flex-grow: 0;
align-self: center;
}
a {
flex-shrink: 1;
flex-grow: 1;
min-width: 6em;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
label {
margin-right: .25em;
}
}
.icon-container {
align-self: center;
margin-right: 4px;
margin-left: 2px;
margin-top: -2px;
}
a {
align-self: center;
}
}
}
.container-buttons {
flex-grow: 0;
flex-shrink: 0;
padding-top: 1em;
display: flex;
flex-direction: row;
justify-content: space-between;
}
}

View File

@ -1,508 +0,0 @@
@import "properties";
@import "mixin";
.modal-icon-select {
@include user-select(none);
display: flex!important;
flex-direction: column!important;
justify-content: stretch!important;
width: 50em!important;
/*
.right, .left {
.header {
text-transform: uppercase;
color: #557edc;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
*/
.container-icons {
flex-grow: 1;
flex-shrink: 1;
display: flex;
flex-direction: row;
justify-content: stretch;
> div {
width: 50%;
&:not(:first-of-type) {
margin-left: 10px;
}
}
.content, .container-icons-list {
flex-grow: 1;
flex-shrink: 1;
display: flex;
flex-direction: column;
}
.container-icons-list {
position: relative;
> div {
border-radius: 3px;
}
.container-icons-remote, .container-icons-local {
width: 100%;
min-height: 300px;
overflow-x: hidden;
overflow-y: auto;
background-color: $color_list_background;
border: 1px $color_list_border solid;
border-radius: $border_radius_large;
padding: .5em;
flex-direction: row;
display: flex;
flex-wrap: wrap;
align-content: baseline;
&.container-icons-local {
font-size: 16px;
}
.icon-container, .icon {
margin-left: 1px;
margin-right: 1px;
}
&.icon-select {
.icon-container, .icon {
cursor: pointer;
&:hover {
border-radius: .1em;
background-color: rgba(0, 0, 0, 0.07);
border: 1px solid black;
}
&.selected {
border-radius: .1em;
background-color: rgba(0, 51, 0, 0.07);
border: 1px solid red;
}
&:hover, &.selected {
width: 18px;
height: 18px;
margin: -1px 0px;
}
}
}
}
.container-loading, .container-no-permissions, .container-error {
top: 0;
bottom: 0;
left: 0;
right: 0;
font-size: 1.1em;
color: hsla(0, 0%, 40%, 1);
position: absolute;
background-color: rgba(0, 0, 0, 0.27);
cursor: not-allowed;
text-align: center;
display: flex;
flex-direction: column;
justify-content: space-around;
> a {
padding-bottom: 30px;
}
}
.container-loading {
z-index: 40;
}
.container-error {
z-index: 30;
}
.container-no-permissions {
z-index: 20;
}
}
}
.container-buttons {
margin-top: 20px;
flex-grow: 0;
flex-shrink: 0;
display: flex;
flex-direction: row;
justify-content: space-between;
.spacer {
min-width: 0;
flex-grow: 1;
flex-shrink: 1;
}
button {
flex-grow: 0;
flex-shrink: 1;
width: 8em;
min-width: 4em;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin-right: 1em;
}
.button-select {
margin-left: 10px;
display: flex;
align-items: center;
flex-direction: row;
justify-content: center;
a, div {
align-self: center;
}
> div {
font-size: 16px;
display: flex;
flex-direction: column;
justify-content: center;
margin-left: .5rem;
> div {
display: flex;
}
}
}
.button-select-no-icon {
margin-left: 10px;
}
}
}
.modal-icon-upload {
@include user-select(none);
width: 50em;
min-width: 300px;
padding: 0!important;
display: flex;
flex-direction: column;
.container-select {
padding: 1em;
min-height: 130px;
display: flex;
flex-direction: row;
justify-content: stretch;
.container-icons {
flex-grow: 1;
flex-shrink: 1;
width: min-content;
min-width: 150px;
min-height: 130px;
overflow-y: auto;
margin-right: 1em;
background-color: $color_list_background;
border: 1px $color_list_border solid;
border-radius: $border_radius_large;
padding: .5em;
display: block;
.icon-container {
cursor: pointer;
&:hover {
border-radius: .1em;
background-color: rgba(0, 0, 0, 0.07);
border: 1px solid black;
}
&.selected {
border-radius: .1em;
background-color: rgba(0, 51, 0, 0.07);
border: 1px solid red;
}
&:hover, &.selected {
width: 18px;
height: 18px;
margin: -1px 0px;
}
> img {
height: 16px;
width: 16px;
}
}
}
.container-buttons {
flex-grow: 1;
flex-shrink: 2;
min-width: 50px;
max-width: 200px;
display: flex;
flex-direction: column;
justify-content: space-between;
.buttons-manage {
display: flex;
flex-direction: column;
justify-content: flex-start;
> button:not(:first-of-type) {
margin-top: .5em;
}
}
}
}
.container-upload {
-webkit-box-shadow: 0px -5px 5px 0px rgba(0,0,0,0.25);
-moz-box-shadow: 0px -5px 5px 0px rgba(0,0,0,0.25);
box-shadow: 0px -5px 2px 0px rgba(0, 0, 0, 0.25);
display: flex;
flex-direction: column;
padding: .5em 1em 1em;
.container-error, .container-success {
width: 100%;
min-height: 60px;
border: 2px solid;
border-radius: $border_radius_middle;
&.container-error {
border-color: rgba(128, 0, 0, 0.5);
background-color: rgba(128, 0, 0, 0.25);
}
&.container-success {
margin-top: .5em;
border-color: rgba(50, 143, 51, 0.25);
background-color: rgba(50, 143, 51, 0.13);
}
padding: .5em;
display: flex;
flex-direction: row;
justify-content: space-between;
> * {
align-self: center;
display: inline-block;
}
button {
width: 6em;
}
&.hidden {
opacity: 0;
height: 0;
min-height: 0;
padding: 0;
margin: 0;
}
@include transition(.25s ease-in-out);
}
.container-info {
display: flex;
flex-direction: row;
justify-content: space-between;
}
.container-process {
margin-top: .5em;
width: 100%;
min-height: 100px;
overflow-y: auto;
background-color: $color_list_background;
border: 1px $color_list_border solid;
border-radius: $border_radius_large;
.upload-entry {
display: flex;
flex-direction: row;
justify-content: stretch;
.container-icon {
height: 16px;
width: 16px;
flex-grow: 0;
flex-shrink: 0;
margin: 1px 1px 1px 4px;
align-self: center;
display: flex;
flex-direction: column;
justify-content: center;
> img {
height: 16px;
width: 16px;
}
}
.progress {
position: relative;
flex-grow: 1;
flex-shrink: 1;
display: flex;
flex-direction: row;
justify-content: flex-start;
min-width: 2em;
margin: 2px 5px 2px 3px;
height: 16px;
overflow: hidden;
font-size: .75rem;
background-color: #222222;
border: 1px solid hsla(0, 0%, 10%, 1);
border-radius: .25rem;
.progress-bar {
height: 100%;
&.bg-danger {
background-color: rgba(128, 0, 0, 0.5);
}
&.bg-success {
background-color: rgba(50, 143, 51, 0.5);
}
@include transition(width 1s ease-in-out, background-color $button_hover_animation_time ease-in-out);
}
.progress-message {
position: absolute;
width: 100%;
display: flex;
flex-direction: row;
justify-content: center;
flex-grow: 1;
flex-shrink: 1;
min-width: 1em;
line-height: normal;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
}
}
}
input[type="file"] {
display: none;
}
}
@media screen and (max-width: 650px) {
.modal-icon-upload {
.container-select {
flex-direction: column;
.container-icons {
width: 100%;
margin-right: 0;
}
.container-buttons {
max-width: unset;
margin-top: 5px;
> button {
width: 100%;
}
.buttons-manage {
display: flex;
flex-direction: row;
justify-content: stretch;
> button {
width: 50%;
flex-grow: 1;
flex-shrink: 1;
min-width: 0;
}
}
}
}
.container-upload {
display: flex;
flex-direction: column;
justify-content: flex-start;
}
}
}

View File

@ -1,5 +1,6 @@
@import "mixin"; @import "mixin";
:global {
.modal-body.modal-identity-improve { .modal-body.modal-identity-improve {
padding: 0!important; padding: 0!important;
@ -186,3 +187,4 @@
display: none; display: none;
} }
} }
}

View File

@ -1,73 +0,0 @@
@import "mixin";
@import "properties";
.modal-invite {
padding: .5em!important;
@include user-select(none);
.general-properties {
flex: 0;
display: flex;
flex-direction: column;
justify-content: stretch;
.form-group {
flex: 0;
}
.container-settings {
flex: 0;
margin-bottom: .5em;
display: flex;
flex-direction: row;
justify-content: space-between;
label {
display: flex;
flex-direction: row;
justify-content: flex-start;
> * {
align-self: center;
}
a {
margin-left: .5em;
}
}
}
}
.text-output {
background-color: $color_list_background;
border: 1px $color_list_border solid;
border-radius: $border_radius_large;
padding: .5em;
min-height: 5em;
width: 100%;
resize: none;
color: #999999;
@include user-select(text);
}
.buttons {
display: flex;
flex-direction: row;
justify-content: space-between;
margin-top: 5px;
.icon {
vertical-align: middle;
margin-right: 5px;
}
}
}

View File

@ -1,3 +1,4 @@
:global {
.modal-body.modal-keyselect { .modal-body.modal-keyselect {
width: max-content!important; width: max-content!important;
@ -48,3 +49,4 @@
} }
} }
} }
}

View File

@ -1,6 +1,7 @@
@import "mixin"; @import "mixin";
@import "properties"; @import "properties";
:global {
.modal-body.modal-latency { .modal-body.modal-latency {
@include user-select(none); @include user-select(none);
@include transform(all $button_hover_animation_time ease-in-out); @include transform(all $button_hover_animation_time ease-in-out);
@ -82,3 +83,4 @@
} }
} }
} }
}

View File

@ -1,6 +1,7 @@
@import "mixin"; @import "mixin";
@import "properties"; @import "properties";
:global {
.modal-body.modal-music-manage { .modal-body.modal-music-manage {
padding: 0 !important; padding: 0 !important;
@ -695,3 +696,4 @@
} }
} }
} }
}

View File

@ -6,6 +6,7 @@ html:root {
--modal-newcomer-divider: #313135; --modal-newcomer-divider: #313135;
} }
:global {
.modal-body.modal-newcomer { .modal-body.modal-newcomer {
display: flex!important; display: flex!important;
flex-direction: column!important; flex-direction: column!important;
@ -157,3 +158,4 @@ html:root {
padding: .5em; padding: .5em;
} }
} }
}

View File

@ -1,88 +0,0 @@
html:root {
--modal-poke-date: cornflowerblue;
--modal-poke-text: #004d00;
}
.container-poke {
display: flex!important;;
flex-direction: column!important;;
.container-servers {
display: flex;
flex-direction: column;
justify-content: stretch;
.server {
display: flex;
flex-direction: column;
justify-content: stretch;
.server-name {
margin-top: 5px;
flex-grow: 0;
flex-shrink: 0;
font-weight: bold;
text-decoration: underline;
}
.poke-list {
display: flex;
flex-direction: column;
justify-content: flex-start;
overflow-y: auto;
overflow-x: auto;
.entry {
display: flex;
flex-direction: row;
justify-content: flex-start;
flex-shrink: 0;
flex-grow: 0;
> * {
white-space: nowrap;
}
.date, .user, .text {
margin-right: 5px;
}
.date {
color: var(--modal-poke-date);
}
.text {
color: var(--modal-poke-text);
}
}
}
}
}
.buttons {
display: flex;
flex-direction: row;
justify-content: stretch;
margin-top: 5px;
flex-shrink: 0;
flex-grow: 0;
.spacer {
flex-grow: 1;
flex-shrink: 1;
}
.button-close {
flex-grow: 0;
flex-shrink: 0;
margin-top: 5px;
width: 150px;
float: right;
}
}
}

View File

@ -1,6 +1,7 @@
@import "properties"; @import "properties";
@import "mixin"; @import "mixin";
:global {
.query-create { .query-create {
display: flex!important; display: flex!important;
flex-direction: column!important; flex-direction: column!important;
@ -373,3 +374,4 @@ html:root {
background: transparent; background: transparent;
} }
} }
}

View File

@ -1,6 +1,7 @@
@import "mixin"; @import "mixin";
@import "properties"; @import "properties";
:global {
.modal-body.modal-server-edit { .modal-body.modal-server-edit {
display: flex!important; display: flex!important;
flex-direction: column!important; flex-direction: column!important;
@ -1150,3 +1151,4 @@
} }
} }
} }
}

View File

@ -1,263 +0,0 @@
@import "mixin";
html:root {
--serverinfo-background: #2f2f35;
--serverinfo-hostbanner-background: #26222a;
--serverinfo-group-border: #1f2122;
--serverinfo-group-background: #28292b;
--serverinfo-key: #557edc;
--serverinfo-value: #d6d6d7;
}
.modal-body.modal-server-info {
padding: 0!important;
width: 55em;
display: flex!important;
flex-direction: column!important;
justify-content: flex-start!important;
background-color: var(--serverinfo-background);
.container-tooltip {
flex-shrink: 0;
flex-grow: 0;
position: relative;
width: 1.6em;
margin-left: .5em;
margin-right: .5em;
font-size: .9em;
display: flex;
flex-direction: column;
justify-content: center;
img {
height: 1em;
width: 1em;
align-self: center;
font-size: 1.2em;
}
.tooltip {
display: none;
}
}
.container-top {
flex-grow: 0;
flex-shrink: 0;
max-height: 9em;
//width: 30em; /* set a default width where we have to grow/shrink */
display: flex;
flex-direction: column;
justify-content: stretch;
.container-hostbanner {
border: none;
border-radius: 0;
background-color: var(--serverinfo-hostbanner-background);
}
&.hidden {
display: none;
}
}
.container-body {
flex-shrink: 1;
min-height: 12em; /* 10em + 2 * 1em margin */
overflow-y: auto;
@include chat-scrollbar-vertical();
}
.group {
flex-grow: 0;
flex-shrink: 0;
margin: 1em;
padding: .5em;
border-radius: .2em;
border: 1px solid var(--serverinfo-group-border);
background-color: var(--serverinfo-group-background);
display: flex;
flex-direction: row;
justify-content: stretch;
height: 10em;
max-height: 10em;
.container-image {
flex-grow: 0;
flex-shrink: 0;
max-width: 15em;
max-height: 9em; /* minus one padding */
display: flex;
flex-direction: column;
justify-content: center;
img {
object-fit: contain;
max-height: 100%;
max-width: 100%;
}
margin-right: 2em;
@include transition(.25s ease-in-out);
}
.container-properties {
flex-shrink: 1;
flex-grow: 1;
min-width: 20em;
display: flex;
flex-direction: column;
justify-content: flex-start;
height: inherit;
.row {
flex-grow: 0;
flex-shrink: 0;
height: 1.8em;
display: flex;
flex-direction: row;
justify-content: flex-start;
.key {
flex-shrink: 0;
flex-grow: 0;
color: var(--serverinfo-key);
text-transform: uppercase;
align-self: center;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
width: 15em;
}
.value {
color: var(--serverinfo-value);
align-self: center;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
.country {
display: inline-block;
margin-right: .25em;
}
&.server-version {
display: flex;
flex-direction: row;
justify-content: flex-start;
a {
flex-shrink: 1;
min-width: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
}
}
.container-network {
display: flex;
flex-direction: row;
justify-content: center;
.container-button {
margin-right: 1em;
flex-shrink: 1e8;
min-width: 5em;
display: flex;
flex-direction: column;
justify-content: flex-end;
button {
height: 2.5em;
width: 12em;
max-width: 100%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
.right {
flex-grow: 1;
flex-shrink: 1;
min-width: 10em;
}
}
}
&.reverse {
flex-direction: row-reverse;
text-align: right;
.container-image {
margin-right: 0;
margin-left: 2em;
}
.container-properties {
.row {
flex-direction: row-reverse;
}
}
}
}
.container-buttons {
margin: 1em;
flex-grow: 0;
flex-shrink: 0;
display: flex;
flex-direction: row;
justify-content: space-between;
button {
min-width: 8em;
}
}
}
@media all and (max-width: 50em) {
.modal-body.modal-server-info {
.container-image {
margin: 0!important;
max-width: 0!important;
}
}
}

View File

@ -1,218 +0,0 @@
@import "mixin";
html:root {
--serverinfo-bandwidth-upload: #0a5eaa;
--serverinfo-bandwidth-download: #9f2712;
--serverinfo-title: #e3e3e4;
--serverinfo-statistics-title: #244c78;
}
.modal-body.modal-server-info-bandwidth {
padding: 0!important;
width: 55em;
display: flex!important;
flex-direction: column!important;
justify-content: flex-start!important;
background-color: #2f2f35;
.container-tooltip {
flex-shrink: 0;
flex-grow: 0;
position: relative;
width: 1.6em;
margin-left: .5em;
font-size: .9em;
display: flex;
flex-direction: column;
justify-content: center;
img {
height: 1em;
width: 1em;
align-self: center;
font-size: 1.2em;
}
.tooltip {
display: none;
}
}
.top {
flex-grow: 0;
flex-shrink: 0;
margin: 1em;
padding: .5em;
display: flex;
flex-direction: row;
justify-content: stretch;
height: 12em;
max-height: 12em;
.container-image {
flex-grow: 0;
flex-shrink: 0;
max-width: 18em;
max-height: 11em; /* minus one padding */
display: flex;
flex-direction: column;
justify-content: center;
img {
object-fit: contain;
max-height: 100%;
max-width: 100%;
}
margin-right: 2em;
@include transition(.25s ease-in-out);
}
.container-stats {
flex-shrink: 1;
flex-grow: 1;
min-width: 25em;
display: flex;
flex-direction: column;
justify-content: space-evenly;
.statistic {
display: flex;
flex-direction: column;
justify-content: flex-start;
> a {
font-size: 1.25em;
color: var(--serverinfo-title);
line-height: normal;
text-transform: uppercase;
}
.values {
display: flex;
flex-direction: row;
justify-content: space-between;
> a {
font-size: 1.2em;
line-height: normal;
}
.upload {
color: var(--serverinfo-bandwidth-upload);
}
.download {
color: var(--serverinfo-bandwidth-download);
}
}
&:not(:first-of-type) {
margin-top: 1em;
}
}
}
}
.bottom {
flex-grow: 0;
flex-shrink: 0;
margin: 1em;
padding: .5em;
border-radius: .2em;
border: 1px solid #1f2122;
background-color: #28292b;
display: flex;
flex-direction: column;
justify-content: stretch;
//height: 15em;
//max-height: 10em;
.statistic {
display: flex;
flex-direction: column;
justify-content: stretch;
.title {
flex-grow: 0;
flex-shrink: 0;
color: var(--serverinfo-statistics-title);
font-size: 1.25em;
text-transform: uppercase;
}
.body {
flex-grow: 0;
flex-shrink: 0;
display: flex;
flex-direction: row;
justify-content: stretch;
height: 7em;
.container-canvas {
flex-grow: 1;
flex-shrink: 1;
min-width: 6em;
margin-right: 1em;
}
.values {
flex-grow: 0;
flex-shrink: 0;
display: flex;
flex-direction: column;
justify-content: center;
width: 8em;
text-align: right;
.upload {
color: var(--serverinfo-bandwidth-upload);
}
.download {
color: var(--serverinfo-bandwidth-download);
}
}
}
&:not(:first-of-type) {
margin-top: 1.5em;
}
}
}
}
@media all and (max-width: 50em) {
.modal-body.modal-server-info {
.container-image {
margin: 0!important;
max-width: 0!important;
}
}
}

Some files were not shown because too many files have changed in this diff Show More