diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 8a69c3cdc..032c15843 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -15,6 +15,7 @@ "chartjs-adapter-date-fns": "2.0.0", "chartjs-plugin-annotation": "1.4.0", "chartjs-plugin-trendline": "1.0.2", + "color-blend": "4.0.0", "crypto-browserify": "3.12.0", "damerau-levenshtein": "1.0.8", "date-fns": "2.28.0", @@ -5027,6 +5028,14 @@ "node": ">=0.10.0" } }, + "node_modules/color-blend": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/color-blend/-/color-blend-4.0.0.tgz", + "integrity": "sha512-fYODTHhI/NG+B5GnzvuL3kiFrK/UnkUezWFTgEPBTY5V+kpyfAn95Vn9sJeeCX6omrCOdxnqCL3CvH+6sXtIbw==", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -21733,6 +21742,11 @@ "object-visit": "^1.0.0" } }, + "color-blend": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/color-blend/-/color-blend-4.0.0.tgz", + "integrity": "sha512-fYODTHhI/NG+B5GnzvuL3kiFrK/UnkUezWFTgEPBTY5V+kpyfAn95Vn9sJeeCX6omrCOdxnqCL3CvH+6sXtIbw==" + }, "color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", diff --git a/frontend/package.json b/frontend/package.json index 81c4aa847..d965c63d3 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -64,6 +64,7 @@ "chartjs-adapter-date-fns": "2.0.0", "chartjs-plugin-annotation": "1.4.0", "chartjs-plugin-trendline": "1.0.2", + "color-blend": "4.0.0", "crypto-browserify": "3.12.0", "damerau-levenshtein": "1.0.8", "date-fns": "2.28.0", diff --git a/frontend/src/styles/test.scss b/frontend/src/styles/test.scss index e86a9d43b..5936e82af 100644 --- a/frontend/src/styles/test.scss +++ b/frontend/src/styles/test.scss @@ -504,22 +504,9 @@ place-content: center center; } .box:nth-child(1) { - background: var(--colorful-error-color); border-radius: var(--roundness) 0 0 var(--roundness); } - .box:nth-child(2) { - background: var(--colorful-error-color); - filter: opacity(0.6); - } - .box:nth-child(3) { - background: var(--sub-color); - } - .box:nth-child(4) { - background: var(--main-color); - filter: opacity(0.6); - } .box:nth-child(5) { - background: var(--main-color); border-radius: 0 var(--roundness) var(--roundness) 0; } } @@ -546,25 +533,8 @@ letter.incorrect.extra { color: var(--error-extra-color); } - &.unreached letter { - filter: opacity(0.2); - } - &.heatmap0 letter { - color: var(--colorful-error-color); - } - &.heatmap1 letter { - color: var(--colorful-error-color); - filter: opacity(0.6); - } - &.heatmap2 letter { - color: var(--sub-color); - } - &.heatmap3 letter { - color: var(--main-color); - filter: opacity(0.6); - } - &.heatmap4 letter { - color: var(--main-color); + &.heatmapInherit letter { + color: inherit; } } &.rightToLeftTest { diff --git a/frontend/src/ts/popups/result-tags-popup.ts b/frontend/src/ts/popups/result-tags-popup.ts index e1881b657..b626a43c1 100644 --- a/frontend/src/ts/popups/result-tags-popup.ts +++ b/frontend/src/ts/popups/result-tags-popup.ts @@ -241,4 +241,11 @@ $("#resultEditTagsPanelWrapper .confirmButton").on("click", async () => { } }); +$(document).on("keydown", (event) => { + if (event.key === "Escape" && isPopupVisible(wrapperId)) { + hide(); + event.preventDefault(); + } +}); + Skeleton.save(wrapperId); diff --git a/frontend/src/ts/test/test-ui.ts b/frontend/src/ts/test/test-ui.ts index ba8c38127..b955deddb 100644 --- a/frontend/src/ts/test/test-ui.ts +++ b/frontend/src/ts/test/test-ui.ts @@ -51,6 +51,7 @@ ConfigEvent.subscribe((eventKey, eventValue, nosave) => { } if (eventKey === "fontSize" && !nosave) { updateWordsHeight(true); + updateWordsInputPosition(true); } if (eventValue === undefined || typeof eventValue !== "boolean") return; @@ -137,8 +138,8 @@ export function updateActiveElement( }); } } catch (e) {} - if (initial || shouldUpdateWordsInputPosition()) { - updateWordsInputPosition(initial); + if (!initial && shouldUpdateWordsInputPosition()) { + updateWordsInputPosition(); } } @@ -220,8 +221,21 @@ export function updateWordsInputPosition(initial = false): void { return; } + const computed = window.getComputedStyle(activeWord); + const activeWordMargin = + parseInt(computed.marginTop) + parseInt(computed.marginBottom); + + const wordsWrapperTop = + (document.querySelector("#wordsWrapper") as HTMLElement | null) + ?.offsetTop || 0; + if (Config.tapeMode !== "off") { - el.style.top = activeWord.offsetTop + "px"; + el.style.top = + wordsWrapperTop + + activeWord.offsetHeight + + activeWordMargin * 0.25 + + -el.offsetHeight + + "px"; el.style.left = activeWord.offsetLeft + "px"; return; } @@ -231,10 +245,22 @@ export function updateWordsInputPosition(initial = false): void { !posUpdateLangList.some((l) => Config.language.startsWith(l)) ) { el.style.left = "0px"; - el.style.top = activeWord.offsetHeight * 2 + "px"; + el.style.top = + wordsWrapperTop + + activeWord.offsetHeight * 2 + + activeWordMargin * 1.5 + + -el.offsetHeight + + "px"; } else { - el.style.top = activeWord.offsetTop + "px"; el.style.left = activeWord.offsetLeft + "px"; + el.style.top = + activeWord.offsetTop - + activeWordMargin + + wordsWrapperTop + + activeWord.offsetHeight + + activeWordMargin + + -el.offsetHeight + + "px"; } } @@ -985,7 +1011,7 @@ export function toggleResultWords(): void { } } -export function applyBurstHeatmap(): void { +export async function applyBurstHeatmap(): Promise { if (Config.burstHeatmap) { $("#resultWordsHistory .heatmapLegend").removeClass("hidden"); @@ -1007,26 +1033,39 @@ export function applyBurstHeatmap(): void { adatm.push(Math.abs(median - burst)); }); const step = Misc.mean(adatm); + + const themeColors = await ThemeColors.getAll(); + + const colors = [ + themeColors.colorfulError, + Misc.blendTwoHexColors(themeColors.colorfulError, themeColors.text), + themeColors.text, + Misc.blendTwoHexColors(themeColors.main, themeColors.text), + themeColors.main, + ]; + + const unreachedColor = themeColors.sub; + const steps = [ { val: 0, - class: "heatmap0", + colorId: 0, }, { val: median - step * 1.5, - class: "heatmap1", + colorId: 1, }, { val: median - step * 0.5, - class: "heatmap2", + colorId: 2, }, { val: median + step * 0.5, - class: "heatmap3", + colorId: 3, }, { val: median + step * 1.5, - class: "heatmap4", + colorId: 4, }, ]; @@ -1048,26 +1087,29 @@ export function applyBurstHeatmap(): void { }); $("#resultWordsHistory .words .word").each((_, word) => { - let cls = ""; const wordBurstAttr = $(word).attr("burst"); if (wordBurstAttr === undefined) { - cls = "unreached"; + $(word).css("color", unreachedColor); } else { const wordBurstVal = parseInt(wordBurstAttr); steps.forEach((step) => { - if (wordBurstVal >= step.val) cls = step.class; + if (wordBurstVal >= step.val) { + $(word).addClass("heatmapInherit"); + $(word).css("color", colors[step.colorId]); + } }); } - $(word).addClass(cls); + }); + + $("#resultWordsHistory .heatmapLegend .boxes .box").each((index, box) => { + $(box).css("background", colors[index]); }); } else { $("#resultWordsHistory .heatmapLegend").addClass("hidden"); - $("#resultWordsHistory .words .word").removeClass("heatmap0"); - $("#resultWordsHistory .words .word").removeClass("heatmap1"); - $("#resultWordsHistory .words .word").removeClass("heatmap2"); - $("#resultWordsHistory .words .word").removeClass("heatmap3"); - $("#resultWordsHistory .words .word").removeClass("heatmap4"); - $("#resultWordsHistory .words .word").removeClass("unreached"); + $("#resultWordsHistory .words .word").removeClass("heatmapInherit"); + $("#resultWordsHistory .words .word").css("color", ""); + + $("#resultWordsHistory .heatmapLegend .boxes .box").css("color", ""); } } diff --git a/frontend/src/ts/utils/misc.ts b/frontend/src/ts/utils/misc.ts index de528c598..9b3c20110 100644 --- a/frontend/src/ts/utils/misc.ts +++ b/frontend/src/ts/utils/misc.ts @@ -1,4 +1,5 @@ import * as Loader from "../elements/loader"; +import { normal as normalBlend } from "color-blend"; async function fetchJson(url: string): Promise { try { @@ -235,6 +236,62 @@ export async function getContributorsList(): Promise { } } +export function blendTwoHexColors(color1: string, color2: string): string { + const rgb1 = hexToRgb(color1); + const rgb2 = hexToRgb(color2); + + if (rgb1 && rgb2) { + const rgba1 = { + r: rgb1.r, + g: rgb1.g, + b: rgb1.b, + a: 1, + }; + const rgba2 = { + r: rgb2.r, + g: rgb2.g, + b: rgb2.b, + a: 0.5, + }; + const blended = normalBlend(rgba1, rgba2); + console.log(blended); + return rgbToHex(blended.r, blended.g, blended.b); + } else { + return "#000000"; + } +} + +function hexToRgb(hex: string): + | { + r: number; + g: number; + b: number; + } + | undefined { + if (hex.length != 4 && hex.length != 7 && !hex.startsWith("#")) { + return undefined; + } + let r: number; + let g: number; + let b: number; + if (hex.length == 4) { + r = ("0x" + hex[1] + hex[1]) as unknown as number; + g = ("0x" + hex[2] + hex[2]) as unknown as number; + b = ("0x" + hex[3] + hex[3]) as unknown as number; + } else if (hex.length == 7) { + r = ("0x" + hex[1] + hex[2]) as unknown as number; + g = ("0x" + hex[3] + hex[4]) as unknown as number; + b = ("0x" + hex[5] + hex[6]) as unknown as number; + } else { + return undefined; + } + return { r, g, b }; +} + +function rgbToHex(r: number, g: number, b: number): string { + return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1); +} + function hexToHSL(hex: string): { hue: number; sat: number; diff --git a/frontend/static/html/pages/test.html b/frontend/static/html/pages/test.html index 349b0807f..17bfe952b 100644 --- a/frontend/static/html/pages/test.html +++ b/frontend/static/html/pages/test.html @@ -113,20 +113,20 @@ Click here or start typing to focus +
-
diff --git a/frontend/static/languages/code_lua.json b/frontend/static/languages/code_lua.json index 73646834a..fc5dc134e 100644 --- a/frontend/static/languages/code_lua.json +++ b/frontend/static/languages/code_lua.json @@ -46,6 +46,7 @@ "rawset", "require", "select", + "self", "setfenv", "setmetatable", "tonumber", diff --git a/frontend/static/quotes/russian.json b/frontend/static/quotes/russian.json index 028278b87..fbe37c9c3 100644 --- a/frontend/static/quotes/russian.json +++ b/frontend/static/quotes/russian.json @@ -1660,7 +1660,7 @@ { "id": 279, "source": "Кундера Милан - Неспешность", - "text": "Дружба необходима человеку для того, чтобы у него как следует работала память. Помнить о своём прошлом, вечно хранить его в душe - таково необходимое условие, позволяющее нам, как говорится, сберечь цельность нашего \"я\". Чтобы это \"я\" не съёживалось, не утрачивало своей полноты, его нужно орошать воспоминаниями, как горшок с цветами, а такая поливка невозможна без постоянного общения со свидетелями прошлого, то есть с друзьями. Они - наше зеркало, наша память; от них требуется лишь одно - хотя бы время от времени протирать это зеркало, чтобы мы могли в него смотреться.", + "text": "Дружба необходима человеку для того, чтобы у него как следует работала память. Помнить о своём прошлом, вечно хранить его в душе - таково необходимое условие, позволяющее нам, как говорится, сберечь цельность нашего \"я\". Чтобы это \"я\" не съёживалось, не утрачивало своей полноты, его нужно орошать воспоминаниями, как горшок с цветами, а такая поливка невозможна без постоянного общения со свидетелями прошлого, то есть с друзьями. Они - наше зеркало, наша память; от них требуется лишь одно - хотя бы время от времени протирать это зеркало, чтобы мы могли в него смотреться.", "length": 575 }, {