Merge branch 'develop' into canary
# Conflicts: # ChangeLog.md # package-lock.json # package.jsoncanary
commit
c559fdff6c
|
@ -25,9 +25,10 @@ node_modules/
|
|||
/todo.txt
|
||||
/tmp/
|
||||
|
||||
# All out config files are .ts files
|
||||
# All our config files are .ts files
|
||||
/*.js
|
||||
/*.js.map
|
||||
!babel.config.js
|
||||
|
||||
/webpack/*.js
|
||||
/webpack/*.js.map
|
||||
|
|
|
@ -6,6 +6,10 @@
|
|||
- Improved chat box behaviour
|
||||
- Automatically crawling all channels on server join for new messages (requires TeaSpeak 1.4.16-b2 or higher)
|
||||
|
||||
* **12.07.20**
|
||||
- Made the loader compatible with ES5 to support older browsers
|
||||
- Updated the loader animation
|
||||
|
||||
* **15.06.20**
|
||||
- Recoded the permission editor with react
|
||||
- Fixed sever permission editor display bugs
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
module.exports = function (api) {
|
||||
api.cache(false);
|
||||
const presets = [
|
||||
[
|
||||
"@babel/preset-env",
|
||||
{
|
||||
"corejs": { "version":3 },
|
||||
"useBuiltIns": "usage",
|
||||
"targets": {
|
||||
"edge": "17",
|
||||
"firefox": "60",
|
||||
"chrome": "67",
|
||||
"safari": "11.1",
|
||||
"ie": "11"
|
||||
}
|
||||
}
|
||||
]
|
||||
];
|
||||
const plugins = [
|
||||
["@babel/transform-runtime"],
|
||||
["@babel/plugin-transform-modules-commonjs"]
|
||||
];
|
||||
return {
|
||||
presets,
|
||||
plugins
|
||||
};
|
||||
};
|
2
file.ts
2
file.ts
|
@ -121,7 +121,7 @@ const APP_FILE_LIST_SHARED_SOURCE: ProjectResource[] = [
|
|||
},
|
||||
{ /* shared image files */
|
||||
"type": "img",
|
||||
"search-pattern": /.*\.(svg|png)/,
|
||||
"search-pattern": /.*\.(svg|png|gif)/,
|
||||
"build-target": "dev|rel",
|
||||
|
||||
"path": "img/",
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
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;
|
|
@ -0,0 +1,128 @@
|
|||
import * as loader from "./loader/loader";
|
||||
import {Stage} from "./loader/loader";
|
||||
|
||||
let overlay: HTMLDivElement;
|
||||
let setupContainer: HTMLDivElement;
|
||||
let idleContainer: HTMLDivElement;
|
||||
let idleSteamContainer: HTMLDivElement;
|
||||
let loaderStageContainer: HTMLDivElement;
|
||||
|
||||
let finalizing = false;
|
||||
let initializeTimestamp;
|
||||
|
||||
let verbose = false;
|
||||
let apngSupport = undefined;
|
||||
|
||||
async function detectAPNGSupport() {
|
||||
const image = new Image();
|
||||
const ctx = document.createElement("canvas").getContext("2d");
|
||||
|
||||
// frame 1 (skipped on apng-supporting browsers): [0, 0, 0, 255]
|
||||
// frame 2: [0, 0, 0, 0]
|
||||
image.src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACGFjVEwAAAABAAAAAcMq2TYAAAANSURBVAiZY2BgYPgPAAEEAQB9ssjfAAAAGmZjVEwAAAAAAAAAAQAAAAEAAAAAAAAAAAD6A+gBAbNU+2sAAAARZmRBVAAAAAEImWNgYGBgAAAABQAB6MzFdgAAAABJRU5ErkJggg==";
|
||||
await new Promise(resolve => image.onload = resolve);
|
||||
|
||||
ctx.drawImage(image, 0, 0);
|
||||
apngSupport = ctx.getImageData(0, 0, 1, 1).data[3] === 0;
|
||||
console.log("Browser APNG support: %o", apngSupport);
|
||||
}
|
||||
|
||||
function initializeElements() {
|
||||
overlay = document.getElementById("loader-overlay") as HTMLDivElement;
|
||||
if(!overlay)
|
||||
throw "missing loader overlay";
|
||||
|
||||
for(const lazyImage of [...overlay.getElementsByTagName("lazy-img")]) {
|
||||
const image = document.createElement("img");
|
||||
image.alt = lazyImage.getAttribute("alt");
|
||||
image.src = lazyImage.getAttribute(apngSupport ? "src-apng" : "src-gif") || lazyImage.getAttribute("src");
|
||||
image.className = lazyImage.className;
|
||||
lazyImage.replaceWith(image);
|
||||
}
|
||||
|
||||
setupContainer = overlay.getElementsByClassName("setup")[0] as HTMLDivElement;
|
||||
if(!setupContainer)
|
||||
throw "missing setup container";
|
||||
|
||||
idleContainer = overlay.getElementsByClassName("idle")[0] as HTMLDivElement;
|
||||
if(!idleContainer)
|
||||
throw "missing idle container";
|
||||
|
||||
idleSteamContainer = idleContainer.getElementsByClassName("steam")[0] as HTMLDivElement;
|
||||
if(!idleSteamContainer)
|
||||
throw "missing idle steam container";
|
||||
|
||||
loaderStageContainer = overlay.getElementsByClassName("loader-stage")[0] as HTMLDivElement;
|
||||
if(!loaderStageContainer)
|
||||
throw "missing loader stage container";
|
||||
|
||||
setupContainer.onanimationend = setupAnimationFinished;
|
||||
idleSteamContainer.onanimationiteration = idleSteamAnimationLooped;
|
||||
overlay.onanimationend = overlayAnimationFinished;
|
||||
}
|
||||
|
||||
export async function initialize() {
|
||||
await detectAPNGSupport();
|
||||
try {
|
||||
initializeElements();
|
||||
} catch (error) {
|
||||
console.error("Failed to setup animations: %o", error);
|
||||
loader.critical_error("Animation setup failed", error);
|
||||
return false;
|
||||
}
|
||||
|
||||
StageNames[Stage.SETUP] = "starting app";
|
||||
StageNames[Stage.TEMPLATES] = "loading templates";
|
||||
StageNames[Stage.STYLE] = "loading styles";
|
||||
StageNames[Stage.JAVASCRIPT] = "loading app";
|
||||
StageNames[Stage.JAVASCRIPT_INITIALIZING] = "initializing";
|
||||
StageNames[Stage.FINALIZING] = "rounding up";
|
||||
StageNames[Stage.LOADED] = "starting app";
|
||||
|
||||
overlay.classList.add("initialized");
|
||||
setupContainer.classList.add("visible");
|
||||
|
||||
initializeTimestamp = Date.now();
|
||||
return true;
|
||||
}
|
||||
|
||||
export function abort() {
|
||||
overlay?.remove();
|
||||
}
|
||||
|
||||
export function finalize() {
|
||||
finalizing = true;
|
||||
|
||||
if(loaderStageContainer)
|
||||
loaderStageContainer.innerText = "app loaded successfully (" + (Date.now() - initializeTimestamp) + "ms)";
|
||||
}
|
||||
|
||||
const StageNames = {};
|
||||
export function updateState(state: Stage, tasks: string[]) {
|
||||
if(loaderStageContainer)
|
||||
loaderStageContainer.innerText = StageNames[state] + (tasks.length === 1 ? " (task: " + tasks[0] + ")" : " (tasks: " + tasks.join(",") + ")");
|
||||
}
|
||||
|
||||
function setupAnimationFinished() {
|
||||
verbose && console.log("Entering idle animation");
|
||||
|
||||
setupContainer.classList.remove("visible");
|
||||
idleContainer.classList.add("visible");
|
||||
}
|
||||
|
||||
function idleSteamAnimationLooped() {
|
||||
verbose && console.log("Idle animation looped. Should finalize: %o", finalizing);
|
||||
if(!finalizing)
|
||||
return;
|
||||
|
||||
overlay.classList.add("finishing");
|
||||
}
|
||||
|
||||
function overlayAnimationFinished(event: AnimationEvent) {
|
||||
/* the text animation is the last one */
|
||||
if(event.animationName !== "swipe-out-text")
|
||||
return;
|
||||
|
||||
verbose && console.log("Animation finished");
|
||||
overlay.remove();
|
||||
}
|
|
@ -1,8 +1,15 @@
|
|||
import * as loader from "./targets/app";
|
||||
import * as loader_base from "./loader/loader";
|
||||
window["loader"] = loader_base;
|
||||
/* let the loader register himself at the window first */
|
||||
setTimeout(loader.run, 0);
|
||||
import "core-js/stable";
|
||||
import "./polifill";
|
||||
|
||||
import * as loader from "./loader/loader";
|
||||
window["loader"] = loader;
|
||||
/* let the loader register himself at the window first */
|
||||
|
||||
import * as AppLoader from "./targets/app";
|
||||
setTimeout(AppLoader.run, 0);
|
||||
|
||||
import * as EmptyLoader from "./targets/empty";
|
||||
//setTimeout(EmptyLoader.run, 0);
|
||||
|
||||
export {};
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import * as script_loader from "./script_loader";
|
||||
import * as style_loader from "./style_loader";
|
||||
import * as template_loader from "./template_loader";
|
||||
import * as Animation from "../animation";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
|
@ -31,7 +32,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 {
|
||||
|
@ -39,30 +40,37 @@ export enum Stage {
|
|||
loading loader required files (incl this)
|
||||
*/
|
||||
INITIALIZING,
|
||||
|
||||
/*
|
||||
setting up the loading process
|
||||
*/
|
||||
SETUP,
|
||||
|
||||
/*
|
||||
loading all style sheet files
|
||||
*/
|
||||
STYLE,
|
||||
|
||||
/*
|
||||
loading all javascript files
|
||||
*/
|
||||
JAVASCRIPT,
|
||||
|
||||
/*
|
||||
loading all template files
|
||||
*/
|
||||
TEMPLATES,
|
||||
|
||||
/*
|
||||
initializing static/global stuff
|
||||
*/
|
||||
JAVASCRIPT_INITIALIZING,
|
||||
|
||||
/*
|
||||
finalizing load process
|
||||
*/
|
||||
FINALIZING,
|
||||
|
||||
/*
|
||||
invoking main task
|
||||
*/
|
||||
|
@ -72,7 +80,7 @@ export enum Stage {
|
|||
}
|
||||
|
||||
let cache_tag: string | undefined;
|
||||
let current_stage: Stage = undefined;
|
||||
let currentStage: Stage = undefined;
|
||||
const tasks: {[key:number]:Task[]} = {};
|
||||
|
||||
/* test if all files shall be load from cache or fetch again */
|
||||
|
@ -109,12 +117,12 @@ export function module_mapping() : ModuleMapping[] { return module_mapping_; }
|
|||
export function get_cache_version() { return cache_tag; }
|
||||
|
||||
export function finished() {
|
||||
return current_stage == Stage.DONE;
|
||||
return currentStage == Stage.DONE;
|
||||
}
|
||||
export function running() { return typeof(current_stage) !== "undefined"; }
|
||||
export function running() { return typeof(currentStage) !== "undefined"; }
|
||||
|
||||
export function register_task(stage: Stage, task: Task) {
|
||||
if(current_stage > stage) {
|
||||
if(currentStage > stage) {
|
||||
if(config.error)
|
||||
console.warn("Register loading task, but it had already been finished. Executing task anyways!");
|
||||
|
||||
|
@ -139,20 +147,41 @@ export function register_task(stage: Stage, task: Task) {
|
|||
tasks[stage] = task_array.sort((a, b) => a.priority - b.priority);
|
||||
}
|
||||
|
||||
type RunningTask = {
|
||||
taskId: number,
|
||||
name: string,
|
||||
promise: Promise<void> | undefined
|
||||
};
|
||||
let runningTasks: RunningTask[] = [];
|
||||
let runningTaskIdIndex = 1;
|
||||
|
||||
export function setCurrentTaskName(taskId: number, name: string) {
|
||||
const task = runningTasks.find(e => e.taskId === taskId);
|
||||
if(!task) {
|
||||
console.warn("Tried to set task name of unknown task %d", taskId);
|
||||
return;
|
||||
}
|
||||
|
||||
task.name = name;
|
||||
Animation.updateState(currentStage, runningTasks.map(e => e.name));
|
||||
}
|
||||
|
||||
export async function execute() {
|
||||
document.getElementById("loader-overlay").classList.add("started");
|
||||
if(!await Animation.initialize())
|
||||
return;
|
||||
|
||||
loader_cache_tag();
|
||||
|
||||
const load_begin = Date.now();
|
||||
|
||||
let begin: number = 0;
|
||||
let end: number = Date.now();
|
||||
while(current_stage <= Stage.LOADED || typeof(current_stage) === "undefined") {
|
||||
while(currentStage <= Stage.LOADED || typeof(currentStage) === "undefined") {
|
||||
|
||||
let current_tasks: Task[] = [];
|
||||
while((tasks[current_stage] || []).length > 0) {
|
||||
if(current_tasks.length == 0 || current_tasks[0].priority == tasks[current_stage][0].priority) {
|
||||
current_tasks.push(tasks[current_stage].pop());
|
||||
let pendingTasks: Task[] = [];
|
||||
while((tasks[currentStage] || []).length > 0) {
|
||||
if(pendingTasks.length == 0 || pendingTasks[0].priority == tasks[currentStage][0].priority) {
|
||||
pendingTasks.push(tasks[currentStage].pop());
|
||||
} else break;
|
||||
}
|
||||
|
||||
|
@ -161,23 +190,45 @@ export async function execute() {
|
|||
task: Task
|
||||
}[] = [];
|
||||
|
||||
const promises: Promise<void>[] = [];
|
||||
for(const task of current_tasks) {
|
||||
for(const task of pendingTasks) {
|
||||
const rTask = {
|
||||
taskId: ++runningTaskIdIndex,
|
||||
name: task.name,
|
||||
promise: undefined
|
||||
} as RunningTask;
|
||||
|
||||
try {
|
||||
if(config.verbose) console.debug("Executing loader %s (%d)", task.name, task.priority);
|
||||
const promise = task.function();
|
||||
if(config.verbose)
|
||||
console.debug("Executing loader %s (%d)", task.name, task.priority);
|
||||
|
||||
runningTasks.push(rTask);
|
||||
const promise = task.function(rTask.taskId);
|
||||
if(!promise) {
|
||||
runningTasks.splice(runningTasks.indexOf(rTask), 1);
|
||||
console.error("Loading task %s hasn't returned a promise!", task.name);
|
||||
continue;
|
||||
}
|
||||
promises.push(promise.catch(error => {
|
||||
|
||||
rTask.promise = promise.catch(error => {
|
||||
errors.push({
|
||||
task: task,
|
||||
error: error
|
||||
});
|
||||
|
||||
return Promise.resolve();
|
||||
}));
|
||||
}).then(() => {
|
||||
const index = runningTasks.indexOf(rTask);
|
||||
if(index === -1) {
|
||||
console.warn("Running task (%s) finished, but it has been unregistered already!", task.name);
|
||||
return;
|
||||
}
|
||||
runningTasks.splice(index, 1);
|
||||
});
|
||||
} catch(error) {
|
||||
const index = runningTasks.indexOf(rTask);
|
||||
if(index !== -1)
|
||||
runningTasks.splice(index, 1);
|
||||
|
||||
errors.push({
|
||||
task: task,
|
||||
error: error
|
||||
|
@ -185,41 +236,47 @@ export async function execute() {
|
|||
}
|
||||
}
|
||||
|
||||
if(promises.length > 0) {
|
||||
await Promise.all([...promises]);
|
||||
if(runningTasks.length > 0) {
|
||||
Animation.updateState(currentStage, runningTasks.map(e => e.name));
|
||||
await Promise.all(runningTasks.map(e => e.promise));
|
||||
}
|
||||
|
||||
if(errors.length > 0) {
|
||||
if(config.loader_groups) console.groupEnd();
|
||||
if(config.loader_groups)
|
||||
console.groupEnd();
|
||||
console.error("Failed to execute loader. The following tasks failed (%d):", errors.length);
|
||||
for(const error of errors)
|
||||
console.error(" - %s: %o", error.task.name, error.error);
|
||||
|
||||
throw "failed to process step " + Stage[current_stage];
|
||||
throw "failed to process step " + Stage[currentStage];
|
||||
}
|
||||
|
||||
if(current_tasks.length == 0) {
|
||||
if(typeof(current_stage) === "undefined") {
|
||||
current_stage = -1;
|
||||
if(pendingTasks.length == 0) {
|
||||
if(typeof(currentStage) === "undefined") {
|
||||
currentStage = -1;
|
||||
if(config.verbose) console.debug("[loader] Booting app");
|
||||
} else if(current_stage < Stage.INITIALIZING) {
|
||||
} else if(currentStage < Stage.INITIALIZING) {
|
||||
if(config.loader_groups) console.groupEnd();
|
||||
if(config.verbose) console.debug("[loader] Entering next state (%s). Last state took %dms", Stage[current_stage + 1], (end = Date.now()) - begin);
|
||||
if(config.verbose) console.debug("[loader] Entering next state (%s). Last state took %dms", Stage[currentStage + 1], (end = Date.now()) - begin);
|
||||
} else {
|
||||
if(config.loader_groups) console.groupEnd();
|
||||
if(config.verbose) console.debug("[loader] Finish invoke took %dms", (end = Date.now()) - begin);
|
||||
}
|
||||
|
||||
begin = end;
|
||||
current_stage += 1;
|
||||
currentStage += 1;
|
||||
|
||||
if(current_stage != Stage.DONE && config.loader_groups)
|
||||
console.groupCollapsed("Executing loading stage %s", Stage[current_stage]);
|
||||
if(currentStage != Stage.DONE && config.loader_groups)
|
||||
console.groupCollapsed("Executing loading stage %s", Stage[currentStage]);
|
||||
}
|
||||
}
|
||||
|
||||
if(config.verbose) console.debug("[loader] finished loader. (Total time: %dms)", Date.now() - load_begin);
|
||||
if(config.verbose)
|
||||
console.debug("[loader] finished loader. (Total time: %dms)", Date.now() - load_begin);
|
||||
|
||||
Animation.finalize();
|
||||
}
|
||||
|
||||
export function execute_managed() {
|
||||
execute().then(() => {
|
||||
if(config.verbose) {
|
||||
|
@ -242,28 +299,12 @@ export function execute_managed() {
|
|||
});
|
||||
}
|
||||
|
||||
let _fadeout_warned;
|
||||
export function hide_overlay() {
|
||||
if(typeof($) === "undefined") {
|
||||
if(!_fadeout_warned)
|
||||
console.warn("Could not fadeout loader screen. Missing jquery functions.");
|
||||
_fadeout_warned = true;
|
||||
return;
|
||||
}
|
||||
const animation_duration = 750;
|
||||
|
||||
$(".loader .bookshelf_wrapper").animate({top: 0, opacity: 0}, animation_duration);
|
||||
$(".loader .half").animate({width: 0}, animation_duration, () => {
|
||||
$(".loader").detach();
|
||||
});
|
||||
}
|
||||
|
||||
/* critical error handler */
|
||||
export type ErrorHandler = (message: string, detail: string) => void;
|
||||
let _callback_critical_error: ErrorHandler;
|
||||
let _callback_critical_called: boolean = false;
|
||||
export function critical_error(message: string, detail?: string) {
|
||||
document.getElementById("loader-overlay").classList.add("started");
|
||||
Animation.abort();
|
||||
|
||||
if(_callback_critical_called) {
|
||||
console.warn("[CRITICAL] %s", message);
|
||||
|
@ -312,7 +353,6 @@ export const templates = template_loader;
|
|||
|
||||
/* Hello World message */
|
||||
{
|
||||
|
||||
const clog = console.log;
|
||||
const print_security = () => {
|
||||
{
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import {config, critical_error, SourcePath} from "./loader";
|
||||
import {load_parallel, LoadSyntaxError, ParallelOptions, script_name} from "./utils";
|
||||
import {load_parallel, LoadCallback, LoadSyntaxError, ParallelOptions, script_name} from "./utils";
|
||||
|
||||
let _script_promises: {[key: string]: Promise<void>} = {};
|
||||
|
||||
|
@ -88,13 +88,14 @@ export async function load(path: SourcePath, options: Options) : Promise<void> {
|
|||
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) : Promise<void> {
|
||||
const result = await load_parallel<SourcePath>(paths, e => load(e, options), e => script_name(e, false), options);
|
||||
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:");
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import {config, critical_error, SourcePath} from "./loader";
|
||||
import {load_parallel, LoadSyntaxError, ParallelOptions, script_name} from "./utils";
|
||||
import {load_parallel, LoadCallback, LoadSyntaxError, ParallelOptions, script_name} from "./utils";
|
||||
|
||||
let _style_promises: {[key: string]: Promise<void>} = {};
|
||||
|
||||
|
@ -121,8 +121,8 @@ export async function load(path: SourcePath, options: Options) : Promise<void> {
|
|||
}
|
||||
|
||||
export type MultipleOptions = Options | ParallelOptions;
|
||||
export async function load_multiple(paths: SourcePath[], options: MultipleOptions) : Promise<void> {
|
||||
const result = await load_parallel<SourcePath>(paths, e => load(e, options), e => script_name(e, false), options);
|
||||
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 style sheets:");
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import {config, critical_error, SourcePath} from "./loader";
|
||||
import {load_parallel, LoadSyntaxError, ParallelOptions, script_name} from "./utils";
|
||||
import {load_parallel, LoadCallback, LoadSyntaxError, ParallelOptions, script_name} from "./utils";
|
||||
|
||||
let _template_promises: {[key: string]: Promise<void>} = {};
|
||||
|
||||
|
@ -69,8 +69,8 @@ export async function load(path: SourcePath, options: Options) : Promise<void> {
|
|||
}
|
||||
|
||||
export type MultipleOptions = Options | ParallelOptions;
|
||||
export async function load_multiple(paths: SourcePath[], options: MultipleOptions) : Promise<void> {
|
||||
const result = await load_parallel<SourcePath>(paths, e => load(e, options), e => script_name(e, false), options);
|
||||
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:");
|
||||
|
|
|
@ -10,11 +10,7 @@ export class LoadSyntaxError {
|
|||
|
||||
export function script_name(path: SourcePath, html: boolean) {
|
||||
if(Array.isArray(path)) {
|
||||
let buffer = "";
|
||||
let _or = " or ";
|
||||
for(let entry of path)
|
||||
buffer += _or + script_name(entry, html);
|
||||
return buffer.slice(_or.length);
|
||||
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
|
||||
|
@ -35,31 +31,40 @@ export interface ParallelResult<T> {
|
|||
skipped: T[];
|
||||
}
|
||||
|
||||
export async function load_parallel<T>(requests: T[], executor: (_: T) => Promise<void>, stringify: (_: T) => string, options: ParallelOptions) : Promise<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>> {
|
||||
const result: ParallelResult<T> = { failed: [], succeeded: [], skipped: [] };
|
||||
const pending_requests = requests.slice(0).reverse(); /* we're only able to pop from the back */
|
||||
const current_requests = {};
|
||||
const pendingRequests = requests.slice(0).reverse(); /* we're only able to pop from the back */
|
||||
const currentRequests = {};
|
||||
|
||||
while (pending_requests.length > 0) {
|
||||
while(typeof options.max_parallel_requests !== "number" || options.max_parallel_requests <= 0 || Object.keys(current_requests).length < options.max_parallel_requests) {
|
||||
const script = pending_requests.pop();
|
||||
const name = stringify(script);
|
||||
if(typeof callback === "undefined")
|
||||
callback = () => {};
|
||||
|
||||
current_requests[name] = executor(script).catch(e => result.failed.push({ request: script, error: e })).then(() => {
|
||||
delete current_requests[name];
|
||||
options.max_parallel_requests = 1;
|
||||
while (pendingRequests.length > 0) {
|
||||
while(typeof options.max_parallel_requests !== "number" || options.max_parallel_requests <= 0 || Object.keys(currentRequests).length < options.max_parallel_requests) {
|
||||
const element = pendingRequests.pop();
|
||||
const name = stringify(element);
|
||||
|
||||
callback(element, "loading");
|
||||
currentRequests[name] = executor(element).catch(e => result.failed.push({ request: element, error: e })).then(() => {
|
||||
delete currentRequests[name];
|
||||
callback(element, "loaded");
|
||||
});
|
||||
if(pending_requests.length == 0) break;
|
||||
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(current_requests).map(e => current_requests[e]));
|
||||
await Promise.race(Object.keys(currentRequests).map(e => currentRequests[e]));
|
||||
if(result.failed.length > 0)
|
||||
break; /* finish loading the other requests and than show the error */
|
||||
}
|
||||
await Promise.all(Object.keys(current_requests).map(e => current_requests[e]));
|
||||
result.skipped.push(...pending_requests);
|
||||
await Promise.all(Object.keys(currentRequests).map(e => currentRequests[e]));
|
||||
result.skipped.push(...pendingRequests);
|
||||
return result;
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
/* IE11 */
|
||||
if(!Element.prototype.remove)
|
||||
Element.prototype.remove = function() {
|
||||
this.parentElement?.removeChild(this);
|
||||
};
|
||||
|
||||
function ReplaceWithPolyfill() {
|
||||
let parent = this.parentNode, i = arguments.length, currentNode;
|
||||
if (!parent) return;
|
||||
if (!i) { parent.removeChild(this); }
|
||||
while (i--) { // i-- decrements i and returns the value of i before the decrement
|
||||
currentNode = arguments[i];
|
||||
if (typeof currentNode !== 'object'){
|
||||
currentNode = this.ownerDocument.createTextNode(currentNode);
|
||||
} else if (currentNode.parentNode){
|
||||
currentNode.parentNode.removeChild(currentNode);
|
||||
}
|
||||
// the value of "i" below is after the decrement
|
||||
if (!i) // if currentNode is the first argument (currentNode === arguments[0])
|
||||
parent.replaceChild(currentNode, this);
|
||||
else // if currentNode isn't the first
|
||||
parent.insertBefore(currentNode, this.nextSibling);
|
||||
}
|
||||
}
|
||||
if (!Element.prototype.replaceWith)
|
||||
Element.prototype.replaceWith = ReplaceWithPolyfill;
|
||||
|
||||
if (!CharacterData.prototype.replaceWith)
|
||||
CharacterData.prototype.replaceWith = ReplaceWithPolyfill;
|
||||
|
||||
if (!DocumentType.prototype.replaceWith)
|
||||
DocumentType.prototype.replaceWith = ReplaceWithPolyfill;
|
||||
|
||||
// Source: https://github.com/jserz/js_piece/blob/master/DOM/ParentNode/append()/append().md
|
||||
(function (arr) {
|
||||
arr.forEach(function (item) {
|
||||
if (item.hasOwnProperty('append')) {
|
||||
return;
|
||||
}
|
||||
Object.defineProperty(item, 'append', {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
writable: true,
|
||||
value: function append() {
|
||||
var argArr = Array.prototype.slice.call(arguments),
|
||||
docFrag = document.createDocumentFragment();
|
||||
|
||||
argArr.forEach(function (argItem) {
|
||||
var isNode = argItem instanceof Node;
|
||||
docFrag.appendChild(isNode ? argItem : document.createTextNode(String(argItem)));
|
||||
});
|
||||
|
||||
this.appendChild(docFrag);
|
||||
}
|
||||
});
|
||||
});
|
||||
})([Element.prototype, Document.prototype, DocumentFragment.prototype]);
|
|
@ -1,5 +1,8 @@
|
|||
import "./shared";
|
||||
import * as loader from "../loader/loader";
|
||||
import {config} from "../loader/loader";
|
||||
import {config, SourcePath} from "../loader/loader";
|
||||
import {script_name} from "../loader/utils";
|
||||
import { detect as detectBrowser } from "detect-browser";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
|
@ -44,21 +47,18 @@ interface Manifest {
|
|||
}};
|
||||
}
|
||||
|
||||
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 () => {
|
||||
load_scripts: async taskId => {
|
||||
if(!window.require) {
|
||||
await loader.scripts.load(["vendor/jquery/jquery.min.js"], { cache_tag: cache_tag() });
|
||||
} else {
|
||||
/*
|
||||
loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
|
||||
name: "forum sync",
|
||||
priority: 10,
|
||||
function: async () => {
|
||||
forum.sync_main();
|
||||
}
|
||||
});
|
||||
*/
|
||||
await loader.scripts.load_multiple(["vendor/jquery/jquery.min.js"], { cache_tag: cache_tag() }, LoaderTaskCallback(taskId));
|
||||
}
|
||||
|
||||
await loader.scripts.load_multiple([
|
||||
|
@ -71,8 +71,9 @@ const loader_javascript = {
|
|||
], {
|
||||
cache_tag: cache_tag(),
|
||||
max_parallel_requests: -1
|
||||
});
|
||||
}, LoaderTaskCallback(taskId));
|
||||
|
||||
loader.setCurrentTaskName(taskId, "manifest");
|
||||
let manifest: Manifest;
|
||||
try {
|
||||
const response = await fetch(config.baseUrl + "js/manifest.json");
|
||||
|
@ -99,7 +100,7 @@ const loader_javascript = {
|
|||
await loader.scripts.load_multiple(manifest.chunks[chunk_name].files.map(e => "js/" + e.file), {
|
||||
cache_tag: undefined,
|
||||
max_parallel_requests: -1
|
||||
});
|
||||
}, LoaderTaskCallback(taskId));
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -125,7 +126,7 @@ const loader_webassembly = {
|
|||
};
|
||||
|
||||
const loader_style = {
|
||||
load_style: async () => {
|
||||
load_style: async taskId => {
|
||||
const options = {
|
||||
cache_tag: cache_tag(),
|
||||
max_parallel_requests: -1
|
||||
|
@ -133,22 +134,24 @@ const loader_style = {
|
|||
|
||||
await loader.style.load_multiple([
|
||||
"vendor/xbbcode/src/xbbcode.css"
|
||||
], options);
|
||||
], options, LoaderTaskCallback(taskId));
|
||||
|
||||
await loader.style.load_multiple([
|
||||
"vendor/emoji-picker/src/jquery.lsxemojipicker.css"
|
||||
], options);
|
||||
], options, LoaderTaskCallback(taskId));
|
||||
|
||||
await loader.style.load_multiple([
|
||||
["vendor/highlight/styles/darcula.css", ""], /* empty string means not required */
|
||||
], options);
|
||||
], options, LoaderTaskCallback(taskId));
|
||||
|
||||
if(__build.mode === "debug") {
|
||||
await loader_style.load_style_debug();
|
||||
await loader_style.load_style_debug(taskId);
|
||||
} else {
|
||||
await loader_style.load_style_release();
|
||||
await loader_style.load_style_release(taskId);
|
||||
}
|
||||
},
|
||||
|
||||
load_style_debug: async () => {
|
||||
load_style_debug: async taskId => {
|
||||
await loader.style.load_multiple([
|
||||
"css/static/main.css",
|
||||
"css/static/main-layout.css",
|
||||
|
@ -196,17 +199,17 @@ const loader_style = {
|
|||
], {
|
||||
cache_tag: cache_tag(),
|
||||
max_parallel_requests: -1
|
||||
});
|
||||
}, LoaderTaskCallback(taskId));
|
||||
},
|
||||
|
||||
load_style_release: async () => {
|
||||
load_style_release: async taskId => {
|
||||
await loader.style.load_multiple([
|
||||
"css/static/base.css",
|
||||
"css/static/main.css",
|
||||
], {
|
||||
cache_tag: cache_tag(),
|
||||
max_parallel_requests: -1
|
||||
});
|
||||
}, LoaderTaskCallback(taskId));
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -228,36 +231,12 @@ loader.register_task(loader.Stage.INITIALIZING, {
|
|||
priority: 50
|
||||
});
|
||||
|
||||
loader.register_task(loader.Stage.INITIALIZING, {
|
||||
name: "Browser detection",
|
||||
function: async () => {
|
||||
navigator.browserSpecs = (function(){
|
||||
let ua = navigator.userAgent, tem, M = ua.match(/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i) || [];
|
||||
if(/trident/i.test(M[1])){
|
||||
tem = /\brv[ :]+(\d+)/g.exec(ua) || [];
|
||||
return {name:'IE',version:(tem[1] || '')};
|
||||
}
|
||||
if(M[1]=== 'Chrome'){
|
||||
tem = ua.match(/\b(OPR|Edge)\/(\d+)/);
|
||||
if(tem != null) return {name:tem[1].replace('OPR', 'Opera'),version:tem[2]};
|
||||
}
|
||||
M = M[2]? [M[1], M[2]]: [navigator.appName, navigator.appVersion, '-?'];
|
||||
if((tem = ua.match(/version\/(\d+)/i))!= null)
|
||||
M.splice(1, 1, tem[1]);
|
||||
return {name:M[0], version:M[1]};
|
||||
})();
|
||||
|
||||
console.log("Resolved browser manufacturer to \"%s\" version \"%s\"", navigator.browserSpecs.name, navigator.browserSpecs.version);
|
||||
},
|
||||
priority: 30
|
||||
});
|
||||
|
||||
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';
|
||||
(<any>window)["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!");
|
||||
|
@ -274,7 +253,7 @@ loader.register_task(loader.Stage.INITIALIZING, {
|
|||
});
|
||||
|
||||
loader.register_task(loader.Stage.JAVASCRIPT, {
|
||||
name: "javascript",
|
||||
name: "scripts",
|
||||
function: loader_javascript.load_scripts,
|
||||
priority: 10
|
||||
});
|
||||
|
@ -287,7 +266,7 @@ loader.register_task(loader.Stage.STYLE, {
|
|||
|
||||
loader.register_task(loader.Stage.TEMPLATES, {
|
||||
name: "templates",
|
||||
function: async () => {
|
||||
function: async taskId => {
|
||||
await loader.templates.load_multiple([
|
||||
"templates.html",
|
||||
"templates/modal/musicmanage.html",
|
||||
|
@ -295,17 +274,11 @@ loader.register_task(loader.Stage.TEMPLATES, {
|
|||
], {
|
||||
cache_tag: cache_tag(),
|
||||
max_parallel_requests: -1
|
||||
});
|
||||
}, LoaderTaskCallback(taskId));
|
||||
},
|
||||
priority: 10
|
||||
});
|
||||
|
||||
loader.register_task(loader.Stage.LOADED, {
|
||||
name: "loaded handler",
|
||||
function: async () => loader.hide_overlay(),
|
||||
priority: 5
|
||||
});
|
||||
|
||||
loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
|
||||
name: "lsx emoji picker setup",
|
||||
function: async () => await (window as any).setup_lsx_emoji_picker({twemoji: typeof(window.twemoji) !== "undefined"}),
|
||||
|
@ -368,8 +341,6 @@ loader.register_task(loader.Stage.SETUP, {
|
|||
});
|
||||
|
||||
export function run() {
|
||||
window["Module"] = (window["Module"] || {}) as any; /* Why? */
|
||||
|
||||
/* TeaClient */
|
||||
if(node_require) {
|
||||
if(__build.target !== "client") {
|
||||
|
|
|
@ -71,12 +71,6 @@ loader.register_task(loader.Stage.STYLE, {
|
|||
priority: 10
|
||||
});
|
||||
|
||||
loader.register_task(loader.Stage.LOADED, {
|
||||
name: "loaded handler",
|
||||
function: async () => loader.hide_overlay(),
|
||||
priority: 0
|
||||
});
|
||||
|
||||
/* register tasks */
|
||||
loader.register_task(loader.Stage.INITIALIZING, {
|
||||
name: "safari fix",
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
import "./shared";
|
||||
import * as loader from "../loader/loader";
|
||||
import {Stage} from "../loader/loader";
|
||||
|
||||
export function run() {
|
||||
loader.register_task(Stage.JAVASCRIPT_INITIALIZING, {
|
||||
name: "doing nothing",
|
||||
priority: 1,
|
||||
function: async taskId => {
|
||||
console.log("Doing nothing");
|
||||
|
||||
for(let index of [1, 2, 3]) {
|
||||
await new Promise(resolve => {
|
||||
const callback = () => {
|
||||
document.removeEventListener("click", resolve);
|
||||
resolve();
|
||||
};
|
||||
|
||||
document.addEventListener("click", callback);
|
||||
});
|
||||
loader.setCurrentTaskName(taskId, "try again (" + index + ")");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
loader.execute_managed();
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
import * as loader from "../loader/loader";
|
||||
import {Stage} from "../loader/loader";
|
||||
import {detect as detectBrowser} from "detect-browser";
|
||||
|
||||
if(__build.target === "web") {
|
||||
loader.register_task(Stage.SETUP, {
|
||||
name: "outdated browser checker",
|
||||
function: async () => {
|
||||
const browser = detectBrowser();
|
||||
navigator.browserSpecs = browser;
|
||||
|
||||
if(!browser)
|
||||
return;
|
||||
|
||||
console.log("Resolved browser manufacturer to \"%s\" version \"%s\" on %s", browser.name, browser.version, browser.os);
|
||||
if(browser.type && browser.type !== "browser") {
|
||||
loader.critical_error("Your device isn't supported.", "User agent type " + browser.type + " isn't supported.");
|
||||
throw "unsupported user type";
|
||||
}
|
||||
|
||||
switch (browser?.name) {
|
||||
case "aol":
|
||||
case "bot":
|
||||
case "crios":
|
||||
case "ie":
|
||||
loader.critical_error("Browser not supported", "We're sorry, but your browser isn't supported.");
|
||||
throw "unsupported browser";
|
||||
|
||||
}
|
||||
},
|
||||
priority: 50
|
||||
});
|
||||
}
|
||||
|
||||
/* directly disable all context menus */ //disableGlobalContextMenu
|
||||
if(!location.search.match(/(.*[?&]|^)disableGlobalContextMenu=1($|&.*)/)) {
|
||||
document.addEventListener("contextmenu", event => event.preventDefault());
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@import "loader";
|
||||
@import "overlay";
|
|
@ -1,331 +1,186 @@
|
|||
$thickness: 5px;
|
||||
$duration: 2500;
|
||||
$delay: $duration/6;
|
||||
$background: #222222;
|
||||
$setup-time: 80s / 24; /* 24 frames / sec; the initial sequence is 80 seconds */
|
||||
|
||||
@mixin polka($size, $dot, $base, $accent) {
|
||||
background: $base;
|
||||
background-image: radial-gradient($accent $dot, transparent 0);
|
||||
background-size: $size $size;
|
||||
background-position: 0 -2.5px;
|
||||
}
|
||||
|
||||
.loader {
|
||||
margin: 0;
|
||||
|
||||
display: block;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
|
||||
z-index: 900;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.loader .half {
|
||||
position: fixed;
|
||||
background: #222222;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 50%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.loader .half.right {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.loader .half.left {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.bookshelf_wrapper {
|
||||
position: relative;
|
||||
top: 40%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.books_list {
|
||||
margin: 0 auto;
|
||||
width: 300px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.book_item {
|
||||
#loader-overlay {
|
||||
position: absolute;
|
||||
top: -120px;
|
||||
box-sizing: border-box;
|
||||
list-style: none;
|
||||
width: 40px;
|
||||
height: 120px;
|
||||
opacity: 0;
|
||||
background-color: #1e6cc7;
|
||||
border: $thickness solid white;
|
||||
transform-origin: bottom left;
|
||||
transform: translateX(300px);
|
||||
animation: travel #{$duration}ms linear infinite;
|
||||
overflow: hidden;
|
||||
|
||||
&.first {
|
||||
top: -140px;
|
||||
height: 140px;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
|
||||
&:before,
|
||||
&:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: $thickness;
|
||||
background-color: white;
|
||||
}
|
||||
background: #1e1e1e;
|
||||
|
||||
&:after {
|
||||
top: initial;
|
||||
bottom: 10px;
|
||||
}
|
||||
user-select: none;
|
||||
|
||||
z-index: 10000;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
.container {
|
||||
flex-shrink: 0;
|
||||
|
||||
display: block;
|
||||
position: relative;
|
||||
|
||||
width: 1000px;
|
||||
height: 1000px;
|
||||
|
||||
align-self: center;
|
||||
margin-bottom: 10vh;
|
||||
|
||||
transition-duration: .5s;
|
||||
}
|
||||
|
||||
&.second,
|
||||
&.fifth {
|
||||
&:before,
|
||||
&:after {
|
||||
box-sizing: border-box;
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: $thickness*3.5;
|
||||
border-top: $thickness solid white;
|
||||
border-bottom: $thickness solid white;
|
||||
}
|
||||
|
||||
&:after {
|
||||
top: initial;
|
||||
bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
&.third {
|
||||
&:before,
|
||||
&:after {
|
||||
box-sizing: border-box;
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 9px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
border: $thickness solid white;
|
||||
}
|
||||
|
||||
&:after {
|
||||
top: initial;
|
||||
bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
&.fourth {
|
||||
top: -130px;
|
||||
height: 130px;
|
||||
|
||||
&:before {
|
||||
box-sizing: border-box;
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 46px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: $thickness*3.5;
|
||||
border-top: $thickness solid white;
|
||||
border-bottom: $thickness solid white;
|
||||
}
|
||||
}
|
||||
|
||||
&.fifth {
|
||||
top: -100px;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
&.sixth {
|
||||
top: -140px;
|
||||
height: 140px;
|
||||
|
||||
&:before {
|
||||
box-sizing: border-box;
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 31px;
|
||||
left: 0px;
|
||||
width: 100%;
|
||||
height: $thickness;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
&:after {
|
||||
box-sizing: border-box;
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
left: 9px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
border: $thickness solid white;
|
||||
}
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
animation-delay: #{$delay*1}ms;
|
||||
}
|
||||
|
||||
&:nth-child(3) {
|
||||
animation-delay: #{$delay*2}ms;
|
||||
}
|
||||
|
||||
&:nth-child(4) {
|
||||
animation-delay: #{$delay*3}ms;
|
||||
}
|
||||
|
||||
&:nth-child(5) {
|
||||
animation-delay: #{$delay*4}ms;
|
||||
}
|
||||
|
||||
&:nth-child(6) {
|
||||
animation-delay: #{$delay*5}ms;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.shelf {
|
||||
width: 300px;
|
||||
height: $thickness;
|
||||
margin: 0 auto;
|
||||
background-color: white;
|
||||
position: relative;
|
||||
|
||||
&:before,
|
||||
&:after {
|
||||
content: '';
|
||||
.setup, .idle {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
@include polka(10px, 30%, $background, rgba(255, 255, 255, 0.5));
|
||||
top: 200%;
|
||||
left: 5%;
|
||||
animation: move #{$duration/10}ms linear infinite;
|
||||
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
|
||||
display: none;
|
||||
|
||||
&.visible {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
&:after {
|
||||
top: 400%;
|
||||
left: 7.5%;
|
||||
.setup.visible {
|
||||
animation: loader-initial-sequence 0s cubic-bezier(.81,.01,.65,1.16) $setup-time forwards;
|
||||
}
|
||||
|
||||
.idle {
|
||||
img {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.steam {
|
||||
position: absolute;
|
||||
|
||||
top: 282px;
|
||||
left: 380px;
|
||||
|
||||
width: 249px;
|
||||
height: 125px;
|
||||
background: 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;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes move {
|
||||
@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 {
|
||||
background-position-x: 0;
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
|
||||
40% {
|
||||
opacity: 1;
|
||||
transform: translate3d(-60px, 0, 0) skew(-5deg, 0) rotateY(-6deg);
|
||||
}
|
||||
|
||||
to {
|
||||
background-position-x: 10px;
|
||||
opacity: 0;
|
||||
transform: translate3d(700px, 0, 0) skew(30deg, 0) rotateZ(-6deg);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@keyframes travel {
|
||||
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateX(300px) rotateZ(0deg) scaleY(1);
|
||||
@keyframes swipe-out-text {
|
||||
from {
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
|
||||
6.5% {
|
||||
transform: translateX(279.5px) rotateZ(0deg) scaleY(1.1);
|
||||
}
|
||||
|
||||
8.8% {
|
||||
transform: translateX(273.6px) rotateZ(0deg) scaleY(1);
|
||||
}
|
||||
|
||||
10% {
|
||||
40% {
|
||||
opacity: 1;
|
||||
transform: translateX(270px) rotateZ(0deg);
|
||||
transform: translate3d(-30px, 20px, 0) skew(-5deg, 0);
|
||||
}
|
||||
|
||||
17.6% {
|
||||
transform: translateX(247.2px) rotateZ(-30deg);
|
||||
to {
|
||||
opacity: 0;
|
||||
transform: translate3d(550px, 0, 0) skew(30deg, 0) scale(.96, 1.25) rotateZ(6deg);
|
||||
}
|
||||
}
|
||||
|
||||
45% {
|
||||
transform: translateX(165px) rotateZ(-30deg);
|
||||
}
|
||||
|
||||
49.5% {
|
||||
transform: translateX(151.5px) rotateZ(-45deg);
|
||||
}
|
||||
|
||||
61.5% {
|
||||
transform: translateX(115.5px) rotateZ(-45deg);
|
||||
}
|
||||
|
||||
67% {
|
||||
transform: translateX(99px) rotateZ(-60deg);
|
||||
}
|
||||
|
||||
76% {
|
||||
transform: translateX(72px) rotateZ(-60deg);
|
||||
}
|
||||
|
||||
83.5% {
|
||||
opacity: 1;
|
||||
transform: translateX(49.5px) rotateZ(-90deg);
|
||||
}
|
||||
|
||||
90% {
|
||||
@keyframes overlay-fade {
|
||||
to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0;
|
||||
transform: translateX(0px) rotateZ(-90deg);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* Automated loader timeout */
|
||||
$loader_timeout: 2.5s;
|
||||
.loader:not(.started) {
|
||||
&, & > .half, & > .bookshelf_wrapper {
|
||||
-moz-animation: _loader_hide 0s ease-in $loader_timeout forwards;
|
||||
-webkit-animation: _loader_hide 0s ease-in $loader_timeout forwards;
|
||||
-o-animation: _loader_hide 0s ease-in $loader_timeout forwards;
|
||||
animation: _loader_hide 0s ease-in $loader_timeout forwards;
|
||||
-webkit-animation-fill-mode: forwards;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
}
|
||||
|
||||
.loader:not(.started) + #critical-load {
|
||||
#loader-overlay:not(.initialized) + #critical-load {
|
||||
display: block !important;
|
||||
opacity: 0;
|
||||
|
||||
-moz-animation: _loader_show 0s ease-in $loader_timeout forwards;
|
||||
-webkit-animation: _loader_show 0s ease-in $loader_timeout forwards;
|
||||
-o-animation: _loader_show 0s ease-in $loader_timeout forwards;
|
||||
animation: _loader_show 0s ease-in $loader_timeout forwards;
|
||||
-webkit-animation-fill-mode: forwards;
|
||||
animation-fill-mode: forwards;
|
||||
animation: loader-setup-timeout 0s ease-in $setup-time forwards;
|
||||
|
||||
.error::before {
|
||||
content: 'Failed to startup app loader!';
|
||||
content: 'Failed to startup loader!';
|
||||
}
|
||||
|
||||
.detail::before {
|
||||
|
@ -333,29 +188,8 @@ $loader_timeout: 2.5s;
|
|||
}
|
||||
}
|
||||
|
||||
@keyframes _loader_hide {
|
||||
to {
|
||||
width: 0;
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes _loader_hide {
|
||||
to {
|
||||
width: 0;
|
||||
height: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes _loader_show {
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes _loader_show {
|
||||
@keyframes loader-setup-timeout {
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
#overlay-no-js, #critical-load {
|
||||
z-index: 10000;
|
||||
display: none;
|
||||
position: fixed;
|
||||
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
|
||||
background: #1e1e1e;
|
||||
text-align: center;
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
<%
|
||||
/* given on compile time */
|
||||
var build_target;
|
||||
var initial_script;
|
||||
var initial_css;
|
||||
%>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, min-zoom=1, max-zoom: 1, user-scalable=no">
|
||||
<meta name="description" content="The TeaSpeak Web client is a in the browser running client for the VoIP communication software TeaSpeak." />
|
||||
<meta name="keywords" content="TeaSpeak, TeaWeb, TeaSpeak-Web,Web client TeaSpeak, веб клієнт TeaSpeak, TSDNS, багатомовність, мультимовність, теми, функціонал"/>
|
||||
|
||||
<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"> %>
|
||||
|
||||
<%# Using an absolute path here since the manifest.json works only with such. %>
|
||||
<link rel="manifest" href="/manifest.json">
|
||||
|
||||
<% if(build_target === "client") { %>
|
||||
<title>TeaClient</title>
|
||||
<meta name='og:title' content='TeaClient'>
|
||||
<% } 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'>
|
||||
<%# <link rel="apple-touch-icon" sizes="194x194" href="/apple-touch-icon.png" type="image/png"> %>
|
||||
<% } %>
|
||||
|
||||
<x-properties id="properties" style="display: none">
|
||||
<%#
|
||||
We don't need to put any properties down here.
|
||||
But this tag is here to not brick the settings class.
|
||||
But it will be removed quite soonly as soon this class has been fixed
|
||||
%>
|
||||
</x-properties>
|
||||
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
<meta name="format-detection" content="telephone=no">
|
||||
|
||||
<!-- Global site tag (gtag.js) - Google Analytics -->
|
||||
<script defer async src="https://www.googletagmanager.com/gtag/js?id=UA-113151733-4"></script>
|
||||
<script>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
|
||||
function gtag() {
|
||||
dataLayer.push(arguments);
|
||||
}
|
||||
|
||||
gtag('js', new Date());
|
||||
gtag('config', 'UA-113151733-4');
|
||||
</script>
|
||||
|
||||
<%# We're preloading the PNG file by default since most browser have APNG support %>
|
||||
<link rel="preload" as="image" href="img/loader/initial-sequence.png">
|
||||
<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">
|
||||
|
||||
<%- initial_css %>
|
||||
</head>
|
||||
<body>
|
||||
<!-- No javascript error -->
|
||||
<noscript>
|
||||
<div id="overlay-no-js">
|
||||
<div class="container">
|
||||
<img src="img/script.svg" height="128px" alt="no script" >
|
||||
<h1>Please enable JavaScript</h1>
|
||||
<h3>TeaSpeak web could not run without it!</h3>
|
||||
<h3>Its like you, without coffee</h3>
|
||||
</div>
|
||||
</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-apng="img/loader/initial-sequence.png" src-gif="img/loader/initial-sequence.gif" alt="initial loading sequence"></lazy-img>
|
||||
</div>
|
||||
<div class="idle">
|
||||
<lazy-img class="bowl" src="img/loader/bowl.png" alt="bowl"></lazy-img>
|
||||
<lazy-img class="text" src="img/loader/text.png" alt="TeaSpeak"></lazy-img>
|
||||
<div class="steam"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="loader-stage"></div>
|
||||
</div>
|
||||
|
||||
<!-- Critical load error -->
|
||||
<div class="fulloverlay" id="critical-load">
|
||||
<div class="container">
|
||||
<img src="img/loading_error_right.svg" alt="load error" />
|
||||
|
||||
<h1 class="error"></h1>
|
||||
<h3 class="detail"></h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%- initial_script %>
|
||||
</body>
|
||||
</html>
|
|
@ -24,6 +24,9 @@
|
|||
"author": "TeaSpeak (WolverinDEV)",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.10.4",
|
||||
"@babel/plugin-transform-runtime": "^7.10.4",
|
||||
"@babel/preset-env": "^7.10.4",
|
||||
"@google-cloud/translate": "^5.3.0",
|
||||
"@types/dompurify": "^2.0.1",
|
||||
"@types/ejs": "^3.0.2",
|
||||
|
@ -38,6 +41,7 @@
|
|||
"@types/react-dom": "^16.9.5",
|
||||
"@types/sha256": "^0.2.0",
|
||||
"@types/websocket": "0.0.40",
|
||||
"babel-loader": "^8.1.0",
|
||||
"chunk-manifest-webpack-plugin": "^1.1.2",
|
||||
"circular-dependency-plugin": "^5.2.0",
|
||||
"clean-css": "^4.2.1",
|
||||
|
@ -81,6 +85,7 @@
|
|||
},
|
||||
"homepage": "https://www.teaspeak.de",
|
||||
"dependencies": {
|
||||
"detect-browser": "^5.1.1",
|
||||
"@types/emoji-mart": "^3.0.2",
|
||||
"dompurify": "^2.0.8",
|
||||
"emoji-mart": "^3.0.0",
|
||||
|
|
|
@ -1,220 +0,0 @@
|
|||
<%
|
||||
/* given build compile */
|
||||
var build_target;
|
||||
var initial_script;
|
||||
var initial_css;
|
||||
%>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
|
||||
<!-- App min width: 450px -->
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, min-zoom=1, max-zoom: 1, user-scalable=no">
|
||||
<meta name="description" content="The TeaSpeak Web client is a in the browser running client for the VoIP communication software TeaSpeak." />
|
||||
<meta name="keywords" content="TeaSpeak, TeaWeb, TeaSpeak-Web,Web client TeaSpeak, веб клієнт TeaSpeak, TSDNS, багатомовність, мультимовність, теми, функціонал"/>
|
||||
|
||||
<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/">
|
||||
<!-- WHAT THE HELL? <meta name="og:image" content="https://www.whatsapp.com/img/whatsapp-promo.png"> -->
|
||||
|
||||
<!-- TODO Needs some fix -->
|
||||
<link rel="manifest" href="manifest.json">
|
||||
|
||||
<% if(build_target === "client") { %>
|
||||
<title>TeaClient</title>
|
||||
<meta name='og:title' content='TeaClient'>
|
||||
<% } 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'>
|
||||
<!-- <link rel="apple-touch-icon" sizes="194x194" href="/apple-touch-icon.png" type="image/png"> -->
|
||||
<% } %>
|
||||
|
||||
<x-properties id="properties" style="display: none">
|
||||
<!--
|
||||
We don't need to put any properties down here.
|
||||
But this tag is here to not brick the settings class.
|
||||
But it will be removed quite soonly as soon this class has been fixed
|
||||
-->
|
||||
</x-properties>
|
||||
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
<meta name="format-detection" content="telephone=no">
|
||||
|
||||
<!-- Global site tag (gtag.js) - Google Analytics -->
|
||||
<script defer async src="https://www.googletagmanager.com/gtag/js?id=UA-113151733-4"></script>
|
||||
<script>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
|
||||
function gtag() {
|
||||
dataLayer.push(arguments);
|
||||
}
|
||||
|
||||
gtag('js', new Date());
|
||||
gtag('config', 'UA-113151733-4');
|
||||
</script>
|
||||
|
||||
<!-- required static style for the critical page and the enable javascript page -->
|
||||
<style>
|
||||
.fulloverlay {
|
||||
z-index: 10000;
|
||||
display: none;
|
||||
position: fixed;
|
||||
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
|
||||
background-color: gray;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.fulloverlay .container {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
|
||||
top: 20%;
|
||||
}
|
||||
|
||||
#critical-load.shown {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
|
||||
.no-js {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
|
||||
<%- initial_css %>
|
||||
</head>
|
||||
<body>
|
||||
<!-- No javascript error -->
|
||||
<noscript>
|
||||
<div class="fulloverlay no-js">
|
||||
<div class="container">
|
||||
<img src="img/script.svg" height="128px">
|
||||
<h1>Please enable JavaScript</h1>
|
||||
<h3>TeaSpeak web could not run without it!</h3>
|
||||
<h3>Its like you, without coffee</h3>
|
||||
</div>
|
||||
</div>
|
||||
</noscript>
|
||||
|
||||
<!-- loader setup -->
|
||||
<div id="style"></div>
|
||||
<div id="scripts"></div>
|
||||
|
||||
<!-- Loading screen -->
|
||||
<div class="loader" id="loader-overlay">
|
||||
<div class="half right"></div>
|
||||
<div class="half left"></div>
|
||||
<div class="bookshelf_wrapper">
|
||||
<ul class="books_list">
|
||||
<li class="book_item first"></li>
|
||||
<li class="book_item second"></li>
|
||||
<li class="book_item third"></li>
|
||||
<li class="book_item fourth"></li>
|
||||
<li class="book_item fifth"></li>
|
||||
<li class="book_item sixth"></li>
|
||||
</ul>
|
||||
<div class="shelf"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Critical load error -->
|
||||
<div class="fulloverlay" id="critical-load">
|
||||
<div class="container">
|
||||
<img src="img/loading_error_right.svg" style="height: 12em" />
|
||||
|
||||
<h1 class="error" style="color: red; margin-bottom: 0"></h1>
|
||||
<h3 class="detail" style="margin-top: .5em"></h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%# <!-- Old debgging stuff back for the days where we designed the client -->
|
||||
<?php if($localhost && false) { ?>
|
||||
<div id="spoiler-style" style="z-index: 1000000; position: absolute; display: block; background: white; right: 5px; left: 5px; top: 34px;">
|
||||
<!-- <img src="https://www.chromatic-solutions.de/teaspeak/window/connect_opened.png"> -->
|
||||
<!-- <img src="http://puu.sh/DZDgO/9149c0a1aa.png"> -->
|
||||
<!-- <img src="http://puu.sh/E0QUb/ce5e3f93ae.png"> -->
|
||||
<!-- <img src="img/style/default.png"> -->
|
||||
<!-- <img src="img/style/user-selected.png"> -->
|
||||
<!-- <img src="img/style/privat_chat.png"> -->
|
||||
<!-- <img src="http://puu.sh/E1aBL/3c40ae3c2c.png"> -->
|
||||
<!-- <img src="http://puu.sh/E2qb2/b27bb2fde5.png"> -->
|
||||
<!-- <img src="http://puu.sh/E2UQR/1e0d7e03a3.png"> -->
|
||||
<!-- <img src="http://puu.sh/E38yX/452e27864c.png"> -->
|
||||
<!-- <img src="http://puu.sh/E3fjq/e2b4447bcd.png"> -->
|
||||
<!-- <img src="http://puu.sh/E3WlW/f791a9e7b1.png"> -->
|
||||
<!-- <img src="http://puu.sh/E4lHJ/1a4afcdf0b.png"> -->
|
||||
<!-- <img src="http://puu.sh/E4HKK/5ee74d4cc7.png"> -->
|
||||
<!-- <img src="http://puu.sh/E6LN1/8518c10898.png"> -->
|
||||
<!--
|
||||
http://puu.sh/E8IoF/242ed5ca3a.png
|
||||
http://puu.sh/E8Ip9/9632d33591.png
|
||||
http://puu.sh/E8Ips/6c314253e5.png
|
||||
http://puu.sh/E8IpG/015a38b184.png
|
||||
http://puu.sh/E8IpY/5be454a15e.png
|
||||
|
||||
Identity imporve: http://puu.sh/E9jTp/380a734677.png
|
||||
Identity select: http://puu.sh/E9jYi/3003c58a2f.png
|
||||
|
||||
Server Info Bandwidth: http://puu.sh/E9jTe/b41f6386de.png
|
||||
Server Info: http://puu.sh/E9jT6/302912ae34.png
|
||||
|
||||
Bookmarks: http://puu.sh/Eb5w4/8d38fe5b8f.png
|
||||
|
||||
serveredit_1.png https://www.hypixel-koo.cf/tsapoijdsadpoijsadsapj.png
|
||||
serveredit_2.png https://www.hypixel-koo.cf/tsandljsandljsamndoj3oiwejlkjmnlksandljsadmnlmsadnlsa.png
|
||||
serveredit_3.png https://www.hypixel-koo.cf/toiuhsadouhgdsapoiugdsapouhdsapouhdsaouhwouhwwouhwwoiuhwoihwwoihwoijhwwoknw.png
|
||||
|
||||
Query accounts: https://puu.sh/EhvkJ/7551f548e3.png
|
||||
Channel info: https://puu.sh/EhuVH/1e21540589.png
|
||||
-->
|
||||
|
||||
<!-- <img src="http://puu.sh/E6NXv/eb2f19c7c3.png"> -->
|
||||
<!-- <img src="http://puu.sh/E9jT6/302912ae34.png"> -->
|
||||
<!-- <img src="http://puu.sh/E9jTe/b41f6386de.png"> -->
|
||||
<!-- <img src="img/style/ban-list.png"> -->
|
||||
<!-- <img src="http://puu.sh/E9jTe/b41f6386de.png"> -->
|
||||
<!-- <img src="https://puu.sh/EhuVH/1e21540589.png"> -->
|
||||
<img src="https://puu.sh/EhvkJ/7551f548e3.png">
|
||||
</div>
|
||||
<button class="toggle-spoiler-style" style="height: 30px; width: 100px; z-index: 100000000; position: absolute; bottom: 2px;">toggle style</button>
|
||||
<script>
|
||||
const init = (jQuery) => {
|
||||
if(typeof jQuery === "undefined") {
|
||||
setTimeout(() => init($), 1000);
|
||||
return;
|
||||
}
|
||||
jQuery("#spoiler-style").hide();
|
||||
jQuery(".toggle-spoiler-style").on('click', () => {
|
||||
jQuery("#spoiler-style").toggle();
|
||||
});
|
||||
};
|
||||
setTimeout(() => init($), 1000);
|
||||
</script>
|
||||
%>
|
||||
<%- initial_script %>
|
||||
</body>
|
||||
</html>
|
|
@ -34,8 +34,6 @@ import {spawnFileTransferModal} from "tc-shared/ui/modal/transfer/ModalFileTrans
|
|||
import {MenuEntryType, spawn_context_menu} from "tc-shared/ui/elements/ContextMenu";
|
||||
import {copy_to_clipboard} from "tc-shared/utils/helpers";
|
||||
import ContextMenuEvent = JQuery.ContextMenuEvent;
|
||||
import {spawnPermissionEditorModal} from "tc-shared/ui/modal/permission/ModalPermissionEditor";
|
||||
import {spawnGroupCreate} from "tc-shared/ui/modal/ModalGroups";
|
||||
|
||||
/* required import for init */
|
||||
require("./proto").initialize();
|
||||
|
|
|
@ -18,9 +18,11 @@
|
|||
"webpack/WatLoader.ts",
|
||||
"webpack/DevelBlocks.ts",
|
||||
|
||||
"loader/IndexGenerator.ts",
|
||||
|
||||
"file.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"node_modules"
|
||||
]
|
||||
}
|
|
@ -3,7 +3,7 @@ import * as fs from "fs";
|
|||
import trtransformer from "./tools/trgen/ts_transformer";
|
||||
import {exec} from "child_process";
|
||||
import * as util from "util";
|
||||
import EJSGenerator = require("./webpack/EJSGenerator");
|
||||
import LoaderIndexGenerator = require("./loader/IndexGenerator");
|
||||
|
||||
const path = require('path');
|
||||
const webpack = require("webpack");
|
||||
|
@ -44,6 +44,16 @@ const generate_definitions = async (target: string) => {
|
|||
} as any;
|
||||
};
|
||||
|
||||
const isLoaderFile = (file: string) => {
|
||||
if(file.startsWith(__dirname)) {
|
||||
const path = file.substr(__dirname.length).replace(/\\/g, "/");
|
||||
if(path.startsWith("/loader/")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
export const config = async (target: "web" | "client") => { return {
|
||||
entry: {
|
||||
"loader": "./loader/app/index.ts"
|
||||
|
@ -77,21 +87,10 @@ export const config = async (target: "web" | "client") => { return {
|
|||
}),
|
||||
new webpack.DefinePlugin(await generate_definitions(target)),
|
||||
|
||||
new EJSGenerator({
|
||||
variables: {
|
||||
build_target: target
|
||||
},
|
||||
input: path.join(__dirname, "shared/html/index.html.ejs"),
|
||||
new LoaderIndexGenerator({
|
||||
buildTarget: target,
|
||||
output: path.join(__dirname, "dist/index.html"),
|
||||
initialJSEntryChunk: "loader",
|
||||
minify: !isDevelopment,
|
||||
embedInitialJSEntryChunk: !isDevelopment,
|
||||
|
||||
embedInitialCSSFile: !isDevelopment,
|
||||
initialCSSFile: {
|
||||
localFile: path.join(__dirname, "loader/css/loader.css"),
|
||||
publicFile: "css/loader.css"
|
||||
}
|
||||
isDevelopment: isDevelopment
|
||||
})
|
||||
].filter(e => !!e),
|
||||
module: {
|
||||
|
@ -127,7 +126,7 @@ export const config = async (target: "web" | "client") => { return {
|
|||
]
|
||||
},
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
test: (module: string) => module.match(/\.tsx?$/) && !isLoaderFile(module),
|
||||
exclude: /node_modules/,
|
||||
|
||||
loader: [
|
||||
|
@ -154,6 +153,25 @@ export const config = async (target: "web" | "client") => { return {
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
test: (module: string) => module.match(/\.tsx?$/) && isLoaderFile(module),
|
||||
exclude: /(node_modules|bower_components)/,
|
||||
|
||||
loader: [
|
||||
{
|
||||
loader: "babel-loader",
|
||||
options: {
|
||||
presets: ["@babel/preset-env"] //Preset used for env setup
|
||||
}
|
||||
},
|
||||
{
|
||||
loader: "ts-loader",
|
||||
options: {
|
||||
transpileOnly: true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
test: /\.was?t$/,
|
||||
loader: [
|
||||
|
|
|
@ -31,31 +31,34 @@ class EJSGenerator {
|
|||
this.options = options;
|
||||
}
|
||||
|
||||
private async generate_entry_js_tag(compilation: Compilation) {
|
||||
private async generateEntryJsTag(compilation: Compilation) {
|
||||
const entry_group = compilation.chunkGroups.find(e => e.options.name === this.options.initialJSEntryChunk);
|
||||
if(!entry_group) return; /* not the correct compilation */
|
||||
if(entry_group.chunks.length !== 1) throw "Unsupported entry chunk size. We only support one at the moment.";
|
||||
if(entry_group.chunks[0].files.length !== 1)
|
||||
throw "Entry chunk has too many files. We only support to inline one!";
|
||||
const file = entry_group.chunks[0].files[0];
|
||||
if(path.extname(file) !== ".js")
|
||||
throw "Entry chunk file has unknown extension";
|
||||
|
||||
if(!this.options.embedInitialJSEntryChunk) {
|
||||
return '<script type="application/javascript" src=' + compilation.compiler.options.output.publicPath + file + ' async defer></script>';
|
||||
} else {
|
||||
const script = await util.promisify(fs.readFile)(path.join(compilation.compiler.outputPath, file));
|
||||
return `<script type="application/javascript">${script}</script>`;
|
||||
}
|
||||
const tags = entry_group.chunks.map(chunk => {
|
||||
if(chunk.files.length !== 1)
|
||||
throw "invalid chunk file count";
|
||||
|
||||
const file = chunk.files[0];
|
||||
if(path.extname(file) !== ".js")
|
||||
throw "Entry chunk file has unknown extension";
|
||||
|
||||
if(!this.options.embedInitialJSEntryChunk) {
|
||||
return '<script type="application/javascript" src=' + compilation.compiler.options.output.publicPath + file + ' async defer></script>';
|
||||
} else {
|
||||
const script = fs.readFileSync(path.join(compilation.compiler.outputPath, file));
|
||||
return `<script type="application/javascript">${script}</script>`;
|
||||
}
|
||||
});
|
||||
return tags.join("\n");
|
||||
}
|
||||
|
||||
private async generate_entry_css_tag() {
|
||||
private async generateEntryCssTag() {
|
||||
if(this.options.embedInitialCSSFile) {
|
||||
const style = await util.promisify(fs.readFile)(this.options.initialCSSFile.localFile);
|
||||
return `<style>${style}</style>`
|
||||
} else {
|
||||
//<link rel="preload" href="mystyles.css" as="style" onload="this.rel='stylesheet'">
|
||||
return `<link rel="preload" as="style" onload="this.rel='stylesheet'" href="${this.options.initialCSSFile.publicFile}">`
|
||||
return `<link rel="stylesheet" href="${this.options.initialCSSFile.publicFile}">`
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -64,11 +67,12 @@ class EJSGenerator {
|
|||
const input = await util.promisify(fs.readFile)(this.options.input);
|
||||
const variables = Object.assign({}, this.options.variables);
|
||||
|
||||
variables["initial_script"] = await this.generate_entry_js_tag(compilation);
|
||||
variables["initial_css"] = await this.generate_entry_css_tag();
|
||||
variables["initial_script"] = await this.generateEntryJsTag(compilation);
|
||||
variables["initial_css"] = await this.generateEntryCssTag();
|
||||
|
||||
let generated = await ejs.render(input.toString(), variables, {
|
||||
beautify: false /* uglify is a bit dump and does not understands ES6 */
|
||||
beautify: false, /* uglify is a bit dump and does not understands ES6 */
|
||||
context: this
|
||||
});
|
||||
|
||||
if(this.options.minify) {
|
||||
|
@ -82,12 +86,21 @@ class EJSGenerator {
|
|||
removeTagWhitespace: true,
|
||||
minifyCSS: true,
|
||||
minifyJS: true,
|
||||
minifyURLs: true
|
||||
minifyURLs: true,
|
||||
});
|
||||
}
|
||||
|
||||
await util.promisify(fs.writeFile)(this.options.output, generated);
|
||||
});
|
||||
|
||||
compiler.hooks.afterCompile.tapPromise(this.constructor.name, async compilation => {
|
||||
const file = path.resolve(this.options.input);
|
||||
if(compilation.fileDependencies.has(file))
|
||||
return;
|
||||
|
||||
console.log("Adding additional watch to %s", file);
|
||||
compilation.fileDependencies.add(file);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue