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
/dist/
/dist-package/
/declarations/
/travis-build/

View File

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

View File

@ -1,4 +1,83 @@
# 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**
- Improved the browser IPC module
- Added support for client invite links

View File

@ -1,10 +1,10 @@
export = api => {
export default api => {
api.cache(false);
const presets = [
[
"@babel/preset-env",
{
"corejs": { "version":3 },
"corejs": {"version": 3},
"useBuiltIns": "usage",
"targets": {
"edge": "17",
@ -16,10 +16,12 @@ export = api => {
}
]
];
const plugins = [
["@babel/transform-runtime"],
["@babel/plugin-transform-modules-commonjs"]
];
return {
presets,
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__);
import "./index.scss";
import "tc-shared/main";
import "tc-shared/entry-points/ModalWindow";

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[] = [
{ /* 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 */
"type": "html",
"search-pattern": /^.*([a-zA-Z]+)\.(html|json)$/,
"build-target": "dev|rel",
"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/"
},
{ /* 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 */
"type": "wav",
"search-pattern": /.*\.wav$/,
"build-target": "dev|rel",
"path": "audio/",
"local-path": "./shared/audio/"
},
{ /* shared data sound files */
"type": "json",
"search-pattern": /.*\.json/,
"search-pattern": /.*\.(wav|json)$/,
"build-target": "dev|rel",
"path": "audio/",
@ -87,15 +64,6 @@ const APP_FILE_LIST_SHARED_SOURCE: ProjectResource[] = [
"path": "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[] = [];
@ -297,7 +265,7 @@ namespace server {
} else {
server = http.createServer(handleHTTPRequest);
}
await new Promise((resolve, reject) => {
await new Promise<void>((resolve, reject) => {
server.on('error', reject);
server.listen(options.port, () => {
server.off("error", reject);
@ -308,7 +276,7 @@ namespace server {
export async function shutdown() {
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;
}
}
@ -428,7 +396,7 @@ namespace watcher {
this._process.addListener("error", this.handle_error.bind(this));
try {
await new Promise((resolve, reject) => {
await new Promise<void>((resolve, reject) => {
const id = setTimeout(reject, 5000, "timeout");
this._callback_init = () => {
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 {Stage} from "./loader/loader";
import {getUrlParameter} from "./loader/utils";
import {getUrlParameter} from "./loader/Utils";
let overlay: 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;
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";
import "./polifill";
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);
if(window["loader"]) {
throw "an loader instance has already been defined";
}
setTimeout(() => appLoader.execute(), 0);
export {};
export * from "./loader/loader";
export * as loaderAnimation from "./animation";
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 () {};
import "./bootstrap";
/* FIXME: This is glue! */
if(window["loader"]) {
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 => {
const match = location.search.match(new RegExp("(.*[?&]|^)" + key + "=([^&]+)($|&.*)"));
if(!match)
if(!match) {
return undefined;
}
return match[2];
};
@ -16,24 +14,15 @@ export class LoadSyntaxError {
}
}
export function script_name(path: SourcePath, html: boolean) {
if(Array.isArray(path)) {
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 ParallelOptions {
maxParallelRequests?: number
}
export interface ParallelResult<T> {
succeeded: T[];
failed: {
request: T,
error: T
error: any
}[],
skipped: T[];
@ -41,15 +30,22 @@ export interface ParallelResult<T> {
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 pendingRequests = requests.slice(0).reverse(); /* we're only able to pop from the back */
const currentRequests = {};
if(typeof callback === "undefined")
if(typeof callback === "undefined") {
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(Object.keys(currentRequests).length < maxParallelRequests) {
const element = pendingRequests.pop();
@ -60,18 +56,21 @@ export async function load_parallel<T>(requests: T[], executor: (_: T) => Promis
delete currentRequests[name];
callback(element, "loaded");
});
if(pendingRequests.length == 0)
if(pendingRequests.length == 0) {
break;
}
}
/*
* Wait 'till a new "slot" for downloading is free.
* This should also not throw because any errors will be caught before.
*/
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 */
}
}
await Promise.all(Object.keys(currentRequests).map(e => currentRequests[e]));
result.skipped.push(...pendingRequests);
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 {getUrlParameter} from "./utils";
import {getUrlParameter} from "./Utils";
import {LoaderPerformanceLogger} from "./Performance";
export interface ApplicationLoader {
execute();
@ -76,30 +75,9 @@ export enum Stage {
DONE
}
let cache_tag: string | undefined;
let currentStage: Stage = undefined;
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 = {
application: string,
modules: {
@ -111,8 +89,6 @@ export type ModuleMapping = {
const module_mapping_: ModuleMapping[] = [];
export function module_mapping() : ModuleMapping[] { return module_mapping_; }
export function get_cache_version() { return cache_tag; }
export function finished() {
return currentStage == Stage.DONE;
}
@ -176,22 +152,28 @@ export function setCurrentTaskName(taskId: number, name: string) {
}
export async function execute(customLoadingAnimations: boolean) {
if(!await Animation.initialize(customLoadingAnimations))
if(!await Animation.initialize(customLoadingAnimations)) {
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 end: number = Date.now();
while(currentStage <= Stage.LOADED || typeof(currentStage) === "undefined") {
while(currentStage <= Stage.LOADED || typeof currentStage === "undefined") {
let pendingTasks: InternalTask[] = [];
while((tasks[currentStage] || []).length > 0) {
if(pendingTasks.length == 0 || pendingTasks[0].priority == tasks[currentStage][0].priority) {
pendingTasks.push(tasks[currentStage].pop());
} else break;
} else {
break;
}
}
const errors: {
@ -284,7 +266,7 @@ export async function execute(customLoadingAnimations: boolean) {
}
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);
}
@ -353,79 +335,6 @@ export function critical_error_handler(handler?: ErrorHandler, override?: boolea
}
/* loaders */
export type DependSource = {
url: string;
depends: string[];
}
export type SourcePath = string | DependSource | string[];
export type SourcePath = string;
export const scripts = script_loader;
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);
}
}
export let loaderPerformance = new LoaderPerformanceLogger();

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 {config} from "./loader/loader";
import {script_name} from "./loader/utils";
import {config, loaderPerformance} from "./loader/loader";
import {loadStyles} from "./loader/StyleLoader";
import {loadScripts} from "./loader/ScriptLoader";
export interface TeaManifest {
export interface ApplicationManifest {
version: number;
chunks: {
@ -11,6 +12,10 @@ export interface TeaManifest {
hash: string,
file: string
}[],
css_files: {
hash: string,
file: string
}[],
modules: {
id: string,
context: string,
@ -20,24 +25,33 @@ export interface TeaManifest {
};
}
let manifest: TeaManifest;
export async function loadManifest() : Promise<TeaManifest> {
let manifest: ApplicationManifest;
export async function loadManifest() : Promise<ApplicationManifest> {
if(manifest) {
return manifest;
}
const requestResource = loaderPerformance.logResourceRequest("json", "manifest.json");
try {
const response = await fetch(config.baseUrl + "js/manifest.json?_date=" + Date.now());
if(!response.ok) throw response.status + " " + response.statusText;
requestResource.markExecuting();
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();
requestResource.markExecuted({ status: "success" });
} catch(error) {
requestResource.markExecuted({ status: "error-event" });
console.error("Failed to load javascript manifest: %o", error);
loader.critical_error("Failed to load manifest.json", error);
throw "failed to load manifest.json";
}
if(manifest.version !== 2)
if(manifest.version !== 2) {
throw "invalid manifest version";
}
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.");
throw "missing entry chunk";
}
loader.module_mapping().push({
application: chunkName,
modules: manifest.chunks[chunkName].modules
});
await loader.scripts.load_multiple(manifest.chunks[chunkName].files.map(e => "js/" + e.file), {
cache_tag: undefined,
max_parallel_requests: 4
}, (script, state) => {
if(state !== "loading")
const kMaxRequests = 4;
await loadStyles(manifest.chunks[chunkName].css_files.map(e => e.file), {
maxParallelRequests: kMaxRequests
}, (entry, state) => {
if (state !== "loading") {
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 */
if(Element.prototype.remove === undefined)
if(Element.prototype.remove === undefined) {
Object.defineProperty(Element.prototype, "remove", {
enumerable: false,
configurable: false,
@ -8,6 +8,7 @@ if(Element.prototype.remove === undefined)
this.parentElement.removeChild(this);
}
});
}
/* IE11 */
function ReplaceWithPolyfill() {
@ -28,14 +29,17 @@ function ReplaceWithPolyfill() {
parent.insertBefore(currentNode, this.nextSibling);
}
}
if (!Element.prototype.replaceWith)
if (!Element.prototype.replaceWith) {
Element.prototype.replaceWith = ReplaceWithPolyfill;
}
if (!CharacterData.prototype.replaceWith)
if (!CharacterData.prototype.replaceWith) {
CharacterData.prototype.replaceWith = ReplaceWithPolyfill;
}
if (!DocumentType.prototype.replaceWith)
if (!DocumentType.prototype.replaceWith) {
DocumentType.prototype.replaceWith = ReplaceWithPolyfill;
}
// Source: https://github.com/jserz/js_piece/blob/master/DOM/ParentNode/append()/append().md
(function (arr) {

View File

@ -1,62 +1,15 @@
import "./shared";
import * as loader from "../loader/loader";
import {ApplicationLoader, SourcePath} from "../loader/loader";
import {script_name} from "../loader/utils";
import {ApplicationLoader} from "../loader/loader";
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, {
name: "secure tester",
function: async () => {
/* we need https or localhost to use some things like the storage API */
if(typeof isSecureContext === "undefined")
(<any>window)["isSecureContext"] = location.protocol !== 'https:' || location.hostname === 'localhost';
if(typeof isSecureContext === "undefined") {
(window as any)["isSecureContext"] = location.protocol !== 'https:' || location.hostname === 'localhost';
}
if(!isSecureContext) {
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
});
loader.register_task(loader.Stage.INITIALIZING, {
name: "webassembly tester",
function: loader_webassembly.test_webassembly,
priority: 20
});
loader.register_task(loader.Stage.JAVASCRIPT, {
name: "scripts",
function: loader_javascript.load_scripts,
priority: 10
});
loader.register_task(loader.Stage.TEMPLATES, {
name: "templates",
name: "manifest",
function: async taskId => {
await loader.templates.load_multiple([
"templates.html",
"templates/modal/musicmanage.html",
"templates/modal/newcomer.html",
], {
cache_tag: getCacheTag(),
max_parallel_requests: -1
}, LoaderTaskCallback(taskId));
loader.setCurrentTaskName(taskId, "manifest");
await loadManifest();
await loadManifestTarget(__build.entry_chunk_name, taskId);
},
priority: 10
});

View File

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

View File

@ -2,7 +2,7 @@ import "./shared";
import * as loader from "../loader/loader";
import {ApplicationLoader, Stage} from "../loader/loader";
import {loadManifest, loadManifestTarget} from "../maifest";
import {getUrlParameter} from "../loader/utils";
import {getUrlParameter} from "../loader/Utils";
export default class implements ApplicationLoader {
execute() {
@ -10,7 +10,7 @@ export default class implements ApplicationLoader {
function: async taskId => {
await loadManifest();
const entryChunk = getUrlParameter("chunk");
const entryChunk = getUrlParameter("loader-chunk");
if(!entryChunk) {
loader.critical_error("Missing entry chunk parameter");
throw "Missing entry chunk parameter";
@ -45,19 +45,6 @@ export default class implements ApplicationLoader {
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);
}
}

View File

@ -1,6 +1,6 @@
import * as loader 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, {
name: "app init",
@ -13,14 +13,11 @@ loader.register_task(Stage.SETUP, {
}
window.__native_client_init_hook();
window.native_client = true;
} else {
if(__build.target !== "web") {
loader.critical_error("App seems not to be compiled for the web.", "This app has been compiled for " + __build.target);
return;
}
window.native_client = false;
}
},
priority: 1000
@ -50,7 +47,6 @@ loader.register_task(Stage.SETUP, {
case "ie":
loader.critical_error("Browser not supported", "We're sorry, but your browser isn't supported.");
throw "unsupported browser";
}
},
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 = {
name: string,
priority: number, /* tasks with the same priority will be executed in sync */
function: () => Promise<void>
function: (taskId: number) => Promise<void>
};
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_handler(handler?: ErrorHandler, override?: boolean);
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>
<html lang="en">
<head>
@ -16,20 +9,20 @@ 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: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. %>
<link rel="manifest" href="/manifest.json">
<% /* Using an absolute path here since the manifest.json works only with such. */ %>
<% /* <link rel="manifest" href="/manifest.json"> */ %>
<% if(build_target === "client") { %>
<% if(buildTarget === "client") { %>
<title>TeaClient</title>
<meta name='og:title' content='TeaClient'>
<% } else { %>
<% } else { %>
<title>TeaSpeak-Web</title>
<meta name='og:title' content='TeaSpeak-Web'>
<link rel='shortcut icon' href='img/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='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"> */ %>
<% } %>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="format-detection" content="telephone=no">
@ -47,19 +40,21 @@ var initial_css;
gtag('config', 'UA-113151733-4');
</script>
<link rel="preload" as="image" href="img/loader/initial-sequence.gif">
<link rel="preload" as="image" href="img/loader/bowl.png">
<%# 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/initial-sequence.gif") %>">
<link rel="preload" as="image" href="<%= require("./images/bowl.png") %>">
<% /* We don't preload the bowl since it's only a div background */ %>
<link rel="preload" as="image" href="<%= require("./images/text.png") %>">
<%- initial_css %>
<%= htmlWebpackPlugin.tags.headTags %>
</head>
<body>
<!-- No javascript error -->
<noscript>
<div id="overlay-no-js">
<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>
<h3>TeaSpeak web could not run without it!</h3>
<h3>Its like you, without coffee</h3>
@ -67,24 +62,20 @@ var initial_css;
</div>
</noscript>
<!-- loader setup -->
<div id="style"></div>
<div id="scripts"></div>
<!-- Loading screen -->
<div class="loader" id="loader-overlay">
<div class="container">
<div class="setup">
<lazy-img src="img/loader/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/initial-sequence.gif") %>" alt="initial loading sequence" x-animation-depend="normal"></lazy-img>
<lazy-img src="<%= require("./images/halloween-initial-sequence.gif") %>" alt="initial loading sequence" x-animation-depend="halloween"></lazy-img>
</div>
<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="text" src="img/loader/text.png" alt="TeaSpeak" 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="<%= require("./images/text.png") %>" alt="TeaSpeak" x-animation-depend="normal"></lazy-img>
<div class="steam"></div>
</div>
<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>
@ -94,13 +85,15 @@ var initial_css;
<!-- Critical load error -->
<div class="fulloverlay" id="critical-load">
<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>
<h3 class="detail"></h3>
</div>
</div>
<%- initial_script %>
<%= htmlWebpackPlugin.tags.bodyTags %>
</body>
</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.",
"scripts": {
"compile-project-base": "tsc -p tsbaseconfig.json",
"compile-tr-gen": "tsc -p tools/trgen/tsconfig.json",
"trgen": "node tools/trgen/index.js",
"tsc": "tsc",
"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-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"
},
"author": "TeaSpeak (WolverinDEV)",
@ -21,6 +21,7 @@
"@babel/plugin-transform-runtime": "^7.10.4",
"@babel/preset-env": "^7.10.4",
"@google-cloud/translate": "^5.3.0",
"@svgr/webpack": "^5.5.0",
"@types/dompurify": "^2.0.1",
"@types/ejs": "^3.0.2",
"@types/emoji-mart": "^3.0.2",
@ -29,12 +30,12 @@
"@types/html-minifier": "^3.5.3",
"@types/jquery": "^3.3.34",
"@types/jsrender": "^1.0.5",
"@types/loader-utils": "^1.1.3",
"@types/lodash": "^4.14.149",
"@types/moment": "^2.13.0",
"@types/node": "^12.7.2",
"@types/react-color": "^3.0.4",
"@types/react-dom": "^16.9.5",
"@types/react-grid-layout": "^1.1.1",
"@types/remarkable": "^1.7.4",
"@types/sdp-transform": "^2.4.4",
"@types/sha256": "^0.2.0",
@ -42,14 +43,14 @@
"@types/websocket": "0.0.40",
"@types/xml-parser": "^1.2.29",
"@wasm-tool/wasm-pack-plugin": "^1.3.1",
"autoprefixer": "^10.2.5",
"babel-loader": "^8.1.0",
"chunk-manifest-webpack-plugin": "^1.1.2",
"circular-dependency-plugin": "^5.2.0",
"clean-css": "^4.2.1",
"clean-webpack-plugin": "^3.0.0",
"copy-webpack-plugin": "^8.0.0",
"css-loader": "^3.6.0",
"csso-cli": "^3.0.0",
"ejs": "^3.0.2",
"css-minimizer-webpack-plugin": "^1.3.0",
"exports-loader": "^0.7.0",
"fast-xml-parser": "^3.17.4",
"file-loader": "^6.0.0",
@ -57,11 +58,15 @@
"gulp": "^4.0.2",
"html-loader": "^1.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",
"mini-css-extract-plugin": "^0.9.0",
"mini-css-extract-plugin": "^1.3.9",
"mkdirp": "^0.5.1",
"node-sass": "^4.14.1",
"postcss": "^8.2.8",
"postcss-loader": "^5.2.0",
"potpack": "^1.0.1",
"raw-loader": "^4.0.0",
"sass": "1.22.10",
@ -73,14 +78,15 @@
"terser-webpack-plugin": "4.2.3",
"ts-loader": "^6.2.2",
"tsd": "^0.13.1",
"typescript": "^3.7.0",
"typescript": "^4.2",
"url-loader": "^4.1.1",
"wabt": "^1.0.13",
"webpack": "^4.42.1",
"webpack": "^5.26.1",
"webpack-bundle-analyzer": "^3.6.1",
"webpack-cli": "^3.3.11",
"webpack-svg-sprite-generator": "^1.0.17",
"worker-plugin": "^4.0.3",
"xml-parser": "^1.2.1"
"webpack-cli": "^4.5.0",
"webpack-dev-server": "^3.11.2",
"webpack-svg-sprite-generator": "^5.0.4",
"zip-webpack-plugin": "^4.0.1"
},
"repository": {
"type": "git",
@ -91,8 +97,11 @@
},
"homepage": "https://www.teaspeak.de",
"dependencies": {
"@types/react-transition-group": "^4.4.0",
"@types/crypto-js": "^4.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",
"dompurify": "^2.0.8",
"emoji-mart": "git+https://github.com/WolverinDEV/emoji-mart.git",
@ -104,16 +113,17 @@
"moment": "^2.24.0",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-grid-layout": "^1.2.2",
"react-player": "^2.5.0",
"react-transition-group": "^4.4.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",
"simple-jsonp-promise": "^1.1.0",
"simplebar-react": "^2.2.0",
"stream-browserify": "^3.0.0",
"twemoji": "^13.0.0",
"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"
}
}

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
# shellcheck disable=SC1090
source "$(dirname "$0")/resolve_commands.sh"
cd "$(dirname "$0")/../" || { echo "Failed to enter the base directory"; exit 1; }
if [[ $# -lt 2 ]]; then
echo "Invalid argument count!"
exit 1
@ -41,7 +39,7 @@ if [[ $_exit_code -ne 0 ]]; then
exit 1
fi
echo "Generating required build tooks"
echo "Generating required build hooks"
chmod +x ./tools/build_trgen.sh
./tools/build_trgen.sh; _exit_code=$?
if [[ $_exit_code -ne 0 ]]; then
@ -49,32 +47,18 @@ if [[ $_exit_code -ne 0 ]]; then
exit 1
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
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
echo "Failed to build the $build_target applcation"
echo "Failed to build the $build_target application"
exit 1
fi
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
echo "Failed to build the $build_target applcation"
echo "Failed to build the $build_target application"
exit 1
fi
fi
echo "Generating environment"
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!"
echo "$build_target build successfully!"

View File

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

View File

@ -1,7 +1,7 @@
#!/usr/bin/env bash
BASEDIR=$(dirname "$0")
cd "$BASEDIR/../"
cd "$BASEDIR/../" || exit
# This script cleanups all generated files
function remove_if_exists() {
@ -35,7 +35,7 @@ function cleanup_files() {
for file in "${files[@]}"
do :
echo " - $file"
rm ${file}
rm "${file}"
done
}
@ -43,6 +43,8 @@ if [[ "$1" == "full" ]]; then
echo "Full cleanup. Deleting generated javascript and css files"
cleanup_files "shared/js" "*.js" "JavaScript"
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.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
TMP_FILE_NAME="TeaSpeakUI.tar.gz"
TMP_DIR_NAME="tmp"
cd "$(dirname "$0")/../" || { echo "failed to enter base directory"; exit 1; }
cd "$(dirname "$0")" || { echo "failed to enter base directory"; exit 1; }
source "./helper.sh"
if [[ "$#" -ne 3 ]]; then
echo "Illegal number of parameters (url | channel | required version)"
exit 1
fi
if [[ ! -d client-api/environment/ui-files/ ]]; then
echo "Missing UI Files"
exit 1
fi
# shellcheck disable=SC2154
if [[ "${teaclient_deploy_secret}" == "" ]]; then
echo "Missing deploy secret!"
exit 1
fi
if [[ -e "${TMP_FILE_NAME}" ]]; then
echo "Temp file already exists!"
echo "Deleting it!"
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
fi
cd "${__cur_dir}" || { echo "failed to enter original dir"; exit 1; }
echo "${php_result}" > "${file::-4}.html"
done < <(find "${TMP_DIR_NAME}" -name '*.php' -print0)
cd ${TMP_DIR_NAME} || { echo "failed to enter the temp dir"; exit 1; }
tar chvzf ${TMP_FILE_NAME} ./*; _exit_code=$?
if [[ $_exit_code -ne 0 ]]; then
echo "Failed to pack file ($_exit_code)"
package_file=$(find_release_package "client" "release")
if [[ $? -ne 0 ]]; then
echo "$package_file"
exit 1
fi
mv ${TMP_FILE_NAME} ../../../../
cd ../
rm -r ${TMP_DIR_NAME}
cd ../../../
RESP=$(curl \
# We require a .tar.gz file and not a zip file.
# So we're extracting the contents and tar.gz ing them
temp_dir=$(mktemp -d)
unzip "$package_file" -d "$temp_dir/raw/"
if [[ $? -ne 0 ]]; then
rm -r "$temp_dir" &>/dev/null
echo "Failed to unpack package."
exit 1
fi
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 \
-X POST \
-F "required_client=$3" \
-F "type=deploy-ui-build" \
-F "channel=$2" \
-F "version=$APPLICATION_VERSION" \
-F "git_ref=$GIT_HASH" \
-F "version=$application_version" \
-F "git_ref=$git_hash" \
-F "secret=${teaclient_deploy_secret}" \
-F "file=@$(pwd)/TeaSpeakUI.tar.gz" \
-F "file=@$package_file" \
"$1"
)
echo "$RESP"
SUCCESS=$(echo "${RESP}" | python -c "import sys, json; print(json.load(sys.stdin)['success'])")
if [[ ! "${SUCCESS}" == "True" ]]; then
ERROR=$(echo "${RESP}" | python -c "import sys, json; print(json.load(sys.stdin)['error'])" 2>/dev/null); _exit_code=$?
if [[ $_exit_code -ne 0 ]]; then
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 -r "$temp_dir"
echo "Server upload result: $upload_result"
success=$(echo "${upload_result}" | python -c "import sys, json; print(json.load(sys.stdin)['success'])")
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
else
echo "Build successfully deployed!"
exit 0
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
}
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_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() {
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"
#Execute the command
for command in "${@:3}"; do
echo "$> $command" >> ${LOG_FILE}
echo "$> $command" >> "${LOG_FILE}"
if [[ ${build_verbose} -gt 0 ]]; then
echo "$> $command"
fi
@ -72,18 +72,18 @@ function execute() {
error=""
if [[ ${build_verbose} -gt 0 ]]; then
if [[ -f ${LOG_FILE}.tmp ]]; then
rm ${LOG_FILE}.tmp
rm "${LOG_FILE}.tmp"
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=$(cat ${LOG_FILE}.tmp)
cat ${LOG_FILE}.tmp >> ${LOG_FILE}
rm ${LOG_FILE}.tmp
error=$(cat "${LOG_FILE}.tmp")
cat "${LOG_FILE}.tmp" >> "${LOG_FILE}"
rm "${LOG_FILE}.tmp"
else
error=$(${command} 2>&1)
error_code=$?
echo "$error" >> ${LOG_FILE}
echo "$error" >> "${LOG_FILE}"
fi
@ -154,51 +154,20 @@ source ./scripts/install_dependencies.sh
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() {
execute \
"Building release package" \
"Failed to build 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() {
execute \
"Building debug package" \
"Failed to build debug" \
"./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/web_package.sh
if [[ ${build_release} ]]; then
execute_build_release
fi

View File

@ -2,13 +2,9 @@
cd "$(dirname "$0")/../../" || { echo "Failed to enter base dir"; exit 1; }
source ./scripts/travis/properties.sh
source ./scripts/helper.sh
git_rev=$(git rev-parse --short HEAD)
[[ ! "$git_rev" =~ [a-z0-9]{6} ]] && {
echo "Failed to parse git rev. Received: '$git_rev'."
exit 1
}
git_rev=$(git_version "short-tag")
if [[ "$1" == "release" ]]; then
echo "Releasing $git_rev as release"
rolling_tag="latest"
@ -20,6 +16,7 @@ else
exit 1
fi
# FIXME: This dosn't work anymore
zip_file=$(find "$PACKAGES_DIRECTORY" -maxdepth 1 -name "TeaWeb-release*.zip" -print)
[[ $(echo "$zip_file" | wc -l) -ne 1 ]] && {
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
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/helper.sh
if [[ -z "${GIT_AUTHTOKEN}" ]]; then
echo "GIT_AUTHTOKEN isn't set. Don't deploying build!"
exit 0
fi
GIT_COMMIT_SHORT=$(git rev-parse --short HEAD)
GIT_COMMIT_LONG=$(git rev-parse HEAD)
echo "Deploying $GIT_COMMIT_SHORT ($GIT_COMMIT_LONG) to github."
git_release_executable="/tmp/git-release"
install_git_release() {
if [[ -x "${git_release_executable}" ]]; then
# File already available. No need to install it.
return 0
fi
GIT_RELEASE_EXECUTABLE="/tmp/git-release"
if [[ ! -x ${GIT_RELEASE_EXECUTABLE} ]]; then
if [[ ! -f ${GIT_RELEASE_EXECUTABLE} ]]; then
if [[ ! -f ${git_release_executable} ]]; then
echo "Downloading github-release-linux (1.2.4)"
if [[ -f /tmp/git-release.gz ]]; then
rm /tmp/git-release.gz
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 ]] || {
echo "Failed to download github-release-linux"
exit 1
}
gunzip /tmp/git-release.gz; _exit_code=$?;
gunzip /tmp/git-release.gz
_exit_code=$?
[[ $_exit_code -eq 0 ]] || {
echo "Failed to unzip github-release-linux"
exit 1
}
chmod +x /tmp/git-release;
chmod +x /tmp/git-release
echo "Download of github-release-linux (1.2.4) finished"
else
chmod +x ${GIT_RELEASE_EXECUTABLE}
chmod +x ${git_release_executable}
fi
if [[ ! -x ${GIT_RELEASE_EXECUTABLE} ]]; then
if [[ ! -x ${git_release_executable} ]]; then
echo "git-release isn't executable"
exit 1
fi
fi
}
install_git_release
cd "$(dirname "$0")/../../" || { echo "Failed to enter base dir"; exit 1; }
echo "Generating release"
${GIT_RELEASE_EXECUTABLE} release \
git_versions_tag=$(git_version "short-tag")
echo "Deploying $git_versions_tag ($(git_version "long-tag")) to GitHub."
echo "Generating release tag"
${git_release_executable} release \
--repo "TeaWeb" \
--owner "TeaSpeak" \
--token "${GIT_AUTHTOKEN}" \
--title "Travis autobuild ${GIT_COMMIT_SHORT}" \
--tag "${GIT_COMMIT_SHORT}" \
--description "This is a autobuild release from travis"
--title "Travis auto build $git_versions_tag" \
--tag "$git_versions_tag" \
--description "This is a auto build release from travis"
[[ $? -eq 0 ]] || {
echo "Failed to generate git release"
exit 1
}
echo "Uploading release files"
folders=("${LOG_FILE}" "${PACKAGES_DIRECTORY}")
uploaded_files=()
failed_files=()
for folder in "${folders[@]}"; do
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 \
upload_package() {
local package_file
package_file=$(find_release_package "web" "$1")
if [[ $? -eq 0 ]]; then
echo "Uploading $1 package ($package_file)"
${git_release_executable} upload \
--repo "TeaWeb" \
--owner "TeaSpeak" \
--token "${GIT_AUTHTOKEN}" \
--tag "${GIT_COMMIT_SHORT}" \
--file "$file" \
--name "`basename $file`"
--tag "$git_versions_tag" \
--file "$package_file" \
--name "$(basename "$package_file")"
[[ $? -eq 0 ]] && {
echo " Uploaded.";
uploaded_files+="$file"
} || {
echo "Failed to generate git release"
failed_files+="$file"
}
done
done
echo "Successfully uploaded $1 package"
else
echo "Skipping $1 package upload: $package_file"
fi
}
echo "Successfully uploaded ${#uploaded_files[@]} files. ${#failed_files[@]} uploads failed."
upload_package "development"
upload_package "release"
exit 0

View File

@ -5,8 +5,12 @@ if [[ -z "$1" ]]; then
exit 1
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/helper.sh
if [[ -z "${SSH_KEY}" ]]; then
echo "Missing environment variable SSH_KEY. Please set it before using this script!"
@ -20,31 +24,28 @@ chmod 600 /tmp/sftp_key
exit 1
}
file=$(find "$PACKAGES_DIRECTORY" -maxdepth 1 -name "TeaWeb-release*.zip" -print)
[[ $(echo "$file" | wc -l) -ne 1 ]] && {
echo "Invalid release file count (Expected 1 but got $(echo "$file" | wc -l)): ${file[*]}"
package_file=$(find_release_package "web" "release")
if [[ $? -ne 0 ]]; then
echo "$package_file"
exit 1
}
[[ ! -e "$file" ]] && {
echo "File ($file) does not exists"
exit 1
}
#TeaSpeak-Travis-Web
# ssh -oStrictHostKeyChecking=no $h TeaSpeak-Travis-Web@dev.web.teaspeak.de
fi
upload_name=$(basename "$package_file")
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 -ne 0 ]] && {
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
put $file tmp-upload/$filename
put $package_file tmp-upload/$upload_name
EOF
_exit_code=$?
[[ $_exit_code -ne 0 ]] && {
echo "Failed to upload the .zip file ($_exit_code)"
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 $?

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.2 NPM](#122-npm)
- [1.3 Git bash](#13-git-bash)
- [1.4 Rust (Options)](#14-rust-optional-will-be-installed-automatically)
### 1.1 IDE
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.
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.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 "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/main-layout.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/general.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/frame-chat.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/server-log.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/scroll.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/hostbanner.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/htmltags.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/menu-bar.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/mixin.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/modal.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/modals.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/modal-about.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/modal-avatar.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/modal-banclient.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/modal-banlist.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/modal-bookmarks.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/modal-channelinfo.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/modal-clientinfo.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/modal-connect.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 "./static/properties.scss"
import "./static/main-layout.scss"
import "./static/general.scss"
import "./static/frame-chat.scss"
import "./static/htmltags.scss"
import "./static/mixin.scss"
import "./static/modal.scss"
import "./static/modals.scss"
import "./static/modal-banclient.scss"
import "./static/modal-banlist.scss"
import "./static/modal-clientinfo.scss"
import "./static/modal-identity.scss"
import "./static/modal-newcomer.scss"
import "./static/modal-keyselect.scss"
import "./static/modal-query.scss"
import "./static/modal-server.scss"
import "./static/modal-musicmanage.scss"
import "./static/modal-settings.scss"
import "./static/overlay-image-preview.scss"
import "./static/color-variables.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/ts/tab.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/ts/country.scss"
import "./static/ts/tab.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;
}

File diff suppressed because one or more lines are too long

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 @@
.htmltag-client, .htmltag-channel {
:global {
.htmltag-client, .htmltag-channel {
color: var(--text);
font-weight: bold;
cursor: pointer;
}
}

View File

@ -16,46 +16,27 @@ html:root {
--channel-chat-seperator: #1e1e1e;
--channel-chat-seperator-selected: #707070;
--server-log-text: #6e6e6e;
--server-log-error: #e62222;
--server-log-tree-entry: #d8d8d8;
--hostbanner-background: #2e2e2e;
}
.hide-small {
opacity: 1;
transition: opacity $animation_length linear;
}
.show-small {
display: none;
opacity: 0;
transition: opacity $animation_length linear;
}
.app-container {
:global {
/* Still in use for the global hang in point */
.app-container {
right: 0;
left: 0;
top: 0;
overflow: auto;
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;
.container-seperator {
$animation_seperator_length: .1s;
.container-seperator {
@include transition(all $animation_seperator_length ease-in-out);
background: var(--channel-chat-seperator);
@ -81,12 +62,13 @@ $animation_seperator_length: .1s;
background-color: var(--channel-chat-seperator-selected);
}
}
}
html, body {
html, body {
overflow: hidden;
}
}
body {
body {
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;
}
@mixin chat-scrollbar() {
@mixin chat-scrollbar($width: .5em) {
& {
/* for moz */
scrollbar-color: #353535 #555;
scrollbarWidth: .5em;
scrollbarWidth: $width;
}
&::-webkit-scrollbar-track {
border-radius: .25em;
border-radius: $width / 2;
background-color: transparent;
cursor: pointer;
}
&::-webkit-scrollbar {
width: .5em;
height: .5em;
width: $width;
height: $width;
background-color: transparent;
cursor: pointer;
}
&::-webkit-scrollbar-thumb {
border-radius: .25em;
border-radius: $width / 2;
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,4 +1,5 @@
.modal-avatar-list {
:global {
.modal-avatar-list {
display: flex!important;;
flex-direction: row!important;;
@ -172,9 +173,9 @@
}
}
}
}
}
.modal-avatar-upload {
.modal-avatar-upload {
display: flex;
flex-direction: column;
justify-content: stretch;
@ -289,10 +290,13 @@
}
}
}
}
}
@media all and (max-width: 40rem) {
:global {
.modal-avatar-upload .container-preview .previews {
flex-direction: column;
}
}
}

View File

@ -1,8 +1,9 @@
@import "mixin";
@import "properties";
//TODO: Resize style!
.modal-body.modal-ban-client {
:global {
//TODO: Resize style!
.modal-body.modal-ban-client {
padding: 0!important;
display: flex!important;
@ -290,4 +291,5 @@
width: 6em;
}
}
}
}

View File

@ -3,7 +3,8 @@
$category_slide_animation_length: .25s;
.modal-body.modal-ban-list {
:global {
.modal-body.modal-ban-list {
padding: 0!important;
display: flex!important;
@ -806,4 +807,5 @@ $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,7 +1,8 @@
@import "mixin";
@import "properties";
.modal-body.modal-client-info {
:global {
.modal-body.modal-client-info {
padding: 0!important;
$avatar_size: 12em;
@ -615,4 +616,5 @@
}
}
}
}
}

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,6 +1,7 @@
@import "mixin";
.modal-body.modal-identity-improve {
:global {
.modal-body.modal-identity-improve {
padding: 0!important;
display: flex!important;
@ -85,9 +86,9 @@
}
}
}
}
}
.modal-body.modal-identity-import {
.modal-body.modal-identity-import {
padding: 0!important;
display: flex;
@ -185,4 +186,5 @@
.file-selector {
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,4 +1,5 @@
.modal-body.modal-keyselect {
:global {
.modal-body.modal-keyselect {
width: max-content!important;
.body {
@ -47,4 +48,5 @@
margin-left: 1em;
}
}
}
}

View File

@ -1,7 +1,8 @@
@import "mixin";
@import "properties";
.modal-body.modal-latency {
:global {
.modal-body.modal-latency {
@include user-select(none);
@include transform(all $button_hover_animation_time ease-in-out);
@ -81,4 +82,5 @@
margin-right: 1em;
}
}
}
}

View File

@ -1,7 +1,8 @@
@import "mixin";
@import "properties";
.modal-body.modal-music-manage {
:global {
.modal-body.modal-music-manage {
padding: 0 !important;
display: flex !important;;
@ -655,9 +656,9 @@
}
}
}
}
}
.tooltip-music-permission-overview {
.tooltip-music-permission-overview {
padding-left: .25em;
padding-right: .25em;
text-align: left;
@ -694,4 +695,5 @@
display: none;
}
}
}
}

View File

@ -6,7 +6,8 @@ html:root {
--modal-newcomer-divider: #313135;
}
.modal-body.modal-newcomer {
:global {
.modal-body.modal-newcomer {
display: flex!important;
flex-direction: column!important;
justify-content: stretch!important;
@ -156,4 +157,5 @@ html:root {
border-top: 1.25px solid var(--modal-newcomer-divider);
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,7 +1,8 @@
@import "properties";
@import "mixin";
.query-create {
:global {
.query-create {
display: flex!important;
flex-direction: column!important;
@ -23,9 +24,9 @@
margin-top: 5px;
text-align: right;
}
}
}
.query-created {
.query-created {
display: flex!important;
flex-direction: column!important;
@ -52,9 +53,9 @@
flex-grow: 1;
}
}
}
}
html:root {
html:root {
--modal-query-title: #e0e0e0;
--modal-query-list: #28292b;
@ -68,9 +69,9 @@ html:root {
--modal-query-key: #557edc;
--modal-query-copy-hover: #28292b;
}
}
.modal-body.modal-query-manage {
.modal-body.modal-query-manage {
display: flex!important;
flex-direction: row!important;
justify-content: stretch!important;
@ -372,4 +373,5 @@ html:root {
.container-seperator {
background: transparent;
}
}
}

View File

@ -1,7 +1,8 @@
@import "mixin";
@import "properties";
.modal-body.modal-server-edit {
:global {
.modal-body.modal-server-edit {
display: flex!important;
flex-direction: column!important;
justify-content: stretch!important;
@ -1149,4 +1150,5 @@
margin-left: 1em;
}
}
}
}

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