-
-
-
+
+
@@ -10,24 +19,19 @@
-
+
-
+
-
@@ -88,12 +110,15 @@ body {
@page {
margin: 0;
padding: 0;
+ size: v-bind(formatStyle);
}
+
@media print {
button {
display: none;
}
}
+
.page {
height: v-bind(pageHeight);
width: v-bind(pageWidth);
@@ -101,25 +126,48 @@ body {
position: relative;
break-inside: avoid;
box-sizing: border-box;
- .body {
+
+ .content {
display: flex;
flex-direction: column;
- align-items: center;
- justify-content: space-around;
height: 100%;
- width: 100%;
+ justify-content: space-between;
+
+ .body {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: space-around;
+ height: 100%;
+ width: 100%;
+ }
+
+ .footer {
+ .pagination {
+ width: 100%;
+ display: flex;
+ flex-direction: row;
+ font: v-bind(paginationFont);
+ color: v-bind(paginationColor);
+ justify-content: v-bind(paginationJustify);
+ margin: v-bind(paginationMargin);
+ }
+ }
}
+
.borders {
.border {
position: absolute;
border-right: 1px dashed black;
border-bottom: 1px dashed black;
}
+
.margin {
position: absolute;
border-right: 1px dashed red;
border-bottom: 1px dashed red;
}
+
.margin.left,
.margin.right,
.border.left,
@@ -128,6 +176,7 @@ body {
bottom: 0;
width: 0;
}
+
.margin.top,
.margin.bottom,
.border.top,
diff --git a/client/src/components/Solutions.vue b/client/src/components/Solutions.vue
index bcd0871..367bbb5 100644
--- a/client/src/components/Solutions.vue
+++ b/client/src/components/Solutions.vue
@@ -1,21 +1,13 @@
-
-
+
+
+
- {{ j + 1 }}
-
+ {{ j + solutionStyle.pagination.startIdx }}
+
@@ -23,12 +15,19 @@
+
+
\ No newline at end of file
diff --git a/client/src/components/WordsIndex.vue b/client/src/components/WordsIndex.vue
index f6f94f5..8cb7db2 100644
--- a/client/src/components/WordsIndex.vue
+++ b/client/src/components/WordsIndex.vue
@@ -1,42 +1,36 @@
-
-
+
+
+
+
-
+
{{ word }}
-
-
-
+
+
+
+
+
diff --git a/client/src/components/fonts/FontSelector.vue b/client/src/components/fonts/FontSelector.vue
new file mode 100644
index 0000000..46d3cf4
--- /dev/null
+++ b/client/src/components/fonts/FontSelector.vue
@@ -0,0 +1,89 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/client/src/components/fonts/load-font.ts b/client/src/components/fonts/load-font.ts
new file mode 100644
index 0000000..b16d75f
--- /dev/null
+++ b/client/src/components/fonts/load-font.ts
@@ -0,0 +1,18 @@
+import { LocalFont } from "grid";
+import { api } from "../../api";
+export const loadedFonts = new Set();
+
+export function loadFont(font: LocalFont) {
+ if (loadedFonts.has(font.family)) {
+ return Promise.resolve();
+ }
+ return api.db.getFont(font.family)
+ .then((font) => {
+ const fontface = new FontFace(font.family, `url(${font.content})`);
+ return fontface.load();
+ })
+ .then((fontface) => {
+ loadedFonts.add(font.family);
+ document.fonts.add(fontface);
+ });
+}
\ No newline at end of file
diff --git a/client/src/components/forms/AlignmentSelect.vue b/client/src/components/forms/AlignmentSelect.vue
new file mode 100644
index 0000000..656f5aa
--- /dev/null
+++ b/client/src/components/forms/AlignmentSelect.vue
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/client/src/components/forms/BannedWords.vue b/client/src/components/forms/BannedWords.vue
new file mode 100644
index 0000000..102d26f
--- /dev/null
+++ b/client/src/components/forms/BannedWords.vue
@@ -0,0 +1,112 @@
+
+
+
+
+
+
+
+ {{ $t("forms.bannedwords") }}
+
+ unban(word)">
+ {{ word }}
+
+
+
+
+
+
+
+
+../../worker
\ No newline at end of file
diff --git a/client/src/components/forms/ExportOptions.vue b/client/src/components/forms/ExportOptions.vue
new file mode 100644
index 0000000..82ac3c6
--- /dev/null
+++ b/client/src/components/forms/ExportOptions.vue
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/client/src/components/forms/FormatPicker.vue b/client/src/components/forms/FormatPicker.vue
index 1dc203b..170074c 100644
--- a/client/src/components/forms/FormatPicker.vue
+++ b/client/src/components/forms/FormatPicker.vue
@@ -1,6 +1,6 @@
- {{ $t('forms.format') }}
+ {{ $t("forms.format") }}
@@ -13,31 +13,8 @@
- {{ $t('forms.margins') }}
-
-
-
-
-
-
-
-
-
-
-
-
+ {{ $t("forms.margins") }}
+
@@ -45,6 +22,7 @@
import { ref, defineProps, defineEmits, computed, watchEffect } from "vue";
import { useModel } from "../../js/useModel";
import { Format } from "grid";
+import MarginForm from "./MarginForm.vue";
/**
* Form to edit paper format and margins
*/
@@ -130,7 +108,7 @@ const format = computed({
},
});
-const value = useModel(props, emit);
+const value = useModel(props, emit);
\ No newline at end of file
diff --git a/client/src/components/forms/MarginForm.vue b/client/src/components/forms/MarginForm.vue
new file mode 100644
index 0000000..e62c3d5
--- /dev/null
+++ b/client/src/components/forms/MarginForm.vue
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/client/src/components/forms/ModalOptions.vue b/client/src/components/forms/ModalOptions.vue
index ffb3f66..0877a6d 100644
--- a/client/src/components/forms/ModalOptions.vue
+++ b/client/src/components/forms/ModalOptions.vue
@@ -5,88 +5,43 @@
-
+
{{ value.title }}
-
-
-
-
+
-
+ }" />
-
+
-
+
{{ $t("forms.randomize") }}
-
+
-
+
- {{
+ {{
$t("buttons.no")
}}
- {{
+ {{
$t("buttons.yes")
}}
@@ -110,7 +65,7 @@ import { Grid } from "grid";
import { useModel } from "../../js/useModel";
import generate from "../../js/maze-generator";
import { api } from "../../api";
-import { workerController } from "../../search-worker";
+import { workerController } from "../../worker";
/**
* Form to edit grid metadata: rows, cols. title, comment and options
@@ -121,9 +76,10 @@ const props = defineProps<{
*/
modelValue: Grid;
}>();
-const opts = ref<{ label: string; value: string }[]>([]);
+const opts = ref<{ label: string; value: string; }[]>([]);
const randomConfirmVisible = ref(false);
const visible = ref(false);
+const generating = ref(false);
const emit = defineEmits<{
/**
* v-model event
@@ -139,7 +95,7 @@ const emit = defineEmits<{
*/
(event: "open"): void;
}>();
-const value = useModel(props, emit);
+const value = useModel(props, emit);
function onUpdate(path: string, newvalue: string | number) {
value.value[path] = newvalue;
nextTick(() => {
@@ -147,28 +103,32 @@ function onUpdate(path: string, newvalue: string | number) {
});
}
function onRandomize() {
- workerController.getDistribution().then((distribution) => {
- generate({ grid: value.value, distribution });
- emit("update-size", value.value);
- randomConfirmVisible.value = false;
- });
+ generating.value = true;
+ nextTick()
+ .then(() => workerController.getDistribution())
+ .then((distribution) => {
+ generate({ grid: value.value, distribution });
+ emit("update-size", value.value);
+ randomConfirmVisible.value = false;
+ generating.value = false;
+ });
}
watchEffect(() => {
if (!visible.value) return;
emit("open");
});
-watch(value.value, () => {
- if (!visible.value) return;
- emit("update:modelValue", value.value);
-});
+// watch(value.value, () => {
+// if (!visible.value) return;
+// emit("update:modelValue", value.value);
+// });
const defaultSelectOpt = computed(() => {
if (!props.modelValue) return "default";
- return opts.value.find((opt) => opt.value === props.modelValue.optionsId)
+ return opts.value.find((opt) => opt.value === props.modelValue.styleId)
?.label;
});
onMounted(() => {
api.db
- .getOptions()
+ .getStyles()
.then((res) => {
opts.value = res.map((opt) => {
return { label: opt.name, value: opt.id };
@@ -184,11 +144,13 @@ onMounted(() => {
.n-form {
width: 100%;
}
+
.rowcols {
display: flex;
justify-content: space-between;
}
-.rowcols > .n-form-item {
+
+.rowcols>.n-form-item {
max-width: 100px;
}
-
\ No newline at end of file
+../../worker
\ No newline at end of file
diff --git a/client/src/components/forms/PaginationStyle.vue b/client/src/components/forms/PaginationStyle.vue
new file mode 100644
index 0000000..9520d8a
--- /dev/null
+++ b/client/src/components/forms/PaginationStyle.vue
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/client/src/components/forms/Sizeinput.vue b/client/src/components/forms/Sizeinput.vue
index a508895..c7c4cf5 100644
--- a/client/src/components/forms/Sizeinput.vue
+++ b/client/src/components/forms/Sizeinput.vue
@@ -11,11 +11,12 @@ import { ref, defineProps, defineEmits, computed } from "vue";
* Form edit a string value like "10px" or "10mm"
* Has a unit picker and a number input
*/
-const props = defineProps<{
+const props = defineProps<{
/**
* The value to edit
*/
- modelValue: string }>();
+ modelValue: string;
+}>();
const emit = defineEmits<{
/**
* v-model event
@@ -23,15 +24,12 @@ const emit = defineEmits<{
*/
(event: "update:modelValue", value: string): void;
}>();
-const units = ["cm", "mm", "px", "pt", "em"];
+const units = ["cm", "mm", "px", "pt", "em", "rem"];
const unitSet = new Set(units);
-const options = ref([
- units.map((u) => {
- return { label: u, value: u };
- }),
-]);
-
+const options = ref(units.map((u) => {
+ return { label: u, value: u };
+}));
const value = computed({
get: () => {
const res = +props.modelValue.slice(0, -2);
@@ -43,7 +41,6 @@ const value = computed({
value ? `${value}${unit.value}` : `0${unit.value}`
),
});
-
const unit = computed({
get: () => {
const res = props.modelValue.slice(-2);
@@ -51,13 +48,13 @@ const unit = computed({
},
set: (unit) => emit("update:modelValue", `${value.value}${unit}`),
});
-
\ No newline at end of file
diff --git a/client/src/components/forms/SolutionsStyleForm.vue b/client/src/components/forms/SolutionsStyleForm.vue
new file mode 100644
index 0000000..bcacec4
--- /dev/null
+++ b/client/src/components/forms/SolutionsStyleForm.vue
@@ -0,0 +1,55 @@
+
+ {{ $t("forms.gridNum") }}
+
+
+ {{ $t("forms.wordIndex") }}
+
+ {{ $t("forms.wordLength") }}
+
+ {{ $t("forms.pagination") }}
+
+ {{ $t("forms.solutionsGrid") }}
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/client/src/components/forms/TextStyle.vue b/client/src/components/forms/TextStyle.vue
deleted file mode 100644
index 7caa3e7..0000000
--- a/client/src/components/forms/TextStyle.vue
+++ /dev/null
@@ -1,56 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/client/src/components/forms/Words.vue b/client/src/components/forms/Words.vue
index 83801e7..08fd1cf 100644
--- a/client/src/components/forms/Words.vue
+++ b/client/src/components/forms/Words.vue
@@ -2,31 +2,17 @@
-
+
-
+
{{ $t("forms.myWords") }}
-
+ deleteWord(word)">
{{ word }}
@@ -34,7 +20,7 @@
-
diff --git a/client/src/components/sidebars/Autofill.vue b/client/src/components/sidebars/Autofill.vue
new file mode 100644
index 0000000..36ae762
--- /dev/null
+++ b/client/src/components/sidebars/Autofill.vue
@@ -0,0 +1,132 @@
+
+
+
+
+ onDelete(i)">{{ word }}
+
+
+
+ Run
+
+
+
+
+
+../../worker
\ No newline at end of file
diff --git a/client/src/components/sidebars/Buttons.vue b/client/src/components/sidebars/Buttons.vue
new file mode 100644
index 0000000..c63fe77
--- /dev/null
+++ b/client/src/components/sidebars/Buttons.vue
@@ -0,0 +1,135 @@
+
+
+
+ {{ orderingText() }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/client/src/components/sidebars/Definition.vue b/client/src/components/sidebars/Definition.vue
new file mode 100644
index 0000000..a962770
--- /dev/null
+++ b/client/src/components/sidebars/Definition.vue
@@ -0,0 +1,168 @@
+
+
+
+ {{ $t(`errors.${error}`) }}
+
+
+
+
+
+
+
+
+../../worker
\ No newline at end of file
diff --git a/client/src/components/Suggestion.vue b/client/src/components/sidebars/Suggestion.vue
similarity index 55%
rename from client/src/components/Suggestion.vue
rename to client/src/components/sidebars/Suggestion.vue
index 1519f42..0f47113 100644
--- a/client/src/components/Suggestion.vue
+++ b/client/src/components/sidebars/Suggestion.vue
@@ -1,69 +1,27 @@
-
-
- {{ orderingText() }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
\ No newline at end of file
diff --git a/client/src/components/svg-renderer/Grid.vue b/client/src/components/svg-renderer/Grid.vue
index ee4eee9..a55e042 100644
--- a/client/src/components/svg-renderer/Grid.vue
+++ b/client/src/components/svg-renderer/Grid.vue
@@ -1,170 +1,70 @@
-
+
+
+
-
-
-
-
-
-
- {{ sp.text }}
-
-
-
- {{ cell.text || cell.suggestion }}
+
+
+
+
+
+ {{ sp.text }}
+
+ {{ cell.text || cell.suggestion }}
+
-
-
-
-
+
+
+
+
-
-
-
+
+
+
-
+
-
+
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/client/src/components/svg-renderer/GridHighlight.vue b/client/src/components/svg-renderer/GridHighlight.vue
index fae50c8..5c68de8 100644
--- a/client/src/components/svg-renderer/GridHighlight.vue
+++ b/client/src/components/svg-renderer/GridHighlight.vue
@@ -1,16 +1,28 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
-
+
+
{{ $t("tooltips.add", { word }) }}
@@ -19,14 +31,12 @@
{{ word }} {{ $t("tooltips.incomplete") }}
- {{ word }} {{ $t("tooltips.nodef") }}
+ {{ word }} {{ $t("tooltips.nodef") }}
- {{ word }} {{ $t("tooltips.noarrow") }}
-
+ {{ word }} {{ $t("tooltips.noarrow") }}
+
+ {{ word }} {{ $t("tooltips.toomanyarrows") }}
+
{{ hotLetters }}
@@ -41,6 +51,7 @@ import {
defineProps,
ref,
toRaw,
+ watch,
watchEffect,
} from "vue";
import {
@@ -48,19 +59,21 @@ import {
CellProba,
Direction,
Grid,
- GridOptions,
+ GridStyle,
GridValidity,
nullCell,
} from "grid";
+import throttle from "lodash.throttle";
import {
cellAndBorderSize,
useTransform,
} from "./utils";
import chroma from "chroma-js";
-import { dico } from "../../search-worker/dico";
import { api } from "../../api";
+import { Mode } from "../../types";
+import { workerController } from "../../worker";
+import { onMounted } from "vue";
-export type Mode = "normal" | "check" | "heatmap";
/**
* Button to add words from the grid to the dictionnary
@@ -74,11 +87,10 @@ const props = defineProps<{
mode: Mode;
grid: Grid;
gridVersion: number;
- options: GridOptions;
+ style: GridStyle;
zoom: number;
offset: [number, number];
dir: Direction;
- validity?: GridValidity;
}>();
const emit = defineEmits<{
/**
@@ -94,12 +106,21 @@ const hovered = ref(false);
const hotLetters = ref("");
const cellHeat = ref("");
const width = ref(0);
+const validity = ref();
const height = ref(0);
const visible = computed(() => {
return !Grid.equal(props.cell, nullCell) && !props.cell.definition;
});
const cellWidth = ref(0);
const tooltip = ref("");
+function refreshValidity() {
+ workerController.checkGrid(props.grid);
+}
+const throttledRefresValidity = throttle(refreshValidity, 60);
+workerController.on("check-result", (data) => {
+ validity.value = data;
+});
+
watchEffect(() => {
if (hovered.value) return;
if (props.mode === "heatmap") {
@@ -113,10 +134,11 @@ watchEffect(() => {
return;
}
const bounds = props.grid.getBounds(props.cell, props.dir);
- if (!bounds || !bounds.length || bounds.length === 1 || !props.validity) {
+ if (!bounds || !bounds.length || bounds.length === 1 || !validity.value) {
transform.value = "";
width.value = 0;
height.value = 0;
+ tooltip.value = "";
return;
}
const { cells, length } = bounds;
@@ -130,9 +152,34 @@ watchEffect(() => {
props.dir === "horizontal" ? 1 : length
);
transform.value = useTransform(props, cells[0]);
- const validity =
- props.validity[props.dir][`${bounds.start.y}-${bounds.start.x}`];
- tooltip.value = validity ? validity.problem : "";
+ const cellVal =
+ validity.value[props.dir][`${bounds.start.y}-${bounds.start.x}`];
+ tooltip.value = cellVal ? cellVal.problem : "";
+});
+
+const highlights = computed(() => {
+ if (!validity.value || props.mode !== 'check') return [];
+ const res = Object.entries(validity.value[props.dir])
+ .map(([key, { problem }]) => {
+ const [y, x] = key.split("-").map(Number);
+ const bounds = props.grid.getBounds({ x, y }, props.dir);
+ return {
+ key,
+ style: {
+ width: cellAndBorderSize(
+ props,
+ props.dir === "horizontal" ? bounds.length : 1
+ ),
+ height: cellAndBorderSize(
+ props,
+ props.dir === "horizontal" ? 1 : bounds.length
+ ),
+ transform: useTransform(props, bounds.cells[0])
+ },
+ problem,
+ };
+ });
+ return res;
});
function getLetters(cell: Cell, heatmapLetters: CellProba[][]) {
@@ -151,37 +198,6 @@ function getLetters(cell: Cell, heatmapLetters: CellProba[][]) {
.join(", ");
}
-function getHeat(cell: Cell, heatmapLetters: CellProba[][]) {
- if (!heatmapLetters || !cell) return "";
- const row = heatmapLetters[cell.y];
- if (!row) return "";
- const cellHeatmap = row[cell.x];
- if (!cellHeatmap) return "";
- const {
- horizontal,
- vertical,
- validH,
- validV,
- empty,
- bestWordsH,
- bestWordsV,
- } = cellHeatmap;
- const bestWords = (
- (props.dir === "horizontal" ? bestWordsH : bestWordsV) || []
- )
- .slice(0, 5)
- .reduce((acc, index) => {
- acc.push(dico.words[dico.sorted[index]]);
- return acc;
- }, []);
- if ((!validH && !validV) || !empty) return "";
- return bestWords.join(", ");
- // if (validH && validV)
- // return `Horizontal: ${horizontal}, Vertical: ${vertical}`;
- // if (!validV) return `Horizontal: ${horizontal}`;
- // if (!validH) return `Vertical: ${vertical}`;
-}
-
const colorScale = chroma.scale(["red", "#22C", "#014"]).mode("lab");
async function refreshHeatmap() {
const inters = props.cellProbas.map((row) =>
@@ -222,7 +238,9 @@ async function refreshHeatmap() {
if (!ctx) return;
colors.forEach((row, i) => {
row.forEach((color, j) => {
- if (!props.cellProbas[i][j].empty || !color) return;
+ if (!props.cellProbas[i][j].empty
+ || props.grid.cells[i][j].definition
+ || !color) return;
const [r, g, b, a] = color;
ctx.fillStyle = `rgba(${r},${g},${b},${a})`;
@@ -234,21 +252,42 @@ async function refreshHeatmap() {
watchEffect(() => {
hotLetters.value = getLetters(props.cell, props.cellProbas);
- // cellHeat.value = getHeat(props.cell, props.cellProbas);
refreshHeatmap();
});
-function onMouseMove(evt: MouseEvent) {}
+watch([props.mode, props.grid], () => {
+ throttledRefresValidity();
+});
+onMounted(() => {
+ throttledRefresValidity();
+});
+function onMouseMove(evt: MouseEvent) { }
function add() {
api.db.pushWord(toRaw(word.value)).then(() => {
tooltip.value = "";
emit("update");
});
}
+/*
+"rgba(255, 107, 107, 0.8)",
+"rgba(255, 209, 102, 0.8)",
+"rgba(6, 214, 160, 0.8)",
+"rgba(17, 138, 178, 0.8)",
+"rgba(255, 127, 80, 0.8)",
+"rgba(94, 96, 206, 0.8)"
+*/
-
\ No newline at end of file
+
+.gridhighlightcontainer>.lines {
+ position: relative;
+}
+
+.gridhighlightcontainer>.lines>span {
+ width: 1500px;
+ height: 1px;
+ background-color: red;
+ position: absolute;
+}
+
+.gridhighlightcontainer>.v-lines {
+ position: relative;
+}
+
+.gridhighlightcontainer>.v-lines>span {
+ left: 204px;
+ width: 102px;
+ height: 29px;
+ background-color: red;
+ position: absolute;
+ opacity: 0.5;
+}
+
+.problem {
+ z-index: -1;
+}
+
+.incomplete {
+ background-color: rgba(255, 107, 107, 0.8);
+}
+
+.nodef {
+ background-color: rgba(17, 138, 178, 0.8);
+}
+
+.noarrow {
+ background-color: rgba(255, 209, 102, 0.8);
+}
+
+.too-many-arrows {
+ background-color: rgba(6, 214, 160, 0.8);
+}
+
diff --git a/client/src/components/svg-renderer/GridInput.vue b/client/src/components/svg-renderer/GridInput.vue
index 94b50bf..5e2793d 100644
--- a/client/src/components/svg-renderer/GridInput.vue
+++ b/client/src/components/svg-renderer/GridInput.vue
@@ -1,45 +1,24 @@