Merge branch 'develop' into canary
# Conflicts: # ChangeLog.md # package-lock.json # package.jsoncanary
commit
c559fdff6c
|
@ -25,9 +25,10 @@ node_modules/
|
||||||
/todo.txt
|
/todo.txt
|
||||||
/tmp/
|
/tmp/
|
||||||
|
|
||||||
# All out config files are .ts files
|
# All our config files are .ts files
|
||||||
/*.js
|
/*.js
|
||||||
/*.js.map
|
/*.js.map
|
||||||
|
!babel.config.js
|
||||||
|
|
||||||
/webpack/*.js
|
/webpack/*.js
|
||||||
/webpack/*.js.map
|
/webpack/*.js.map
|
||||||
|
|
|
@ -6,6 +6,10 @@
|
||||||
- Improved chat box behaviour
|
- Improved chat box behaviour
|
||||||
- Automatically crawling all channels on server join for new messages (requires TeaSpeak 1.4.16-b2 or higher)
|
- 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**
|
* **15.06.20**
|
||||||
- Recoded the permission editor with react
|
- Recoded the permission editor with react
|
||||||
- Fixed sever permission editor display bugs
|
- 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 */
|
{ /* shared image files */
|
||||||
"type": "img",
|
"type": "img",
|
||||||
"search-pattern": /.*\.(svg|png)/,
|
"search-pattern": /.*\.(svg|png|gif)/,
|
||||||
"build-target": "dev|rel",
|
"build-target": "dev|rel",
|
||||||
|
|
||||||
"path": "img/",
|
"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 "core-js/stable";
|
||||||
import * as loader_base from "./loader/loader";
|
import "./polifill";
|
||||||
window["loader"] = loader_base;
|
|
||||||
/* let the loader register himself at the window first */
|
import * as loader from "./loader/loader";
|
||||||
setTimeout(loader.run, 0);
|
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 {};
|
export {};
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import * as script_loader from "./script_loader";
|
import * as script_loader from "./script_loader";
|
||||||
import * as style_loader from "./style_loader";
|
import * as style_loader from "./style_loader";
|
||||||
import * as template_loader from "./template_loader";
|
import * as template_loader from "./template_loader";
|
||||||
|
import * as Animation from "../animation";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
|
@ -31,7 +32,7 @@ export let config: Config = {
|
||||||
export type Task = {
|
export type Task = {
|
||||||
name: string,
|
name: string,
|
||||||
priority: number, /* tasks with the same priority will be executed in sync */
|
priority: number, /* tasks with the same priority will be executed in sync */
|
||||||
function: () => Promise<void>
|
function: (taskId?: number) => Promise<void>
|
||||||
};
|
};
|
||||||
|
|
||||||
export enum Stage {
|
export enum Stage {
|
||||||
|
@ -39,30 +40,37 @@ export enum Stage {
|
||||||
loading loader required files (incl this)
|
loading loader required files (incl this)
|
||||||
*/
|
*/
|
||||||
INITIALIZING,
|
INITIALIZING,
|
||||||
|
|
||||||
/*
|
/*
|
||||||
setting up the loading process
|
setting up the loading process
|
||||||
*/
|
*/
|
||||||
SETUP,
|
SETUP,
|
||||||
|
|
||||||
/*
|
/*
|
||||||
loading all style sheet files
|
loading all style sheet files
|
||||||
*/
|
*/
|
||||||
STYLE,
|
STYLE,
|
||||||
|
|
||||||
/*
|
/*
|
||||||
loading all javascript files
|
loading all javascript files
|
||||||
*/
|
*/
|
||||||
JAVASCRIPT,
|
JAVASCRIPT,
|
||||||
|
|
||||||
/*
|
/*
|
||||||
loading all template files
|
loading all template files
|
||||||
*/
|
*/
|
||||||
TEMPLATES,
|
TEMPLATES,
|
||||||
|
|
||||||
/*
|
/*
|
||||||
initializing static/global stuff
|
initializing static/global stuff
|
||||||
*/
|
*/
|
||||||
JAVASCRIPT_INITIALIZING,
|
JAVASCRIPT_INITIALIZING,
|
||||||
|
|
||||||
/*
|
/*
|
||||||
finalizing load process
|
finalizing load process
|
||||||
*/
|
*/
|
||||||
FINALIZING,
|
FINALIZING,
|
||||||
|
|
||||||
/*
|
/*
|
||||||
invoking main task
|
invoking main task
|
||||||
*/
|
*/
|
||||||
|
@ -72,7 +80,7 @@ export enum Stage {
|
||||||
}
|
}
|
||||||
|
|
||||||
let cache_tag: string | undefined;
|
let cache_tag: string | undefined;
|
||||||
let current_stage: Stage = undefined;
|
let currentStage: Stage = undefined;
|
||||||
const tasks: {[key:number]:Task[]} = {};
|
const tasks: {[key:number]:Task[]} = {};
|
||||||
|
|
||||||
/* test if all files shall be load from cache or fetch again */
|
/* 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 get_cache_version() { return cache_tag; }
|
||||||
|
|
||||||
export function finished() {
|
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) {
|
export function register_task(stage: Stage, task: Task) {
|
||||||
if(current_stage > stage) {
|
if(currentStage > stage) {
|
||||||
if(config.error)
|
if(config.error)
|
||||||
console.warn("Register loading task, but it had already been finished. Executing task anyways!");
|
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);
|
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() {
|
export async function execute() {
|
||||||
document.getElementById("loader-overlay").classList.add("started");
|
if(!await Animation.initialize())
|
||||||
|
return;
|
||||||
|
|
||||||
loader_cache_tag();
|
loader_cache_tag();
|
||||||
|
|
||||||
const load_begin = Date.now();
|
const load_begin = Date.now();
|
||||||
|
|
||||||
let begin: number = 0;
|
let begin: number = 0;
|
||||||
let end: number = Date.now();
|
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[] = [];
|
let pendingTasks: Task[] = [];
|
||||||
while((tasks[current_stage] || []).length > 0) {
|
while((tasks[currentStage] || []).length > 0) {
|
||||||
if(current_tasks.length == 0 || current_tasks[0].priority == tasks[current_stage][0].priority) {
|
if(pendingTasks.length == 0 || pendingTasks[0].priority == tasks[currentStage][0].priority) {
|
||||||
current_tasks.push(tasks[current_stage].pop());
|
pendingTasks.push(tasks[currentStage].pop());
|
||||||
} else break;
|
} else break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,23 +190,45 @@ export async function execute() {
|
||||||
task: Task
|
task: Task
|
||||||
}[] = [];
|
}[] = [];
|
||||||
|
|
||||||
const promises: Promise<void>[] = [];
|
for(const task of pendingTasks) {
|
||||||
for(const task of current_tasks) {
|
const rTask = {
|
||||||
|
taskId: ++runningTaskIdIndex,
|
||||||
|
name: task.name,
|
||||||
|
promise: undefined
|
||||||
|
} as RunningTask;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if(config.verbose) console.debug("Executing loader %s (%d)", task.name, task.priority);
|
if(config.verbose)
|
||||||
const promise = task.function();
|
console.debug("Executing loader %s (%d)", task.name, task.priority);
|
||||||
|
|
||||||
|
runningTasks.push(rTask);
|
||||||
|
const promise = task.function(rTask.taskId);
|
||||||
if(!promise) {
|
if(!promise) {
|
||||||
|
runningTasks.splice(runningTasks.indexOf(rTask), 1);
|
||||||
console.error("Loading task %s hasn't returned a promise!", task.name);
|
console.error("Loading task %s hasn't returned a promise!", task.name);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
promises.push(promise.catch(error => {
|
|
||||||
|
rTask.promise = promise.catch(error => {
|
||||||
errors.push({
|
errors.push({
|
||||||
task: task,
|
task: task,
|
||||||
error: error
|
error: error
|
||||||
});
|
});
|
||||||
|
|
||||||
return Promise.resolve();
|
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) {
|
} catch(error) {
|
||||||
|
const index = runningTasks.indexOf(rTask);
|
||||||
|
if(index !== -1)
|
||||||
|
runningTasks.splice(index, 1);
|
||||||
|
|
||||||
errors.push({
|
errors.push({
|
||||||
task: task,
|
task: task,
|
||||||
error: error
|
error: error
|
||||||
|
@ -185,41 +236,47 @@ export async function execute() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(promises.length > 0) {
|
if(runningTasks.length > 0) {
|
||||||
await Promise.all([...promises]);
|
Animation.updateState(currentStage, runningTasks.map(e => e.name));
|
||||||
|
await Promise.all(runningTasks.map(e => e.promise));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(errors.length > 0) {
|
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);
|
console.error("Failed to execute loader. The following tasks failed (%d):", errors.length);
|
||||||
for(const error of errors)
|
for(const error of errors)
|
||||||
console.error(" - %s: %o", error.task.name, error.error);
|
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(pendingTasks.length == 0) {
|
||||||
if(typeof(current_stage) === "undefined") {
|
if(typeof(currentStage) === "undefined") {
|
||||||
current_stage = -1;
|
currentStage = -1;
|
||||||
if(config.verbose) console.debug("[loader] Booting app");
|
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.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 {
|
} else {
|
||||||
if(config.loader_groups) console.groupEnd();
|
if(config.loader_groups) console.groupEnd();
|
||||||
if(config.verbose) console.debug("[loader] Finish invoke took %dms", (end = Date.now()) - begin);
|
if(config.verbose) console.debug("[loader] Finish invoke took %dms", (end = Date.now()) - begin);
|
||||||
}
|
}
|
||||||
|
|
||||||
begin = end;
|
begin = end;
|
||||||
current_stage += 1;
|
currentStage += 1;
|
||||||
|
|
||||||
if(current_stage != Stage.DONE && config.loader_groups)
|
if(currentStage != Stage.DONE && config.loader_groups)
|
||||||
console.groupCollapsed("Executing loading stage %s", Stage[current_stage]);
|
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() {
|
export function execute_managed() {
|
||||||
execute().then(() => {
|
execute().then(() => {
|
||||||
if(config.verbose) {
|
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 */
|
/* critical error handler */
|
||||||
export type ErrorHandler = (message: string, detail: string) => void;
|
export type ErrorHandler = (message: string, detail: string) => void;
|
||||||
let _callback_critical_error: ErrorHandler;
|
let _callback_critical_error: ErrorHandler;
|
||||||
let _callback_critical_called: boolean = false;
|
let _callback_critical_called: boolean = false;
|
||||||
export function critical_error(message: string, detail?: string) {
|
export function critical_error(message: string, detail?: string) {
|
||||||
document.getElementById("loader-overlay").classList.add("started");
|
Animation.abort();
|
||||||
|
|
||||||
if(_callback_critical_called) {
|
if(_callback_critical_called) {
|
||||||
console.warn("[CRITICAL] %s", message);
|
console.warn("[CRITICAL] %s", message);
|
||||||
|
@ -312,7 +353,6 @@ export const templates = template_loader;
|
||||||
|
|
||||||
/* Hello World message */
|
/* Hello World message */
|
||||||
{
|
{
|
||||||
|
|
||||||
const clog = console.log;
|
const clog = console.log;
|
||||||
const print_security = () => {
|
const print_security = () => {
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import {config, critical_error, SourcePath} from "./loader";
|
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>} = {};
|
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;
|
throw "Missing dependency " + depend;
|
||||||
await _script_promises[depend];
|
await _script_promises[depend];
|
||||||
}
|
}
|
||||||
|
|
||||||
await load_script_url(source.url + (options.cache_tag || ""));
|
await load_script_url(source.url + (options.cache_tag || ""));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type MultipleOptions = Options | ParallelOptions;
|
type MultipleOptions = Options | ParallelOptions;
|
||||||
export async function load_multiple(paths: SourcePath[], options: MultipleOptions) : Promise<void> {
|
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);
|
const result = await load_parallel<SourcePath>(paths, e => load(e, options), e => script_name(e, false), options, callback);
|
||||||
if(result.failed.length > 0) {
|
if(result.failed.length > 0) {
|
||||||
if(config.error) {
|
if(config.error) {
|
||||||
console.error("Failed to load the following scripts:");
|
console.error("Failed to load the following scripts:");
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import {config, critical_error, SourcePath} from "./loader";
|
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>} = {};
|
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 type MultipleOptions = Options | ParallelOptions;
|
||||||
export async function load_multiple(paths: SourcePath[], options: MultipleOptions) : Promise<void> {
|
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);
|
const result = await load_parallel<SourcePath>(paths, e => load(e, options), e => script_name(e, false), options, callback);
|
||||||
if(result.failed.length > 0) {
|
if(result.failed.length > 0) {
|
||||||
if(config.error) {
|
if(config.error) {
|
||||||
console.error("Failed to load the following style sheets:");
|
console.error("Failed to load the following style sheets:");
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import {config, critical_error, SourcePath} from "./loader";
|
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>} = {};
|
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 type MultipleOptions = Options | ParallelOptions;
|
||||||
export async function load_multiple(paths: SourcePath[], options: MultipleOptions) : Promise<void> {
|
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);
|
const result = await load_parallel<SourcePath>(paths, e => load(e, options), e => script_name(e, false), options, callback);
|
||||||
if(result.failed.length > 0) {
|
if(result.failed.length > 0) {
|
||||||
if(config.error) {
|
if(config.error) {
|
||||||
console.error("Failed to load the following template files:");
|
console.error("Failed to load the following template files:");
|
||||||
|
|
|
@ -10,11 +10,7 @@ export class LoadSyntaxError {
|
||||||
|
|
||||||
export function script_name(path: SourcePath, html: boolean) {
|
export function script_name(path: SourcePath, html: boolean) {
|
||||||
if(Array.isArray(path)) {
|
if(Array.isArray(path)) {
|
||||||
let buffer = "";
|
return path.filter(e => !!e).map(e => script_name(e, html)).join(" or ");
|
||||||
let _or = " or ";
|
|
||||||
for(let entry of path)
|
|
||||||
buffer += _or + script_name(entry, html);
|
|
||||||
return buffer.slice(_or.length);
|
|
||||||
} else if(typeof(path) === "string")
|
} else if(typeof(path) === "string")
|
||||||
return html ? "<code>" + path + "</code>" : path;
|
return html ? "<code>" + path + "</code>" : path;
|
||||||
else
|
else
|
||||||
|
@ -35,31 +31,40 @@ export interface ParallelResult<T> {
|
||||||
skipped: 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 result: ParallelResult<T> = { failed: [], succeeded: [], skipped: [] };
|
||||||
const pending_requests = requests.slice(0).reverse(); /* we're only able to pop from the back */
|
const pendingRequests = requests.slice(0).reverse(); /* we're only able to pop from the back */
|
||||||
const current_requests = {};
|
const currentRequests = {};
|
||||||
|
|
||||||
while (pending_requests.length > 0) {
|
if(typeof callback === "undefined")
|
||||||
while(typeof options.max_parallel_requests !== "number" || options.max_parallel_requests <= 0 || Object.keys(current_requests).length < options.max_parallel_requests) {
|
callback = () => {};
|
||||||
const script = pending_requests.pop();
|
|
||||||
const name = stringify(script);
|
|
||||||
|
|
||||||
current_requests[name] = executor(script).catch(e => result.failed.push({ request: script, error: e })).then(() => {
|
options.max_parallel_requests = 1;
|
||||||
delete current_requests[name];
|
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.
|
* Wait 'till a new "slot" for downloading is free.
|
||||||
* This should also not throw because any errors will be caught before.
|
* This should also not throw because any errors will be caught before.
|
||||||
*/
|
*/
|
||||||
await Promise.race(Object.keys(current_requests).map(e => current_requests[e]));
|
await Promise.race(Object.keys(currentRequests).map(e => currentRequests[e]));
|
||||||
if(result.failed.length > 0)
|
if(result.failed.length > 0)
|
||||||
break; /* finish loading the other requests and than show the error */
|
break; /* finish loading the other requests and than show the error */
|
||||||
}
|
}
|
||||||
await Promise.all(Object.keys(current_requests).map(e => current_requests[e]));
|
await Promise.all(Object.keys(currentRequests).map(e => currentRequests[e]));
|
||||||
result.skipped.push(...pending_requests);
|
result.skipped.push(...pendingRequests);
|
||||||
return result;
|
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 * 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 {
|
declare global {
|
||||||
interface Window {
|
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 */
|
/* all javascript loaders */
|
||||||
const loader_javascript = {
|
const loader_javascript = {
|
||||||
load_scripts: async () => {
|
load_scripts: async taskId => {
|
||||||
if(!window.require) {
|
if(!window.require) {
|
||||||
await loader.scripts.load(["vendor/jquery/jquery.min.js"], { cache_tag: cache_tag() });
|
await loader.scripts.load_multiple(["vendor/jquery/jquery.min.js"], { cache_tag: cache_tag() }, LoaderTaskCallback(taskId));
|
||||||
} else {
|
|
||||||
/*
|
|
||||||
loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
|
|
||||||
name: "forum sync",
|
|
||||||
priority: 10,
|
|
||||||
function: async () => {
|
|
||||||
forum.sync_main();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await loader.scripts.load_multiple([
|
await loader.scripts.load_multiple([
|
||||||
|
@ -71,8 +71,9 @@ const loader_javascript = {
|
||||||
], {
|
], {
|
||||||
cache_tag: cache_tag(),
|
cache_tag: cache_tag(),
|
||||||
max_parallel_requests: -1
|
max_parallel_requests: -1
|
||||||
});
|
}, LoaderTaskCallback(taskId));
|
||||||
|
|
||||||
|
loader.setCurrentTaskName(taskId, "manifest");
|
||||||
let manifest: Manifest;
|
let manifest: Manifest;
|
||||||
try {
|
try {
|
||||||
const response = await fetch(config.baseUrl + "js/manifest.json");
|
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), {
|
await loader.scripts.load_multiple(manifest.chunks[chunk_name].files.map(e => "js/" + e.file), {
|
||||||
cache_tag: undefined,
|
cache_tag: undefined,
|
||||||
max_parallel_requests: -1
|
max_parallel_requests: -1
|
||||||
});
|
}, LoaderTaskCallback(taskId));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -125,7 +126,7 @@ const loader_webassembly = {
|
||||||
};
|
};
|
||||||
|
|
||||||
const loader_style = {
|
const loader_style = {
|
||||||
load_style: async () => {
|
load_style: async taskId => {
|
||||||
const options = {
|
const options = {
|
||||||
cache_tag: cache_tag(),
|
cache_tag: cache_tag(),
|
||||||
max_parallel_requests: -1
|
max_parallel_requests: -1
|
||||||
|
@ -133,22 +134,24 @@ const loader_style = {
|
||||||
|
|
||||||
await loader.style.load_multiple([
|
await loader.style.load_multiple([
|
||||||
"vendor/xbbcode/src/xbbcode.css"
|
"vendor/xbbcode/src/xbbcode.css"
|
||||||
], options);
|
], options, LoaderTaskCallback(taskId));
|
||||||
|
|
||||||
await loader.style.load_multiple([
|
await loader.style.load_multiple([
|
||||||
"vendor/emoji-picker/src/jquery.lsxemojipicker.css"
|
"vendor/emoji-picker/src/jquery.lsxemojipicker.css"
|
||||||
], options);
|
], options, LoaderTaskCallback(taskId));
|
||||||
|
|
||||||
await loader.style.load_multiple([
|
await loader.style.load_multiple([
|
||||||
["vendor/highlight/styles/darcula.css", ""], /* empty string means not required */
|
["vendor/highlight/styles/darcula.css", ""], /* empty string means not required */
|
||||||
], options);
|
], options, LoaderTaskCallback(taskId));
|
||||||
|
|
||||||
if(__build.mode === "debug") {
|
if(__build.mode === "debug") {
|
||||||
await loader_style.load_style_debug();
|
await loader_style.load_style_debug(taskId);
|
||||||
} else {
|
} 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([
|
await loader.style.load_multiple([
|
||||||
"css/static/main.css",
|
"css/static/main.css",
|
||||||
"css/static/main-layout.css",
|
"css/static/main-layout.css",
|
||||||
|
@ -196,17 +199,17 @@ const loader_style = {
|
||||||
], {
|
], {
|
||||||
cache_tag: cache_tag(),
|
cache_tag: cache_tag(),
|
||||||
max_parallel_requests: -1
|
max_parallel_requests: -1
|
||||||
});
|
}, LoaderTaskCallback(taskId));
|
||||||
},
|
},
|
||||||
|
|
||||||
load_style_release: async () => {
|
load_style_release: async taskId => {
|
||||||
await loader.style.load_multiple([
|
await loader.style.load_multiple([
|
||||||
"css/static/base.css",
|
"css/static/base.css",
|
||||||
"css/static/main.css",
|
"css/static/main.css",
|
||||||
], {
|
], {
|
||||||
cache_tag: cache_tag(),
|
cache_tag: cache_tag(),
|
||||||
max_parallel_requests: -1
|
max_parallel_requests: -1
|
||||||
});
|
}, LoaderTaskCallback(taskId));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -228,36 +231,12 @@ loader.register_task(loader.Stage.INITIALIZING, {
|
||||||
priority: 50
|
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, {
|
loader.register_task(loader.Stage.INITIALIZING, {
|
||||||
name: "secure tester",
|
name: "secure tester",
|
||||||
function: async () => {
|
function: async () => {
|
||||||
/* we need https or localhost to use some things like the storage API */
|
/* we need https or localhost to use some things like the storage API */
|
||||||
if(typeof isSecureContext === "undefined")
|
if(typeof isSecureContext === "undefined")
|
||||||
(<any>window)["isSecureContext"] = location.protocol !== 'https:' && location.hostname !== 'localhost';
|
(<any>window)["isSecureContext"] = location.protocol !== 'https:' || location.hostname === 'localhost';
|
||||||
|
|
||||||
if(!isSecureContext) {
|
if(!isSecureContext) {
|
||||||
loader.critical_error("TeaWeb cant run on unsecured sides.", "App requires to be loaded via HTTPS!");
|
loader.critical_error("TeaWeb cant run on unsecured sides.", "App requires to be loaded via HTTPS!");
|
||||||
|
@ -274,7 +253,7 @@ loader.register_task(loader.Stage.INITIALIZING, {
|
||||||
});
|
});
|
||||||
|
|
||||||
loader.register_task(loader.Stage.JAVASCRIPT, {
|
loader.register_task(loader.Stage.JAVASCRIPT, {
|
||||||
name: "javascript",
|
name: "scripts",
|
||||||
function: loader_javascript.load_scripts,
|
function: loader_javascript.load_scripts,
|
||||||
priority: 10
|
priority: 10
|
||||||
});
|
});
|
||||||
|
@ -287,7 +266,7 @@ loader.register_task(loader.Stage.STYLE, {
|
||||||
|
|
||||||
loader.register_task(loader.Stage.TEMPLATES, {
|
loader.register_task(loader.Stage.TEMPLATES, {
|
||||||
name: "templates",
|
name: "templates",
|
||||||
function: async () => {
|
function: async taskId => {
|
||||||
await loader.templates.load_multiple([
|
await loader.templates.load_multiple([
|
||||||
"templates.html",
|
"templates.html",
|
||||||
"templates/modal/musicmanage.html",
|
"templates/modal/musicmanage.html",
|
||||||
|
@ -295,17 +274,11 @@ loader.register_task(loader.Stage.TEMPLATES, {
|
||||||
], {
|
], {
|
||||||
cache_tag: cache_tag(),
|
cache_tag: cache_tag(),
|
||||||
max_parallel_requests: -1
|
max_parallel_requests: -1
|
||||||
});
|
}, LoaderTaskCallback(taskId));
|
||||||
},
|
},
|
||||||
priority: 10
|
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, {
|
loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
|
||||||
name: "lsx emoji picker setup",
|
name: "lsx emoji picker setup",
|
||||||
function: async () => await (window as any).setup_lsx_emoji_picker({twemoji: typeof(window.twemoji) !== "undefined"}),
|
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() {
|
export function run() {
|
||||||
window["Module"] = (window["Module"] || {}) as any; /* Why? */
|
|
||||||
|
|
||||||
/* TeaClient */
|
/* TeaClient */
|
||||||
if(node_require) {
|
if(node_require) {
|
||||||
if(__build.target !== "client") {
|
if(__build.target !== "client") {
|
||||||
|
|
|
@ -71,12 +71,6 @@ loader.register_task(loader.Stage.STYLE, {
|
||||||
priority: 10
|
priority: 10
|
||||||
});
|
});
|
||||||
|
|
||||||
loader.register_task(loader.Stage.LOADED, {
|
|
||||||
name: "loaded handler",
|
|
||||||
function: async () => loader.hide_overlay(),
|
|
||||||
priority: 0
|
|
||||||
});
|
|
||||||
|
|
||||||
/* register tasks */
|
/* register tasks */
|
||||||
loader.register_task(loader.Stage.INITIALIZING, {
|
loader.register_task(loader.Stage.INITIALIZING, {
|
||||||
name: "safari fix",
|
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;
|
$setup-time: 80s / 24; /* 24 frames / sec; the initial sequence is 80 seconds */
|
||||||
$duration: 2500;
|
|
||||||
$delay: $duration/6;
|
|
||||||
$background: #222222;
|
|
||||||
|
|
||||||
@mixin polka($size, $dot, $base, $accent) {
|
#loader-overlay {
|
||||||
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 {
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -120px;
|
overflow: hidden;
|
||||||
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;
|
|
||||||
|
|
||||||
&.first {
|
top: 0;
|
||||||
top: -140px;
|
left: 0;
|
||||||
height: 140px;
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
|
||||||
&:before,
|
background: #1e1e1e;
|
||||||
&:after {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
top: 10px;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: $thickness;
|
|
||||||
background-color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:after {
|
user-select: none;
|
||||||
top: initial;
|
|
||||||
bottom: 10px;
|
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,
|
.setup, .idle {
|
||||||
&.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: '';
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
top: 0;
|
||||||
@include polka(10px, 30%, $background, rgba(255, 255, 255, 0.5));
|
left: 0;
|
||||||
top: 200%;
|
right: 0;
|
||||||
left: 5%;
|
bottom: 0;
|
||||||
animation: move #{$duration/10}ms linear infinite;
|
|
||||||
|
display: none;
|
||||||
|
|
||||||
|
&.visible {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:after {
|
.setup.visible {
|
||||||
top: 400%;
|
animation: loader-initial-sequence 0s cubic-bezier(.81,.01,.65,1.16) $setup-time forwards;
|
||||||
left: 7.5%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.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 {
|
from {
|
||||||
background-position-x: 0;
|
transform: translate3d(0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
40% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translate3d(-60px, 0, 0) skew(-5deg, 0) rotateY(-6deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
to {
|
to {
|
||||||
background-position-x: 10px;
|
opacity: 0;
|
||||||
|
transform: translate3d(700px, 0, 0) skew(30deg, 0) rotateZ(-6deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes travel {
|
@keyframes swipe-out-text {
|
||||||
|
from {
|
||||||
0% {
|
transform: translate3d(0, 0, 0);
|
||||||
opacity: 0;
|
|
||||||
transform: translateX(300px) rotateZ(0deg) scaleY(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
6.5% {
|
40% {
|
||||||
transform: translateX(279.5px) rotateZ(0deg) scaleY(1.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
8.8% {
|
|
||||||
transform: translateX(273.6px) rotateZ(0deg) scaleY(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
10% {
|
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
transform: translateX(270px) rotateZ(0deg);
|
transform: translate3d(-30px, 20px, 0) skew(-5deg, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
17.6% {
|
to {
|
||||||
transform: translateX(247.2px) rotateZ(-30deg);
|
opacity: 0;
|
||||||
|
transform: translate3d(550px, 0, 0) skew(30deg, 0) scale(.96, 1.25) rotateZ(6deg);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
45% {
|
@keyframes overlay-fade {
|
||||||
transform: translateX(165px) rotateZ(-30deg);
|
to {
|
||||||
}
|
|
||||||
|
|
||||||
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% {
|
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
100% {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateX(0px) rotateZ(-90deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Automated loader timeout */
|
/* Automated loader timeout */
|
||||||
$loader_timeout: 2.5s;
|
#loader-overlay:not(.initialized) + #critical-load {
|
||||||
.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 {
|
|
||||||
display: block !important;
|
display: block !important;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
|
||||||
-moz-animation: _loader_show 0s ease-in $loader_timeout forwards;
|
animation: loader-setup-timeout 0s ease-in $setup-time 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;
|
|
||||||
|
|
||||||
.error::before {
|
.error::before {
|
||||||
content: 'Failed to startup app loader!';
|
content: 'Failed to startup loader!';
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail::before {
|
.detail::before {
|
||||||
|
@ -333,29 +188,8 @@ $loader_timeout: 2.5s;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes _loader_hide {
|
|
||||||
to {
|
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@-webkit-keyframes _loader_hide {
|
@keyframes loader-setup-timeout {
|
||||||
to {
|
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes _loader_show {
|
|
||||||
to {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@-webkit-keyframes _loader_show {
|
|
||||||
to {
|
to {
|
||||||
opacity: 1;
|
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)",
|
"author": "TeaSpeak (WolverinDEV)",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"devDependencies": {
|
"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",
|
"@google-cloud/translate": "^5.3.0",
|
||||||
"@types/dompurify": "^2.0.1",
|
"@types/dompurify": "^2.0.1",
|
||||||
"@types/ejs": "^3.0.2",
|
"@types/ejs": "^3.0.2",
|
||||||
|
@ -38,6 +41,7 @@
|
||||||
"@types/react-dom": "^16.9.5",
|
"@types/react-dom": "^16.9.5",
|
||||||
"@types/sha256": "^0.2.0",
|
"@types/sha256": "^0.2.0",
|
||||||
"@types/websocket": "0.0.40",
|
"@types/websocket": "0.0.40",
|
||||||
|
"babel-loader": "^8.1.0",
|
||||||
"chunk-manifest-webpack-plugin": "^1.1.2",
|
"chunk-manifest-webpack-plugin": "^1.1.2",
|
||||||
"circular-dependency-plugin": "^5.2.0",
|
"circular-dependency-plugin": "^5.2.0",
|
||||||
"clean-css": "^4.2.1",
|
"clean-css": "^4.2.1",
|
||||||
|
@ -81,6 +85,7 @@
|
||||||
},
|
},
|
||||||
"homepage": "https://www.teaspeak.de",
|
"homepage": "https://www.teaspeak.de",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"detect-browser": "^5.1.1",
|
||||||
"@types/emoji-mart": "^3.0.2",
|
"@types/emoji-mart": "^3.0.2",
|
||||||
"dompurify": "^2.0.8",
|
"dompurify": "^2.0.8",
|
||||||
"emoji-mart": "^3.0.0",
|
"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 {MenuEntryType, spawn_context_menu} from "tc-shared/ui/elements/ContextMenu";
|
||||||
import {copy_to_clipboard} from "tc-shared/utils/helpers";
|
import {copy_to_clipboard} from "tc-shared/utils/helpers";
|
||||||
import ContextMenuEvent = JQuery.ContextMenuEvent;
|
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 */
|
/* required import for init */
|
||||||
require("./proto").initialize();
|
require("./proto").initialize();
|
||||||
|
|
|
@ -18,9 +18,11 @@
|
||||||
"webpack/WatLoader.ts",
|
"webpack/WatLoader.ts",
|
||||||
"webpack/DevelBlocks.ts",
|
"webpack/DevelBlocks.ts",
|
||||||
|
|
||||||
|
"loader/IndexGenerator.ts",
|
||||||
|
|
||||||
"file.ts"
|
"file.ts"
|
||||||
],
|
],
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"node_modules",
|
"node_modules"
|
||||||
]
|
]
|
||||||
}
|
}
|
|
@ -3,7 +3,7 @@ import * as fs from "fs";
|
||||||
import trtransformer from "./tools/trgen/ts_transformer";
|
import trtransformer from "./tools/trgen/ts_transformer";
|
||||||
import {exec} from "child_process";
|
import {exec} from "child_process";
|
||||||
import * as util from "util";
|
import * as util from "util";
|
||||||
import EJSGenerator = require("./webpack/EJSGenerator");
|
import LoaderIndexGenerator = require("./loader/IndexGenerator");
|
||||||
|
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const webpack = require("webpack");
|
const webpack = require("webpack");
|
||||||
|
@ -44,6 +44,16 @@ const generate_definitions = async (target: string) => {
|
||||||
} as any;
|
} 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 {
|
export const config = async (target: "web" | "client") => { return {
|
||||||
entry: {
|
entry: {
|
||||||
"loader": "./loader/app/index.ts"
|
"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 webpack.DefinePlugin(await generate_definitions(target)),
|
||||||
|
|
||||||
new EJSGenerator({
|
new LoaderIndexGenerator({
|
||||||
variables: {
|
buildTarget: target,
|
||||||
build_target: target
|
|
||||||
},
|
|
||||||
input: path.join(__dirname, "shared/html/index.html.ejs"),
|
|
||||||
output: path.join(__dirname, "dist/index.html"),
|
output: path.join(__dirname, "dist/index.html"),
|
||||||
initialJSEntryChunk: "loader",
|
isDevelopment: isDevelopment
|
||||||
minify: !isDevelopment,
|
|
||||||
embedInitialJSEntryChunk: !isDevelopment,
|
|
||||||
|
|
||||||
embedInitialCSSFile: !isDevelopment,
|
|
||||||
initialCSSFile: {
|
|
||||||
localFile: path.join(__dirname, "loader/css/loader.css"),
|
|
||||||
publicFile: "css/loader.css"
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
].filter(e => !!e),
|
].filter(e => !!e),
|
||||||
module: {
|
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/,
|
exclude: /node_modules/,
|
||||||
|
|
||||||
loader: [
|
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$/,
|
test: /\.was?t$/,
|
||||||
loader: [
|
loader: [
|
||||||
|
|
|
@ -31,31 +31,34 @@ class EJSGenerator {
|
||||||
this.options = options;
|
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);
|
const entry_group = compilation.chunkGroups.find(e => e.options.name === this.options.initialJSEntryChunk);
|
||||||
if(!entry_group) return; /* not the correct compilation */
|
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) {
|
const tags = entry_group.chunks.map(chunk => {
|
||||||
return '<script type="application/javascript" src=' + compilation.compiler.options.output.publicPath + file + ' async defer></script>';
|
if(chunk.files.length !== 1)
|
||||||
} else {
|
throw "invalid chunk file count";
|
||||||
const script = await util.promisify(fs.readFile)(path.join(compilation.compiler.outputPath, file));
|
|
||||||
return `<script type="application/javascript">${script}</script>`;
|
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) {
|
if(this.options.embedInitialCSSFile) {
|
||||||
const style = await util.promisify(fs.readFile)(this.options.initialCSSFile.localFile);
|
const style = await util.promisify(fs.readFile)(this.options.initialCSSFile.localFile);
|
||||||
return `<style>${style}</style>`
|
return `<style>${style}</style>`
|
||||||
} else {
|
} else {
|
||||||
//<link rel="preload" href="mystyles.css" as="style" onload="this.rel='stylesheet'">
|
return `<link rel="stylesheet" href="${this.options.initialCSSFile.publicFile}">`
|
||||||
return `<link rel="preload" as="style" onload="this.rel='stylesheet'" href="${this.options.initialCSSFile.publicFile}">`
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,11 +67,12 @@ class EJSGenerator {
|
||||||
const input = await util.promisify(fs.readFile)(this.options.input);
|
const input = await util.promisify(fs.readFile)(this.options.input);
|
||||||
const variables = Object.assign({}, this.options.variables);
|
const variables = Object.assign({}, this.options.variables);
|
||||||
|
|
||||||
variables["initial_script"] = await this.generate_entry_js_tag(compilation);
|
variables["initial_script"] = await this.generateEntryJsTag(compilation);
|
||||||
variables["initial_css"] = await this.generate_entry_css_tag();
|
variables["initial_css"] = await this.generateEntryCssTag();
|
||||||
|
|
||||||
let generated = await ejs.render(input.toString(), variables, {
|
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) {
|
if(this.options.minify) {
|
||||||
|
@ -82,12 +86,21 @@ class EJSGenerator {
|
||||||
removeTagWhitespace: true,
|
removeTagWhitespace: true,
|
||||||
minifyCSS: true,
|
minifyCSS: true,
|
||||||
minifyJS: true,
|
minifyJS: true,
|
||||||
minifyURLs: true
|
minifyURLs: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await util.promisify(fs.writeFile)(this.options.output, generated);
|
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