Auto-format code with VS Code

Format JS and HTML using VS Code extension: https://marketplace.visualstudio.com/items?itemName=jbockle.jbockle-format-files

Dedented some files for now to make the diff intelligible:
- tools.js
- menus.js
- speech-recognition.js
- simulate-random-gestures.js
main
Isaiah Odhner 2022-01-18 13:33:44 -05:00
parent 5f64f1c251
commit 8053b38e58
37 changed files with 2692 additions and 2633 deletions

View File

@ -191,7 +191,7 @@ module.exports = {
"no-label-var": "error",
// "no-shadow": "error",
// "no-use-before-define": "error",
// To target specific variables to rename or otherwise address:
"no-restricted-globals": ["error", "event", "canvas", "ctx", "colors", "i", "j", "k", "x", "y", "z", "width", "height", "w", "h"],

View File

@ -9,7 +9,7 @@
// @noframes
// ==/UserScript==
(function() {
(function () {
'use strict';
let cleanUp = null;
@ -26,10 +26,10 @@
img.style.left = "0";
img.style.pointerEvents = "all";
img.draggable = false;
img.addEventListener("mouseenter", ()=> {
img.addEventListener("mouseenter", () => {
img.style.left = `${-2 * screenshotWidth}px`;
});
img.addEventListener("mouseleave", ()=> {
img.addEventListener("mouseleave", () => {
img.style.left = "0";
});
var container = document.createElement("div");
@ -52,25 +52,25 @@
container.appendChild(img);
document.body.appendChild(outerContainer);
cleanUp = ()=> {
cleanUp = () => {
originalImg.style.opacity = "";
container.style.transformOrigin = "center center";
container.style.transition = "opacity 0.2s ease, transform 0.2s ease";
container.style.opacity = 0;
container.style.transform = "scale(0.9)";
setTimeout(()=> {
setTimeout(() => {
outerContainer.remove();
}, 500);
cleanUp = null;
};
}
addEventListener("keydown", e=> {
addEventListener("keydown", e => {
if (e.key === "d") {
if (cleanUp) {
cleanUp();
} else {
var originalImg = document.elementFromPoint(innerWidth/2, innerHeight/2);
var originalImg = document.elementFromPoint(innerWidth / 2, innerHeight / 2);
if (!originalImg || !originalImg.matches("img")) {
console.warn("Didn't find an image in the middle of the page. Found", originalImg);
return;
@ -84,7 +84,7 @@
// mousedown is TAKEN - with stopPropagation, presumably
// (useCapture doesn't help)
addEventListener("pointerdown", (e)=> {
addEventListener("pointerdown", (e) => {
if (cleanUp) { cleanUp(); }
});

View File

@ -18,7 +18,7 @@ context('tool tests', () => {
});
beforeEach(() => {
if (before_first_real_test) return;
cy.window().then({timeout: 60000}, async (win)=> {
cy.window().then({ timeout: 60000 }, async (win) => {
win.selected_colors.foreground = "#000";
win.selected_colors.background = "#fff";
win.brush_shape = win.default_brush_shape;
@ -31,11 +31,11 @@ context('tool tests', () => {
});
});
const simulateGesture = (win, {start, end, shift, shiftToggleChance=0.01, secondary, secondaryToggleChance, target}) => {
const simulateGesture = (win, { start, end, shift, shiftToggleChance = 0.01, secondary, secondaryToggleChance, target }) => {
target = target || win.$(".main-canvas")[0];
let startWithinRect = target.getBoundingClientRect();
let canvasAreaRect = win.$(".canvas-area")[0].getBoundingClientRect();
let startMinX = Math.max(startWithinRect.left, canvasAreaRect.left);
let startMaxX = Math.min(startWithinRect.right, canvasAreaRect.right);
let startMinY = Math.max(startWithinRect.top, canvasAreaRect.top);
@ -44,7 +44,7 @@ context('tool tests', () => {
let startPointY = startMinY + start.y * (startMaxY - startMinY);
let endPointX = startMinX + end.x * (startMaxX - startMinX);
let endPointY = startMinY + end.y * (startMaxY - startMinY);
const $cursor = win.$(`<img src="images/cursors/default.png" class="user-cursor"/>`);
$cursor.css({
position: "absolute",
@ -57,7 +57,7 @@ context('tool tests', () => {
});
$cursor.appendTo(".jspaint");
let triggerMouseEvent = (type, point) => {
const clientX = point.x;
const clientY = point.y;
// const el_over = win.document.elementFromPoint(clientX, clientY);
@ -72,7 +72,7 @@ context('tool tests', () => {
if (do_nothing) {
return;
}
let event = new win.$.Event(type, {
view: window,
bubbles: true,
@ -89,7 +89,7 @@ context('tool tests', () => {
});
win.$(target).trigger(event);
};
let t = 0;
const stepsInGesture = 3;
let pointForTime = (t) => {
@ -98,8 +98,8 @@ context('tool tests', () => {
y: startPointY + (endPointY - startPointY) * Math.pow(t, 0.3),
};
};
return new Promise((resolve)=> {
return new Promise((resolve) => {
triggerMouseEvent("pointerenter", pointForTime(t)); // so dynamic cursors follow the simulation cursor
triggerMouseEvent("pointerdown", pointForTime(t));
let move = () => {
@ -112,9 +112,9 @@ context('tool tests', () => {
// }
if (t > 1) {
triggerMouseEvent("pointerup", pointForTime(t));
$cursor.remove();
resolve();
} else {
triggerMouseEvent("pointermove", pointForTime(t));
@ -160,30 +160,30 @@ context('tool tests', () => {
it(`eraser tool`, () => {
cy.get(`.tool[title='Eraser/Color Eraser']`).click();
// gesture([{x: 50, y: 50}, {x: 100, y: 100}]);
cy.window().then({timeout: 60000}, async (win)=> {
for (let row=0; row<4; row++) {
cy.window().then({ timeout: 60000 }, async (win) => {
for (let row = 0; row < 4; row++) {
const secondary = !!(row % 2);
const increaseSize = row >= 2;
let $options = win.$(`.chooser > *`);
for (let o=0; o<$options.length; o++) {
for (let o = 0; o < $options.length; o++) {
$options[o].click();
if (increaseSize) {
for (let i = 0; i < 5; i++) {
win.$('body').trigger(new win.$.Event("keydown", {key: "NumpadPlus", keyCode: 107, which: 107}));
win.$('body').trigger(new win.$.Event("keydown", { key: "NumpadPlus", keyCode: 107, which: 107 }));
}
}
win.selected_colors.background = "#f0f";
const start = {x: 0.05 + o*0.05, y: 0.1 + 0.1*row};
const end = {x: start.x + 0.04, y: start.y + 0.04};
await simulateGesture(win, {shift: false, secondary: false, start, end});
const start = { x: 0.05 + o * 0.05, y: 0.1 + 0.1 * row };
const end = { x: start.x + 0.04, y: start.y + 0.04 };
await simulateGesture(win, { shift: false, secondary: false, start, end });
if (secondary) {
// eslint-disable-next-line require-atomic-updates
win.selected_colors.background = "#ff0";
// eslint-disable-next-line require-atomic-updates
win.selected_colors.foreground = "#f0f";
const start = {x: 0.04 + o*0.05, y: 0.11 + 0.1*row};
const end = {x: start.x + 0.03, y: start.y + 0.02};
await simulateGesture(win, {shift: false, secondary: true, start, end});
const start = { x: 0.04 + o * 0.05, y: 0.11 + 0.1 * row };
const end = { x: start.x + 0.03, y: start.y + 0.02 };
await simulateGesture(win, { shift: false, secondary: true, start, end });
}
}
}
@ -191,13 +191,13 @@ context('tool tests', () => {
cy.get(".main-canvas").matchImageSnapshot();
});
["Brush", "Pencil", "Rectangle", "Rounded Rectangle", "Ellipse", "Line"].forEach((toolName)=> {
["Brush", "Pencil", "Rectangle", "Rounded Rectangle", "Ellipse", "Line"].forEach((toolName) => {
it(`${toolName.toLowerCase()} tool`, () => {
cy.get(`.tool[title='${toolName}']`).click();
// gesture([{x: 50, y: 50}, {x: 100, y: 100}]);
cy.get(".swatch:nth-child(22)").rightclick();
cy.window().then({timeout: 60000}, async (win)=> {
for (let row=0; row<4; row++) {
cy.window().then({ timeout: 60000 }, async (win) => {
for (let row = 0; row < 4; row++) {
const secondary = !!(row % 2);
const increaseSize = row >= 2;
let $options = win.$(`.chooser > *`);
@ -205,16 +205,16 @@ context('tool tests', () => {
if ($options.length === 0) {
$options = win.$("<dummy>");
}
for (let o=0; o<$options.length; o++) {
for (let o = 0; o < $options.length; o++) {
$options[o].click();
if (increaseSize && (o === 0 || toolName==="Brush" || toolName==="Line")) {
if (increaseSize && (o === 0 || toolName === "Brush" || toolName === "Line")) {
for (let i = 0; i < 5; i++) {
win.$('body').trigger(new win.$.Event("keydown", {key: "NumpadPlus", keyCode: 107, which: 107}));
win.$('body').trigger(new win.$.Event("keydown", { key: "NumpadPlus", keyCode: 107, which: 107 }));
}
}
const start = {x: 0.05 + o*0.05, y: 0.1 + 0.1*row};
const end = {x: start.x + 0.04, y: start.y + 0.04};
await simulateGesture(win, {shift: false, secondary: !!secondary, start, end});
const start = { x: 0.05 + o * 0.05, y: 0.1 + 0.1 * row };
const end = { x: start.x + 0.04, y: start.y + 0.04 };
await simulateGesture(win, { shift: false, secondary: !!secondary, start, end });
}
}
});

View File

@ -59,7 +59,7 @@ context('visual tests', () => {
cy.get('.tools-component').matchImageSnapshot(toolboxCompareOptions);
});
beforeEach(()=> {
beforeEach(() => {
if (Cypress.$('.window:visible')[0]) {
cy.get('.window:visible .window-close-button').click();
cy.get('.window').should('not.be.visible');
@ -90,7 +90,7 @@ context('visual tests', () => {
// @TODO: make menus more testable, with IDs
cy.get('.menus > .menu-container:nth-child(6) > .menu-button > .menu-hotkey').click();
cy.get('.menus > .menu-container:nth-child(6) > .menu-popup > table > tr:nth-child(1)').click();
cy.get('.window:visible .folder', {timeout: 10000}); // wait for sidebar contents to load
cy.get('.window:visible .folder', { timeout: 10000 }); // wait for sidebar contents to load
// @TODO: wait for iframe to load
cy.get('.window:visible').matchImageSnapshot(Object.assign({}, withTextCompareOptions, { blackout: ["iframe"] }));
});
@ -133,7 +133,7 @@ context('visual tests', () => {
cy.matchImageSnapshot(withTextCompareOptions);
});
const test_edit_colors_dialog = (expand=true) => {
const test_edit_colors_dialog = (expand = true) => {
cy.contains(".menu-button", "Colors").click();
cy.contains(".menu-item", "Edit Colors").click();
cy.wait(100);
@ -169,7 +169,7 @@ context('visual tests', () => {
cy.matchImageSnapshot(withTextCompareOptions);
});
it('classic theme edit colors dialog', ()=> {
it('classic theme edit colors dialog', () => {
test_edit_colors_dialog(false);
});

1309
index.html

File diff suppressed because it is too large Load Diff

View File

@ -1,16 +1,16 @@
/** Used by the Colors Box and by the Edit Colors dialog */
function $Swatch(color){
function $Swatch(color) {
const $swatch = $(E("div")).addClass("swatch");
const swatch_canvas = make_canvas();
$(swatch_canvas).css({pointerEvents: "none"}).appendTo($swatch);
$(swatch_canvas).css({ pointerEvents: "none" }).appendTo($swatch);
// @TODO: clean up event listener
$G.on("theme-load", ()=> { update_$swatch($swatch); });
$G.on("theme-load", () => { update_$swatch($swatch); });
$swatch.data("swatch", color);
update_$swatch($swatch, color);
return $swatch;
}
@ -37,36 +37,36 @@ function update_$swatch($swatch, new_color) {
});
}
function $ColorBox(vertical){
function $ColorBox(vertical) {
const $cb = $(E("div")).addClass("color-box");
const $current_colors = $Swatch(selected_colors.ternary).addClass("current-colors");
const $palette = $(E("div")).addClass("palette");
$cb.append($current_colors, $palette);
const $foreground_color = $Swatch(selected_colors.foreground).addClass("color-selection foreground-color");
const $background_color = $Swatch(selected_colors.background).addClass("color-selection background-color");
$current_colors.append($background_color, $foreground_color);
$G.on("option-changed", () => {
update_$swatch($foreground_color, selected_colors.foreground);
update_$swatch($background_color, selected_colors.background);
update_$swatch($current_colors, selected_colors.ternary);
});
$current_colors.on("pointerdown", () => {
const new_bg = selected_colors.foreground;
selected_colors.foreground = selected_colors.background;
selected_colors.background = new_bg;
$G.triggerHandler("option-changed");
});
const make_color_button = (color) => {
const $b = $Swatch(color).addClass("color-button");
$b.appendTo($palette);
const double_click_period_ms = 400;
let within_double_click_period = false;
let double_click_button = null;
@ -77,10 +77,10 @@ function $ColorBox(vertical){
// @TODO: allow metaKey for ternary color, and selection cropping, on macOS?
ctrl = e.ctrlKey;
button = e.button;
if(button === 0){
if (button === 0) {
$c.data("$last_fg_color_button", $b);
}
const color_selection_slot = ctrl ? "ternary" : button === 0 ? "foreground" : button === 2 ? "background" : null;
if (color_selection_slot) {
if (within_double_click_period && button === double_click_button) {
@ -89,7 +89,7 @@ function $ColorBox(vertical){
selected_colors[color_selection_slot] = $b.data("swatch");
$G.trigger("option-changed");
}
clearTimeout(double_click_tid);
double_click_tid = setTimeout(() => {
within_double_click_period = false;
@ -113,13 +113,13 @@ function $ColorBox(vertical){
$some_button.outerHeight() +
parseFloat(getComputedStyle($some_button[0]).getPropertyValue("margin-top")) +
parseFloat(getComputedStyle($some_button[0]).getPropertyValue("margin-bottom"));
$palette.height(Math.ceil(palette.length/2) * height_per_button);
$palette.height(Math.ceil(palette.length / 2) * height_per_button);
} else {
const width_per_button =
$some_button.outerWidth() +
parseFloat(getComputedStyle($some_button[0]).getPropertyValue("margin-left")) +
parseFloat(getComputedStyle($some_button[0]).getPropertyValue("margin-right"));
$palette.width(Math.ceil(palette.length/2) * width_per_button);
$palette.width(Math.ceil(palette.length / 2) * width_per_button);
}
// the "last foreground color button" starts out as the first in the palette
@ -129,15 +129,15 @@ function $ColorBox(vertical){
if (vertical) {
$c = $Component(localize("Colors"), "colors-component", "tall", $cb);
$c.appendTo(get_direction() === "rtl" ? $left : $right); // opposite ToolBox by default
}else{
} else {
$c = $Component(localize("Colors"), "colors-component", "wide", $cb);
$c.appendTo($bottom);
}
build_palette();
$(window).on("theme-change", build_palette);
$c.rebuild_palette = build_palette;
return $c;
}

View File

@ -5,8 +5,8 @@
function get_segments(component_area_el, pos_axis, exclude_component_el) {
const $other_components = $(component_area_el).find(".component").not(exclude_component_el);
return $other_components.toArray().map((component_el)=> {
const segment = {element: component_el};
return $other_components.toArray().map((component_el) => {
const segment = { element: component_el };
if (pos_axis === "top") {
segment.pos = component_el.offsetTop;
segment.length = component_el.clientHeight;
@ -22,7 +22,7 @@ function get_segments(component_area_el, pos_axis, exclude_component_el) {
}
function adjust_segments(segments, total_available_length) {
segments.sort((a, b)=> a.pos - b.pos);
segments.sort((a, b) => a.pos - b.pos);
// Clamp
for (const segment of segments) {
@ -72,14 +72,14 @@ function apply_segments(component_area_el, pos_axis, segments) {
}
}
function $Component(title, className, orientation, $el){
function $Component(title, className, orientation, $el) {
// A draggable widget that can be undocked into a window
const $c = $(E("div")).addClass("component");
$c.addClass(className);
$c.addClass(orientation);
$c.append($el);
$c.css("touch-action", "none");
const $w = new $ToolWindow($c);
$w.title(title);
$w.hide();
@ -87,17 +87,17 @@ function $Component(title, className, orientation, $el){
tall: "vertical",
wide: "horizontal",
}[orientation]);
// Nudge the Colors component over a tiny bit
if(className === "colors-component" && orientation === "wide"){
if (className === "colors-component" && orientation === "wide") {
$c.css("position", "relative");
$c.css(`margin-${get_direction() === "rtl" ? "right" : "left"}`, "3px");
}
let iid;
if($("body").hasClass("eye-gaze-mode")){
if ($("body").hasClass("eye-gaze-mode")) {
// @TODO: don't use an interval for this!
iid = setInterval(()=> {
iid = setInterval(() => {
const scale = 3;
$c.css({
transform: `scale(${scale})`,
@ -107,7 +107,7 @@ function $Component(title, className, orientation, $el){
});
}, 200);
}
let ox, oy;
let ox2, oy2;
let w, h;
@ -117,15 +117,15 @@ function $Component(title, className, orientation, $el){
let $last_docked_to;
let $dock_to;
let $ghost;
if(orientation === "tall"){
if (orientation === "tall") {
pos_axis = "top";
}else if(get_direction() === "rtl"){
} else if (get_direction() === "rtl") {
pos_axis = "right";
}else{
} else {
pos_axis = "left";
}
const dock_to = $dock_to => {
$w.hide();
@ -145,7 +145,7 @@ function $Component(title, className, orientation, $el){
// console.log("before adjustment", JSON.stringify(segments, (_key,val)=> (val instanceof Element) ? val.className : val));
adjust_segments(segments, total_available_length);
// console.log("after adjustment", JSON.stringify(segments, (_key,val)=> (val instanceof Element) ? val.className : val));
apply_segments($dock_to[0], pos_axis, segments);
// Save where it's now docked to
@ -156,7 +156,7 @@ function $Component(title, className, orientation, $el){
const component_area_el = $c.closest(".component-area")[0];
// must get layout state *before* changing it
const segments = get_segments(component_area_el, pos_axis, $c[0]);
$c.css("position", "relative");
$c.css(`margin-${pos_axis}`, "");
@ -175,15 +175,15 @@ function $Component(title, className, orientation, $el){
// console.log("after adjustment", JSON.stringify(segments, (_key,val)=> (val instanceof Element) ? val.className : val));
apply_segments(component_area_el, pos_axis, segments);
};
$w.on("window-drag-start", (e)=> {
$w.on("window-drag-start", (e) => {
e.preventDefault();
});
const imagine_window_dimensions = ()=> {
const imagine_window_dimensions = () => {
const prev_window_shown = $w.is(":visible");
$w.show();
let $spacer;
let {offsetLeft, offsetTop} = $c[0];
let { offsetLeft, offsetTop } = $c[0];
if ($c.closest(".tool-window").length == 0) {
const styles = getComputedStyle($c[0]);
$spacer = $(E("div")).addClass("component").css({
@ -194,7 +194,7 @@ function $Component(title, className, orientation, $el){
// let padding be influenced by CSS
});
$w.append($spacer);
({offsetLeft, offsetTop} = $spacer[0]);
({ offsetLeft, offsetTop } = $spacer[0]);
}
const rect = $w[0].getBoundingClientRect();
if ($spacer) {
@ -206,11 +206,11 @@ function $Component(title, className, orientation, $el){
const w_styles = getComputedStyle($w[0]);
offsetLeft += parseFloat(w_styles.borderLeftWidth);
offsetTop += parseFloat(w_styles.borderTopWidth);
return {rect, offsetLeft, offsetTop};
return { rect, offsetLeft, offsetTop };
};
const imagine_docked_dimensions = ($dock_to=(pos_axis === "top" ? $left : $bottom))=> {
const imagine_docked_dimensions = ($dock_to = (pos_axis === "top" ? $left : $bottom)) => {
if ($c.closest(".tool-window").length == 0) {
return {rect: $c[0].getBoundingClientRect()};
return { rect: $c[0].getBoundingClientRect() };
}
const styles = getComputedStyle($c[0]);
const $spacer = $(E("div")).addClass("component").css({
@ -223,18 +223,18 @@ function $Component(title, className, orientation, $el){
if ($spacer) {
$spacer.remove();
}
return {rect};
return { rect };
};
const render_ghost = (e)=> {
const render_ghost = (e) => {
const {rect} = $dock_to ? imagine_docked_dimensions($dock_to) : imagine_window_dimensions()
const { rect } = $dock_to ? imagine_docked_dimensions($dock_to) : imagine_window_dimensions()
// Make sure these dimensions are odd numbers
// so the alternating pattern of the border is unbroken
w = (~~(rect.width/2))*2 + 1;
h = (~~(rect.height/2))*2 + 1;
if(!$ghost){
w = (~~(rect.width / 2)) * 2 + 1;
h = (~~(rect.height / 2)) * 2 + 1;
if (!$ghost) {
$ghost = $(E("div")).addClass("component-ghost dock");
$ghost.appendTo("body");
}
@ -248,88 +248,88 @@ function $Component(title, className, orientation, $el){
top: e.clientY + ($dock_to ? oy : oy2) + inset,
});
if($dock_to){
if ($dock_to) {
$ghost.addClass("dock");
}else{
} else {
$ghost.removeClass("dock");
}
};
$c.add($w.$titlebar).on("pointerdown", e => {
// Only start a drag via a left click directly on the component element or titlebar
if(e.button !== 0){ return; }
const validTarget =
if (e.button !== 0) { return; }
const validTarget =
$c.is(e.target) ||
(
$(e.target).closest($w.$titlebar).length > 0 &&
$(e.target).closest("button").length === 0
);
if(!validTarget){ return; }
if (!validTarget) { return; }
// Don't allow dragging in eye gaze mode
if($("body").hasClass("eye-gaze-mode")){ return; }
if ($("body").hasClass("eye-gaze-mode")) { return; }
const docked = imagine_docked_dimensions();
const rect = $c[0].getBoundingClientRect();
ox = rect.left - e.clientX;
oy = rect.top - e.clientY;
ox = -Math.min(Math.max(-ox, 0), docked.rect.width);
oy = -Math.min(Math.max(-oy, 0), docked.rect.height);
const {offsetLeft, offsetTop} = imagine_window_dimensions();
const { offsetLeft, offsetTop } = imagine_window_dimensions();
ox2 = rect.left - offsetLeft - e.clientX;
oy2 = rect.top - offsetTop - e.clientY;
$("body").addClass("dragging");
$("body").css({cursor: "default"}).addClass("cursor-bully");
$("body").css({ cursor: "default" }).addClass("cursor-bully");
$G.on("pointermove", drag_update_position);
$G.one("pointerup", e => {
$G.off("pointermove", drag_update_position);
drag_onpointerup(e);
$("body").removeClass("dragging");
$("body").css({cursor: ""}).removeClass("cursor-bully");
$("body").css({ cursor: "" }).removeClass("cursor-bully");
$canvas.trigger("pointerleave"); // prevent magnifier preview showing until you move the mouse
});
render_ghost(e);
drag_update_position(e);
// Prevent text selection anywhere within the component
e.preventDefault();
});
const drag_update_position = e => {
$ghost.css({
left: e.clientX + ox,
top: e.clientY + oy,
});
$dock_to = null;
const {width, height} = imagine_docked_dimensions().rect;
const { width, height } = imagine_docked_dimensions().rect;
const dock_ghost_left = e.clientX + ox;
const dock_ghost_top = e.clientY + oy;
const dock_ghost_right = dock_ghost_left + width;
const dock_ghost_bottom = dock_ghost_top + height;
const q = 5;
if(orientation === "tall"){
if (orientation === "tall") {
pos_axis = "top";
if(dock_ghost_left-q < $left[0].getBoundingClientRect().right){
if (dock_ghost_left - q < $left[0].getBoundingClientRect().right) {
$dock_to = $left;
}
if(dock_ghost_right+q > $right[0].getBoundingClientRect().left){
if (dock_ghost_right + q > $right[0].getBoundingClientRect().left) {
$dock_to = $right;
}
}else{
} else {
pos_axis = get_direction() === "rtl" ? "right" : "left";
if(dock_ghost_top-q < $top[0].getBoundingClientRect().bottom){
if (dock_ghost_top - q < $top[0].getBoundingClientRect().bottom) {
$dock_to = $top;
}
if(dock_ghost_bottom+q > $bottom[0].getBoundingClientRect().top){
if (dock_ghost_bottom + q > $bottom[0].getBoundingClientRect().top) {
$dock_to = $bottom;
}
}
if($dock_to){
if ($dock_to) {
const dock_to_rect = $dock_to[0].getBoundingClientRect();
pos = (
pos_axis === "top" ? dock_ghost_top : pos_axis === "right" ? dock_ghost_right : dock_ghost_left
@ -343,42 +343,42 @@ function $Component(title, className, orientation, $el){
e.preventDefault();
};
const drag_onpointerup = e => {
$w.hide();
// If the component is docked to a component area (a side)
if($c.parent().is(".component-area")){
if ($c.parent().is(".component-area")) {
// Save where it's docked so we can dock back later
$last_docked_to = $c.parent();
if($dock_to){
if ($dock_to) {
last_docked_to_pos = pos;
}
}
if($dock_to){
if ($dock_to) {
// Dock component to $dock_to
dock_to($dock_to);
} else {
undock_to(e.clientX + ox2, e.clientY + oy2);
}
$ghost && $ghost.remove();
$ghost = null;
$G.trigger("resize");
};
$c.dock = ($dock_to) => {
pos = last_docked_to_pos ?? 0;
dock_to($dock_to ?? $last_docked_to);
};
$c.undock_to = undock_to;
$c.show = () => {
$($c[0]).show(); // avoid recursion
if($.contains($w[0], $c[0])){
if ($.contains($w[0], $c[0])) {
$w.show();
}
return $c;
@ -388,23 +388,23 @@ function $Component(title, className, orientation, $el){
return $c;
};
$c.toggle = () => {
if($c.is(":visible")){
if ($c.is(":visible")) {
$c.hide();
}else{
} else {
$c.show();
}
return $c;
};
$c.destroy = ()=> {
$c.destroy = () => {
$w.close();
$c.remove();
clearInterval(iid);
};
$w.on("close", e => {
e.preventDefault();
$w.hide();
});
return $c;
}

View File

@ -1,7 +1,7 @@
function $FontBox(){
function $FontBox() {
const $fb = $(E("div")).addClass("font-box");
const $family = $(E("select")).addClass("inset-deep").attr({
"aria-label": "Font Family",
"aria-description": localize("Selects the font used by the text."),
@ -26,13 +26,13 @@ function $FontBox(){
$button_group.append($bold, $italic, $underline, $vertical);
$fb.append($family, $size, $button_group);
const update_font = () => {
text_tool_font.size = Number($size.val());
text_tool_font.family = $family.val();
$G.trigger("option-changed");
};
FontDetective.each(font => {
const $option = $(E("option"));
$option.val(font).text(font.name);
@ -41,21 +41,21 @@ function $FontBox(){
update_font();
}
});
if (text_tool_font.family) {
$family.val(text_tool_font.family);
}
$family.on("change", update_font);
$size.on("change", update_font);
const $w = $ToolWindow();
$w.title(localize("Fonts"));
$w.$content.append($fb);
$w.center();
return $w;
function $Toggle(xi, thing, label, description) {
const $button = $(E("button")).addClass("toggle").attr({
"aria-pressed": false,
@ -85,7 +85,7 @@ function $FontBox(){
$button.attr("aria-pressed", $button.hasClass("selected"));
update_font();
});
if(text_tool_font[thing]){
if (text_tool_font[thing]) {
$button.addClass("selected").attr("aria-pressed", true);
}
return $button;

View File

@ -1,25 +1,25 @@
let theme_dev_blob_url;
function $ToolBox(tools, is_extras){
function $ToolBox(tools, is_extras) {
const $tools = $(E("div")).addClass("tools");
const $tool_options = $(E("div")).addClass("tool-options");
let showing_tooltips = false;
$tools.on("pointerleave", () => {
showing_tooltips = false;
$status_text.default();
});
const $buttons = $($.map(tools, (tool, i) => {
const $b = $(E("div")).addClass("tool");
$b.appendTo($tools);
tool.$button = $b;
$b.attr("title", tool.name);
const $icon = $(E("span")).addClass("tool-icon");
$icon.appendTo($b);
const update_css = ()=> {
const update_css = () => {
const use_svg = !theme_dev_blob_url && (
(
(window.devicePixelRatio >= 3 || (window.devicePixelRatio % 1) !== 0)
@ -40,37 +40,37 @@ function $ToolBox(tools, is_extras){
};
update_css();
$G.on("theme-load resize", update_css);
$b.on("click", e => {
if (e.shiftKey || e.ctrlKey) {
select_tool(tool, true);
return;
}
if(selected_tool === tool && tool.deselect){
if (selected_tool === tool && tool.deselect) {
select_tools(return_to_tools);
}else{
} else {
select_tool(tool);
}
});
$b.on("pointerenter", () => {
const show_tooltip = () => {
showing_tooltips = true;
$status_text.text(tool.description);
};
if(showing_tooltips){
if (showing_tooltips) {
show_tooltip();
}else{
} else {
const tid = setTimeout(show_tooltip, 300);
$b.on("pointerleave", () => {
clearTimeout(tid);
});
}
});
return $b[0];
}));
const $c = $Component(
is_extras ? "Extra Tools" : localize("Tools"),
is_extras ? "tools-component extra-tools-component" : "tools-component",
@ -80,7 +80,7 @@ function $ToolBox(tools, is_extras){
$c.appendTo(get_direction() === "rtl" ? $right : $left); // opposite ColorBox by default
$c.update_selected_tool = () => {
$buttons.removeClass("selected");
selected_tools.forEach((selected_tool)=> {
selected_tools.forEach((selected_tool) => {
selected_tool.$button.addClass("selected");
});
$tool_options.children().detach();
@ -102,14 +102,14 @@ function $ToolBox(tools, is_extras){
let dev_theme_tool_icons = false;
try {
dev_theme_tool_icons = localStorage.dev_theme_tool_icons === "true";
// eslint-disable-next-line no-empty
// eslint-disable-next-line no-empty
} catch (e) { }
if (dev_theme_tool_icons) {
let last_update_id = 0;
$G.on("session-update", ()=> {
$G.on("session-update", () => {
last_update_id += 1;
const this_update_id = last_update_id;
main_canvas.toBlob((blob)=> {
main_canvas.toBlob((blob) => {
// avoid a race condition particularly when loading the document initially when the default canvas size is large, giving a larger PNG
if (this_update_id !== last_update_id) {
return;

View File

@ -2,7 +2,7 @@
function make_window_supporting_scale(options) {
const $w = new $Window(options);
const scale_for_eye_gaze_mode_and_center = ()=> {
const scale_for_eye_gaze_mode_and_center = () => {
if (!$w.is(".edit-colors-window, .storage-manager, .attributes-window, .flip-and-rotate, .stretch-and-skew")) {
return;
}
@ -36,13 +36,13 @@ function make_window_supporting_scale(options) {
// requestAnimationFrame(scale_for_eye_gaze_mode_and_center);
};
if(!options.$component){
if (!options.$component) {
$w.center();
const scale_for_eye_gaze_mode_and_center_next_frame = ()=> {
const scale_for_eye_gaze_mode_and_center_next_frame = () => {
requestAnimationFrame(scale_for_eye_gaze_mode_and_center);
};
const on_close = ()=> {
const on_close = () => {
$w.off("close", on_close);
$G.off("eye-gaze-mode-toggled resize", scale_for_eye_gaze_mode_and_center_next_frame);
};
@ -57,7 +57,7 @@ function make_window_supporting_scale(options) {
contain: "none",
});
}
return $w;
}

View File

@ -7,7 +7,7 @@ function Handles(options) {
const get_ghost_offset_left = options.get_ghost_offset_left || (() => 0);
const get_ghost_offset_top = options.get_ghost_offset_top || (() => 0);
const size_only = options.size_only || false;
const HANDLE_MIDDLE = 0;
const HANDLE_START = -1;
const HANDLE_END = 1;
@ -39,63 +39,63 @@ function Handles(options) {
}
$h.css("touch-action", "none");
let rect;
let dragged = false;
const resizes_height = y_axis !== HANDLE_MIDDLE;
const resizes_width = x_axis !== HANDLE_MIDDLE;
if(size_only && (y_axis === HANDLE_TOP || x_axis === HANDLE_LEFT)){
if (size_only && (y_axis === HANDLE_TOP || x_axis === HANDLE_LEFT)) {
$h.addClass("useless-handle");
$grab_region.remove();
}else{
} else {
let cursor_fname;
if((x_axis === HANDLE_LEFT && y_axis === HANDLE_TOP) || (x_axis === HANDLE_RIGHT && y_axis === HANDLE_BOTTOM)){
if ((x_axis === HANDLE_LEFT && y_axis === HANDLE_TOP) || (x_axis === HANDLE_RIGHT && y_axis === HANDLE_BOTTOM)) {
cursor_fname = "nwse-resize";
}else if((x_axis === HANDLE_RIGHT && y_axis === HANDLE_TOP) || (x_axis === HANDLE_LEFT && y_axis === HANDLE_BOTTOM)){
} else if ((x_axis === HANDLE_RIGHT && y_axis === HANDLE_TOP) || (x_axis === HANDLE_LEFT && y_axis === HANDLE_BOTTOM)) {
cursor_fname = "nesw-resize";
}else if(resizes_width){
} else if (resizes_width) {
cursor_fname = "ew-resize";
}else if(resizes_height){
} else if (resizes_height) {
cursor_fname = "ns-resize";
}
let fallback_cursor = "";
if(y_axis === HANDLE_TOP){ fallback_cursor += "n"; }
if(y_axis === HANDLE_BOTTOM){ fallback_cursor += "s"; }
if(x_axis === HANDLE_LEFT){ fallback_cursor += "w"; }
if(x_axis === HANDLE_RIGHT){ fallback_cursor += "e"; }
if (y_axis === HANDLE_TOP) { fallback_cursor += "n"; }
if (y_axis === HANDLE_BOTTOM) { fallback_cursor += "s"; }
if (x_axis === HANDLE_LEFT) { fallback_cursor += "w"; }
if (x_axis === HANDLE_RIGHT) { fallback_cursor += "e"; }
fallback_cursor += "-resize";
const cursor = make_css_cursor(cursor_fname, [16, 16], fallback_cursor);
$h.add($grab_region).css({cursor});
$h.add($grab_region).css({ cursor });
const drag = (event) => {
$resize_ghost.appendTo($object_container);
dragged = true;
rect = options.get_rect();
const m = to_canvas_coords(event);
let delta_x = 0;
let delta_y = 0;
let width, height;
// @TODO: decide between Math.floor/Math.ceil/Math.round for these values
if(x_axis === HANDLE_RIGHT){
if (x_axis === HANDLE_RIGHT) {
delta_x = 0;
width = ~~(m.x - rect.x);
}else if(x_axis === HANDLE_LEFT){
} else if (x_axis === HANDLE_LEFT) {
delta_x = ~~(m.x - rect.x);
width = ~~(rect.x + rect.width - m.x);
}else{
} else {
width = ~~(rect.width);
}
if(y_axis === HANDLE_BOTTOM){
if (y_axis === HANDLE_BOTTOM) {
delta_y = 0;
height = ~~(m.y - rect.y);
}else if(y_axis === HANDLE_TOP){
} else if (y_axis === HANDLE_TOP) {
delta_y = ~~(m.y - rect.y);
height = ~~(rect.y + rect.height - m.y);
}else{
} else {
height = ~~(rect.height);
}
let new_rect = {
@ -126,16 +126,16 @@ function Handles(options) {
};
$h.add($grab_region).on("pointerdown", event => {
dragged = false;
if(event.button === 0){
if (event.button === 0) {
$G.on("pointermove", drag);
$("body").css({cursor}).addClass("cursor-bully");
$("body").css({ cursor }).addClass("cursor-bully");
}
$G.one("pointerup", ()=> {
$G.one("pointerup", () => {
$G.off("pointermove", drag);
$("body").css({cursor: ""}).removeClass("cursor-bully");
$("body").css({ cursor: "" }).removeClass("cursor-bully");
$resize_ghost.remove();
if(dragged){
if (dragged) {
options.set_rect(rect);
}
$handles_container.trigger("update");
@ -145,7 +145,7 @@ function Handles(options) {
event.preventDefault();
});
}
const update_handle = () => {
const rect = options.get_rect();
const hs = $h.width();
@ -154,14 +154,14 @@ function Handles(options) {
const x = get_handles_offset_left();
const y = get_handles_offset_top();
const grab_size = 32;
for ({len_key, pos_key, region, offset} of [
{len_key: "width", pos_key: "left", region: x_axis, offset: x},
{len_key: "height", pos_key: "top", region: y_axis, offset: y},
for ({ len_key, pos_key, region, offset } of [
{ len_key: "width", pos_key: "left", region: x_axis, offset: x },
{ len_key: "height", pos_key: "top", region: y_axis, offset: y },
]) {
let middle_start = Math.max(
rect[len_key] * magnification / 2 - grab_size/2,
rect[len_key] * magnification / 2 - grab_size / 2,
Math.min(
grab_size/2,
grab_size / 2,
rect[len_key] * magnification / 3
)
);
@ -171,9 +171,9 @@ function Handles(options) {
middle_start = 0;
middle_end = magnification;
}
const start_start = -grab_size/2;
const start_start = -grab_size / 2;
const start_end = Math.min(
grab_size/2,
grab_size / 2,
middle_start
);
const end_start = rect[len_key] * magnification - start_end;
@ -197,7 +197,7 @@ function Handles(options) {
[len_key]: middle_end - middle_start,
});
} else if (region === HANDLE_END) {
$h.css({ [pos_key]: offset + (rect[len_key] * magnification - hs/2) });
$h.css({ [pos_key]: offset + (rect[len_key] * magnification - hs / 2) });
$grab_region.css({
[pos_key]: offset + end_start,
[len_key]: end_end - end_start,
@ -205,17 +205,17 @@ function Handles(options) {
}
}
};
$handles_container.on("update resize scroll", update_handle);
$G.on("resize theme-load", update_handle);
setTimeout(update_handle, 50);
handles.push($h[0], $grab_region[0]);
});
this.handles = handles;
// It shouldn't scroll when hiding/showing handles, so don't use jQuery hide/show or CSS display.
this.hide = ()=> { $(handles).css({opacity: 0, pointerEvents: "none"}); };
this.show = ()=> { $(handles).css({opacity: "", pointerEvents: ""}); };
this.hide = () => { $(handles).css({ opacity: 0, pointerEvents: "none" }); };
this.show = () => { $(handles).css({ opacity: "", pointerEvents: "" }); };
}

View File

@ -1,7 +1,7 @@
class OnCanvasHelperLayer extends OnCanvasObject {
constructor(x, y, width, height, hideMainCanvasHandles, pixelRatio = 1) {
super(x, y, width, height, hideMainCanvasHandles);
this.$el.addClass("helper-layer");
this.$el.css({
pointerEvents: "none",

View File

@ -10,7 +10,7 @@ class OnCanvasObject {
if (this.hideMainCanvasHandles) {
canvas_handles.hide();
}
$G.on("resize theme-load", this._global_resize_handler = ()=> {
$G.on("resize theme-load", this._global_resize_handler = () => {
this.position();
});
}

View File

@ -32,7 +32,7 @@ class OnCanvasSelection extends OnCanvasObject {
});
this.position();
const instantiate = ()=> {
const instantiate = () => {
if (img_or_canvas) {
// (this applies when pasting a selection)
// NOTE: need to create a Canvas because something about imgs makes dragging not work with magnification
@ -60,13 +60,13 @@ class OnCanvasSelection extends OnCanvasObject {
$handles_container: this.$el,
$object_container: $canvas_area,
outset: 2,
get_rect: ()=> ({x: this.x, y: this.y, width: this.width, height: this.height}),
set_rect: ({x, y, width, height}) => {
get_rect: () => ({ x: this.x, y: this.y, width: this.width, height: this.height }),
set_rect: ({ x, y, width, height }) => {
undoable({
name: "Resize Selection",
icon: get_icon_for_tool(get_tool_by_id(TOOL_SELECT)),
soft: true,
}, ()=> {
}, () => {
this.x = x;
this.y = y;
this.width = width;
@ -75,20 +75,20 @@ class OnCanvasSelection extends OnCanvasObject {
this.resize();
});
},
get_ghost_offset_left: ()=> parseFloat($canvas_area.css("padding-left")) + 1,
get_ghost_offset_top: ()=> parseFloat($canvas_area.css("padding-top")) + 1,
get_ghost_offset_left: () => parseFloat($canvas_area.css("padding-left")) + 1,
get_ghost_offset_top: () => parseFloat($canvas_area.css("padding-top")) + 1,
});
let mox, moy;
const pointermove = e => {
make_or_update_undoable({
match: (history_node)=>
match: (history_node) =>
(e.shiftKey && history_node.name.match(/^(Smear|Stamp|Move) Selection$/)) ||
(!e.shiftKey && history_node.name.match(/^Move Selection$/)),
name: e.shiftKey ? "Smear Selection" : "Move Selection",
update_name: true,
icon: get_icon_for_tool(get_tool_by_id(TOOL_SELECT)),
soft: true,
}, ()=> {
}, () => {
const m = to_canvas_coords(e);
this.x = Math.max(Math.min(m.x - mox, main_canvas.width), -this.width);
this.y = Math.max(Math.min(m.y - moy, main_canvas.height), -this.height);
@ -120,7 +120,7 @@ class OnCanvasSelection extends OnCanvasObject {
name: "Stamp Selection",
icon: get_icon_for_tool(get_tool_by_id(TOOL_SELECT)),
soft: true,
}, ()=> {
}, () => {
this.draw();
});
}
@ -131,7 +131,7 @@ class OnCanvasSelection extends OnCanvasObject {
name: "Stamp Selection",
icon: get_icon_for_tool(get_tool_by_id(TOOL_SELECT)),
soft: true,
}, ()=> {
}, () => {
this.draw();
});
}
@ -141,7 +141,7 @@ class OnCanvasSelection extends OnCanvasObject {
$status_position.text("");
$status_size.text("");
};
instantiate();
}
cut_out_background() {
@ -216,10 +216,10 @@ class OnCanvasSelection extends OnCanvasObject {
// @FIXME: work with transparent selected background color
// (support treating partially transparent background colors as transparency)
if (
Math.abs(sourceImageData.data[i+0] - background_color_rgba[0]) <= match_threshold &&
Math.abs(sourceImageData.data[i+1] - background_color_rgba[1]) <= match_threshold &&
Math.abs(sourceImageData.data[i+2] - background_color_rgba[2]) <= match_threshold &&
Math.abs(sourceImageData.data[i+3] - background_color_rgba[3]) <= match_threshold
Math.abs(sourceImageData.data[i + 0] - background_color_rgba[0]) <= match_threshold &&
Math.abs(sourceImageData.data[i + 1] - background_color_rgba[1]) <= match_threshold &&
Math.abs(sourceImageData.data[i + 2] - background_color_rgba[2]) <= match_threshold &&
Math.abs(sourceImageData.data[i + 3] - background_color_rgba[3]) <= match_threshold
) {
in_cutout = false;
}

View File

@ -12,7 +12,7 @@ class OnCanvasTextBox extends OnCanvasObject {
foreignObject.setAttribute("x", 0);
foreignObject.setAttribute("y", 0);
svg.append(foreignObject);
// inline styles so that they'll be serialized for the SVG
this.$editor.css({
position: "absolute",
@ -37,7 +37,7 @@ class OnCanvasTextBox extends OnCanvasObject {
this.canvas.style.pointerEvents = "none";
this.$el.append(this.canvas);
const update_size = ()=> {
const update_size = () => {
this.position();
this.$el.triggerHandler("update"); // update handles
this.$editor.add(render_textarea).css({
@ -46,7 +46,7 @@ class OnCanvasTextBox extends OnCanvasObject {
});
};
const auto_size = ()=> {
const auto_size = () => {
// Auto-expand, and apply minimum size.
edit_textarea.style.height = "";
edit_textarea.style.minHeight = "0px";
@ -60,13 +60,13 @@ class OnCanvasTextBox extends OnCanvasObject {
update_size();
};
const update = ()=> {
requestAnimationFrame(()=> {
const update = () => {
requestAnimationFrame(() => {
edit_textarea.scrollTop = 0; // prevent scrolling edit textarea to keep in sync
});
const font = text_tool_font;
const get_solid_color = (swatch)=> `rgba(${get_rgba_from_color(swatch).join(", ")}`;
const get_solid_color = (swatch) => `rgba(${get_rgba_from_color(swatch).join(", ")}`;
font.color = get_solid_color(selected_colors.foreground);
font.background = tool_transparent_mode ? "transparent" : get_solid_color(selected_colors.background);
this.$editor.add(this.canvas).css({
@ -96,7 +96,7 @@ class OnCanvasTextBox extends OnCanvasObject {
render_textarea.removeChild(render_textarea.firstChild);
}
render_textarea.appendChild(document.createTextNode(edit_textarea.value));
svg.setAttribute("width", this.width);
svg.setAttribute("height", this.height);
foreignObject.setAttribute("width", this.width);
@ -104,15 +104,15 @@ class OnCanvasTextBox extends OnCanvasObject {
var svg_source = new XMLSerializer().serializeToString(svg);
var data_url = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg_source)}`;
var img = new Image();
img.onload = ()=> {
img.onload = () => {
this.canvas.width = this.width;
this.canvas.height = this.height;
this.canvas.ctx.drawImage(img, 0, 0);
update_helper_layer(); // @TODO: under-grid specific helper layer?
};
img.onerror = (event)=> {
img.onerror = (event) => {
window.console && console.log("Failed to load image", event);
};
img.src = data_url;
@ -120,8 +120,8 @@ class OnCanvasTextBox extends OnCanvasObject {
$G.on("option-changed", this._on_option_changed = update);
this.$editor.on("input", this._on_input = update);
this.$editor.on("scroll", this._on_scroll = ()=> {
requestAnimationFrame(()=> {
this.$editor.on("scroll", this._on_scroll = () => {
requestAnimationFrame(() => {
edit_textarea.scrollTop = 0; // prevent scrolling edit textarea to keep in sync
});
});
@ -131,7 +131,7 @@ class OnCanvasTextBox extends OnCanvasObject {
touchAction: "none",
});
this.position();
this.$el.append(this.$editor);
this.$editor[0].focus();
this.handles = new Handles({
@ -139,8 +139,8 @@ class OnCanvasTextBox extends OnCanvasObject {
$object_container: $canvas_area,
outset: 2,
thick: true,
get_rect: ()=> ({x: this.x, y: this.y, width: this.width, height: this.height}),
set_rect: ({x, y, width, height}) => {
get_rect: () => ({ x: this.x, y: this.y, width: this.width, height: this.height }),
set_rect: ({ x, y, width, height }) => {
this.x = x;
this.y = y;
this.width = width;
@ -152,7 +152,7 @@ class OnCanvasTextBox extends OnCanvasObject {
this.canvas.width = width;
this.canvas.height = height;
},
constrain_rect: ({x, y, width, height}, x_axis, y_axis)=> {
constrain_rect: ({ x, y, width, height }, x_axis, y_axis) => {
// remember dimensions
const old_x = this.x;
const old_y = this.y;
@ -176,7 +176,7 @@ class OnCanvasTextBox extends OnCanvasObject {
if (y_axis === -1) {
y = Math.min(this.y, old_y + old_height - this.height);
}
// remember constrained dimensions
width = this.width;
height = this.height;
@ -188,10 +188,10 @@ class OnCanvasTextBox extends OnCanvasObject {
this.height = old_height;
update_size();
return {x, y, width, height};
return { x, y, width, height };
},
get_ghost_offset_left: ()=> parseFloat($canvas_area.css("padding-left")) + 1,
get_ghost_offset_top: ()=> parseFloat($canvas_area.css("padding-top")) + 1,
get_ghost_offset_left: () => parseFloat($canvas_area.css("padding-left")) + 1,
get_ghost_offset_top: () => parseFloat($canvas_area.css("padding-top")) + 1,
});
let mox, moy; // mouse offset
const pointermove = e => {
@ -234,7 +234,7 @@ class OnCanvasTextBox extends OnCanvasObject {
OnCanvasTextBox.$fontbox = null;
}
const $fb = OnCanvasTextBox.$fontbox = OnCanvasTextBox.$fontbox || new $FontBox();
const displace_font_box = ()=> {
const displace_font_box = () => {
// move the font box out of the way if it's overlapping the OnCanvasTextBox
const fb_rect = $fb[0].getBoundingClientRect();
const tb_rect = this.$el[0].getBoundingClientRect();
@ -257,12 +257,12 @@ class OnCanvasTextBox extends OnCanvasObject {
update();
displace_font_box();
// In case a software keyboard opens, like Optikey for eye gaze / head tracking users,
// or perhaps a handwriting input for pen tablet users, or *partially* for mobile browsers.
// Mobile browsers generally scroll the view for a textbox well enough, but
// don't include the custom behavior of moving the font box out of the way.
$(window).on("resize", this._on_window_resize = ()=> {
$(window).on("resize", this._on_window_resize = () => {
this.$editor[0].scrollIntoView({ block: 'nearest', inline: 'nearest' });
displace_font_box();
});

View File

@ -25,7 +25,7 @@ function get_hotkey(text) {
}
let localizations = {};
window.localize = (english_text, ...interpolations)=> {
window.localize = (english_text, ...interpolations) => {
function find_localization(english_text) {
const amp_index = index_of_hotkey(english_text);
if (amp_index > -1) {
@ -51,7 +51,7 @@ window.localize = (english_text, ...interpolations)=> {
}
function interpolate(text, interpolations) {
for (let i = 0; i < interpolations.length; i++) {
text = text.replace(`%${i+1}`, interpolations[i]);
text = text.replace(`%${i + 1}`, interpolations[i]);
}
return text;
}
@ -1038,7 +1038,7 @@ for (const accepted_language of accepted_languages) {
function get_language() {
return current_language;
}
function get_direction(language=current_language) {
function get_direction(language = current_language) {
return language.match(/^(ar|dv|fa|ha|he|ks|ku|ms|pa|ps|sd|ug|yi)\b/i) ? "rtl" : "ltr";
}
function load_language(language) {
@ -1059,8 +1059,8 @@ function load_language(language) {
stylesheet.setAttribute("href", href);
// hack to wait for stylesheet to load
const img = document.createElement("img");
img.onerror = ()=> {
$(()=> {
img.onerror = () => {
$(() => {
$G.triggerHandler("theme-load"); // signal layout change
});
};

File diff suppressed because it is too large Load Diff

View File

@ -34,7 +34,7 @@ try {
// eslint-disable-next-line no-empty
} catch (error) { }
if (dev_edit_colors) {
$(()=> {
$(() => {
show_edit_colors_window();
$(".define-custom-colors-button").click();
$edit_colors_window.css({
@ -58,7 +58,7 @@ function show_edit_colors_window($swatch_to_edit, color_selection_slot_to_edit)
const $palette = $swatch_to_edit.closest(".palette, .color-box");
const swatch_index = $palette.find(".swatch").toArray().indexOf($swatch_to_edit[0]);
const initial_color = selected_colors[color_selection_slot_to_edit];
choose_color(initial_color, (color)=> {
choose_color(initial_color, (color) => {
// The palette may have changed or rerendered due to switching themes,
// toggling vertical color box mode, or monochrome document mode.
$swatch_to_edit = $($palette.find(".swatch")[swatch_index]);
@ -77,15 +77,15 @@ function show_edit_colors_window($swatch_to_edit, color_selection_slot_to_edit)
const selection_monochrome_info = (selection && selection.canvas) ? detect_monochrome(selection.canvas.ctx) : main_monochrome_info;
const selection_matches_main_canvas_colors =
selection_monochrome_info.isMonochrome &&
selection_monochrome_info.presentNonTransparentRGBAs.every((rgba)=>
main_monochrome_info.presentNonTransparentRGBAs.map(rgba=> rgba.toString()).includes(rgba.toString())
selection_monochrome_info.presentNonTransparentRGBAs.every((rgba) =>
main_monochrome_info.presentNonTransparentRGBAs.map(rgba => rgba.toString()).includes(rgba.toString())
);
if (
main_monochrome_info.isMonochrome &&
selection_monochrome_info.isMonochrome &&
selection_matches_main_canvas_colors
) {
const recolor = (ctx, present_rgbas)=> {
const recolor = (ctx, present_rgbas) => {
// HTML5 Canvas API is unreliable for exact colors.
// 1. The specifications specify unpremultiplied alpha, but in practice browsers use premultiplied alpha for performance.
// 2. Some browsers implement protections against fingerprinting that return slightly random data
@ -94,10 +94,10 @@ function show_edit_colors_window($swatch_to_edit, color_selection_slot_to_edit)
// Some global system color profile might apply however, I don't know how all that works.)
if (
present_rgbas.length === 2 &&
present_rgbas.every((present_rgba)=> `${present_rgba}` !== `${old_rgba}`)
present_rgbas.every((present_rgba) => `${present_rgba}` !== `${old_rgba}`)
) {
// Find the nearer color in the image data to replace.
const distances = present_rgbas.map((rgba)=>
const distances = present_rgbas.map((rgba) =>
Math.abs(rgba[0] - old_rgba[0]) +
Math.abs(rgba[1] - old_rgba[1]) +
Math.abs(rgba[2] - old_rgba[2]) +
@ -116,7 +116,7 @@ function show_edit_colors_window($swatch_to_edit, color_selection_slot_to_edit)
undoable({
name: "Recolor",
icon: get_help_folder_icon("p_color.png"),
}, ()=> {
}, () => {
recolor(main_ctx, main_monochrome_info.presentNonTransparentRGBAs);
if (selection && selection.canvas) {
recolor(selection.canvas.ctx, selection_monochrome_info.presentNonTransparentRGBAs);
@ -140,7 +140,7 @@ function show_edit_colors_window($swatch_to_edit, color_selection_slot_to_edit)
update_$swatch($swatch_to_edit, color);
selected_colors[color_selection_slot_to_edit] = color;
$G.triggerHandler("option-changed");
window.console && console.log(`Updated palette: ${palette.map(()=> `%c█`).join("")}`, ...palette.map((color)=> `color: ${color};`));
window.console && console.log(`Updated palette: ${palette.map(() => `%c█`).join("")}`, ...palette.map((color) => `color: ${color};`));
}
});
}
@ -160,18 +160,18 @@ function choose_color(initial_color, callback) {
let custom_colors_index = 0;
const get_current_color = ()=> `hsl(${hue_degrees}deg, ${sat_percent}%, ${lum_percent}%)`;
const set_color_from_rgb = (r, g, b)=> {
const get_current_color = () => `hsl(${hue_degrees}deg, ${sat_percent}%, ${lum_percent}%)`;
const set_color_from_rgb = (r, g, b) => {
const [h, s, l] = rgb_to_hsl(r, g, b);
hue_degrees = h * 360;
sat_percent = s * 100;
lum_percent = l * 100;
};
const set_color = (color)=> {
const set_color = (color) => {
const [r, g, b] = get_rgba_from_color(color);
set_color_from_rgb(r, g, b);
};
const select = ($swatch)=> {
const select = ($swatch) => {
$w.$content.find(".swatch").removeClass("selected");
$swatch.addClass("selected");
set_color($swatch[0].dataset.color);
@ -183,8 +183,8 @@ function choose_color(initial_color, callback) {
update_inputs("hslrgb");
};
const make_color_grid = (colors, id)=> {
const $color_grid = $(`<div class="color-grid" tabindex="0">`).attr({id});
const make_color_grid = (colors, id) => {
const $color_grid = $(`<div class="color-grid" tabindex="0">`).attr({ id });
for (const color of colors) {
const $swatch = $Swatch(color);
$swatch.appendTo($color_grid).addClass("inset-deep");
@ -192,7 +192,7 @@ function choose_color(initial_color, callback) {
}
let $local_last_focus = $color_grid.find(".swatch:first-child");
const num_colors_per_row = 8;
const navigate = (relative_index)=> {
const navigate = (relative_index) => {
const $focused = $color_grid.find(".swatch:focus");
if (!$focused.length) { return; }
const $swatches = $color_grid.find(".swatch");
@ -205,7 +205,7 @@ function choose_color(initial_color, callback) {
if (!$to_focus.length) { return; }
$to_focus.focus();
};
$color_grid.on("keydown", (event)=> {
$color_grid.on("keydown", (event) => {
// console.log(event.code);
if (event.code === "ArrowRight") { navigate(+1); }
if (event.code === "ArrowLeft") { navigate(-1); }
@ -218,17 +218,17 @@ function choose_color(initial_color, callback) {
draw();
}
});
$color_grid.on("pointerdown", (event)=> {
$color_grid.on("pointerdown", (event) => {
const $swatch = $(event.target).closest(".swatch");
if ($swatch.length) {
select($swatch);
draw();
}
});
$color_grid.on("dragstart", (event)=> {
$color_grid.on("dragstart", (event) => {
event.preventDefault();
});
$color_grid.on("focusin", (event)=> {
$color_grid.on("focusin", (event) => {
if (event.target.closest(".swatch")) {
$local_last_focus = $(event.target.closest(".swatch"));
} else {
@ -241,7 +241,7 @@ function choose_color(initial_color, callback) {
// since the parent grid is previous in the tab order
$color_grid.attr("tabindex", -1);
});
$color_grid.on("focusout", (event)=> {
$color_grid.on("focusout", (event) => {
$color_grid.attr("tabindex", 0);
});
return $color_grid;
@ -271,26 +271,26 @@ function choose_color(initial_color, callback) {
}
const $define_custom_colors_button = $(`<button class="define-custom-colors-button">`)
.html(display_hotkey("&Define Custom Colors >>"))
.appendTo($left)
.on("click", (e)=> {
// prevent the form from submitting
// @TODO: instead, prevent the form's submit event in $Window.js in os-gui (or don't have a form? idk)
e.preventDefault();
.html(display_hotkey("&Define Custom Colors >>"))
.appendTo($left)
.on("click", (e) => {
// prevent the form from submitting
// @TODO: instead, prevent the form's submit event in $Window.js in os-gui (or don't have a form? idk)
e.preventDefault();
$right.show();
$w.addClass("defining-custom-colors"); // for mobile layout
$define_custom_colors_button.attr("disabled", "disabled");
// assuming small viewport implies mobile implies an onscreen keyboard,
// and that you probably don't want to use the keyboard to choose colors
if ($w.width() >= 300) {
inputs_by_component_letter.h.focus();
}
maybe_reenable_button_for_mobile_navigation();
});
$right.show();
$w.addClass("defining-custom-colors"); // for mobile layout
$define_custom_colors_button.attr("disabled", "disabled");
// assuming small viewport implies mobile implies an onscreen keyboard,
// and that you probably don't want to use the keyboard to choose colors
if ($w.width() >= 300) {
inputs_by_component_letter.h.focus();
}
maybe_reenable_button_for_mobile_navigation();
});
// for mobile layout, re-enable button because it's a navigation button in that case, rather than one-time expand action
const maybe_reenable_button_for_mobile_navigation = ()=> {
const maybe_reenable_button_for_mobile_navigation = () => {
// if ($right.is(":hidden")) {
if ($w.width() < 300 || document.body.classList.contains("eye-gaze-mode")) {
$define_custom_colors_button.removeAttr("disabled");
@ -315,43 +315,43 @@ function choose_color(initial_color, callback) {
left: 10,
top: 198,
});
let mouse_down_on_rainbow_canvas = false;
let crosshair_shown_on_rainbow_canvas = false;
const draw = ()=> {
const draw = () => {
if (!mouse_down_on_rainbow_canvas || crosshair_shown_on_rainbow_canvas) {
// rainbow
for (let y = 0; y < rainbow_canvas.height; y += 6) {
for (let x = -1; x < rainbow_canvas.width; x += 3) {
rainbow_canvas.ctx.fillStyle = `hsl(${x/rainbow_canvas.width*360}deg, ${(1-y/rainbow_canvas.height)*100}%, 50%)`;
rainbow_canvas.ctx.fillStyle = `hsl(${x / rainbow_canvas.width * 360}deg, ${(1 - y / rainbow_canvas.height) * 100}%, 50%)`;
rainbow_canvas.ctx.fillRect(x, y, 3, 6);
}
}
// crosshair
if (!mouse_down_on_rainbow_canvas) {
const x = ~~(hue_degrees/360*rainbow_canvas.width);
const y = ~~((1-sat_percent/100)*rainbow_canvas.height);
const x = ~~(hue_degrees / 360 * rainbow_canvas.width);
const y = ~~((1 - sat_percent / 100) * rainbow_canvas.height);
rainbow_canvas.ctx.fillStyle = "black";
rainbow_canvas.ctx.fillRect(x-1, y-9, 3, 5);
rainbow_canvas.ctx.fillRect(x-1, y+5, 3, 5);
rainbow_canvas.ctx.fillRect(x-9, y-1, 5, 3);
rainbow_canvas.ctx.fillRect(x+5, y-1, 5, 3);
rainbow_canvas.ctx.fillRect(x - 1, y - 9, 3, 5);
rainbow_canvas.ctx.fillRect(x - 1, y + 5, 3, 5);
rainbow_canvas.ctx.fillRect(x - 9, y - 1, 5, 3);
rainbow_canvas.ctx.fillRect(x + 5, y - 1, 5, 3);
}
crosshair_shown_on_rainbow_canvas = !mouse_down_on_rainbow_canvas;
}
for (let y = -2; y < luminosity_canvas.height; y += 6) {
luminosity_canvas.ctx.fillStyle = `hsl(${hue_degrees}deg, ${sat_percent}%, ${(1-y/luminosity_canvas.height)*100}%)`;
luminosity_canvas.ctx.fillStyle = `hsl(${hue_degrees}deg, ${sat_percent}%, ${(1 - y / luminosity_canvas.height) * 100}%)`;
luminosity_canvas.ctx.fillRect(0, y, luminosity_canvas.width, 6);
}
lum_arrow_canvas.ctx.fillStyle = getComputedStyle($w.$content[0]).getPropertyValue("--ButtonText");
for (let x = 0; x < lum_arrow_canvas.width; x++) {
lum_arrow_canvas.ctx.fillRect(x, lum_arrow_canvas.width-x-1, 1, 1+x*2);
lum_arrow_canvas.ctx.fillRect(x, lum_arrow_canvas.width - x - 1, 1, 1 + x * 2);
}
lum_arrow_canvas.style.position = "absolute";
lum_arrow_canvas.style.left = "215px";
lum_arrow_canvas.style.top = `${3 + ~~((1-lum_percent/100)*luminosity_canvas.height)}px`;
lum_arrow_canvas.style.top = `${3 + ~~((1 - lum_percent / 100) * luminosity_canvas.height)}px`;
result_canvas.ctx.fillStyle = get_current_color();
result_canvas.ctx.fillRect(0, 0, result_canvas.width, result_canvas.height);
@ -361,52 +361,52 @@ function choose_color(initial_color, callback) {
$(luminosity_canvas).addClass("luminosity-canvas inset-shallow");
$(result_canvas).addClass("result-color-canvas inset-shallow").attr("id", "color-solid-canvas");
const select_hue_sat = (event)=> {
hue_degrees = Math.min(1, Math.max(0, event.offsetX/rainbow_canvas.width))*360;
sat_percent = Math.min(1, Math.max(0, (1 - event.offsetY/rainbow_canvas.height)))*100;
const select_hue_sat = (event) => {
hue_degrees = Math.min(1, Math.max(0, event.offsetX / rainbow_canvas.width)) * 360;
sat_percent = Math.min(1, Math.max(0, (1 - event.offsetY / rainbow_canvas.height))) * 100;
update_inputs("hsrgb");
draw();
event.preventDefault();
};
$(rainbow_canvas).on("pointerdown", (event)=> {
$(rainbow_canvas).on("pointerdown", (event) => {
mouse_down_on_rainbow_canvas = true;
select_hue_sat(event);
$(rainbow_canvas).on("pointermove", select_hue_sat);
if (event.pointerId !== 1234567890) { // for Eye Gaze Mode simulated clicks
rainbow_canvas.setPointerCapture(event.pointerId);
}
});
$G.on("pointerup pointercancel", (event)=> {
$G.on("pointerup pointercancel", (event) => {
$(rainbow_canvas).off("pointermove", select_hue_sat);
// rainbow_canvas.releasePointerCapture(event.pointerId);
mouse_down_on_rainbow_canvas = false;
draw();
});
const select_lum = (event)=> {
lum_percent = Math.min(1, Math.max(0, (1 - event.offsetY/luminosity_canvas.height)))*100;
const select_lum = (event) => {
lum_percent = Math.min(1, Math.max(0, (1 - event.offsetY / luminosity_canvas.height))) * 100;
update_inputs("lrgb");
draw();
event.preventDefault();
};
$(luminosity_canvas).on("pointerdown", (event)=> {
$(luminosity_canvas).on("pointerdown", (event) => {
select_lum(event);
$(luminosity_canvas).on("pointermove", select_lum);
if (event.pointerId !== 1234567890) { // for Eye Gaze Mode simulated clicks
luminosity_canvas.setPointerCapture(event.pointerId);
}
});
$G.on("pointerup pointercancel", (event)=> {
$G.on("pointerup pointercancel", (event) => {
$(luminosity_canvas).off("pointermove", select_lum);
// luminosity_canvas.releasePointerCapture(event.pointerId);
});
const inputs_by_component_letter = {};
["hsl", "rgb"].forEach((color_model, color_model_index)=> {
[...color_model].forEach((component_letter, component_index)=> {
["hsl", "rgb"].forEach((color_model, color_model_index) => {
[...color_model].forEach((component_letter, component_index) => {
const text_with_hotkey = {
h: "Hu&e:",
s: "&Sat:",
@ -456,7 +456,7 @@ function choose_color(initial_color, callback) {
});
// listening for input events on input elements using event delegation (looks a little weird)
$right.on("input", "input", (event)=> {
$right.on("input", "input", (event) => {
const input = event.target;
const component_letter = input.dataset.componentLetter;
if (component_letter) {
@ -489,7 +489,7 @@ function choose_color(initial_color, callback) {
update_inputs("rgb");
} else {
let [r, g, b] = get_rgba_from_color(get_current_color());
const rgb = {r, g, b};
const rgb = { r, g, b };
rgb[component_letter] = n;
set_color_from_rgb(rgb.r, rgb.g, rgb.b);
update_inputs("hsl");
@ -501,7 +501,7 @@ function choose_color(initial_color, callback) {
}
}
});
$right.on("focusout", "input", (event)=> {
$right.on("focusout", "input", (event) => {
const input = event.target;
const component_letter = input.dataset.componentLetter;
if (component_letter) {
@ -513,7 +513,7 @@ function choose_color(initial_color, callback) {
}
});
$w.on("keydown", (event)=> {
$w.on("keydown", (event) => {
if (event.altKey && !event.ctrlKey && !event.metaKey && !event.shiftKey) {
switch (event.key) {
case "o":
@ -546,7 +546,7 @@ function choose_color(initial_color, callback) {
inputs_by_component_letter.b.focus();
break;
case "a":
if ($add_to_custom_colors_button.is(":visible")) {
if ($add_to_custom_colors_button.is(":visible")) {
$add_to_custom_colors_button.click();
}
break;
@ -563,7 +563,7 @@ function choose_color(initial_color, callback) {
event.stopPropagation();
});
const update_inputs = (components)=> {
const update_inputs = (components) => {
for (const component_letter of components) {
const input = inputs_by_component_letter[component_letter];
const [r, g, b] = get_rgba_from_color(get_current_color());
@ -581,21 +581,21 @@ function choose_color(initial_color, callback) {
$right.append(rainbow_canvas, luminosity_canvas, result_canvas, $color_solid_label, lum_arrow_canvas);
const $add_to_custom_colors_button = $(`<button class="add-to-custom-colors-button">`)
.html(display_hotkey("&Add To Custom Colors"))
.appendTo($right)
.on("click", (event)=> {
// prevent the form from submitting
// @TODO: instead, prevent the form's submit event in $Window.js in os-gui (or don't have a form? idk)
event.preventDefault();
.html(display_hotkey("&Add To Custom Colors"))
.appendTo($right)
.on("click", (event) => {
// prevent the form from submitting
// @TODO: instead, prevent the form's submit event in $Window.js in os-gui (or don't have a form? idk)
event.preventDefault();
const color = get_current_color();
custom_colors[custom_colors_index] = color;
// console.log(custom_colors_swatches_reordered, custom_colors_index, custom_colors_swatches_reordered[custom_colors_index]));
update_$swatch($(custom_colors_swatches_list_order[custom_colors_index]), color);
custom_colors_index = (custom_colors_index + 1) % custom_colors.length;
const color = get_current_color();
custom_colors[custom_colors_index] = color;
// console.log(custom_colors_swatches_reordered, custom_colors_index, custom_colors_swatches_reordered[custom_colors_index]));
update_$swatch($(custom_colors_swatches_list_order[custom_colors_index]), color);
custom_colors_index = (custom_colors_index + 1) % custom_colors.length;
$w.removeClass("defining-custom-colors"); // for mobile layout
});
$w.removeClass("defining-custom-colors"); // for mobile layout
});
$w.$Button(localize("OK"), () => {
callback(get_current_color());
@ -619,7 +619,7 @@ function choose_color(initial_color, callback) {
custom_colors_index = Math.max(0, custom_colors_swatches_list_order.indexOf(
$custom_colors_grid.find(".swatch.selected")[0]
));
set_color(initial_color);
update_inputs("hslrgb");

View File

@ -51,7 +51,7 @@ async function write_blob_to_file_path(filePath, blob) {
window.systemHooks = window.systemHooks || {};
window.systemHooks.showSaveFileDialog = async ({ formats, defaultFileName, defaultPath, defaultFileFormatID, getBlob, savedCallbackUnreliable }) => {
// First filter in filters list determines default selected file type.
// @TODO: default to existing extension, except it would be awkward to rearrange the list...
// const suggestedExtension = get_file_extension(defaultFileName);

View File

@ -104,13 +104,13 @@ const createWindow = () => {
// Similarly, if there's an error, the app will be harder to close (perhaps worse as it's less likely to show a Not Responding dialog).
// And this also prevents it from closing with Ctrl+C in the terminal, which is arguably a feature.
mainWindow.webContents.send('close-window-prompt');
event.preventDefault();
event.preventDefault();
});
// Open links without target=_blank externally.
mainWindow.webContents.on('will-navigate', (e, url) => {
// check that the URL is not part of the app
if(!url.includes("file://")){
if (!url.includes("file://")) {
e.preventDefault();
shell.openExternal(url);
}
@ -118,7 +118,7 @@ const createWindow = () => {
// Open links with target=_blank externally.
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
// check that the URL is not part of the app
if(!url.includes("file://")){
if (!url.includes("file://")) {
shell.openExternal(url);
}
return { action: "deny" };

View File

@ -18,8 +18,8 @@ if (/MSIE \d|Trident.*rv:/.test(navigator.userAgent)) {
document.write(
'<style>body { text-align: center; }</style>' +
'<div className="not-supported">' +
'<h1 className="not-supported-header">Internet Explorer is not supported!</h1>' +
'<p className="not-supported-details">Try Chrome, Firefox, or Edge.</p>' +
'<h1 className="not-supported-header">Internet Explorer is not supported!</h1>' +
'<p className="not-supported-details">Try Chrome, Firefox, or Edge.</p>' +
'</div>'
);
}

View File

@ -11,31 +11,31 @@ extra_tools = [{
// @XXX: copy pasted all this brush caching/rendering code!
// @TODO: DRY!
const csz = get_brush_canvas_size(brush_size, brush_shape);
if(
if (
this.rendered_shape !== brush_shape ||
this.rendered_color !== stroke_color ||
this.rendered_size !== brush_size
){
) {
brush_canvas.width = csz;
brush_canvas.height = csz;
// don't need to do brush_ctx.disable_image_smoothing() currently because images aren't drawn to the brush
brush_ctx.fillStyle = brush_ctx.strokeStyle = stroke_color;
render_brush(brush_ctx, brush_shape, brush_size);
this.rendered_color = stroke_color;
this.rendered_size = brush_size;
this.rendered_shape = brush_shape;
}
const draw_brush = (x, y) => {
ctx.drawImage(brush_canvas, Math.ceil(x-csz/2), Math.ceil(y-csz/2));
ctx.drawImage(brush_canvas, Math.ceil(x - csz / 2), Math.ceil(y - csz / 2));
};
const r = airbrush_size * 2;
for(let i = 0; i < 6 + r/5; i++){
const rx = (Math.random()*2-1) * r;
const ry = (Math.random()*2-1) * r;
const d = rx*rx + ry*ry;
if(d <= r * r){
for (let i = 0; i < 6 + r / 5; i++) {
const rx = (Math.random() * 2 - 1) * r;
const ry = (Math.random() * 2 - 1) * r;
const d = rx * rx + ry * ry;
if (d <= r * r) {
draw_brush(x + ~~rx, y + ~~ry);
}
}
@ -67,26 +67,26 @@ extra_tools = [{
// @XXX: copy pasted all this brush caching/rendering code!
// @TODO: DRY!
const csz = get_brush_canvas_size(brush_size, brush_shape);
if(
if (
this.rendered_shape !== brush_shape ||
this.rendered_color !== stroke_color ||
this.rendered_size !== brush_size
){
) {
brush_canvas.width = csz;
brush_canvas.height = csz;
// don't need to do brush_ctx.disable_image_smoothing() currently because images aren't drawn to the brush
brush_ctx.fillStyle = brush_ctx.strokeStyle = stroke_color;
render_brush(brush_ctx, brush_shape, brush_size);
this.rendered_color = stroke_color;
this.rendered_size = brush_size;
this.rendered_shape = brush_shape;
}
const draw_brush = (x, y) => {
ctx.drawImage(brush_canvas, Math.ceil(x-csz/2), Math.ceil(y-csz/2));
ctx.drawImage(brush_canvas, Math.ceil(x - csz / 2), Math.ceil(y - csz / 2));
};
for(let i = 0; i < 60; i++){
for (let i = 0; i < 60; i++) {
const x_diff = x - this.position.x;
const y_diff = y - this.position.y;
const dist = Math.hypot(x_diff, y_diff);
@ -107,7 +107,7 @@ extra_tools = [{
cursor: ["airbrush", [7, 22], "crosshair"],
continuous: "time",
paint(ctx, x, y) {
},
$options: $choose_airbrush_size
}];

File diff suppressed because it is too large Load Diff

View File

@ -10,7 +10,7 @@ function show_help() {
root: "help",
contentsFile: "help/mspaint.hhc",
}).$help_window;
$help_window.on("close", ()=> {
$help_window.on("close", () => {
$help_window = null;
});
}
@ -18,7 +18,7 @@ function show_help() {
// shared code with 98.js.org
// (copy-pasted / manually synced for now)
function open_help_viewer(options){
function open_help_viewer(options) {
const $help_window = $Window({
title: options.title || "Help Topics",
icons: {
@ -34,19 +34,19 @@ function open_help_viewer(options){
const $main = $(E("div")).addClass("main");
const $toolbar = $(E("div")).addClass("toolbar");
const add_toolbar_button = (name, sprite_n, action_fn, enabled_fn)=> {
const add_toolbar_button = (name, sprite_n, action_fn, enabled_fn) => {
const $button = $("<button class='lightweight'>")
.append($("<span>").text(name))
.appendTo($toolbar)
.on("click", ()=> {
action_fn();
});
.append($("<span>").text(name))
.appendTo($toolbar)
.on("click", () => {
action_fn();
});
$("<div class='icon'/>")
.appendTo($button)
.css({
backgroundPosition: `${-sprite_n * 55}px 0px`,
});
const update_enabled = ()=> {
.appendTo($button)
.css({
backgroundPosition: `${-sprite_n * 55}px 0px`,
});
const update_enabled = () => {
$button[0].disabled = enabled_fn && !enabled_fn();
};
update_enabled();
@ -54,12 +54,12 @@ function open_help_viewer(options){
$help_window.on("update-buttons", update_enabled);
return $button;
};
const measure_sidebar_width = ()=>
const measure_sidebar_width = () =>
$contents.outerWidth() +
parseFloat(getComputedStyle($contents[0]).getPropertyValue("margin-left")) +
parseFloat(getComputedStyle($contents[0]).getPropertyValue("margin-right")) +
$resizer.outerWidth();
const $hide_button = add_toolbar_button("Hide", 0, ()=> {
const $hide_button = add_toolbar_button("Hide", 0, () => {
const toggling_width = measure_sidebar_width();
$contents.hide();
$resizer.hide();
@ -69,7 +69,7 @@ function open_help_viewer(options){
$help_window.css("left", $help_window.offset().left + toggling_width);
$help_window.bringTitleBarInBounds();
});
const $show_button = add_toolbar_button("Show", 5, ()=> {
const $show_button = add_toolbar_button("Show", 5, () => {
$contents.show();
$resizer.show();
$show_button.hide();
@ -86,31 +86,31 @@ function open_help_viewer(options){
}
$help_window.css("max-width", "");
}).hide();
add_toolbar_button("Back", 1, ()=> {
add_toolbar_button("Back", 1, () => {
$iframe[0].contentWindow.history.back();
ignore_one_load = true;
back_length -= 1;
forward_length += 1;
}, ()=> back_length > 0);
add_toolbar_button("Forward", 2, ()=> {
}, () => back_length > 0);
add_toolbar_button("Forward", 2, () => {
$iframe[0].contentWindow.history.forward();
ignore_one_load = true;
forward_length -= 1;
back_length += 1;
}, ()=> forward_length > 0);
add_toolbar_button("Options", 3, ()=> {}, ()=> false); // @TODO: hotkey and underline on O
add_toolbar_button("Web Help", 4, ()=> {
}, () => forward_length > 0);
add_toolbar_button("Options", 3, () => { }, () => false); // @TODO: hotkey and underline on O
add_toolbar_button("Web Help", 4, () => {
iframe.src = "help/online_support.htm";
});
const $iframe = $Iframe({src: "help/default.html"}).addClass("inset-deep");
const $iframe = $Iframe({ src: "help/default.html" }).addClass("inset-deep");
const iframe = $iframe[0];
iframe.$window = $help_window; // for focus handling integration
const $resizer = $(E("div")).addClass("resizer");
const $contents = $(E("ul")).addClass("contents inset-deep");
// @TODO: fix race conditions
$iframe.on("load", ()=> {
$iframe.on("load", () => {
if (!ignore_one_load) {
back_length += 1;
forward_length = 0;
@ -123,9 +123,9 @@ function open_help_viewer(options){
$main.append($contents, $resizer, $iframe);
$help_window.$content.append($toolbar, $main);
$help_window.css({width: 800, height: 600});
$help_window.css({ width: 800, height: 600 });
$iframe.attr({name: "help-frame"});
$iframe.attr({ name: "help-frame" });
$iframe.css({
backgroundColor: "white",
border: "",
@ -152,13 +152,13 @@ function open_help_viewer(options){
bottom: 0,
zIndex: 1,
});
$resizer.on("pointerdown", (e)=> {
$resizer.on("pointerdown", (e) => {
let pointermove, pointerup;
const getPos = (e)=>
const getPos = (e) =>
Math.min($help_window.width() - 100, Math.max(20,
e.clientX - $help_window.$content.offset().left
));
$G.on("pointermove", pointermove = (e)=> {
$G.on("pointermove", pointermove = (e) => {
$resizer.css({
position: "absolute",
left: getPos(e)
@ -167,7 +167,7 @@ function open_help_viewer(options){
marginRight: resizer_width,
});
});
$G.on("pointerup", pointerup = (e)=> {
$G.on("pointerup", pointerup = (e) => {
$G.off("pointermove", pointermove);
$G.off("pointerup", pointerup);
$resizer.css({
@ -180,7 +180,7 @@ function open_help_viewer(options){
});
});
});
const parse_object_params = $object => {
// parse an $(<object>) to a plain object of key value pairs
const object = {};
@ -189,9 +189,9 @@ function open_help_viewer(options){
}
return object;
};
let $last_expanded;
const $Item = text => {
const $item = $(E("div")).addClass("item").text(text.trim());
$item.on("mousedown", () => {
@ -200,8 +200,8 @@ function open_help_viewer(options){
});
$item.on("click", () => {
const $li = $item.parent();
if($li.is(".folder")){
if($last_expanded){
if ($li.is(".folder")) {
if ($last_expanded) {
$last_expanded.not($li).removeClass("expanded");
}
$li.toggleClass("expanded");
@ -210,31 +210,31 @@ function open_help_viewer(options){
});
return $item;
};
const $default_item_li = $(E("li")).addClass("page");
$default_item_li.append($Item("Welcome to Help").on("click", ()=> {
$iframe.attr({src: "help/default.html"});
$default_item_li.append($Item("Welcome to Help").on("click", () => {
$iframe.attr({ src: "help/default.html" });
}));
$contents.append($default_item_li);
function renderItem(source_li, $folder_items_ul) {
const object = parse_object_params($(source_li).children("object"));
if ($(source_li).find("li").length > 0){
if ($(source_li).find("li").length > 0) {
const $folder_li = $(E("li")).addClass("folder");
$folder_li.append($Item(object.Name));
$contents.append($folder_li);
const $folder_items_ul = $(E("ul"));
$folder_li.append($folder_items_ul);
$(source_li).children("ul").children().get().forEach((li)=> {
$(source_li).children("ul").children().get().forEach((li) => {
renderItem(li, $folder_items_ul);
});
} else {
const $item_li = $(E("li")).addClass("page");
$item_li.append($Item(object.Name).on("click", ()=> {
$iframe.attr({src: `${options.root}/${object.Local}`});
$item_li.append($Item(object.Name).on("click", () => {
$iframe.attr({ src: `${options.root}/${object.Local}` });
}));
if ($folder_items_ul) {
$folder_items_ul.append($item_li);
@ -244,15 +244,15 @@ function open_help_viewer(options){
}
}
fetch(options.contentsFile).then((response)=> {
response.text().then((hhc)=> {
$($.parseHTML(hhc)).filter("ul").children().get().forEach((li)=> {
fetch(options.contentsFile).then((response) => {
response.text().then((hhc) => {
$($.parseHTML(hhc)).filter("ul").children().get().forEach((li) => {
renderItem(li, null);
});
}, (error)=> {
}, (error) => {
show_error_message(`${localize("Failed to launch help.")} Failed to read ${options.contentsFile}.`, error);
});
}, (/* error */)=> {
}, (/* error */) => {
// access to error message is not allowed either, basically
if (location.protocol === "file:") {
showMessageBox({
@ -272,7 +272,7 @@ function open_help_viewer(options){
show_error_message(`${localize("Failed to launch help.")} ${localize("Access to %1 was denied.", options.contentsFile)}`);
}
});
// @TODO: keyboard accessability
// $help_window.on("keydown", (e)=> {
// switch(e.keyCode){
@ -290,64 +290,64 @@ function open_help_viewer(options){
var programs_being_loaded = 0;
function $Iframe(options){
function $Iframe(options) {
var $iframe = $("<iframe allowfullscreen sandbox='allow-same-origin allow-scripts allow-forms allow-pointer-lock allow-modals allow-popups allow-downloads'>");
var iframe = $iframe[0];
var disable_delegate_pointerup = false;
$iframe.focus_contents = function(){
$iframe.focus_contents = function () {
if (!iframe.contentWindow) {
return;
}
if (iframe.contentDocument.hasFocus()) {
return;
}
disable_delegate_pointerup = true;
iframe.contentWindow.focus();
setTimeout(function(){
setTimeout(function () {
iframe.contentWindow.focus();
disable_delegate_pointerup = false;
});
};
// Let the iframe to handle mouseup events outside itself
var delegate_pointerup = function(){
var delegate_pointerup = function () {
if (disable_delegate_pointerup) {
return;
}
// This try-catch may only be needed for running Cypress tests.
try {
if(iframe.contentWindow && iframe.contentWindow.jQuery){
if (iframe.contentWindow && iframe.contentWindow.jQuery) {
iframe.contentWindow.jQuery("body").trigger("pointerup");
}
if(iframe.contentWindow){
const event = new iframe.contentWindow.MouseEvent("mouseup", {button: 0});
if (iframe.contentWindow) {
const event = new iframe.contentWindow.MouseEvent("mouseup", { button: 0 });
iframe.contentWindow.dispatchEvent(event);
const event2 = new iframe.contentWindow.MouseEvent("mouseup", {button: 2});
const event2 = new iframe.contentWindow.MouseEvent("mouseup", { button: 2 });
iframe.contentWindow.dispatchEvent(event2);
}
} catch(error) {
} catch (error) {
console.log("Failed to access iframe to delegate pointerup; got", error);
}
};
$G.on("mouseup blur", delegate_pointerup);
$iframe.destroy = ()=> {
$iframe.destroy = () => {
$G.off("mouseup blur", delegate_pointerup);
};
// @TODO: delegate pointermove events too?
$("body").addClass("loading-program");
programs_being_loaded += 1;
$iframe.on("load", function(){
if(--programs_being_loaded <= 0){
$iframe.on("load", function () {
if (--programs_being_loaded <= 0) {
$("body").removeClass("loading-program");
}
// This try-catch may only be needed for running Cypress tests.
try {
if (window.themeCSSProperties) {
@ -374,40 +374,40 @@ function $Iframe(options){
}
var $contentWindow = $(iframe.contentWindow);
$contentWindow.on("pointerdown click", function(e){
$contentWindow.on("pointerdown click", function (e) {
iframe.$window && iframe.$window.focus();
// from close_menus in $MenuBar
$(".menu-button").trigger("release");
// Close any rogue floating submenus
$(".menu-popup").hide();
});
// We want to disable pointer events for other iframes, but not this one
$contentWindow.on("pointerdown", function(e){
$contentWindow.on("pointerdown", function (e) {
$iframe.css("pointer-events", "all");
$("body").addClass("dragging");
});
$contentWindow.on("pointerup", function(e){
$contentWindow.on("pointerup", function (e) {
$("body").removeClass("dragging");
$iframe.css("pointer-events", "");
});
// $("iframe").css("pointer-events", ""); is called elsewhere.
// Otherwise iframes would get stuck in this interaction mode
iframe.contentWindow.close = function(){
iframe.contentWindow.close = function () {
iframe.$window && iframe.$window.close();
};
// @TODO: hook into saveAs (a la FileSaver.js) and another function for opening files
// iframe.contentWindow.saveAs = function(){
// saveAsDialog();
// };
} catch(error) {
} catch (error) {
console.log("Failed to reach into iframe; got", error);
}
});
if (options.src) {
$iframe.attr({src: options.src});
$iframe.attr({ src: options.src });
}
$iframe.css({
minWidth: 0,
@ -420,9 +420,9 @@ function $Iframe(options){
}
// function $IframeWindow(options){
// var $win = new $Window(options);
// var $iframe = $win.$iframe = $Iframe({src: options.src});
// $win.$content.append($iframe);
// var iframe = $win.iframe = $iframe[0];
@ -441,7 +441,7 @@ function $Iframe(options){
// $win.focus();
// // $iframe.focus_contents();
// });
// $win.setInnerDimensions = ({width, height})=> {
// const width_from_frame = $win.width() - $win.$content.width();
// const height_from_frame = $win.height() - $win.$content.height();
@ -458,25 +458,25 @@ function $Iframe(options){
// display: "flex",
// flexDirection: "column",
// });
// // @TODO: cascade windows
// $win.center();
// $win.hide();
// return $win;
// }
// Fix dragging things (i.e. windows) over iframes (i.e. other windows)
// (when combined with a bit of css, .dragging iframe { pointer-events: none; })
// (and a similar thing in $IframeWindow)
$(window).on("pointerdown", function(e){
$(window).on("pointerdown", function (e) {
//console.log(e.type);
$("body").addClass("dragging");
});
$(window).on("pointerup dragend blur", function(e){
$(window).on("pointerup dragend blur", function (e) {
//console.log(e.type);
if(e.type === "blur"){
if(document.activeElement.tagName.match(/iframe/i)){
if (e.type === "blur") {
if (document.activeElement.tagName.match(/iframe/i)) {
return;
}
}

View File

@ -1,35 +1,35 @@
const TAU = //////|//////
///// | /////
/// tau ///
/// ...--> | <--... ///
/// -' one | turn '- ///
// .' | '. //
// / | \ //
// | | <-.. | //
// | .->| \ | //
// | / | | | //
- - - - - - Math.PI + Math.PI - - - - - 0;
///// | /////
/// tau ///
/// ...--> | <--... ///
/// -' one | turn '- ///
// .' | '. //
// / | \ //
// | | <-.. | //
// | .->| \ | //
// | / | | | //
- - - - - - Math.PI + Math.PI - - - - - 0;
// | \ | | | //
// | '->| / | //
// | | <-'' | //
// \ | / //
// '. | .' //
/// -. | .- ///
/// '''----|----''' ///
/// | ///
////// | /////
//////|////// C/r;
// \ | / //
// '. | .' //
/// -. | .- ///
/// '''----|----''' ///
/// | ///
////// | /////
//////|////// C/r;
const is_pride_month = new Date().getMonth() === 5; // June (0-based, 0 is January)
const $G = $(window);
function make_css_cursor(name, coords, fallback){
function make_css_cursor(name, coords, fallback) {
return `url(images/cursors/${name}.png) ${coords.join(" ")}, ${fallback}`;
}
function E(t){
function E(t) {
return document.createElement(t);
}
@ -39,11 +39,11 @@ N milliseconds. If `immediate` is passed, trigger the function on the
leading edge, instead of the trailing. */
function debounce(func, wait_ms, immediate) {
let timeout;
const debounced_func = function() {
const debounced_func = function () {
const context = this;
const args = arguments;
const later = ()=> {
const later = () => {
timeout = null;
if (!immediate) {
func.apply(context, args);
@ -66,17 +66,17 @@ function debounce(func, wait_ms, immediate) {
return debounced_func;
}
function memoize_synchronous_function(func, max_entries=50000) {
function memoize_synchronous_function(func, max_entries = 50000) {
const cache = {};
const keys = [];
const memoized_func = (...args)=> {
if (args.some((arg)=> arg instanceof CanvasPattern)) {
const memoized_func = (...args) => {
if (args.some((arg) => arg instanceof CanvasPattern)) {
return func.apply(null, args);
}
const key = JSON.stringify(args);
if (cache[key]){
if (cache[key]) {
return cache[key];
} else{
} else {
const val = func.apply(null, args);
cache[key] = val;
keys.push(key);
@ -84,10 +84,10 @@ function memoize_synchronous_function(func, max_entries=50000) {
const oldest_key = keys.shift();
delete cache[oldest_key];
}
return val;
return val;
}
}
memoized_func.clear_memo_cache = ()=> {
memoized_func.clear_memo_cache = () => {
for (const key of keys) {
delete cache[key];
}
@ -96,14 +96,14 @@ function memoize_synchronous_function(func, max_entries=50000) {
return memoized_func;
}
window.get_rgba_from_color = memoize_synchronous_function((color)=> {
window.get_rgba_from_color = memoize_synchronous_function((color) => {
const single_pixel_canvas = make_canvas(1, 1);
single_pixel_canvas.ctx.fillStyle = color;
single_pixel_canvas.ctx.fillRect(0, 0, 1, 1);
const image_data = single_pixel_canvas.ctx.getImageData(0, 0, 1, 1);
// We could just return image_data.data, but let's return an Array instead
// I'm not totally sure image_data.data wouldn't keep the ImageData object around in memory
return Array.from(image_data.data);
@ -130,15 +130,15 @@ function image_data_match(a, b, threshold) {
return true;
}
function make_canvas(width, height){
function make_canvas(width, height) {
const image = width;
const new_canvas = E("canvas");
const new_ctx = new_canvas.getContext("2d");
new_canvas.ctx = new_ctx;
new_ctx.disable_image_smoothing = ()=> {
new_ctx.disable_image_smoothing = () => {
new_ctx.imageSmoothingEnabled = false;
// condition is to avoid a deprecation warning in Firefox
if (new_ctx.imageSmoothingEnabled !== false) {
@ -147,7 +147,7 @@ function make_canvas(width, height){
new_ctx.msImageSmoothingEnabled = false;
}
};
new_ctx.enable_image_smoothing = ()=> {
new_ctx.enable_image_smoothing = () => {
new_ctx.imageSmoothingEnabled = true;
if (new_ctx.imageSmoothingEnabled !== true) {
new_ctx.mozImageSmoothingEnabled = true;
@ -155,36 +155,36 @@ function make_canvas(width, height){
new_ctx.msImageSmoothingEnabled = true;
}
};
// @TODO: simplify the abstraction by defining setters for width/height
// that reset the image smoothing to disabled
// and make image smoothing a parameter to make_canvas
new_ctx.copy = image => {
new_canvas.width = image.naturalWidth || image.width;
new_canvas.height = image.naturalHeight || image.height;
// setting width/height resets image smoothing (along with everything)
new_ctx.disable_image_smoothing();
if (image instanceof ImageData) {
new_ctx.putImageData(image, 0, 0);
} else {
new_ctx.drawImage(image, 0, 0);
}
};
if(width && height){
if (width && height) {
// make_canvas(width, height)
new_canvas.width = width;
new_canvas.height = height;
// setting width/height resets image smoothing (along with everything)
new_ctx.disable_image_smoothing();
}else if(image){
} else if (image) {
// make_canvas(image)
new_ctx.copy(image);
}
return new_canvas;
}
@ -200,11 +200,11 @@ function get_icon_for_tool(tool) {
// not to be confused with load_image_from_uri
function load_image_simple(src) {
return new Promise((resolve, reject)=> {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = ()=> { resolve(img); };
img.onerror = ()=> { reject(new Error(`failed to load image from ${src}`)); };
img.onload = () => { resolve(img); };
img.onerror = () => { reject(new Error(`failed to load image from ${src}`)); };
img.src = src;
});
@ -216,16 +216,16 @@ function get_icon_for_tools(tools) {
}
const icon_canvas = make_canvas(16, 16);
Promise.all(tools.map((tool)=> load_image_simple(`help/${tool.help_icon}`)))
.then((icons)=> {
icons.forEach((icon, i)=> {
const w = icon_canvas.width / icons.length;
const x = i * w;
const h = icon_canvas.height;
const y = 0;
icon_canvas.ctx.drawImage(icon, x, y, w, h, x, y, w, h);
});
})
Promise.all(tools.map((tool) => load_image_simple(`help/${tool.help_icon}`)))
.then((icons) => {
icons.forEach((icon, i) => {
const w = icon_canvas.width / icons.length;
const x = i * w;
const h = icon_canvas.height;
const y = 0;
icon_canvas.ctx.drawImage(icon, x, y, w, h, x, y, w, h);
});
})
return icon_canvas;
}

File diff suppressed because it is too large Load Diff

View File

@ -1,15 +1,15 @@
let $imgur_window;
function show_imgur_uploader(blob){
if($imgur_window){
function show_imgur_uploader(blob) {
if ($imgur_window) {
$imgur_window.close();
}
$imgur_window = $DialogWindow().title("Upload To Imgur").addClass("horizontal-buttons");
const $preview_image_area = $(E("div")).appendTo($imgur_window.$main).addClass("inset-deep");
const $imgur_url_area = $(E("div")).appendTo($imgur_window.$main);
const $imgur_status = $(E("div")).appendTo($imgur_window.$main);
// @TODO: maybe make this preview small but zoomable to full size?
// (starting small (max-width: 100%) and toggling to either scrollable or fullscreen)
// it should be clear that it's not going to upload a downsized version of your image
@ -17,7 +17,7 @@ function show_imgur_uploader(blob){
display: "block", // prevent margin below due to inline display (vertical-align can also be used)
});
const blob_url = URL.createObjectURL(blob);
$preview_image.attr({src: blob_url});
$preview_image.attr({ src: blob_url });
// $preview_image.css({maxWidth: "100%", maxHeight: "400px"});
$preview_image_area.css({
maxWidth: "90vw",
@ -26,7 +26,7 @@ function show_imgur_uploader(blob){
marginBottom: "0.5em",
});
$preview_image.on("load", () => {
$imgur_window.css({width: "auto"});
$imgur_window.css({ width: "auto" });
$imgur_window.center();
});
$imgur_window.on("close", () => {
@ -53,7 +53,7 @@ function show_imgur_uploader(blob){
const parseImgurResponseJSON = responseJSON => {
try {
return JSON.parse(responseJSON);
} catch(error) {
} catch (error) {
$imgur_status.text("Received an invalid JSON response from Imgur: ");
// .append($(E("pre")).text(responseJSON));
@ -63,26 +63,26 @@ function show_imgur_uploader(blob){
// @TODO: DRY, including with show_error_message
$(E("pre"))
.appendTo($imgur_status)
.text(responseJSON)
.css({
background: "white",
color: "#333",
fontFamily: "monospace",
width: "500px",
overflow: "auto",
});
.appendTo($imgur_status)
.text(responseJSON)
.css({
background: "white",
color: "#333",
fontFamily: "monospace",
width: "500px",
overflow: "auto",
});
$(E("pre"))
.appendTo($imgur_status)
.text(error.toString())
.css({
background: "white",
color: "#333",
fontFamily: "monospace",
width: "500px",
overflow: "auto",
});
$imgur_window.css({width: "auto"});
.appendTo($imgur_status)
.text(error.toString())
.css({
background: "white",
color: "#333",
fontFamily: "monospace",
width: "500px",
overflow: "auto",
});
$imgur_window.css({ width: "auto" });
$imgur_window.center();
}
};
@ -91,9 +91,9 @@ function show_imgur_uploader(blob){
const req = new XMLHttpRequest();
if(req.upload){
if (req.upload) {
req.upload.addEventListener('progress', event => {
if(event.lengthComputable){
if (event.lengthComputable) {
const progress_value = event.loaded / event.total;
const percentage_text = `${Math.floor(progress_value * 100)}%`;
$progress.val(progress_value);
@ -102,14 +102,14 @@ function show_imgur_uploader(blob){
}, false);
}
req.addEventListener("readystatechange", () => {
if(req.readyState == 4 && req.status == 200){
req.addEventListener("readystatechange", () => {
if (req.readyState == 4 && req.status == 200) {
$progress.add($progress_percent).remove();
const response = parseImgurResponseJSON(req.responseText);
if(!response) return;
if (!response) return;
if(!response.success){
if (!response.success) {
$imgur_status.text("Failed to upload image :(");
return;
}
@ -117,7 +117,7 @@ function show_imgur_uploader(blob){
$imgur_status.text("");
const $imgur_url = $(E("a")).attr({id: "imgur-url", target: "_blank"});
const $imgur_url = $(E("a")).attr({ id: "imgur-url", target: "_blank" });
$imgur_url.text(url);
$imgur_url.attr('href', url);
@ -126,27 +126,27 @@ function show_imgur_uploader(blob){
).append($imgur_url);
// @TODO: a button to copy the URL to the clipboard
// (also maybe put the URL in a readonly input)
let $ok_button;
const $delete_button = $imgur_window.$Button("Delete", () => {
const req = new XMLHttpRequest();
$delete_button[0].disabled = true;
req.addEventListener("readystatechange", () => {
if(req.readyState == 4 && req.status == 200){
req.addEventListener("readystatechange", () => {
if (req.readyState == 4 && req.status == 200) {
$delete_button.remove();
$ok_button.focus();
const response = parseImgurResponseJSON(req.responseText);
if(!response) return;
if (!response) return;
if(response.success){
if (response.success) {
$imgur_url_area.remove();
$imgur_status.text("Deleted successfully");
}else{
} else {
$imgur_status.text("Failed to delete image :(");
}
}else if(req.readyState == 4){
} else if (req.readyState == 4) {
$imgur_status.text("Error deleting image :(");
$delete_button[0].disabled = false;
$delete_button.focus();
@ -154,7 +154,7 @@ function show_imgur_uploader(blob){
});
req.open("DELETE", `https://api.imgur.com/3/image/${response.data.deletehash}`, true);
req.setRequestHeader("Authorization", "Client-ID 203da2f300125a1");
req.setRequestHeader("Accept", "application/json");
req.send(null);
@ -164,14 +164,14 @@ function show_imgur_uploader(blob){
$ok_button = $imgur_window.$Button(localize("OK"), () => {
$imgur_window.close();
}).focus();
}else if(req.readyState == 4){
} else if (req.readyState == 4) {
$progress.add($progress_percent).remove();
$imgur_status.text("Error uploading image :(");
}
});
req.open("POST", "https://api.imgur.com/3/image", true);
const form_data = new FormData();
form_data.append("image", blob);

View File

@ -3,12 +3,12 @@ let $storage_manager;
let $quota_exceeded_window;
let ignoring_quota_exceeded = false;
async function storage_quota_exceeded(){
if($quota_exceeded_window){
async function storage_quota_exceeded() {
if ($quota_exceeded_window) {
$quota_exceeded_window.close();
$quota_exceeded_window = null;
}
if(ignoring_quota_exceeded){
if (ignoring_quota_exceeded) {
return;
}
const { promise, $window } = showMessageBox({
@ -34,8 +34,8 @@ async function storage_quota_exceeded(){
}
}
function manage_storage(){
if($storage_manager){
function manage_storage() {
if ($storage_manager) {
$storage_manager.close();
}
$storage_manager = $DialogWindow().title("Manage Storage").addClass("storage-manager squish");
@ -47,29 +47,29 @@ function manage_storage(){
$storage_manager.$Button("Close", () => {
$storage_manager.close();
});
const addRow = (k, imgSrc) => {
const $tr = $(E("tr")).appendTo($table);
const $img = $(E("img")).attr({ src: imgSrc }).addClass("thumbnail-img");
const $remove = $(E("button")).text("Remove").addClass("remove-button");
const href = `#${k.replace("image#", "local:")}`;
const $open_link = $(E("a")).attr({href, target: "_blank"}).text(localize("Open"));
const $thumbnail_open_link = $(E("a")).attr({href, target: "_blank"}).addClass("thumbnail-container");
const $open_link = $(E("a")).attr({ href, target: "_blank" }).text(localize("Open"));
const $thumbnail_open_link = $(E("a")).attr({ href, target: "_blank" }).addClass("thumbnail-container");
$thumbnail_open_link.append($img);
$(E("td")).append($thumbnail_open_link).appendTo($tr);
$(E("td")).append($open_link).appendTo($tr);
$(E("td")).append($remove).appendTo($tr);
$remove.on("click", () => {
localStorage.removeItem(k);
$tr.remove();
if($table.find("tr").length == 0){
if ($table.find("tr").length == 0) {
$message.html("<p>All clear!</p>");
}
});
};
let localStorageAvailable = false;
try {
if (localStorage.length > 0) {
@ -82,19 +82,19 @@ function manage_storage(){
localStorageAvailable = localStorage._available;
delete localStorage._available;
}
// eslint-disable-next-line no-empty
} catch (e) {}
// eslint-disable-next-line no-empty
} catch (e) { }
if (localStorageAvailable) {
for(const k in localStorage){
if(k.match(/^image#/)){
for (const k in localStorage) {
if (k.match(/^image#/)) {
let v = localStorage[k];
try {
if (v[0] === '"') {
v = JSON.parse(v);
}
// eslint-disable-next-line no-empty
} catch (e) {}
// eslint-disable-next-line no-empty
} catch (e) { }
addRow(k, v);
}
}
@ -104,7 +104,7 @@ function manage_storage(){
// @TODO: DRY with similar message
// @TODO: instructions for your browser; it's called Cookies in chrome/chromium at least, and "storage" gives NO results
$message.html("<p>Please enable local storage in your browser's settings for local backup. It may be called Cookies, Storage, or Site Data.</p>");
} else if($table.find("tr").length == 0) {
} else if ($table.find("tr").length == 0) {
$message.html("<p>All clear!</p>");
}

View File

@ -1,4 +1,4 @@
(()=> {
(() => {
const looksLikeChrome = !!(window.chrome && (window.chrome.loadTimes || window.chrome.csi));
// NOTE: Microsoft Edge includes window.chrome.app
@ -12,7 +12,7 @@ window.menus = {
speech_recognition: [
"new", "new file", "new document", "create new document", "create a new document", "start new document", "start a new document",
],
action: ()=> { file_new(); },
action: () => { file_new(); },
description: localize("Creates a new document."),
},
{
@ -24,7 +24,7 @@ window.menus = {
"show file picker", "show file chooser", "show file browser", "show finder",
"browser for file", "browse for a file", "browse for an image", "browse for an image file",
],
action: ()=> { file_open(); },
action: () => { file_open(); },
description: localize("Opens an existing document."),
},
{
@ -38,7 +38,7 @@ window.menus = {
"download", "download document", "download file", "download image", "download picture", "download image file",
"download the document", "download the file", "download the image", "download the image file",
],
action: ()=> { file_save(); },
action: () => { file_save(); },
description: localize("Saves the active document."),
},
{
@ -57,7 +57,7 @@ window.menus = {
"save file as a copy", "save file copy", "save file as copy", "save file under a new name", "save file with a new name",
"save image file as a copy", "save image file copy", "save image file as copy", "save image file under a new name", "save image file with a new name",
],
action: ()=> { file_save_as(); },
action: () => { file_save_as(); },
description: localize("Saves the active document with a new name."),
},
MENU_DIVIDER,
@ -98,7 +98,7 @@ window.menus = {
"load picture from address",
"load picture from web address",
],
action: ()=> { file_load_from_url(); },
action: () => { file_load_from_url(); },
description: localize("Opens an image from the web."),
},
{
@ -106,12 +106,12 @@ window.menus = {
speech_recognition: [
"upload to imgur", "upload image to imgur", "upload picture to imgur",
],
action: ()=> {
action: () => {
// include the selection in the saved image
deselect();
main_canvas.toBlob((blob)=> {
sanity_check_blob(blob, ()=> {
main_canvas.toBlob((blob) => {
sanity_check_blob(blob, () => {
show_imgur_uploader(blob);
});
});
@ -126,7 +126,7 @@ window.menus = {
"show autosaves", "show saves", "show saved documents", "show saved files", "show saved pictures", "show saved images", "show local storage",
"autosaves", "autosave", "saved documents", "saved files", "saved pictures", "saved images", "local storage",
],
action: ()=> { manage_storage(); },
action: () => { manage_storage(); },
description: localize("Manages storage of previously created or opened pictures."),
},
MENU_DIVIDER,
@ -135,7 +135,7 @@ window.menus = {
speech_recognition: [
"preview print", "print preview", "show print preview", "show preview of print",
],
action: ()=> {
action: () => {
print();
},
description: localize("Prints the active document and sets printing options."),
@ -147,7 +147,7 @@ window.menus = {
"setup page for print", "setup page for printing", "set-up page for print", "set-up page for printing", "set up page for print", "set up page for printing",
"page setup", "printing setup", "page set-up", "printing set-up", "page set up", "printing set up",
],
action: ()=> {
action: () => {
print();
},
description: localize("Prints the active document and sets printing options."),
@ -159,15 +159,15 @@ window.menus = {
speech_recognition: [
"print", "send to printer", "show print dialog",
"print page", "print image", "print picture", "print drawing",
"print out page", "print out image", "print out picture", "print out drawing",
"print out the page", "print out the image", "print out the picture", "print out the drawing",
"print out page", "print out image", "print out picture", "print out drawing",
"print out the page", "print out the image", "print out the picture", "print out the drawing",
"send page to printer", "send image to printer", "send picture to printer", "send drawing to printer",
"send page to the printer", "send image to the printer", "send picture to the printer", "send drawing to the printer",
"send the page to the printer", "send the image to the printer", "send the picture to the printer", "send the drawing to the printer",
"send the page to printer", "send the image to printer", "send the picture to printer", "send the drawing to printer",
"send page to printer", "send image to printer", "send picture to printer", "send drawing to printer",
"send page to the printer", "send image to the printer", "send picture to the printer", "send drawing to the printer",
"send the page to the printer", "send the image to the printer", "send the picture to the printer", "send the drawing to the printer",
"send the page to printer", "send the image to printer", "send the picture to printer", "send the drawing to printer",
],
action: ()=> {
action: () => {
print();
},
description: localize("Prints the active document and sets printing options."),
@ -178,24 +178,24 @@ window.menus = {
speech_recognition: [
"set as wallpaper",
"set as wallpaper tiled",
"set image as wallpaper tiled", "set picture as wallpaper tiled", "set drawing as wallpaper tiled",
"set image as wallpaper tiled", "set picture as wallpaper tiled", "set drawing as wallpaper tiled",
"use as wallpaper tiled",
"use image as wallpaper tiled", "use picture as wallpaper tiled", "use drawing as wallpaper tiled",
"tile image as wallpaper", "tile picture as wallpaper", "tile drawing as wallpaper",
"use image as wallpaper tiled", "use picture as wallpaper tiled", "use drawing as wallpaper tiled",
"tile image as wallpaper", "tile picture as wallpaper", "tile drawing as wallpaper",
],
action: ()=> { systemHooks.setWallpaperTiled(main_canvas); },
action: () => { systemHooks.setWallpaperTiled(main_canvas); },
description: localize("Tiles this bitmap as the desktop background."),
},
{
item: localize("Set As Wallpaper (&Centered)"), // in mspaint it's Wa&llpaper
speech_recognition: [
"set as wallpaper centered",
"set image as wallpaper centered", "set picture as wallpaper centered", "set drawing as wallpaper centered",
"set image as wallpaper centered", "set picture as wallpaper centered", "set drawing as wallpaper centered",
"use as wallpaper centered",
"use image as wallpaper centered", "use picture as wallpaper centered", "use drawing as wallpaper centered",
"center image as wallpaper", "center picture as wallpaper", "center drawing as wallpaper",
"use image as wallpaper centered", "use picture as wallpaper centered", "use drawing as wallpaper centered",
"center image as wallpaper", "center picture as wallpaper", "center drawing as wallpaper",
],
action: ()=> { systemHooks.setWallpaperCentered(main_canvas); },
action: () => { systemHooks.setWallpaperCentered(main_canvas); },
description: localize("Centers this bitmap as the desktop background."),
},
MENU_DIVIDER,
@ -240,7 +240,7 @@ window.menus = {
"undo", "undo that",
],
enabled: () => undos.length >= 1,
action: ()=> { undo(); },
action: () => { undo(); },
description: localize("Undoes the last action."),
},
{
@ -250,7 +250,7 @@ window.menus = {
"repeat", "redo",
],
enabled: () => redos.length >= 1,
action: ()=> { redo(); },
action: () => { redo(); },
description: localize("Redoes the previously undone action."),
},
{
@ -259,7 +259,7 @@ window.menus = {
speech_recognition: [
"show history", "history",
],
action: ()=> { show_document_history(); },
action: () => { show_document_history(); },
description: localize("Shows the document history and lets you navigate to states not accessible with Undo or Repeat."),
},
MENU_DIVIDER,
@ -272,7 +272,7 @@ window.menus = {
enabled: () =>
// @TODO: support cutting text with this menu item as well (e.g. for the text tool)
!!selection,
action: ()=> {
action: () => {
edit_cut(true);
},
description: localize("Cuts the selection and puts it on the Clipboard."),
@ -286,7 +286,7 @@ window.menus = {
enabled: () =>
// @TODO: support copying text with this menu item as well (e.g. for the text tool)
!!selection,
action: ()=> {
action: () => {
edit_copy(true);
},
description: localize("Copies the selection and puts it on the Clipboard."),
@ -300,7 +300,7 @@ window.menus = {
enabled: () =>
// @TODO: disable if nothing in clipboard or wrong type (if we can access that)
true,
action: ()=> {
action: () => {
edit_paste(true);
},
description: localize("Inserts the contents of the Clipboard."),
@ -312,7 +312,7 @@ window.menus = {
"delete", "clear selection", "delete selection", "delete selected", "delete selected area", "clear selected area", "erase selected", "erase selected area",
],
enabled: () => !!selection,
action: ()=> { delete_selection(); },
action: () => { delete_selection(); },
description: localize("Deletes the selection."),
},
{
@ -323,29 +323,29 @@ window.menus = {
"select the whole image", "select the whole picture", "select the whole drawing", "select the whole canvas", "select the whole document",
"select the entire image", "select the entire picture", "select the entire drawing", "select the entire canvas", "select the entire document",
],
action: ()=> { select_all(); },
action: () => { select_all(); },
description: localize("Selects everything."),
},
MENU_DIVIDER,
{
item: `${localize("C&opy To")}...`,
speech_recognition: [
"copy to file", "copy selection to file", "copy selection to a file", "save selection",
"copy to file", "copy selection to file", "copy selection to a file", "save selection",
"save selection as file", "save selection as image", "save selection as picture", "save selection as image file", "save selection as document",
"save selection as a file", "save selection as a image", "save selection as a picture", "save selection as a image file", "save selection as a document",
"save selection to file", "save selection to image", "save selection to picture", "save selection to image file", "save selection to document",
"save selection to a file", "save selection to a image", "save selection to a picture", "save selection to a image file", "save selection to a document",
],
enabled: () => !!selection,
action: ()=> { save_selection_to_file(); },
action: () => { save_selection_to_file(); },
description: localize("Copies the selection to a file."),
},
{
item: `${localize("Paste &From")}...`,
speech_recognition: [
"paste a file", "paste from a file", "insert a file", "insert an image file",
"paste a file", "paste from a file", "insert a file", "insert an image file",
],
action: ()=> { choose_file_to_paste(); },
action: () => { choose_file_to_paste(); },
description: localize("Pastes a file into the selection."),
}
],
@ -358,7 +358,7 @@ window.menus = {
// @TODO: hide/show
],
checkbox: {
toggle: ()=> {
toggle: () => {
$toolbox.toggle();
},
check: () => $toolbox.is(":visible"),
@ -373,7 +373,7 @@ window.menus = {
// @TODO: hide/show
],
checkbox: {
toggle: ()=> {
toggle: () => {
$colorbox.toggle();
},
check: () => $colorbox.is(":visible"),
@ -387,7 +387,7 @@ window.menus = {
// @TODO: hide/show
],
checkbox: {
toggle: ()=> {
toggle: () => {
$status_area.toggle();
},
check: () => $status_area.is(":visible"),
@ -422,7 +422,7 @@ window.menus = {
],
description: localize("Zooms the picture to 100%."),
enabled: () => magnification !== 1,
action: ()=> {
action: () => {
set_magnification(1);
},
},
@ -438,7 +438,7 @@ window.menus = {
],
description: localize("Zooms the picture to 400%."),
enabled: () => magnification !== 4,
action: ()=> {
action: () => {
set_magnification(4);
},
},
@ -456,7 +456,7 @@ window.menus = {
"zoom so canvas fits", "zoom so picture fits", "zoom so image fits", "zoom so document fits",
"zoom so whole canvas is visible", "zoom so whole picture is visible", "zoom so whole image is visible", "zoom so whole document is visible",
"zoom so the whole canvas is visible", "zoom so the whole picture is visible", "zoom so the whole image is visible", "zoom so the whole document is visible",
"fit to window", "fit to view", "fit in window", "fit in view", "fit within window", "fit within view",
"fit picture to window", "fit picture to view", "fit picture in window", "fit picture in view", "fit picture within window", "fit picture within view",
"fit image to window", "fit image to view", "fit image in window", "fit image in view", "fit image within window", "fit image within view",
@ -464,7 +464,7 @@ window.menus = {
"fit document to window", "fit document to view", "fit document in window", "fit document in view", "fit document within window", "fit document within view",
],
description: localize("Zooms the picture to fit within the view."),
action: ()=> {
action: () => {
const rect = $canvas_area[0].getBoundingClientRect();
const margin = 30; // leave a margin so scrollbars won't appear
let mag = Math.min(
@ -482,7 +482,7 @@ window.menus = {
speech_recognition: [
"zoom custom", "custom zoom", "set custom zoom", "set custom zoom level", "zoom to custom level", "zoom to custom", "zoom level", "set zoom level",
],
action: ()=> { show_custom_zoom_window(); },
action: () => { show_custom_zoom_window(); },
},
MENU_DIVIDER,
{
@ -527,7 +527,7 @@ window.menus = {
"show image fullscreen", "show image full-screen", "show image full screen",
// @TODO: exit fullscreen
],
action: ()=> { view_bitmap(); },
action: () => { view_bitmap(); },
description: localize("Displays the entire picture."),
},
MENU_DIVIDER,
@ -567,7 +567,7 @@ window.menus = {
"flip/rotate", "flip slash rotate", "flip and rotate", "flip or rotate", "flip rotate",
// @TODO: parameters to command
],
action: ()=> { image_flip_and_rotate(); },
action: () => { image_flip_and_rotate(); },
description: localize("Flips or rotates the picture or a selection."),
},
{
@ -579,7 +579,7 @@ window.menus = {
"stretch/skew", "stretch slash skew", "stretch and skew", "stretch or skew", "stretch skew",
// @TODO: parameters to command
],
action: ()=> { image_stretch_and_skew(); },
action: () => { image_stretch_and_skew(); },
description: localize("Stretches or skews the picture or a selection."),
},
{
@ -592,7 +592,7 @@ window.menus = {
"invert image colors", "invert picture colors", "invert drawing colors",
"invert colors of image", "invert colors of picture", "invert colors of drawing",
],
action: ()=> { image_invert_colors(); },
action: () => { image_invert_colors(); },
description: localize("Inverts the colors of the picture or a selection."),
},
{
@ -606,7 +606,7 @@ window.menus = {
"image size", "picture size", "canvas size", "document size", "page size",
"configure image size", "configure picture size", "configure canvas size", "configure document size", "configure page size",
],
action: ()=> { image_attributes(); },
action: () => { image_attributes(); },
description: localize("Changes the attributes of the picture."),
},
{
@ -617,8 +617,8 @@ window.menus = {
// @TODO: erase?
],
// (mspaint says "Ctrl+Shft+N")
action: ()=> { !selection && clear(); },
enabled: ()=> !selection,
action: () => { !selection && clear(); },
enabled: () => !selection,
description: localize("Clears the picture."),
// action: ()=> {
// if (selection) {
@ -641,7 +641,7 @@ window.menus = {
// @TODO: hide/show / "draw opaque" / "draw transparent"/translucent?
],
checkbox: {
toggle: ()=> {
toggle: () => {
tool_transparent_mode = !tool_transparent_mode;
$G.trigger("option-changed");
},
@ -658,7 +658,7 @@ window.menus = {
"pick custom color", "choose custom color", "pick a custom color", "choose a custom color",
"edit last color", "create new color", "choose new color", "create a new color", "pick a new color",
],
action: ()=> {
action: () => {
show_edit_colors_window();
},
description: localize("Creates a new color."),
@ -668,15 +668,15 @@ window.menus = {
speech_recognition: [
"get colors", "load colors", "load color palette", "load palette", "load color palette file", "load palette file", "load list of colors",
],
action: async ()=> {
const {file} = await systemHooks.showOpenFileDialog({formats: palette_formats});
AnyPalette.loadPalette(file, (error, new_palette)=> {
action: async () => {
const { file } = await systemHooks.showOpenFileDialog({ formats: palette_formats });
AnyPalette.loadPalette(file, (error, new_palette) => {
if (error) {
show_file_format_errors({ as_palette_error: error });
} else {
palette = new_palette.map((color)=> color.toString());
palette = new_palette.map((color) => color.toString());
$colorbox.rebuild_palette();
window.console && console.log(`Loaded palette: ${palette.map(()=> `%c█`).join("")}`, ...palette.map((color)=> `color: ${color};`));
window.console && console.log(`Loaded palette: ${palette.map(() => `%c█`).join("")}`, ...palette.map((color) => `color: ${color};`));
}
});
},
@ -687,7 +687,7 @@ window.menus = {
speech_recognition: [
"save colors", "save list of colors", "save color palette", "save palette", "save color palette file", "save palette file",
],
action: ()=> {
action: () => {
const ap = new AnyPalette.Palette();
ap.name = "JS Paint Saved Colors";
ap.numberOfColumns = 16; // 14?
@ -703,11 +703,11 @@ window.menus = {
dialogTitle: localize("Save Colors"),
defaultFileName: localize("untitled.pal"),
formats: palette_formats,
getBlob: ()=> {
getBlob: () => {
const file_content = AnyPalette.writePalette(ap, AnyPalette.formats[format_id]);
const blob = new Blob([file_content], {type: "text/plain"});
return new Promise((resolve)=> {
sanity_check_blob(blob, ()=> {
const blob = new Blob([file_content], { type: "text/plain" });
return new Promise((resolve) => {
sanity_check_blob(blob, () => {
resolve(blob);
});
});
@ -724,7 +724,7 @@ window.menus = {
"help topics", "help me", "show help", "help", "show help window", "show help topics", "open help",
"help viewer", "show help viewer", "open help viewer",
],
action: ()=> { show_help(); },
action: () => { show_help(); },
description: localize("Displays Help for the current task or command."),
},
MENU_DIVIDER,
@ -736,7 +736,7 @@ window.menus = {
"application info", "about the application", "application information", "information about the application",
"who made this", "who did this", "who did this xd",
],
action: ()=> { show_about_paint(); },
action: () => { show_about_paint(); },
description: localize("Displays information about this application."),
//description: localize("Displays program information, version number, and copyright."),
}
@ -748,7 +748,7 @@ window.menus = {
speech_recognition: [
// This is a duplicate menu item (for easy access), so it doesn't need speech recognition data here.
],
action: ()=> { show_document_history(); },
action: () => { show_document_history(); },
description: localize("Shows the document history and lets you navigate to states not accessible with Undo or Repeat."),
},
{
@ -770,7 +770,7 @@ window.menus = {
"make animation", "make animation of the history", "make animation of the document history", "make animation from the document history",
"create animation", "create animation of the history", "create animation of the document history", "create animation from the document history",
],
action: ()=> { render_history_as_gif(); },
action: () => { render_history_as_gif(); },
description: localize("Creates an animation from the document history."),
},
// {
@ -854,7 +854,7 @@ window.menus = {
"collaborative",
"collaborating",
],
action: ()=> {
action: () => {
show_multi_user_setup_dialog(true);
},
description: localize("Starts a new multi-user session from the current document."),
@ -904,7 +904,7 @@ window.menus = {
"start collaboration with empty",
"start collaborating with empty",
],
action: ()=> {
action: () => {
show_multi_user_setup_dialog(false);
},
description: localize("Starts a new multi-user session from an empty document."),
@ -937,7 +937,7 @@ window.menus = {
"day mode", "switch to day mode", "use day mode", "set mode to day", "set mode day", "switch to day mode", "switch mode to day", "switch mode day",
"go light", "go bright",
],
action: ()=> {
action: () => {
set_theme("classic.css");
},
enabled: () => get_theme() != "classic.css",
@ -954,7 +954,7 @@ window.menus = {
"night mode", "switch to night mode", "use night mode", "set mode to night", "set mode night", "switch to night mode", "switch mode to night", "switch mode night",
"go dark", "go dim",
],
action: ()=> {
action: () => {
set_theme("dark.css");
},
enabled: () => get_theme() != "dark.css",
@ -965,7 +965,7 @@ window.menus = {
speech_recognition: [
"modern theme", "switch to modern theme", "use modern theme", "set theme to modern", "set theme modern", "switch to modern theme", "switch theme to modern", "switch theme modern",
],
action: ()=> {
action: () => {
set_theme("modern.css");
},
enabled: () => get_theme() != "modern.css",
@ -979,7 +979,7 @@ window.menus = {
"christmas theme", "switch to christmas theme", "use christmas theme", "set theme to christmas", "set theme christmas", "switch to christmas theme", "switch theme to christmas", "switch theme christmas",
"hanukkah theme", "switch to hanukkah theme", "use hanukkah theme", "set theme to hanukkah", "set theme hanukkah", "switch to hanukkah theme", "switch theme to hanukkah", "switch theme hanukkah",
],
action: ()=> {
action: () => {
set_theme("winter.css");
},
enabled: () => get_theme() != "winter.css",
@ -1023,12 +1023,12 @@ window.menus = {
"welcome devil", "welcome the devil", "welcome devil theme", "welcome the devil theme",
"I beseech thee", "I entreat thee", "I summon thee", "I call upon thy name", "I call upon thine name", "Lord Satan", "hail Satan", "hail Lord Satan", "O Mighty Satan", "Oh Mighty Satan",
"In nomine Dei nostri Satanas Luciferi Excelsi", "Rege Satanas", "Ave Satanas","Rege Satana", "Ave Satana",
"In nomine Dei nostri Satanas Luciferi Excelsi", "Rege Satanas", "Ave Satanas", "Rege Satana", "Ave Satana",
"go demonic", "go daemonic", "go occult", "666",
"begin ritual", "begin the ritual", "begin a ritual",
"start ritual", "start the ritual", "start a ritual",
],
action: ()=> {
action: () => {
set_theme("occult.css");
},
enabled: () => get_theme() != "occult.css",
@ -1038,10 +1038,10 @@ window.menus = {
},
{
item: "🌍 " + localize("&Language"),
submenu: available_languages.map((available_language)=> (
submenu: available_languages.map((available_language) => (
{
item: get_language_emoji(available_language) + " " + get_language_endonym(available_language),
action: ()=> {
action: () => {
set_language(available_language);
},
enabled: () => get_language() != available_language,
@ -1077,7 +1077,7 @@ window.menus = {
"eye gaze off",
"start eye gaze",
"stop eye gaze",
"toggle eye gazing",
"enable eye gazing",
"disable eye gazing",
@ -1092,7 +1092,7 @@ window.menus = {
"stop eye gazing",
],
checkbox: {
toggle: ()=> {
toggle: () => {
if (location.hash.match(/eye-gaze-mode/i)) {
// @TODO: confirmation dialog that you could cancel with dwell clicking!
// if (confirm("This will disable eye gaze mode.")) {
@ -1102,7 +1102,7 @@ window.menus = {
change_url_param("eye-gaze-mode", true);
}
},
check: ()=> {
check: () => {
return location.hash.match(/eye-gaze-mode/i);
},
},
@ -1115,18 +1115,18 @@ window.menus = {
"disable speech recognition", "disable speech recognition mode", "turn off speech recognition", "turn off speech recognition mode", "leave speech recognition mode", "exit speech recognition mode",
],
checkbox: {
toggle: ()=> {
toggle: () => {
if (location.hash.match(/speech-recognition-mode/i)) {
change_url_param("speech-recognition-mode", false);
} else {
change_url_param("speech-recognition-mode", true);
}
},
check: ()=> {
check: () => {
return window.speech_recognition_active;
},
},
enabled: ()=> window.speech_recognition_available,
enabled: () => window.speech_recognition_available,
description: localize("Controls the application with voice commands."),
},
{
@ -1141,7 +1141,7 @@ window.menus = {
// @TODO: "use a vertical/horizontal color box", "place palette on the left", "make palette tall/wide", etc.
],
checkbox: {
toggle: ()=> {
toggle: () => {
if (location.hash.match(/eye-gaze-mode/i)) {
// @TODO: confirmation dialog that you could cancel with dwell clicking!
// if (confirm("This will disable eye gaze mode.")) {
@ -1156,11 +1156,11 @@ window.menus = {
change_url_param("vertical-color-box-mode", true);
}
},
check: ()=> {
check: () => {
return location.hash.match(/vertical-color-box-mode|eye-gaze-mode/i);
},
},
enabled: ()=> {
enabled: () => {
return !location.hash.match(/eye-gaze-mode/i);
},
description: localize("Arranges the color box vertically."),
@ -1171,7 +1171,7 @@ window.menus = {
speech_recognition: [
// This is a duplicate menu item (for easy access), so it doesn't need speech recognition data here.
],
action: ()=> { manage_storage(); },
action: () => { manage_storage(); },
description: localize("Manages storage of previously created or opened pictures."),
},
MENU_DIVIDER,
@ -1184,7 +1184,7 @@ window.menus = {
"what's new", "new features",
"show news", "show news update", "news update",
],
action: ()=> { show_news(); },
action: () => { show_news(); },
description: localize("Shows news about JS Paint."),
},
{
@ -1192,7 +1192,7 @@ window.menus = {
speech_recognition: [
"repo on github", "project on github", "show the source code", "show source code",
],
action: ()=> { window.open("https://github.com/1j01/jspaint"); },
action: () => { window.open("https://github.com/1j01/jspaint"); },
description: localize("Shows the project on GitHub."),
},
{
@ -1200,7 +1200,7 @@ window.menus = {
speech_recognition: [
"donate", "make a monetary contribution",
],
action: ()=> { window.open("https://www.paypal.me/IsaiahOdhner"); },
action: () => { window.open("https://www.paypal.me/IsaiahOdhner"); },
description: localize("Supports the project."),
},
],
@ -1208,7 +1208,7 @@ window.menus = {
for (const [top_level_menu_key, menu] of Object.entries(menus)) {
const top_level_menu_name = top_level_menu_key.replace(/&/, "");
const add_literal_navigation_speech_recognition = (menu, ancestor_names)=> {
const add_literal_navigation_speech_recognition = (menu, ancestor_names) => {
for (const menu_item of menu) {
if (menu_item !== MENU_DIVIDER) {
const menu_item_name = menu_item.item.replace(/&|\.\.\.|\(|\)/g, "");
@ -1223,7 +1223,7 @@ for (const [top_level_menu_key, menu] of Object.entries(menus)) {
menu_item_name.replace(/\//, " slash "),
];
}
menu_item_matchers = menu_item_matchers.map((menu_item_matcher)=> {
menu_item_matchers = menu_item_matchers.map((menu_item_matcher) => {
return `${ancestor_names} ${menu_item_matcher}`;
});
menu_item.speech_recognition = (menu_item.speech_recognition || []).concat(menu_item_matchers);

View File

@ -1,7 +1,7 @@
(() => {
const log = (...args)=> {
const log = (...args) => {
window.console && console.log(...args);
};
@ -10,8 +10,8 @@
localStorage._available = true;
localStorageAvailable = localStorage._available;
delete localStorage._available;
// eslint-disable-next-line no-empty
} catch (e) {}
// eslint-disable-next-line no-empty
} catch (e) { }
// @TODO: keep other data in addition to the image data
// such as the file_name and other state
@ -19,29 +19,28 @@
// I could have the image in one storage slot and the state in another
const match_threshold = 1; // 1 is just enough for a workaround for Brave browser's farbling: https://github.com/1j01/jspaint/issues/184
const canvas_has_any_apparent_image_data = ()=>
main_canvas.ctx.getImageData(0, 0, main_canvas.width, main_canvas.height).data.some((v)=> v > match_threshold);
const canvas_has_any_apparent_image_data = () =>
main_canvas.ctx.getImageData(0, 0, main_canvas.width, main_canvas.height).data.some((v) => v > match_threshold);
let $recovery_window;
function show_recovery_window(no_longer_blank) {
$recovery_window && $recovery_window.close();
const $w = $recovery_window = $DialogWindow();
$w.on("close", ()=> {
$w.on("close", () => {
$recovery_window = null;
});
$w.title("Recover Document");
let backup_impossible = false;
try{window.localStorage}catch(e){backup_impossible = true;}
try { window.localStorage } catch (e) { backup_impossible = true; }
$w.$main.append($(`
<h1>Woah!</h1>
<p>Your browser may have cleared the canvas due to memory usage.</p>
<p>Undo to recover the document, and remember to save with <b>File > Save</b>!</p>
${
backup_impossible ?
"<p><b>Note:</b> No automatic backup is possible unless you enable Cookies in your browser.</p>"
: (
no_longer_blank ?
`<p>
${backup_impossible ?
"<p><b>Note:</b> No automatic backup is possible unless you enable Cookies in your browser.</p>"
: (
no_longer_blank ?
`<p>
<b>Note:</b> normally a backup is saved automatically,<br>
but autosave is paused while this dialog is open<br>
to avoid overwriting the (singular) backup.
@ -49,26 +48,26 @@
<p>
(See <b>File &gt; Manage Storage</b> to view backups.)
</p>`
: ""
)
}
: ""
)
}
}
`));
const $undo = $w.$Button("Undo", ()=> {
const $undo = $w.$Button("Undo", () => {
undo();
});
const $redo = $w.$Button("Redo", ()=> {
const $redo = $w.$Button("Redo", () => {
redo();
});
const update_buttons_disabled = ()=> {
const update_buttons_disabled = () => {
$undo.attr("disabled", undos.length < 1);
$redo.attr("disabled", redos.length < 1);
};
$G.on("session-update.session-hook", update_buttons_disabled);
update_buttons_disabled();
$w.$Button(localize("Close"), ()=> {
$w.$Button(localize("Close"), () => {
$w.close();
});
$w.center();
@ -94,7 +93,7 @@
last_undos_length = undos.length;
return save_paused;
}
class LocalSession {
constructor(session_id) {
this.id = session_id;
@ -188,7 +187,7 @@
// Unused
user.color_transparent = `hsla(${user.hue}, ${user.saturation}%, ${user.lightness}%, 0.5)`;
// (@TODO) The color (that may be) used in the toolbar indicating to other users it is selected by this user
user.color_desaturated = `hsla(${user.hue}, ${~~(user.saturation*0.4)}%, ${user.lightness}%, 0.8)`;
user.color_desaturated = `hsla(${user.hue}, ${~~(user.saturation * 0.4)}%, ${user.lightness}%, 0.8)`;
// The image used for other people's cursors
@ -248,7 +247,7 @@
// "so for now I'm showing this message regardless of whether it's working.</p>" +
// "<p>If you're interested in using multiuser mode, please thumbs-up " +
// "<a href='https://github.com/1j01/jspaint/issues/68'>this issue</a> to show interest, and/or subscribe for updates.</p>"
// Wrap the Firebase API because they don't
// provide a great way to clean up event listeners
const _fb_on = (fb, event_type, callback, error_callback) => {
@ -359,7 +358,7 @@
};
this.write_canvas_to_database_soon = debounce(this.write_canvas_to_database_immediately, 100);
let ignore_session_update = false;
$G.on("session-update.session-hook", ()=> {
$G.on("session-update.session-hook", () => {
if (ignore_session_update) {
log("(Ignore session-update from Sync Session undoable)");
return;
@ -388,13 +387,13 @@
const test_canvas = make_canvas(img);
const image_data_remote = test_canvas.ctx.getImageData(0, 0, test_canvas.width, test_canvas.height);
const image_data_local = main_ctx.getImageData(0, 0, main_canvas.width, main_canvas.height);
if (!image_data_match(image_data_remote, image_data_local, 5)) {
ignore_session_update = true;
undoable({
name: "Sync Session",
icon: get_help_folder_icon("p_database.png"),
}, ()=> {
}, () => {
// Write the image data to the canvas
main_ctx.copy(img);
$canvas_area.trigger("resize");
@ -432,7 +431,7 @@
away: false,
});
});
$G.on("blur.session-hook", ()=> {
$G.on("blur.session-hook", () => {
this.fb_user.child("cursor").update({
away: true,
});
@ -498,45 +497,45 @@
let current_session;
const end_current_session = () => {
if(current_session){
if (current_session) {
log("Ending current session");
current_session.end();
current_session = null;
}
};
const generate_session_id = () => (Math.random()*(2 ** 32)).toString(16).replace(".", "");
const generate_session_id = () => (Math.random() * (2 ** 32)).toString(16).replace(".", "");
const update_session_from_location_hash = () => {
const session_match = location.hash.match(/^#?(?:.*,)?(session|local):(.*)$/i);
const load_from_url_match = location.hash.match(/^#?(?:.*,)?(load):(.*)$/i);
if(session_match){
if (session_match) {
const local = session_match[1].toLowerCase() === "local";
const session_id = session_match[2];
if(session_id === ""){
if (session_id === "") {
log("Invalid session ID; session ID cannot be empty");
end_current_session();
}else if(!local && session_id.match(/[./[\]#$]/)){
} else if (!local && session_id.match(/[./[\]#$]/)) {
log("Session ID is not a valid Firebase location; it cannot contain any of ./[]#$");
end_current_session();
}else if(!session_id.match(/[-0-9A-Za-z\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u02af\u1d00-\u1d25\u1d62-\u1d65\u1d6b-\u1d77\u1d79-\u1d9a\u1e00-\u1eff\u2090-\u2094\u2184-\u2184\u2488-\u2490\u271d-\u271d\u2c60-\u2c7c\u2c7e-\u2c7f\ua722-\ua76f\ua771-\ua787\ua78b-\ua78c\ua7fb-\ua7ff\ufb00-\ufb06]+/)){
} else if (!session_id.match(/[-0-9A-Za-z\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u02af\u1d00-\u1d25\u1d62-\u1d65\u1d6b-\u1d77\u1d79-\u1d9a\u1e00-\u1eff\u2090-\u2094\u2184-\u2184\u2488-\u2490\u271d-\u271d\u2c60-\u2c7c\u2c7e-\u2c7f\ua722-\ua76f\ua771-\ua787\ua78b-\ua78c\ua7fb-\ua7ff\ufb00-\ufb06]+/)) {
log("Invalid session ID; it must consist of 'alphanumeric-esque' characters");
end_current_session();
}else if(
current_session && current_session.id === session_id &&
} else if (
current_session && current_session.id === session_id &&
local === (current_session instanceof LocalSession)
){
) {
log("Hash changed but the session ID and session type are the same");
}else{
} else {
// @TODO: Ask if you want to save before starting a new session
end_current_session();
if(local){
if (local) {
log(`Starting a new LocalSession, ID: ${session_id}`);
current_session = new LocalSession(session_id);
}else{
} else {
log(`Starting a new MultiUserSession, ID: ${session_id}`);
current_session = new MultiUserSession(session_id);
}
}
}else if(load_from_url_match){
} else if (load_from_url_match) {
const url = decodeURIComponent(load_from_url_match[2]);
const uris = get_uris(url);
@ -554,11 +553,11 @@
open_from_image_info(info, null, null, true, true);
}, show_resource_load_error_message);
}else{
} else {
log("No session ID in hash");
const old_hash = location.hash;
end_current_session();
change_url_param("local", generate_session_id(), {replace_history_state: true});
change_url_param("local", generate_session_id(), { replace_history_state: true });
log("After replaceState:", location.hash);
if (old_hash === location.hash) {
// e.g. on Wayback Machine

View File

@ -1,4 +1,4 @@
(()=>{
(() => {
let seed = 4; // chosen later
@ -15,8 +15,8 @@ window.simulatingGestures = false;
let gestureTimeoutID;
let periodicGesturesTimeoutID;
let choose = (array)=> array[~~(seededRandom() * array.length)];
let isAnyMenuOpen = ()=> $(".menu-button.active").length > 0;
let choose = (array) => array[~~(seededRandom() * array.length)];
let isAnyMenuOpen = () => $(".menu-button.active").length > 0;
let cursor_image = new Image();
cursor_image.src = "images/cursors/default.png";
@ -32,7 +32,7 @@ $cursor.css({
transition: "opacity 0.5s",
});
window.simulateRandomGesture = (callback, {shift, shiftToggleChance=0.01, secondary, secondaryToggleChance, target=main_canvas}) => {
window.simulateRandomGesture = (callback, { shift, shiftToggleChance = 0.01, secondary, secondaryToggleChance, target = main_canvas }) => {
let startWithinRect = target.getBoundingClientRect();
let canvasAreaRect = $canvas_area[0].getBoundingClientRect();
@ -45,7 +45,7 @@ window.simulateRandomGesture = (callback, {shift, shiftToggleChance=0.01, second
$cursor.appendTo($app);
let triggerMouseEvent = (type, point) => {
if (isAnyMenuOpen()) {
return;
}
@ -122,7 +122,7 @@ window.simulateRandomGesture = (callback, {shift, shiftToggleChance=0.01, second
point.y += startPointY;
return point;
};
triggerMouseEvent("pointerenter", pointForTime(t)); // so dynamic cursors follow the simulation cursor
triggerMouseEvent("pointerdown", pointForTime(t));
let move = () => {
@ -135,7 +135,7 @@ window.simulateRandomGesture = (callback, {shift, shiftToggleChance=0.01, second
}
if (t > 1) {
triggerMouseEvent("pointerup", pointForTime(t));
$cursor.remove();
if (callback) {
@ -161,14 +161,14 @@ window.simulateRandomGesturesPeriodically = () => {
window.console && console.log("Using seed:", seed);
window.console && console.log("Note: Seeds are not guaranteed to work with different versions of the app, but within the same version it should produce the same results given the same starting document & other state & NO interference... except for airbrush randomness");
window.console && console.log(`To use this seed:
window.drawRandomlySeed = ${seed};
document.body.style.width = "${getComputedStyle(document.body).width}";
document.body.style.height = "${getComputedStyle(document.body).height}";
simulateRandomGesturesPeriodically();
delete window.drawRandomlySeed;
`);
window.drawRandomlySeed = ${seed};
document.body.style.width = "${getComputedStyle(document.body).width}";
document.body.style.height = "${getComputedStyle(document.body).height}";
simulateRandomGesturesPeriodically();
delete window.drawRandomlySeed;
`);
let delayBetweenGestures = 500;
let shiftStart = false;
@ -187,8 +187,8 @@ window.simulateRandomGesturesPeriodically = () => {
// scroll randomly absolutely initially so the starting scroll doesn't play into whether a seed reproduces
$canvas_area.scrollTop($canvas_area.width() * seededRandom());
$canvas_area.scrollLeft($canvas_area.height() * seededRandom());
let _simulateRandomGesture = (callback)=> {
let _simulateRandomGesture = (callback) => {
window.simulateRandomGesture(callback, {
shift: shiftStart,
shiftToggleChance,
@ -212,7 +212,7 @@ window.simulateRandomGesturesPeriodically = () => {
}
if (seededRandom() < switchToolsChance) {
let multiToolsPlz = seededRandom() < multiToolsChance;
$(choose($(".tool, tool-button"))).trigger($.Event("click", {shiftKey: multiToolsPlz}));
$(choose($(".tool, tool-button"))).trigger($.Event("click", { shiftKey: multiToolsPlz }));
}
if (seededRandom() < pickToolOptionsChance) {
$(choose($(".tool-options *"))).trigger("click");
@ -222,9 +222,9 @@ window.simulateRandomGesturesPeriodically = () => {
let secondary = seededRandom() < 0.5;
const colorButton = choose($(".swatch, .color-button"));
$(colorButton)
.trigger($.Event("pointerdown", {button: secondary ? 2 : 0}))
.trigger($.Event("click", {button: secondary ? 2 : 0}))
.trigger($.Event("pointerup", {button: secondary ? 2 : 0}));
.trigger($.Event("pointerdown", { button: secondary ? 2 : 0 }))
.trigger($.Event("click", { button: secondary ? 2 : 0 }))
.trigger($.Event("pointerup", { button: secondary ? 2 : 0 }));
}
if (seededRandom() < scrollChance) {
let scrollAmount = (seededRandom() * 2 - 1) * 700;
@ -235,7 +235,7 @@ window.simulateRandomGesturesPeriodically = () => {
}
}
periodicGesturesTimeoutID = setTimeout(() => {
_simulateRandomGesture(()=> {
_simulateRandomGesture(() => {
if (selection && seededRandom() < dragSelectionChance) {
window.simulateRandomGesture(waitThenGo, {
shift: shiftStart,

View File

@ -1,4 +1,4 @@
(function() {
(function () {
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
const SpeechGrammarList = window.SpeechGrammarList || window.webkitSpeechGrammarList;
@ -1251,16 +1251,16 @@ const recognitionFixes = [
// spell-checker:enable
];
const colorNames = [ 'aqua', 'azure', 'beige', 'bisque', 'black', 'blue', 'brown', 'chocolate', 'coral', 'crimson', 'cyan', 'fuchsia', 'ghostwhite', 'gold', 'goldenrod', 'gray', 'green', 'indigo', 'ivory', 'khaki', 'lavender', 'lime', 'linen', 'magenta', 'maroon', 'moccasin', 'navy', 'olive', 'orange', 'orchid', 'peru', 'pink', 'plum', 'purple', 'red', 'salmon', 'sienna', 'silver', 'snow', 'tan', 'teal', 'thistle', 'tomato', 'turquoise', 'violet', 'white', 'yellow'];
const toolNames = tools.map((tool)=> tool.speech_recognition).flat();
const colorNames = ['aqua', 'azure', 'beige', 'bisque', 'black', 'blue', 'brown', 'chocolate', 'coral', 'crimson', 'cyan', 'fuchsia', 'ghostwhite', 'gold', 'goldenrod', 'gray', 'green', 'indigo', 'ivory', 'khaki', 'lavender', 'lime', 'linen', 'magenta', 'maroon', 'moccasin', 'navy', 'olive', 'orange', 'orchid', 'peru', 'pink', 'plum', 'purple', 'red', 'salmon', 'sienna', 'silver', 'snow', 'tan', 'teal', 'thistle', 'tomato', 'turquoise', 'violet', 'white', 'yellow'];
const toolNames = tools.map((tool) => tool.speech_recognition).flat();
// @TODO: select foreground/background/ternary color specifically
// @TODO: switch colors / swap colors / swap foreground and background colors
// @TODO: zoom in/out / increase/decrease magnification, zoom to 20x / 5% etc., zoom out all the way (actual size or best fit if it's too big), actual size
// @TODO: "convert image to black-and-white" / "convert image to monochrome" / "make image monochrome", "increase/decrease threshold" / "more white" / "more black"
// @TODO: in Image Attributes, "Color"/"Colors"/"Not black and white" for Colors
// @TODO: select tool options like selection opacity and brush sizes
// opaque/transparent/translucent/see-through selection / make selection opaque/transparent/translucent/see-through
// "increase size"(too vague) / "increase brush size" / "increase eraser size" / "larger eraser" / "enlarge eraser"
// opaque/transparent/translucent/see-through selection / make selection opaque/transparent/translucent/see-through
// "increase size"(too vague) / "increase brush size" / "increase eraser size" / "larger eraser" / "enlarge eraser"
// @TODO: Is there a way to enable the grammar only as a hint, non-restrictively?
// Construct a grammar that just contains an English dictionary, and set it as lower weight?
@ -1290,13 +1290,13 @@ recognition.maxAlternatives = 1;
window.speech_recognition_active = false;
window.enable_speech_recognition = function() {
window.enable_speech_recognition = function () {
if (!window.speech_recognition_active) {
window.speech_recognition_active = true;
recognition.start();
}
};
window.disable_speech_recognition = function() {
window.disable_speech_recognition = function () {
if (window.speech_recognition_active) {
window.speech_recognition_active = false;
recognition.stop();
@ -1321,7 +1321,7 @@ function fix_up_speech_recognition(command) {
}
return command;
}
recognition.onresult = function(event) {
recognition.onresult = function (event) {
if (document.visibilityState !== "visible") {
return;
}
@ -1343,12 +1343,11 @@ recognition.onresult = function(event) {
const interpretations = interpret_command(command, true);
if (interpretations.length) {
const interpretation = choose_interpretation(interpretations);
$status_text.html(`Speech:&nbsp;<span style="white-space: pre;">${
command.replace(
new RegExp(escapeRegExp(interpretation.match_text), "i"),
(important_text)=> `<b>${important_text}</b>`,
)
}</span>`);
$status_text.html(`Speech:&nbsp;<span style="white-space: pre;">${command.replace(
new RegExp(escapeRegExp(interpretation.match_text), "i"),
(important_text) => `<b>${important_text}</b>`,
)
}</span>`);
console.log(`Interpreting command "${command}" as`, interpretation);
interpretation.exec();
} else {
@ -1357,35 +1356,35 @@ recognition.onresult = function(event) {
}
};
recognition.onspeechend = function() {
recognition.addEventListener("end", ()=> {
recognition.onspeechend = function () {
recognition.addEventListener("end", () => {
recognition.start();
}, {once: true});
}, { once: true });
recognition.stop();
};
recognition.onnomatch = function(event) {
recognition.onnomatch = function (event) {
if (document.visibilityState !== "visible") {
return;
}
$status_text.text("Speech not recognized.");
};
recognition.onstart = function(event) {
recognition.onstart = function (event) {
window.speech_recognition_active = true;
};
recognition.onend = function(event) {
recognition.onend = function (event) {
window.speech_recognition_active = false;
};
recognition.onerror = function(event) {
recognition.onerror = function (event) {
if (event.error.toString().match(/no-speech/)) {
try {
recognition.start();
} catch(error) {
recognition.addEventListener("end", ()=> {
} catch (error) {
recognition.addEventListener("end", () => {
recognition.start();
}, {once: true});
}, { once: true });
}
} else {
$status_text.text('Error occurred in speech recognition: ' + event.error);
@ -1406,22 +1405,22 @@ function choose_interpretation(interpretations) {
interpretation.prioritize
) {
best_interpretation = interpretation;
}
}
}
return best_interpretation;
}
window.interpret_command = (input_text, default_to_entering_text)=> {
window.interpret_command = (input_text, default_to_entering_text) => {
const interpretations = [];
const add_interpretation = (interpretation)=> {
const add_interpretation = (interpretation) => {
interpretations.push(interpretation);
};
for (const color of colorNames) {
if (` ${input_text} `.toLowerCase().indexOf(` ${color.toLowerCase()} `) !== -1) {
add_interpretation({
match_text: color,
exec: ((color)=> ()=> {
exec: ((color) => () => {
selected_colors.foreground = color;
$G.trigger("option-changed");
})(color),
@ -1436,7 +1435,7 @@ window.interpret_command = (input_text, default_to_entering_text)=> {
add_interpretation({
match_text: select_tool_match[0],
tool_id: tool.id,
exec: ((tool)=> ()=> {
exec: ((tool) => () => {
select_tool(tool);
})(tool),
});
@ -1445,7 +1444,7 @@ window.interpret_command = (input_text, default_to_entering_text)=> {
}
const all_menu_items = [];
const collect_menu_items = (menu)=> {
const collect_menu_items = (menu) => {
for (const menu_item of menu) {
if (menu_item !== MENU_DIVIDER) {
all_menu_items.push(menu_item);
@ -1463,7 +1462,7 @@ window.interpret_command = (input_text, default_to_entering_text)=> {
if (` ${input_text} `.toLowerCase().indexOf(` ${menu_item_phrase.toLowerCase()} `) !== -1) {
add_interpretation({
match_text: menu_item_phrase,
exec: ((menu_item)=> ()=> {
exec: ((menu_item) => () => {
if (menu_item.checkbox) {
menu_item.checkbox.toggle();
} else {
@ -1475,12 +1474,12 @@ window.interpret_command = (input_text, default_to_entering_text)=> {
}
}
}
const close_menus_match = input_text.match(/\b(?:(?:close|hide|dismiss) menus?|never ?mind)\b/i);
if (close_menus_match) {
add_interpretation({
match_text: close_menus_match[0],
exec: ()=> {
exec: () => {
// from close_menus in $MenuBar
$(".menu-button").trigger("release");
// Close any rogue floating submenus
@ -1499,7 +1498,7 @@ window.interpret_command = (input_text, default_to_entering_text)=> {
add_interpretation({
match_text: draw_match[0],
sketch_subject: subject_matter,
exec: ()=> {
exec: () => {
find_clipart_and_sketch(subject_matter);
},
});
@ -1507,7 +1506,7 @@ window.interpret_command = (input_text, default_to_entering_text)=> {
}
const buttons = $("button, .menu-button, .menu-item-label, label, .help-window .item").filter(":visible").toArray();
for (const button of buttons) {
const button_text = button.textContent || button.getAttribute("aria-label") || button.title;
let button_text_phrases = [button_text];
@ -1531,13 +1530,13 @@ window.interpret_command = (input_text, default_to_entering_text)=> {
button_text_phrases = [
"Toggle Dwell Clicking", "Toggle Dwell Clicks",
// enable reenable re-enable start resume unpause un-pause
"Enable Dwell Clicking", "Enable Eye Gaze", "Enable Gaze Clicking", "Enable Dwell Clicks", "Enable Gaze Clicks",
"Reenable Dwell Clicking", "Reenable Eye Gaze", "Reenable Gaze Clicking", "Reenable Dwell Clicks", "Reenable Gaze Clicks",
"Re-enable Dwell Clicking", "Re-enable Eye Gaze", "Re-enable Gaze Clicking", "Re-enable Dwell Clicks", "Re-enable Gaze Clicks",
"Start Dwell Clicking", "Start Eye Gaze", "Start Gaze Clicking", "Start Dwell Clicks", "Start Gaze Clicks",
"Resume Dwell Clicking", "Resume Eye Gaze", "Resume Gaze Clicking", "Resume Dwell Clicks", "Resume Gaze Clicks",
"Unpause Dwell Clicking", "Unpause Eye Gaze", "Unpause Gaze Clicking", "Unpause Dwell Clicks", "Unpause Gaze Clicks",
"Un-pause Dwell Clicking", "Un-pause Eye Gaze", "Un-pause Gaze Clicking", "Un-pause Dwell Clicks", "Un-pause Gaze Clicks",
"Enable Dwell Clicking", "Enable Eye Gaze", "Enable Gaze Clicking", "Enable Dwell Clicks", "Enable Gaze Clicks",
"Reenable Dwell Clicking", "Reenable Eye Gaze", "Reenable Gaze Clicking", "Reenable Dwell Clicks", "Reenable Gaze Clicks",
"Re-enable Dwell Clicking", "Re-enable Eye Gaze", "Re-enable Gaze Clicking", "Re-enable Dwell Clicks", "Re-enable Gaze Clicks",
"Start Dwell Clicking", "Start Eye Gaze", "Start Gaze Clicking", "Start Dwell Clicks", "Start Gaze Clicks",
"Resume Dwell Clicking", "Resume Eye Gaze", "Resume Gaze Clicking", "Resume Dwell Clicks", "Resume Gaze Clicks",
"Unpause Dwell Clicking", "Unpause Eye Gaze", "Unpause Gaze Clicking", "Unpause Dwell Clicks", "Unpause Gaze Clicks",
"Un-pause Dwell Clicking", "Un-pause Eye Gaze", "Un-pause Gaze Clicking", "Un-pause Dwell Clicks", "Un-pause Gaze Clicks",
];
}
if (button.matches(".window-close-button")) {
@ -1610,7 +1609,7 @@ window.interpret_command = (input_text, default_to_entering_text)=> {
if (button_text === "Color Box") {
button_text_phrases = ["color box", "color-box", "colorbox"];
}
// top level menu buttons
if (button.matches(".menu-button")) {
button_text_phrases = [
@ -1648,7 +1647,7 @@ window.interpret_command = (input_text, default_to_entering_text)=> {
if (` ${input_text} `.toLowerCase().indexOf(` ${match_phrase.toLowerCase()} `) !== -1) {
add_interpretation({
match_text: match_phrase,
exec: ((button)=> ()=> {
exec: ((button) => () => {
clickButtonVisibly(button);
console.log("click", button);
})(button),
@ -1666,7 +1665,7 @@ window.interpret_command = (input_text, default_to_entering_text)=> {
add_interpretation({
match_text: stop_match[0],
type: "stop-drawing",
exec: ()=> {
exec: () => {
window.stopSimulatingGestures && window.stopSimulatingGestures();
window.trace_and_sketch_stop && window.trace_and_sketch_stop();
},
@ -1710,18 +1709,18 @@ window.interpret_command = (input_text, default_to_entering_text)=> {
add_interpretation({
match_text: line_width_match[0],
size: n,
exec: ()=> {
exec: () => {
if (isFinite(n)) {
// @TODO: DRY with app.js
if(selected_tool.id === TOOL_BRUSH){
if (selected_tool.id === TOOL_BRUSH) {
brush_size = Math.max(1, Math.min(n, 500));
}else if(selected_tool.id === TOOL_ERASER){
} else if (selected_tool.id === TOOL_ERASER) {
eraser_size = Math.max(1, Math.min(n, 500));
}else if(selected_tool.id === TOOL_AIRBRUSH){
} else if (selected_tool.id === TOOL_AIRBRUSH) {
airbrush_size = Math.max(1, Math.min(n, 500));
}else if(selected_tool.id === TOOL_PENCIL){
} else if (selected_tool.id === TOOL_PENCIL) {
pencil_size = Math.max(1, Math.min(n, 50));
}else if(
} else if (
selected_tool.id === TOOL_LINE ||
selected_tool.id === TOOL_CURVE ||
selected_tool.id === TOOL_RECTANGLE ||
@ -1731,10 +1730,10 @@ window.interpret_command = (input_text, default_to_entering_text)=> {
) {
stroke_size = Math.max(1, Math.min(n, 500));
}
$G.trigger("option-changed");
if(button !== undefined && pointer){ // pointer may only be needed for tests
selected_tools.forEach((selected_tool)=> {
if (button !== undefined && pointer) { // pointer may only be needed for tests
selected_tools.forEach((selected_tool) => {
tool_go(selected_tool);
});
}
@ -1751,7 +1750,7 @@ window.interpret_command = (input_text, default_to_entering_text)=> {
const scroll_match = input_text.match(scrolling_regexp);
if (scroll_match) {
const directions = scroll_match[0];
const vector = {x: 0, y: 0};
const vector = { x: 0, y: 0 };
if (directions.match(/up|north/i)) {
vector.y = -1;
}
@ -1764,11 +1763,11 @@ window.interpret_command = (input_text, default_to_entering_text)=> {
if (directions.match(/right|east/i)) {
vector.x = +1;
}
const scroll_pane_el = $(".window *").toArray().filter((el)=> el.scrollWidth > el.clientWidth || el.scrollHeight > el.clientHeight)[0] || $canvas_area[0];
const scroll_pane_el = $(".window *").toArray().filter((el) => el.scrollWidth > el.clientWidth || el.scrollHeight > el.clientHeight)[0] || $canvas_area[0];
add_interpretation({
match_text: scroll_match[0],
exec: ()=> {
const factor = directions.match(/page/) ? 1 : 1/2;
exec: () => {
const factor = directions.match(/page/) ? 1 : 1 / 2;
// scroll_pane_el.scrollLeft += vector.x * scroll_pane_el.clientWidth * factor;
// scroll_pane_el.scrollTop += vector.y * scroll_pane_el.clientHeight * factor;
$(scroll_pane_el).animate({
@ -1786,7 +1785,7 @@ window.interpret_command = (input_text, default_to_entering_text)=> {
if (new_line_match) {
add_interpretation({
match_text: new_line_match[0],
exec: ()=> {
exec: () => {
document.execCommand("insertText", false, "\n");
},
});
@ -1817,7 +1816,7 @@ window.interpret_command = (input_text, default_to_entering_text)=> {
const text_to_insert = input_text.replace(/new[ -]?line|line[ -]?break|carriage return/g, "\n");
add_interpretation({
match_text: input_text,
exec: ()=> {
exec: () => {
if (document.activeElement && document.activeElement.matches("input[type='number']")) {
document.activeElement.value = input_text;
} else {
@ -1836,18 +1835,18 @@ window.interpret_command = (input_text, default_to_entering_text)=> {
return interpretations;
};
window.trace_and_sketch = (subject_imagedata)=> {
window.trace_and_sketch = (subject_imagedata) => {
window.trace_and_sketch_stop && window.trace_and_sketch_stop();
// @TODO: clickable cancel button? (in addition to Escape key handling and the "stop" voice command)
// I'm suggesting saying "stop drawing" rather than "stop" because I think it's more likely to be picked up as speech at all
$status_text.text(`To stop drawing, ${window.speech_recognition_active ? `say "stop drawing", or ` : ""}press Esc.`);
// const subject_imagedata = ctx.getImageData(0, 0, canvas.width, canvas.height);
// const pal = palette.map((color)=> get_rgba_from_color(color)).map(([r, g, b, a])=> ({r, g, b, a}));
const trace_data = ImageTracer.imagedataToTracedata(subject_imagedata, { ltres:1, qtres:0.01, scale:10, /*pal,*/ numberofcolors: 6, });
const {layers} = trace_data;
const trace_data = ImageTracer.imagedataToTracedata(subject_imagedata, { ltres: 1, qtres: 0.01, scale: 10, /*pal,*/ numberofcolors: 6, });
const { layers } = trace_data;
const brush = get_tool_by_id(TOOL_BRUSH);
select_tool(brush);
@ -1855,7 +1854,7 @@ window.trace_and_sketch = (subject_imagedata)=> {
let path_index = 0;
let segment_index = 0;
let active_path;
window.sketching_iid = setInterval(()=> {
window.sketching_iid = setInterval(() => {
const layer = layers[layer_index];
if (!layer) {
clearInterval(window.sketching_iid);
@ -1875,15 +1874,15 @@ window.trace_and_sketch = (subject_imagedata)=> {
brush.pointerup(main_ctx, pointer.x, pointer.y);
return;
}
let {x1, y1, x2, y2} = segment;
let { x1, y1, x2, y2 } = segment;
if (path !== active_path) {
pointer_previous = {x: x1, y: y1};
pointer = {x: x1, y: y1};
pointer_previous = { x: x1, y: y1 };
pointer = { x: x1, y: y1 };
brush.pointerdown(main_ctx, x1, y1);
active_path = path;
}
pointer_previous = {x: x1, y: y1};
pointer = {x: x2, y: y2};
pointer_previous = { x: x1, y: y1 };
pointer = { x: x2, y: y2 };
brush.paint();
pointer_active = true;
pointer_over_canvas = true;
@ -1891,15 +1890,15 @@ window.trace_and_sketch = (subject_imagedata)=> {
segment_index += 1;
}, 20);
};
window.trace_and_sketch_stop = ()=> {
window.trace_and_sketch_stop = () => {
clearInterval(window.sketching_iid);
pointer_active = false;
pointer_over_canvas = false;
};
function find_clipart_and_sketch(subject_matter) {
find_clipart(subject_matter).then((results)=> {
find_clipart(subject_matter).then((results) => {
// @TODO: select less complex images (less file size to width, say?) maybe, and/or better semantic matches by looking for the search terms in the title?
// detect gradients / spread out histogram at least, and reject based on that
let image_url = results[~~(Math.random() * results.length)].image_url;
@ -1909,10 +1908,10 @@ function find_clipart_and_sketch(subject_matter) {
}
const img = new Image();
img.crossOrigin = "Anonymous";
img.onerror = ()=> {
img.onerror = () => {
$status_text.text("Failed to load clipart.");
};
img.onload = ()=> {
img.onload = () => {
// @TODO: find an empty spot on the canvas for the sketch, smaller if need be
const max_sketch_width = 500;
const max_sketch_height = 500;
@ -1932,7 +1931,7 @@ function find_clipart_and_sketch(subject_matter) {
trace_and_sketch(image_data);
};
img.src = image_url;
}, (error)=> {
}, (error) => {
if (error.code === "no-results") {
$status_text.text(`No clipart found for '${subject_matter}'`);
} else {
@ -1944,12 +1943,12 @@ function find_clipart_and_sketch(subject_matter) {
function find_clipart(query) {
const bing_url = new URL(`https://www.bing.com/images/search?q=${encodeURIComponent(query)}&qft=+filterui:photo-clipart&FORM=IRFLTR`)
return fetch(`https://jspaint-cors-proxy.herokuapp.com/${bing_url}`)
.then(response=> response.text())
.then((html)=> {
.then(response => response.text())
.then((html) => {
// handle relative data-src
html = html.replace(
/((?:data-src)=["'])(?!(?:https?:|data:))(\/?)/gi,
($0, $1, $2)=> `${$1}${bing_url.origin}${$2 ? bing_url.pathname : ""}`
($0, $1, $2) => `${$1}${bing_url.origin}${$2 ? bing_url.pathname : ""}`
);
// handle relative src and href in a less error-prone way, with a <base> tag
const doc = new DOMParser().parseFromString(html, "text/html");
@ -1962,34 +1961,34 @@ function find_clipart(query) {
window.search_page_$html = $html;
console.log("window.search_page_html and window.search_page_$html are a available for debugging");
const validate_item = (item)=> item.image_url && (item.image_url.match(/^data:/) ? item.image_url.length > 1000 : true);
const validate_item = (item) => item.image_url && (item.image_url.match(/^data:/) ? item.image_url.length > 1000 : true);
let items = $html.find("[m]").toArray()
.map((el)=> el.getAttribute("m"))
.map((json)=> {
.map((el) => el.getAttribute("m"))
.map((json) => {
try {
return JSON.parse(json);
} catch(error) {
} catch (error) {
return null;
}
})
.filter((maybe_parsed)=> maybe_parsed && maybe_parsed.murl)
.map(({murl, t})=> ({image_url: murl, title: t || ""}))
.filter((maybe_parsed) => maybe_parsed && maybe_parsed.murl)
.map(({ murl, t }) => ({ image_url: murl, title: t || "" }))
.filter(validate_item);
// fallback to thumbnails in case they get rid of the "m" attribute (thumbnails are not as good, more likely to be jpeg)
if (items.length === 0) {
console.log("Fallback to thumbnails");
items = $html.find("img.mimg").toArray()
.map((el)=> ({image_url: el.src || el.dataset.src, title: ""}))
.map((el) => ({ image_url: el.src || el.dataset.src, title: "" }))
.filter(validate_item);
}
// fallback in case they also change the class for images (this may match totally irrelevant things)
if (items.length === 0) {
console.log("Fallback to most imgs");
items = $html.find("img:not(.sw_spd):not(.rms_img):not(.flagIcon)").toArray()
.filter((el)=> !el.closest("[role='navigation'], nav")) // ignore "Related searches", "Refine your search" etc.
.map((el)=> ({image_url: el.src || el.dataset.src, title: ""}))
.filter((el) => !el.closest("[role='navigation'], nav")) // ignore "Related searches", "Refine your search" etc.
.map((el) => ({ image_url: el.src || el.dataset.src, title: "" }))
.filter(validate_item);
}
console.log(`Search results for '${query}':`, items);
@ -2021,7 +2020,7 @@ function clickButtonVisibly(button) {
if (button.matches("button:not(.toggle)")) {
button.style.borderImage = "var(--inset-deep-border-image)";
setTimeout(()=> {
setTimeout(() => {
button.style.borderImage = "";
// delay the button.click() as well, so the pressed state is
// visible even if the button action closes a dialog
@ -2056,9 +2055,9 @@ Expected '${input_text}' to be interpreted as`, expected, `but found no interpre
return;
}
const interpretation = choose_interpretation(interpretations);
const actual = Object.assign({}, interpretation, {prioritize: undefined, exec: undefined});
const actual = Object.assign({}, interpretation, { prioritize: undefined, exec: undefined });
// expected.match_text = expected.match_text || input_text; // puts key in wrong order
expected = Object.assign({match_text: input_text}, expected);
expected = Object.assign({ match_text: input_text }, expected);
const expected_json = JSON.stringify(expected, null, 4);
const actual_json = JSON.stringify(actual, null, 4);
if (expected_json !== actual_json) {
@ -2073,8 +2072,8 @@ All interpretations:`, interpretations);
const fixed_up_input_text = fix_up_speech_recognition(input_text);
if (fixed_up_input_text !== input_text) {
console.error(`Failed test. Speech recognition fixup changed the input from:
'${input_text}' to:
'${fixed_up_input_text}'`);
'${input_text}' to:
'${fixed_up_input_text}'`);
return;
}
}
@ -2085,9 +2084,9 @@ function test_speech(input_text, expected) {
if (typeof expected === "string") {
if (fixed_up_input_text !== expected) {
console.error(`Failed test. Speech recognition fixup changed the input from:
'${input_text}' to:
'${fixed_up_input_text}' instead of:
'${expected}'`);
'${input_text}' to:
'${fixed_up_input_text}' instead of:
'${expected}'`);
return;
}
} else {
@ -2097,40 +2096,40 @@ function test_speech(input_text, expected) {
function test_speech_recognition() {
// test_command("select blue", {color: "blue"}); // @FIXME
test_command("select fill", {tool_id: TOOL_FILL});
test_command("select text", {tool_id: TOOL_TEXT});
test_command("select", {tool_id: TOOL_SELECT});
test_speech("free form select", {tool_id: TOOL_FREE_FORM_SELECT});
test_speech("lips", {match_text: "ellipse", tool_id: TOOL_ELLIPSE});
test_command("select fill", { tool_id: TOOL_FILL });
test_command("select text", { tool_id: TOOL_TEXT });
test_command("select", { tool_id: TOOL_SELECT });
test_speech("free form select", { tool_id: TOOL_FREE_FORM_SELECT });
test_speech("lips", { match_text: "ellipse", tool_id: TOOL_ELLIPSE });
test_command("", null);
// test_command("I got you some new books", null);
// test_command("pan view sorthweast", null); // currently opens View menu
test_command("1 pixel lines", {size: 1});
test_command("1 pixel wide lines", {size: 1});
test_command("set line width to 5", {size: 5});
test_command("1 pixel lines", { size: 1 });
test_command("1 pixel wide lines", { size: 1 });
test_command("set line width to 5", { size: 5 });
// test_command("use medium-small stroke size", {match_text: "use medium-small stroke size", size: NaN});
test_speech("set line lips to a hundred", {match_text: "set line width to a hundred", size: 100});
test_command("use stroke size 10 pixels", {size: 10});
test_speech("set line lips to a hundred", { match_text: "set line width to a hundred", size: 100 });
test_command("use stroke size 10 pixels", { size: 10 });
// test_command("use stroke size of 10 pixels", {match_text: "use stroke size of 10 pixels", size: 10});
test_command("draw a :-)", {sketch_subject: "smiley face"});
test_command("draw a :-)", { sketch_subject: "smiley face" });
// test_command("draw sample text", {sketch_subject: "sample text"}); // @FIXME
test_command("end", {type: "stop-drawing"});
test_command("stop", {type: "stop-drawing"});
test_command("draw a stop sign", {sketch_subject: "stop sign"});
test_command("end", { type: "stop-drawing" });
test_command("stop", { type: "stop-drawing" });
test_command("draw a stop sign", { sketch_subject: "stop sign" });
test_command("pan view southwest", {vector: {x: -1, y: +1}});
test_command("pan southeast", {vector: {x: +1, y: +1}});
test_command("move view northwest", {vector: {x: -1, y: -1}});
test_command("view northwest", {vector: {x: -1, y: -1}});
test_command("move viewport northwest", {vector: {x: -1, y: -1}});
test_command("pan down", {vector: {x: 0, y: +1}});
test_command("scroll down", {vector: {x: 0, y: +1}});
test_command("go downwards", {vector: {x: 0, y: +1}});
test_command("go upward", {vector: {x: 0, y: -1}});
test_command("go downwards and to the left", {vector: {x: -1, y: +1}});
test_command("go up to the left", {vector: {x: -1, y: -1}});
test_speech("cool up", {match_text: "go up", vector: {x: 0, y: -1}});
test_command("scroll the view southward", {vector: {x: 0, y: +1}});
test_command("pan view southwest", { vector: { x: -1, y: +1 } });
test_command("pan southeast", { vector: { x: +1, y: +1 } });
test_command("move view northwest", { vector: { x: -1, y: -1 } });
test_command("view northwest", { vector: { x: -1, y: -1 } });
test_command("move viewport northwest", { vector: { x: -1, y: -1 } });
test_command("pan down", { vector: { x: 0, y: +1 } });
test_command("scroll down", { vector: { x: 0, y: +1 } });
test_command("go downwards", { vector: { x: 0, y: +1 } });
test_command("go upward", { vector: { x: 0, y: -1 } });
test_command("go downwards and to the left", { vector: { x: -1, y: +1 } });
test_command("go up to the left", { vector: { x: -1, y: -1 } });
test_speech("cool up", { match_text: "go up", vector: { x: 0, y: -1 } });
test_command("scroll the view southward", { vector: { x: 0, y: +1 } });
}

View File

@ -3,7 +3,7 @@
const theme_storage_key = "jspaint theme";
const disable_seasonal_theme_key = "jspaint disable seasonal theme";
const href_for = theme => `styles/themes/${theme}`;
let iid;
function wait_for_theme_loaded(theme, callback) {
clearInterval(iid);
@ -55,10 +55,10 @@
localStorage[theme_storage_key] = theme;
localStorage[disable_seasonal_theme_key] = "true"; // any theme change disables seasonal theme (unless of course you select the seasonal theme)
grinch_button?.remove();
// eslint-disable-next-line no-empty
} catch(error) {}
// eslint-disable-next-line no-empty
} catch (error) { }
const signal_theme_load = ()=> {
const signal_theme_load = () => {
$(window).triggerHandler("theme-load");
$(window).trigger("resize"); // not exactly, but get dynamic cursor to update its offset
};

View File

@ -15,9 +15,9 @@ const ChooserCanvas = (
) => {
const c = reuse_canvas(width, height);
let img = ChooserCanvas.cache[url];
if(!img){
if (!img) {
img = ChooserCanvas.cache[url] = E("img");
img.onerror = ()=> {
img.onerror = () => {
delete ChooserCanvas.cache[url];
};
img.src = url;
@ -29,8 +29,8 @@ const ChooserCanvas = (
sourceX, sourceY, sourceWidth, sourceHeight,
destX, destY, destWidth, destHeight
);
// eslint-disable-next-line no-empty
} catch(error) {}
// eslint-disable-next-line no-empty
} catch (error) { }
// if(invert){
// invert_rgb(c.ctx); // can fail due to tainted canvas if running from file: protocol
// }
@ -62,7 +62,7 @@ const ChooserDiv = (
div.classList.add(class_name);
div.style.width = sourceWidth + "px";
div.style.height = sourceHeight + "px";
// @TODO: single listener for all divs
const on_zoom_etc = () => {
const use_svg = (window.devicePixelRatio >= 3 || (window.devicePixelRatio % 1) !== 0);
@ -74,7 +74,7 @@ const ChooserDiv = (
$G.on("theme-load resize", on_zoom_etc);
div._on_zoom_etc = on_zoom_etc;
on_zoom_etc();
div.style.backgroundPosition = `${-sourceX}px ${-sourceY}px`;
div.style.borderColor = "transparent";
div.style.borderStyle = "solid";
@ -92,7 +92,7 @@ const ChooserDiv = (
const $Choose = (things, display, choose, is_chosen, gray_background_for_unselected) => {
const $chooser = $(E("div")).addClass("chooser").css("touch-action", "none");
const choose_thing = (thing) => {
if(is_chosen(thing)){
if (is_chosen(thing)) {
return;
}
choose(thing);
@ -103,11 +103,11 @@ const $Choose = (things, display, choose, is_chosen, gray_background_for_unselec
return;
}
$chooser.empty();
for(let i=0; i<things.length; i++){
for (let i = 0; i < things.length; i++) {
(thing => {
const $option_container = $(E("div")).addClass("chooser-option").appendTo($chooser);
$option_container.data("thing", thing);
const reuse_canvas = (width, height)=> {
const reuse_canvas = (width, height) => {
let option_canvas = $option_container.find("canvas")[0];
if (option_canvas) {
if (option_canvas.width !== width) { option_canvas.width = width; }
@ -144,13 +144,13 @@ const $Choose = (things, display, choose, is_chosen, gray_background_for_unselec
})(things[i]);
}
const onpointerover_while_pointer_down = (event)=> {
const onpointerover_while_pointer_down = (event) => {
const option_container = event.target.closest(".chooser-option");
if (option_container) {
choose_thing($(option_container).data("thing"));
}
};
const ontouchmove_while_pointer_down = (event)=> {
const ontouchmove_while_pointer_down = (event) => {
const touch = event.originalEvent.changedTouches[0];
const target = document.elementFromPoint(touch.clientX, touch.clientY);
const option_container = target.closest(".chooser-option");
@ -159,7 +159,7 @@ const $Choose = (things, display, choose, is_chosen, gray_background_for_unselec
}
event.preventDefault();
};
$chooser.on("pointerdown click", (event)=> {
$chooser.on("pointerdown click", (event) => {
const option_container = event.target.closest(".chooser-option");
if (option_container) {
choose_thing($(option_container).data("thing"));
@ -170,7 +170,7 @@ const $Choose = (things, display, choose, is_chosen, gray_background_for_unselec
$chooser.on("touchmove", ontouchmove_while_pointer_down);
}
});
$G.on("pointerup pointercancel", ()=> {
$G.on("pointerup pointercancel", () => {
$chooser.off("pointerover", onpointerover_while_pointer_down);
$chooser.off("touchmove", ontouchmove_while_pointer_down);
});
@ -180,48 +180,48 @@ const $Choose = (things, display, choose, is_chosen, gray_background_for_unselec
const $ChooseShapeStyle = () => {
const $chooser = $Choose(
[
{stroke: true, fill: false},
{stroke: true, fill: true},
{stroke: false, fill: true}
{ stroke: true, fill: false },
{ stroke: true, fill: true },
{ stroke: false, fill: true }
],
({stroke, fill}, is_chosen, reuse_canvas) => {
({ stroke, fill }, is_chosen, reuse_canvas) => {
const ss_canvas = reuse_canvas(39, 21);
const ss_ctx = ss_canvas.ctx;
// border px inwards amount
let b = 5;
const style = getComputedStyle(ss_canvas);
ss_ctx.fillStyle = is_chosen ? style.getPropertyValue("--HilightText") : style.getPropertyValue("--WindowText");
if(stroke){
if (stroke) {
// just using a solid rectangle for the stroke
// so as not to have to deal with the pixel grid with strokes
ss_ctx.fillRect(b, b, ss_canvas.width-b*2, ss_canvas.height-b*2);
ss_ctx.fillRect(b, b, ss_canvas.width - b * 2, ss_canvas.height - b * 2);
}
// go inward a pixel for the fill
b += 1;
ss_ctx.fillStyle = style.getPropertyValue("--ButtonShadow");
if(fill){
ss_ctx.fillRect(b, b, ss_canvas.width-b*2, ss_canvas.height-b*2);
}else{
ss_ctx.clearRect(b, b, ss_canvas.width-b*2, ss_canvas.height-b*2);
if (fill) {
ss_ctx.fillRect(b, b, ss_canvas.width - b * 2, ss_canvas.height - b * 2);
} else {
ss_ctx.clearRect(b, b, ss_canvas.width - b * 2, ss_canvas.height - b * 2);
}
return ss_canvas;
},
({stroke, fill}) => {
({ stroke, fill }) => {
$chooser.stroke = stroke;
$chooser.fill = fill;
},
({stroke, fill}) => $chooser.stroke === stroke && $chooser.fill === fill
({ stroke, fill }) => $chooser.stroke === stroke && $chooser.fill === fill
).addClass("choose-shape-style");
$chooser.fill = false;
$chooser.stroke = true;
return $chooser;
};
@ -231,9 +231,9 @@ const $choose_brush = $Choose(
const circular_brush_sizes = [7, 4, 1];
const brush_sizes = [8, 5, 2];
const things = [];
brush_shapes.forEach((brush_shape)=> {
brush_shapes.forEach((brush_shape) => {
const sizes = brush_shape === "circle" ? circular_brush_sizes : brush_sizes;
sizes.forEach((brush_size)=> {
sizes.forEach((brush_size) => {
things.push({
shape: brush_shape,
size: brush_size,
@ -245,30 +245,30 @@ const $choose_brush = $Choose(
(o, is_chosen, reuse_canvas) => {
const cb_canvas = reuse_canvas(10, 10);
const style = getComputedStyle(cb_canvas);
const shape = o.shape;
const size = o.size;
const color = is_chosen ? style.getPropertyValue("--HilightText") : style.getPropertyValue("--WindowText");
stamp_brush_canvas(cb_canvas.ctx, 5, 5, shape, size);
replace_colors_with_swatch(cb_canvas.ctx, color);
return cb_canvas;
}, ({shape, size}) => {
}, ({ shape, size }) => {
brush_shape = shape;
brush_size = size;
}, ({shape, size}) => brush_shape === shape && brush_size === size
}, ({ shape, size }) => brush_shape === shape && brush_size === size
).addClass("choose-brush");
const $choose_eraser_size = $Choose(
[4, 6, 8, 10],
(size, is_chosen, reuse_canvas) => {
const ce_canvas = reuse_canvas(39, 16);
const style = getComputedStyle(ce_canvas);
ce_canvas.ctx.fillStyle = is_chosen ? style.getPropertyValue("--HilightText") : style.getPropertyValue("--WindowText");
render_brush(ce_canvas.ctx, "square", size);
return ce_canvas;
},
size => {
@ -285,7 +285,7 @@ const $choose_stroke_size = $Choose(
const center_y = (h - size) / 2;
const style = getComputedStyle(cs_canvas);
cs_canvas.ctx.fillStyle = is_chosen ? style.getPropertyValue("--HilightText") : style.getPropertyValue("--WindowText");
cs_canvas.ctx.fillRect(b, ~~center_y, w - b*2, size);
cs_canvas.ctx.fillRect(b, ~~center_y, w - b * 2, size);
return cs_canvas;
},
size => {
@ -304,11 +304,11 @@ const $choose_magnification = $Choose(
"magnification-option",
is_chosen, // invert if chosen
39, (secret ? 2 : 13), // width, height of destination canvas
i*23, 0, 23, 9, // x, y, width, height from source image
i * 23, 0, 23, 9, // x, y, width, height from source image
8, 2, 23, 9, // x, y, width, height on destination
reuse_div,
);
if(secret){
if (secret) {
$(chooser_el).addClass("secret-option");
}
return chooser_el;
@ -319,30 +319,30 @@ const $choose_magnification = $Choose(
scale => scale === magnification,
true,
).addClass("choose-magnification")
.css({position: "relative"}); // positioning context for above `position: "absolute"` canvas
.css({ position: "relative" }); // positioning context for above `position: "absolute"` canvas
$choose_magnification.on("update", () => {
$choose_magnification
.find(".secret-option")
.parent()
.css({position: "absolute", bottom: "-2px", left: 0, opacity: 0, height: 2, overflow: "hidden" });
.css({ position: "absolute", bottom: "-2px", left: 0, opacity: 0, height: 2, overflow: "hidden" });
});
const airbrush_sizes = [9, 16, 24];
const $choose_airbrush_size = $Choose(
airbrush_sizes,
(size, is_chosen, reuse_canvas) => {
const image_width = 72; // width of source image
const i = airbrush_sizes.indexOf(size); // 0 or 1 or 2
const l = airbrush_sizes.length; // 3
const is_bottom = (i === 2);
const shrink = 4 * !is_bottom;
const w = image_width / l - shrink * 2;
const h = 23;
const source_x = image_width / l * i + shrink;
return ChooserCanvas(
"images/options-airbrush-size.png",
is_chosen, // invert if chosen
@ -368,7 +368,7 @@ const $choose_transparent_mode = $Choose(
return ChooserDiv(
"transparent-mode-option",
false, // never invert it
b+sw+b, b+sh+b, // width, height of created destination canvas
b + sw + b, b + sh + b, // width, height of created destination canvas
0, option ? 22 : 0, sw, sh, // x, y, width, height from source image
b, b, sw, sh, // x, y, width, height on created destination canvas
reuse_div,

File diff suppressed because it is too large Load Diff

View File

@ -30,8 +30,8 @@
cancelAnimationFrame(rAF_ID);
$(rotologo).remove();
$window.css({transform: ""});
$window.css({ transform: "" });
removeEventListener("keydown", space_phase_key_handler);
if (player) {
player.destroy();
@ -71,18 +71,15 @@
$(rotologo).css({
transform:
`perspective(4000px) rotateY(${
Math.sin(Date.now() / 5000)
}turn) rotateX(${
0
`perspective(4000px) rotateY(${Math.sin(Date.now() / 5000)
}turn) rotateX(${0
}turn) translate(-50%, -50%) translateZ(500px)`,
filter:
`hue-rotate(${
Math.sin(Date.now() / 4000)
// @TODO: slow down and stop when you pause
`hue-rotate(${Math.sin(Date.now() / 4000)
// @TODO: slow down and stop when you pause
}turn)`,
});
if ($window.length) {
let el = $window[0];
let offsetLeft = 0;
@ -95,10 +92,8 @@
$window.css({
transform:
`perspective(4000px) rotateY(${
-(offsetLeft + ($window.outerWidth() - parent.innerWidth) / 2) / parent.innerWidth / 3
}turn) rotateX(${
(offsetTop + ($window.outerHeight() - parent.innerHeight) / 2) / parent.innerHeight / 3
`perspective(4000px) rotateY(${-(offsetLeft + ($window.outerWidth() - parent.innerWidth) / 2) / parent.innerWidth / 3
}turn) rotateX(${(offsetTop + ($window.outerHeight() - parent.innerHeight) / 2) / parent.innerHeight / 3
}turn)`,
transformOrigin: "50% 50%",
transformStyle: "preserve-3d",
@ -156,7 +151,7 @@
if (event.data == YT.PlayerState.PLAYING) {
// @TODO: pause and resume this timer with the video
setTimeout(() => {
$(rotologo).css({opacity: 1});
$(rotologo).css({ opacity: 1 });
}, 14150);
}
if (event.data == YT.PlayerState.ENDED) {
@ -167,7 +162,7 @@
// setTimeout/setInterval and check player.getCurrentTime() for when near the end?
// or we might switch to using soundcloud for the audio and so trigger it with that, with a separate video of just clouds
// also fade out the rotologo earlier
$(rotologo).css({opacity: 0});
$(rotologo).css({ opacity: 0 });
// destroy rotologo once faded out
setTimeout(stop_vaporwave, 1200);
}
@ -182,14 +177,14 @@
player.pauseVideo();
is_theoretically_playing = false;
$(player.getIframe())
.add(rotologo)
.css({opacity: "0"});
.add(rotologo)
.css({ opacity: "0" });
} else {
player.playVideo();
is_theoretically_playing = true;
$(player.getIframe())
.add(rotologo)
.css({opacity: ""});
.add(rotologo)
.css({ opacity: "" });
}
e.preventDefault();
// player.getIframe().focus();
@ -207,7 +202,7 @@
};
addEventListener("keydown", Konami.code(toggle_vaporwave));
addEventListener("keydown", (event)=> {
addEventListener("keydown", (event) => {
if (event.key === "Escape") {
stop_vaporwave();
}

View File

@ -1,4 +1,3 @@
<div id="news" hidden>
<article class="news-entry" id="news-3000-aliens">
<h1>Extraterrestrial Life</h1>
@ -17,6 +16,7 @@
background: black;
color: aqua;
}
.news-entry {
background: rgba(0, 255, 255, 0.2);
color: aqua;
@ -26,15 +26,18 @@
max-width: 30em;
margin: 10px;
}
.news-entry > h1 {
.news-entry>h1 {
font-size: 1.3em;
margin: 0;
margin-bottom: 0.5em;
}
.news-entry > time {
.news-entry>time {
font-size: 1.3em;
color: rgba(0, 255, 255, 0.5);
}
.news-entry .new {
color: lime;
}