Fix misaligned image occlusions (#2512)

* Cloze styling is not required in I/O notetype

* Use raw string for IO template

* Rename to notetype.css and use more specific ids

* Move internal i/o styling into runtime

Storing it in the notetype makes it difficult to make changes, and
makes it easier for the user to break.

* Fix misaligned occlusions

At larger screen sizes, the canvas was not increasing above its configured
size, so it ended up being placed top center instead of expanding to fit
the entire container area.

To resolve this, both the image and canvas are forced to the container
size, and the container is constrained to the size of the viewport,
with the same aspect ratio as the image.

Closes #2492
This commit is contained in:
Damien Elmes 2023-05-23 11:59:50 +10:00 committed by GitHub
parent 581f82c589
commit 7686cb8de8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 64 additions and 70 deletions

View File

@ -394,7 +394,7 @@ fn build_and_check_editor(build: &mut Build) -> Result<()> {
} }
fn build_and_check_reviewer(build: &mut Build) -> Result<()> { fn build_and_check_reviewer(build: &mut Build) -> Result<()> {
let reviewer_deps = inputs![":ts:lib", glob!("ts/reviewer/**")]; let reviewer_deps = inputs![":ts:lib", glob!("ts/reviewer/**"),];
build.add( build.add(
"ts:reviewer:reviewer.js", "ts:reviewer:reviewer.js",
EsbuildScript { EsbuildScript {
@ -410,7 +410,7 @@ fn build_and_check_reviewer(build: &mut Build) -> Result<()> {
CompileSass { CompileSass {
input: inputs!["ts/reviewer/reviewer.scss"], input: inputs!["ts/reviewer/reviewer.scss"],
output: "ts/reviewer/reviewer.css", output: "ts/reviewer/reviewer.css",
deps: ":sass".into(), deps: inputs![":sass", "ts/image-occlusion/review.scss"],
load_paths: vec!["."], load_paths: vec!["."],
}, },
)?; )?;

View File

@ -1,41 +0,0 @@
.image-occlusion-canvas {
--inactive-shape-color: #ffeba2;
--active-shape-color: #ff8e8e;
--inactive-shape-border: 1px #212121;
--active-shape-border: 1px #212121;
}
.card {
font-family: arial;
font-size: 20px;
text-align: center;
color: black;
background-color: white;
}
.cloze {
font-weight: bold;
color: blue;
}
.nightMode .cloze {
color: lightblue;
}
#container {
position: relative;
}
img {
position: absolute;
top: 0;
left: 50%;
transform: translate(-50%, 0);
}
#canvas {
position: absolute;
top: 0;
left: 50%;
transform: translate(-50%, 0);
}

View File

@ -47,10 +47,7 @@ impl Collection {
let mgr = MediaManager::new(&self.media_folder, &self.media_db)?; let mgr = MediaManager::new(&self.media_folder, &self.media_db)?;
let actual_image_name_after_adding = mgr.add_file(&image_filename, &image_bytes)?; let actual_image_name_after_adding = mgr.add_file(&image_filename, &image_bytes)?;
let image_tag = format!( let image_tag = format!(r#"<img src="{}">"#, &actual_image_name_after_adding);
r#"<img id="img" src="{}">"#,
&actual_image_name_after_adding
);
let current_deck = self.get_current_deck()?; let current_deck = self.get_current_deck()?;
self.transact(Op::ImageOcclusion, |col| { self.transact(Op::ImageOcclusion, |col| {

View File

@ -0,0 +1,14 @@
#image-occlusion-canvas {
--inactive-shape-color: #ffeba2;
--active-shape-color: #ff8e8e;
--inactive-shape-border: 1px #212121;
--active-shape-border: 1px #212121;
}
.card {
font-family: arial;
font-size: 20px;
text-align: center;
color: black;
background-color: white;
}

View File

@ -51,7 +51,7 @@ impl Collection {
} }
pub(crate) fn image_occlusion_notetype(tr: &I18n) -> Notetype { pub(crate) fn image_occlusion_notetype(tr: &I18n) -> Notetype {
const IMAGE_CLOZE_CSS: &str = include_str!("image_occlusion_styling.css"); const IMAGE_CLOZE_CSS: &str = include_str!("notetype.css");
let mut nt = empty_stock( let mut nt = empty_stock(
NotetypeKind::Cloze, NotetypeKind::Cloze,
OriginalStockKind::ImageOcclusion, OriginalStockKind::ImageOcclusion,
@ -71,31 +71,29 @@ pub(crate) fn image_occlusion_notetype(tr: &I18n) -> Notetype {
let err_loading = tr.notetypes_error_loading_image_occlusion(); let err_loading = tr.notetypes_error_loading_image_occlusion();
let qfmt = format!( let qfmt = format!(
"\ r#"{{{{#{header}}}}}<div>{{{{{header}}}}}</div>{{{{/{header}}}}}
{{{{#{header}}}}}<div>{{{{{header}}}}}</div>{{{{/{header}}}}} <div style="display: none">{{{{cloze:{occlusion}}}}}</div>
<div style=\"display: none\">{{{{cloze:{occlusion}}}}}</div> <div id="err"></div>
<div id=\"err\"></div> <div id="image-occlusion-container">
<div id=container>
{{{{{image}}}}} {{{{{image}}}}}
<canvas id=\"canvas\" class=\"image-occlusion-canvas\"></canvas> <canvas id="image-occlusion-canvas"></canvas>
</div> </div>
<script> <script>
try {{ try {{
anki.setupImageCloze(); anki.setupImageCloze();
}} catch (exc) {{ }} catch (exc) {{
document.getElementById(\"err\").innerHTML = `{err_loading}<br><br>${{exc}}`; document.getElementById("err").innerHTML = `{err_loading}<br><br>${{exc}}`;
}} }}
</script> </script>
" "#
); );
let toggle_masks = tr.notetypes_toggle_masks(); let toggle_masks = tr.notetypes_toggle_masks();
let afmt = format!( let afmt = format!(
"\ r#"{qfmt}
{qfmt} <div><button id="toggle">{toggle_masks}</button></div>
<div><button id=\"toggle\">{toggle_masks}</button></div>
{{{{#{back_extra}}}}}<div>{{{{{back_extra}}}}}</div>{{{{/{back_extra}}}}} {{{{#{back_extra}}}}}<div>{{{{{back_extra}}}}}</div>{{{{/{back_extra}}}}}
", "#,
); );
nt.add_template(nt.name.clone(), qfmt, afmt); nt.add_template(nt.name.clone(), qfmt, afmt);
nt nt

View File

@ -0,0 +1,28 @@
#image-occlusion-container {
position: relative;
// if height-constrained, ensure container is centered
left: 50%;
transform: translate(-50%, 0);
// allow for 20px margin on html element, or short windows can truncate
// image
max-height: calc(95vh - 40px);
}
#image-occlusion-container img {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
// remove the default image limits, as we rely on container
max-width: unset;
max-height: unset;
}
#image-occlusion-canvas {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}

View File

@ -12,17 +12,14 @@ export function setupImageCloze(): void {
} }
function setupImageClozeInner(): void { function setupImageClozeInner(): void {
const canvas = document.querySelector("canvas") as HTMLCanvasElement | null; const canvas = document.querySelector("#image-occlusion-canvas") as HTMLCanvasElement | null;
if (canvas == null) { if (canvas == null) {
return; return;
} }
canvas.style.maxWidth = "100%";
canvas.style.maxHeight = "95vh";
const ctx: CanvasRenderingContext2D = canvas.getContext("2d")!; const ctx: CanvasRenderingContext2D = canvas.getContext("2d")!;
const container = document.getElementById("container") as HTMLDivElement; const container = document.getElementById("image-occlusion-container") as HTMLDivElement;
const image = document.getElementById("img") as HTMLImageElement; const image = document.querySelector("#image-occlusion-container img") as HTMLImageElement;
if (image == null) { if (image == null) {
container.innerText = tr.notetypeErrorNoImageToShow(); container.innerText = tr.notetypeErrorNoImageToShow();
return; return;
@ -32,8 +29,8 @@ function setupImageClozeInner(): void {
canvas.width = size.width; canvas.width = size.width;
canvas.height = size.height; canvas.height = size.height;
// set height for div container (used 'relative' in css) // Enforce aspect ratio of image
container.style.height = `${image.height}px`; container.style.aspectRatio = `${size.width / size.height}`;
// setup button for toggle image occlusion // setup button for toggle image occlusion
const button = document.getElementById("toggle"); const button = document.getElementById("toggle");
@ -151,7 +148,7 @@ function getShapeProperty(): {
activeBorder: { width: number; color: string }; activeBorder: { width: number; color: string };
inActiveBorder: { width: number; color: string }; inActiveBorder: { width: number; color: string };
} { } {
const canvas = document.getElementById("canvas"); const canvas = document.getElementById("image-occlusion-canvas");
const computedStyle = window.getComputedStyle(canvas!); const computedStyle = window.getComputedStyle(canvas!);
// it may throw error if the css variable is not defined // it may throw error if the css variable is not defined
try { try {
@ -199,7 +196,7 @@ function getShapeProperty(): {
} }
const toggleMasks = (): void => { const toggleMasks = (): void => {
const canvas = document.getElementById("canvas") as HTMLCanvasElement; const canvas = document.getElementById("image-occlusion-canvas") as HTMLCanvasElement;
const display = canvas.style.display; const display = canvas.style.display;
if (display === "none") { if (display === "none") {
canvas.style.display = "unset"; canvas.style.display = "unset";

View File

@ -2,6 +2,7 @@
* License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html */ * License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html */
@use "sass/vars"; @use "sass/vars";
@use "ts/image-occlusion/review";
hr { hr {
background-color: vars.palette(darkgray, 0); background-color: vars.palette(darkgray, 0);