aboutsummaryrefslogtreecommitdiff
path: root/site/app/twirl/twirl.js
diff options
context:
space:
mode:
Diffstat (limited to 'site/app/twirl/twirl.js')
-rw-r--r--site/app/twirl/twirl.js479
1 files changed, 479 insertions, 0 deletions
diff --git a/site/app/twirl/twirl.js b/site/app/twirl/twirl.js
new file mode 100644
index 0000000..5f201f6
--- /dev/null
+++ b/site/app/twirl/twirl.js
@@ -0,0 +1,479 @@
+window.twirl = {
+ debug: false, //window.location.href.startsWith("file://"),
+ themes: ["Default", "Basic", "Hacker", "Monoclassic", "Doze"],
+ audioTypes: ["audio/mpeg", "audio/mp4", "audio/ogg", "audio/vorbis", "audio/x-flac","audio/aiff","audio/x-aiff", "audio/vnd.wav", "audio/wave", "audio/x-wav", "audio/wav", "audio/flac"],
+ maxFileSize: 1e+8, // 100MB
+ latencyCorrection: 40,
+ storage: {},
+ errorState: null,
+ audioContext: null,
+ _booted: false,
+ _initialised: false,
+ _remote: {sessionID: null, sending: false},
+ _els: {
+ base: null,
+ toolTip: null,
+ prompt: {},
+ loading: {},
+ contextMenu: null
+ }
+};
+
+twirl.boot = function() {
+ if (twirl._booted) return;
+ twirl.audioContext = new AudioContext();
+ twirl._booted = true;
+};
+
+twirl.init = function() {
+ if (twirl._initialised) return;
+
+ var NoteData = function() {
+ var self = this;
+ this.data = null;
+ fetch("https://apps.csound.1bpm.net/app/twirl/notedata.json").then(function(r) {
+ r.json().then(function(j) {
+ self.data = j;
+ });
+ });
+ };
+ twirl.noteData = new NoteData();
+
+ // storage
+ twirl.storage.data = localStorage.getItem("twirl");
+ if (twirl.storage.data) {
+ twirl.storage.data = JSON.parse(twirl.storage.data);
+ } else {
+ twirl.storage.data = {};
+ }
+
+ // base
+ twirl._els.base = $("<div />").attr("id", "twirl").appendTo($("body"));
+
+ // tooltip
+ twirl._els.toolTip = $("<div />").addClass("twirl_tooltip").appendTo(twirl._els.base);
+
+ // context menu
+ twirl._els.contextMenu = $("<div />").addClass("twirl_contextmenu").appendTo(twirl._els.base);
+
+ // prompt
+ var p = twirl._els.prompt;
+ p.base = $("<div />").attr("id", "twirl_prompt").appendTo(twirl._els.base);
+ $("<div />").attr("id", "twirl_prompt_background").appendTo(p.base);
+ var promptInner = $("<div />").attr("id", "twirl_prompt_inner").appendTo(p.base);
+ p.text = $("<div />").attr("id", "twirl_prompt_text").appendTo(promptInner);
+ p.button = $("<button />").attr("id", "twirl_prompt_button_text").text("OK");
+ p.buttonContainer = $("<div />").attr("id", "twirl_prompt_button").append($("<hr />")).append(p.button).appendTo(promptInner);
+
+ // loading
+ var l = twirl._els.loading;
+ l.base = $("<div />").attr("id", "twirl_loading").appendTo(twirl._els.base);
+ $("<div />").attr("id", "twirl_loading_background").appendTo(l.base);
+ var loadingInner = $("<div />").attr("id", "twirl_loading_inner").appendTo(l.base);
+ l.text = $("<p />").attr("id", "twirl_loading_text").text("Processing").appendTo(loadingInner);
+ l.percentContainer = $("<div />").attr("id", "twirl_loading_percent").appendTo(loadingInner);
+ l.percent = $("<div />").attr("id", "twirl_loading_percent_inner").appendTo(l.percentContainer);
+
+ // theme
+ if (twirl.storage.data.theme) {
+ twirl.setTheme(twirl.storage.data.theme, true);
+ }
+ twirl._initialised = true;
+};
+
+twirl.storage.save = function() {
+ localStorage.setItem("twirl", JSON.stringify(twirl.storage.data));
+};
+
+twirl.random = {
+ rgbColour: function() {
+ return "rgb(" + (Math.round(Math.random() * 50) + 205) + ","
+ + (Math.round(Math.random() * 50) + 205) + ","
+ + (Math.round(Math.random() * 50) + 205) + ")";
+ }
+};
+
+twirl.createIcon = function(definition) {
+ var state = true;
+ var active = true;
+ function formatPath(i) {
+ return "../twirl/icon/" + i + ".svg";
+ }
+ var el = $("<img />");
+
+ if (definition.size) {
+ el.css("width", definition.size + "px");
+ }
+
+ var obj = {
+ el: el,
+ setState: function(tstate) {
+ if (!definition.icon2) return;
+ state = tstate;
+ if (state) {
+ el.attr("src", formatPath(definition.icon));
+ } else {
+ el.attr("src", formatPath(definition.icon2));
+ }
+
+ },
+ setActive: function(state) {
+ if (state) {
+ el.css("opacity", 1);
+ active = true;
+ } else {
+ el.css("opacity", 0.4);
+ active = false;
+ }
+ },
+ definition: definition
+ };
+
+ obj.click = function() {
+ definition.click(obj);
+ };
+
+ el.addClass("twirl_icon").css("opacity", 1).attr("src", formatPath(definition.icon)).on("mouseover", function(event){
+ var label = (!state && definition.label2) ? definition.label2 : definition.label;
+ twirl.tooltip.show(event, label);
+ }).on("mouseout", function(){
+ twirl.tooltip.hide();
+ }).click(function(el) {
+ if (active || definition.clickOnInactive) definition.click(obj);
+ });
+ return obj;
+};
+
+twirl.setTheme = function(name, noSave) {
+ var html = $("html");
+ if (html.attr("class")) {
+ for (let c of html.attr("class").split(/\s+/)) {
+ if (c.startsWith("theme")) {
+ html.removeClass(c);
+ }
+ }
+ }
+ html.addClass("theme" + name[0].toUpperCase() + name.substr(1).toLowerCase());
+ if (!noSave) {
+ twirl.storage.data.theme = name;
+ twirl.storage.save();
+ }
+};
+
+twirl.prompt = {
+ hide: function() {
+ twirl._els.prompt.base.hide();
+ },
+ show: function(text, onComplete, noButton) {
+ var p = twirl._els.prompt;
+ twirl.loading.hide();
+ p.text.empty();
+ if (typeof(text) == "string") {
+ p.text.text(text);
+ } else {
+ p.text.append(text);
+ }
+ if (!noButton) {
+ p.buttonContainer.show();
+ p.button.unbind().click(function(){
+ twirl.prompt.hide();
+ if (onComplete) onComplete();
+ });
+ } else {
+ p.buttonContainer.hide();
+ }
+ p.base.show();
+ }
+};
+
+twirl.loading = {
+ hide: function() {
+ $("body").css("cursor", "default");
+ twirl._els.loading.base.hide();
+ },
+ show: function(text, showPercent) {
+ var l = twirl._els.loading;
+ $("body").css("cursor", "wait");
+ l.text.text((text) ? text : "Processing");
+ if (showPercent) {
+ l.percentContainer.show();
+ } else {
+ l.percentContainer.hide();
+ }
+ l.base.show();
+ },
+ setPercent: function(percent) {
+ twirl._els.loading.percent.width(percent + "%");
+ }
+};
+
+twirl._setContextPosition = function(event, el, augmentations) {
+ var margin = 100;
+ if (!augmentations) augmentations = [0, 0];
+ if (event.clientX >= window.innerWidth - margin) {
+ el.css({right: margin + "px", left: "auto"});
+ } else {
+ el.css({right: "auto", left: (event.clientX + augmentations[0]) + "px"});
+ }
+
+ if (event.clientY >= window.innerHeight - margin) {
+ el.css({bottom: margin + "px", top: "auto"});
+ } else {
+ el.css({bottom: "auto", top: (event.clientY + augmentations[1]) + "px"});
+ }
+};
+
+twirl.contextMenu = {
+ show: function(event, data) {
+ event.preventDefault();
+ twirl._els.contextMenu.empty().unbind().on("mouseout", function(){
+ twirl._els.contextMenu.hide().off("mouseout");
+ });
+ for (let i in data) {
+ let d = data[i];
+ $("<div />").addClass("twirl_contextmenu_item").text(d.name).click(function(){
+ twirl._els.contextMenu.hide().off("mouseout");
+ d.click();
+ }).appendTo(twirl._els.contextMenu).on("mouseout", function(e){
+ e.stopPropagation();
+ });
+ }
+ twirl._setContextPosition(event, twirl._els.contextMenu, [-10, -10]);
+ twirl._els.contextMenu.show();
+ return false;
+ }
+};
+
+twirl.tooltip = {
+ show: function(event, text, colour) {
+ if (!colour) colour = "#bbbbbb";
+ var el = twirl._els.toolTip;
+ el.html(text).css({opacity: 0.8, "background-color": colour});
+ twirl._setContextPosition(event, el, [20, -15]);
+
+ },
+ hide: function() {
+ twirl._els.toolTip.css("opacity", 0);
+ }
+};
+
+twirl.sendErrorState = async function(errorObj) {
+ if (twirl._remote.sending) return;
+ twirl._remote.sending = true;
+ if (typeof(errorObj) == "function") {
+ errorObj = errorObj();
+ }
+ errorObj.application = $("title").text();
+ var data = {
+ request_type: "LogError",
+ error: errorObj
+ };
+
+ if (twirl._remote.sessionID) {
+ data.session_id = twirl._remote.sessionID;
+ }
+ var resp = await fetch("https:///service/", {
+ method: "POST",
+ headers: {
+ "Content-type": "application/json"
+ },
+ body: JSON.stringify(data)
+ });
+ var json = await resp.json();
+ if (json.session_id && !twirl._remote.sessionID) {
+ twirl._remote.sessionID = json.session_id;
+ }
+ twirl._remote.sending = false;
+};
+
+twirl.errorHandler = function(text, onComplete, errorObj) {
+ var errorText = (!text) ? twirl.errorState : text;
+ if (!errorObj) errorObj = {};
+ errorObj.text = errorText;
+ //twirl.sendErrorState(errorObj);
+ twirl.prompt.show(errorText, onComplete);
+ twirl.errorState = null;
+};
+
+twirl.showSettings = function(host, settings, onThemeChange) {
+ var el = $("<div />").css("font-size", "var(--fontSizeDefault)").append($("<h3 />").text("Settings"));
+ var tb = $("<tbody />");
+ $("<table />").append(tb).appendTo(el);
+
+ var currentThemeIndex;
+ if (twirl.storage.data.theme) currentThemeIndex = twirl.themes.indexOf(twirl.storage.data.theme);
+ if (!currentThemeIndex) currentThemeIndex = 0;
+
+ var tpTheme = new twirl.transform.Parameter({
+ definition: {name: "Theme", options: twirl.themes, dfault: currentThemeIndex, fireChanges: false, automatable: false},
+ host: host,
+ onChange: function(val) {
+ twirl.setTheme(twirl.themes[val]);
+ if (onThemeChange) onThemeChange();
+ }
+ });
+ tb.append(tpTheme.getElementRow(true))
+
+ for (let s of settings) {
+ var value;
+ if (s.options && s.storageKey) {
+ if (host.storage[s.storageKey]) value = host.storage[s.storageKey];
+ if (value < 0) value = s.dfault;
+ } else if (s.storageKey) {
+ value = (host.storage[s.storageKey]) ? host.storage[s.storageKey] : s.dfault;
+ if (s.bool) {
+ s.min = 0;
+ s.max = 1;
+ s.step = 1;
+ }
+ } else {
+ value = s.dfault;
+ }
+
+ let param = new twirl.transform.Parameter({
+ definition: {
+ name: s.name,
+ description: s.description,
+ fireChanges: false, automatable: false,
+ min: s.min, max: s.max, step: s.step, dfault: value,
+ options: s.options, asvalue: s.asvalue
+
+ }, host: host, onChange: function(val) {
+ if (s.storageKey) {
+ host.storage[s.storageKey] = val;
+ host.saveStorage();
+ }
+ if (s.onChange) s.onChange(val);
+ }
+ });
+ tb.append(param.getElementRow(true));
+ }
+ twirl.prompt.show(el);
+};
+
+twirl.TopMenu = function(host, menuData, elTarget) {
+ var self = this;
+ var opened = false;
+ var keyHandlers = [];
+
+ function keyHandler(e) {
+ if (!host.visible) return;
+ var nodeType = e.target.nodeName.toLowerCase();
+ if (nodeType == "input" || nodeType == "textarea") return;
+ e.preventDefault();
+ for (let h of keyHandlers) {
+ if (
+ (h.key == e.key.toLowerCase()) &&
+ ((!h.hasOwnProperty("ctrlKey") && !e.ctrlKey) || (h.ctrlKey && e.ctrlKey)) &&
+ ((!h.hasOwnProperty("shiftKey") && !e.shiftKey) || (h.shiftKey && e.shiftKey)) &&
+ ((!h.hasOwnProperty("altKey") && !e.altKey) || (h.altKey && e.altKey))
+ ) {
+ if (h.hasOwnProperty("condition") && !h.condition(host)) {
+ return;
+ }
+ if (h.hasOwnProperty("keyCondition") && !h.keyCondition(host)) {
+ return;
+ }
+ return h.func(host);
+ }
+ }
+ }
+
+ function construct(data) {
+ if (!data) data = menuData;
+ let onPlayDisables = [];
+ elTarget.empty();
+ var elMenuBar = $("<div />").addClass("topmenu").appendTo(elTarget);
+ for (let d of data) {
+ let elTopItem = $("<div />").addClass("topmenu_item").text(d.name).appendTo(elMenuBar);
+ let elMenu = $("<div />").addClass("topmenu_dropdown").appendTo(elTopItem);
+ let showConditions = [];
+ let onShows = [];
+ for (let c of d.contents) {
+ let elItem;
+ if (c.preset) {
+ if (c.preset == "divider") {
+ elItem = $("<hr />").appendTo(elMenu);
+ }
+ } else {
+ elItem = $("<div />").addClass("topmenu_dropdown_item").appendTo(elMenu);
+ if (typeof(c.name) == "function") {
+ onShows.push(function(){
+ elItem.text(c.name(host));
+ });
+ } else {
+ elItem.text(c.name);
+ }
+ if (c.click) elItem.click(function(){
+ if (c.condition && !c.condition(host)) return;
+ elMenu.hide();
+ opened = false;
+ c.click(host);
+ });
+ if (c.shortcut) {
+ $("<div />").addClass("topmenu_dropdown_itemright").text(c.shortcut.name).appendTo(elItem);
+ var obj = {func: c.click};
+ Object.assign(obj, c.shortcut);
+ if (c.condition) obj.condition = c.condition;
+ if (c.keyCondition) obj.keyCondition = c.keyCondition;
+ delete obj.name;
+ keyHandlers.push(obj);
+ }
+ if (c.disableOnPlay) {
+ onPlayDisables.push(elItem);
+ }
+ }
+ if (c.condition) {
+ showConditions.push({el: elItem, func: c.condition});
+ }
+ }
+
+ function showMenu() {
+ for (let c of showConditions) {
+ if (c.func(host)) {
+ c.el.removeClass("topmenu_dropdown_item_disabled");
+ } else {
+ c.el.addClass("topmenu_dropdown_item_disabled");
+ }
+ }
+ for (let o of onShows) {
+ o(host);
+ }
+ elMenu.show();
+ }
+
+
+ elTopItem.on("mouseover", function(){
+ if (opened) {
+ setTimeout(function(){
+ opened = true;
+ }, 10);
+ showMenu();
+ }
+ }).on("click", function() {
+ opened = true;
+ showMenu();
+ }).on("mouseleave", function() {
+ elMenu.hide();
+ setTimeout(function(){
+ opened = false;
+ }, 5);
+ });
+
+ }
+ if (host.onPlays) {
+ host.onPlays.push(async function(playing){
+ for (let o of onPlayDisables) {
+ if (playing) {
+ o.addClass("topmenu_dropdown_item_disabled");
+ } else {
+ o.removeClass("topmenu_dropdown_item_disabled");
+ }
+ }
+ });
+ }
+ $("body").off("keydown", keyHandler).on("keydown", keyHandler);
+ }
+ construct();
+};
+