mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 14:02:21 -04:00
Various tweaks to I/O code (#2478)
* Allow user to select I/O notetype instead of enforcing a specific name * Display a clearer error when I/O note is missing an image Opening the card layout screen from "manage notetypes" was showing an error about the Anki version being too old. Replacement error is not currently translatable. * Preserve existing notetype when adding I/O notetype * Add a 'from clipboard' string The intention is to use this in the future to allow an image occlusion to be created from an image on the clipboard. * Tweak I/O init - Use union type instead of multiple nullable values - Pass the notetype id in to initialization * Fix image insertion in I/O note - The regex expected double quotes, and we were using single ones - Image tags don't need to be closed * Use more consistent naming in image_occlusion.proto * Tweaks to default I/O notetype - Show the header on the front side as well (I presume this is what users expect; if not am happy to revert) - Don't show comments on card (again, I presume users expect to use this field to add notes that aren't displayed during review, as they can use back extra for that) * Fix sticky footer missing background Caused by earlier CSS refactoring
This commit is contained in:
parent
ed334fa45d
commit
f6486da233
17 changed files with 257 additions and 194 deletions
|
@ -66,6 +66,7 @@ editing-warning-cloze-deletions-will-not-work = Warning, cloze deletions will no
|
||||||
editing-mathjax-preview = MathJax Preview
|
editing-mathjax-preview = MathJax Preview
|
||||||
editing-shrink-images = Shrink Images
|
editing-shrink-images = Shrink Images
|
||||||
editing-close-html-tags = Auto-close HTML tags
|
editing-close-html-tags = Auto-close HTML tags
|
||||||
|
editing-from-clipboard = From Clipboard
|
||||||
|
|
||||||
## You don't need to translate these strings, as they will be replaced with different ones soon.
|
## You don't need to translate these strings, as they will be replaced with different ones soon.
|
||||||
|
|
||||||
|
|
|
@ -13,20 +13,23 @@ import "anki/notes.proto";
|
||||||
import "anki/generic.proto";
|
import "anki/generic.proto";
|
||||||
|
|
||||||
service ImageOcclusionService {
|
service ImageOcclusionService {
|
||||||
rpc GetImageForOcclusion(GetImageForOcclusionRequest) returns (ImageData);
|
rpc GetImageForOcclusion(GetImageForOcclusionRequest)
|
||||||
|
returns (GetImageForOcclusionResponse);
|
||||||
rpc AddImageOcclusionNote(AddImageOcclusionNoteRequest)
|
rpc AddImageOcclusionNote(AddImageOcclusionNoteRequest)
|
||||||
returns (collection.OpChanges);
|
returns (collection.OpChanges);
|
||||||
rpc GetImageClozeNote(GetImageOcclusionNoteRequest)
|
rpc GetImageOcclusionNote(GetImageOcclusionNoteRequest)
|
||||||
returns (ImageClozeNoteResponse);
|
returns (GetImageOcclusionNoteResponse);
|
||||||
rpc UpdateImageOcclusionNote(UpdateImageOcclusionNoteRequest)
|
rpc UpdateImageOcclusionNote(UpdateImageOcclusionNoteRequest)
|
||||||
returns (collection.OpChanges);
|
returns (collection.OpChanges);
|
||||||
|
// Adds an I/O notetype if none exists in the collection.
|
||||||
|
rpc AddImageOcclusionNotetype(generic.Empty) returns (collection.OpChanges);
|
||||||
}
|
}
|
||||||
|
|
||||||
message GetImageForOcclusionRequest {
|
message GetImageForOcclusionRequest {
|
||||||
string path = 1;
|
string path = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message ImageData {
|
message GetImageForOcclusionResponse {
|
||||||
bytes data = 1;
|
bytes data = 1;
|
||||||
string name = 2;
|
string name = 2;
|
||||||
}
|
}
|
||||||
|
@ -37,20 +40,28 @@ message AddImageOcclusionNoteRequest {
|
||||||
string header = 3;
|
string header = 3;
|
||||||
string back_extra = 4;
|
string back_extra = 4;
|
||||||
repeated string tags = 5;
|
repeated string tags = 5;
|
||||||
}
|
int64 notetype_id = 6;
|
||||||
|
|
||||||
message ImageClozeNote {
|
|
||||||
bytes image_data = 1;
|
|
||||||
string occlusions = 2;
|
|
||||||
string header = 3;
|
|
||||||
string back_extra = 4;
|
|
||||||
repeated string tags = 5;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message GetImageOcclusionNoteRequest {
|
message GetImageOcclusionNoteRequest {
|
||||||
int64 note_id = 1;
|
int64 note_id = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message GetImageOcclusionNoteResponse {
|
||||||
|
message ImageClozeNote {
|
||||||
|
bytes image_data = 1;
|
||||||
|
string occlusions = 2;
|
||||||
|
string header = 3;
|
||||||
|
string back_extra = 4;
|
||||||
|
repeated string tags = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
oneof value {
|
||||||
|
ImageClozeNote note = 1;
|
||||||
|
string error = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
message UpdateImageOcclusionNoteRequest {
|
message UpdateImageOcclusionNoteRequest {
|
||||||
int64 note_id = 1;
|
int64 note_id = 1;
|
||||||
string occlusions = 2;
|
string occlusions = 2;
|
||||||
|
@ -58,10 +69,3 @@ message UpdateImageOcclusionNoteRequest {
|
||||||
string back_extra = 4;
|
string back_extra = 4;
|
||||||
repeated string tags = 5;
|
repeated string tags = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
message ImageClozeNoteResponse {
|
|
||||||
oneof value {
|
|
||||||
ImageClozeNote note = 1;
|
|
||||||
string error = 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -41,9 +41,9 @@ CsvMetadata = import_export_pb2.CsvMetadata
|
||||||
DupeResolution = CsvMetadata.DupeResolution
|
DupeResolution = CsvMetadata.DupeResolution
|
||||||
Delimiter = import_export_pb2.CsvMetadata.Delimiter
|
Delimiter = import_export_pb2.CsvMetadata.Delimiter
|
||||||
TtsVoice = card_rendering_pb2.AllTtsVoicesResponse.TtsVoice
|
TtsVoice = card_rendering_pb2.AllTtsVoicesResponse.TtsVoice
|
||||||
ImageData = image_occlusion_pb2.ImageData
|
GetImageForOcclusionResponse = image_occlusion_pb2.GetImageForOcclusionResponse
|
||||||
AddImageOcclusionNoteRequest = image_occlusion_pb2.AddImageOcclusionNoteRequest
|
AddImageOcclusionNoteRequest = image_occlusion_pb2.AddImageOcclusionNoteRequest
|
||||||
ImageClozeNoteResponse = image_occlusion_pb2.ImageClozeNoteResponse
|
GetImageOcclusionNoteResponse = image_occlusion_pb2.GetImageOcclusionNoteResponse
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
import os
|
import os
|
||||||
|
@ -462,18 +462,21 @@ class Collection(DeprecatedNamesMixin):
|
||||||
|
|
||||||
# Image Occlusion
|
# Image Occlusion
|
||||||
##########################################################################
|
##########################################################################
|
||||||
def get_image_for_occlusion(self, path: str | None) -> ImageData:
|
|
||||||
|
def get_image_for_occlusion(self, path: str | None) -> GetImageForOcclusionResponse:
|
||||||
return self._backend.get_image_for_occlusion(path=path)
|
return self._backend.get_image_for_occlusion(path=path)
|
||||||
|
|
||||||
def add_image_occlusion_note(
|
def add_image_occlusion_note(
|
||||||
self,
|
self,
|
||||||
image_path: str | None,
|
notetype_id: int,
|
||||||
occlusions: str | None,
|
image_path: str,
|
||||||
header: str | None,
|
occlusions: str,
|
||||||
back_extra: str | None,
|
header: str,
|
||||||
tags: list[str] | None,
|
back_extra: str,
|
||||||
|
tags: list[str],
|
||||||
) -> OpChanges:
|
) -> OpChanges:
|
||||||
return self._backend.add_image_occlusion_note(
|
return self._backend.add_image_occlusion_note(
|
||||||
|
notetype_id=notetype_id,
|
||||||
image_path=image_path,
|
image_path=image_path,
|
||||||
occlusions=occlusions,
|
occlusions=occlusions,
|
||||||
header=header,
|
header=header,
|
||||||
|
@ -481,8 +484,10 @@ class Collection(DeprecatedNamesMixin):
|
||||||
tags=tags,
|
tags=tags,
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_image_cloze_note(self, note_id: int | None) -> ImageClozeNoteResponse:
|
def get_image_occlusion_note(
|
||||||
return self._backend.get_image_cloze_note(note_id=note_id)
|
self, note_id: int | None
|
||||||
|
) -> GetImageOcclusionNoteResponse:
|
||||||
|
return self._backend.get_image_occlusion_note(note_id=note_id)
|
||||||
|
|
||||||
def update_image_occlusion_note(
|
def update_image_occlusion_note(
|
||||||
self,
|
self,
|
||||||
|
|
|
@ -481,7 +481,7 @@ exposed_backend_list = [
|
||||||
# ImageOcclusionService
|
# ImageOcclusionService
|
||||||
"get_image_for_occlusion",
|
"get_image_for_occlusion",
|
||||||
"add_image_occlusion_note",
|
"add_image_occlusion_note",
|
||||||
"get_image_cloze_note",
|
"get_image_occlusion_note",
|
||||||
"update_image_occlusion_note",
|
"update_image_occlusion_note",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ impl ImageOcclusionService for Backend {
|
||||||
fn get_image_for_occlusion(
|
fn get_image_for_occlusion(
|
||||||
&self,
|
&self,
|
||||||
input: pb::image_occlusion::GetImageForOcclusionRequest,
|
input: pb::image_occlusion::GetImageForOcclusionRequest,
|
||||||
) -> Result<pb::image_occlusion::ImageData> {
|
) -> Result<pb::image_occlusion::GetImageForOcclusionResponse> {
|
||||||
self.with_col(|col| col.get_image_for_occlusion(&input.path))
|
self.with_col(|col| col.get_image_for_occlusion(&input.path))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@ impl ImageOcclusionService for Backend {
|
||||||
) -> Result<pb::collection::OpChanges> {
|
) -> Result<pb::collection::OpChanges> {
|
||||||
self.with_col(|col| {
|
self.with_col(|col| {
|
||||||
col.add_image_occlusion_note(
|
col.add_image_occlusion_note(
|
||||||
|
input.notetype_id.into(),
|
||||||
&input.image_path,
|
&input.image_path,
|
||||||
&input.occlusions,
|
&input.occlusions,
|
||||||
&input.header,
|
&input.header,
|
||||||
|
@ -30,11 +31,11 @@ impl ImageOcclusionService for Backend {
|
||||||
.map(Into::into)
|
.map(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_image_cloze_note(
|
fn get_image_occlusion_note(
|
||||||
&self,
|
&self,
|
||||||
input: pb::image_occlusion::GetImageOcclusionNoteRequest,
|
input: pb::image_occlusion::GetImageOcclusionNoteRequest,
|
||||||
) -> Result<pb::image_occlusion::ImageClozeNoteResponse> {
|
) -> Result<pb::image_occlusion::GetImageOcclusionNoteResponse> {
|
||||||
self.with_col(|col| col.get_image_cloze_note(input.note_id.into()))
|
self.with_col(|col| col.get_image_occlusion_note(input.note_id.into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_image_occlusion_note(
|
fn update_image_occlusion_note(
|
||||||
|
@ -52,4 +53,12 @@ impl ImageOcclusionService for Backend {
|
||||||
})
|
})
|
||||||
.map(Into::into)
|
.map(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn add_image_occlusion_notetype(
|
||||||
|
&self,
|
||||||
|
_input: pb::generic::Empty,
|
||||||
|
) -> Result<pb::collection::OpChanges> {
|
||||||
|
self.with_col(|col| col.add_image_occlusion_notetype())
|
||||||
|
.map(Into::into)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,27 +3,22 @@
|
||||||
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
|
||||||
use crate::io::metadata;
|
use crate::io::metadata;
|
||||||
use crate::io::read_file;
|
use crate::io::read_file;
|
||||||
use crate::media::MediaManager;
|
use crate::media::MediaManager;
|
||||||
use crate::notetype::stock::empty_stock;
|
|
||||||
use crate::notetype::CardGenContext;
|
use crate::notetype::CardGenContext;
|
||||||
use crate::notetype::Notetype;
|
use crate::pb::image_occlusion::get_image_occlusion_note_response::ImageClozeNote;
|
||||||
use crate::notetype::NotetypeKind;
|
use crate::pb::image_occlusion::get_image_occlusion_note_response::Value;
|
||||||
use crate::pb::image_occlusion::image_cloze_note_response::Value;
|
use crate::pb::image_occlusion::GetImageForOcclusionResponse;
|
||||||
use crate::pb::image_occlusion::ImageClozeNote;
|
use crate::pb::image_occlusion::GetImageOcclusionNoteResponse;
|
||||||
use crate::pb::image_occlusion::ImageClozeNoteResponse;
|
|
||||||
pub use crate::pb::image_occlusion::ImageData;
|
|
||||||
use crate::pb::notetypes::stock_notetype::OriginalStockKind;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
impl Collection {
|
impl Collection {
|
||||||
pub fn get_image_for_occlusion(&mut self, path: &str) -> Result<ImageData> {
|
pub fn get_image_for_occlusion(&mut self, path: &str) -> Result<GetImageForOcclusionResponse> {
|
||||||
let mut metadata = ImageData {
|
let mut metadata = GetImageForOcclusionResponse {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
metadata.data = read_file(path)?;
|
metadata.data = read_file(path)?;
|
||||||
|
@ -33,6 +28,7 @@ impl Collection {
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn add_image_occlusion_note(
|
pub fn add_image_occlusion_note(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
notetype_id: NotetypeId,
|
||||||
image_path: &str,
|
image_path: &str,
|
||||||
occlusions: &str,
|
occlusions: &str,
|
||||||
header: &str,
|
header: &str,
|
||||||
|
@ -52,13 +48,20 @@ impl Collection {
|
||||||
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!(
|
||||||
"<img id='img' src='{}'></img>",
|
r#"<img id="img" src="{}">"#,
|
||||||
&actual_image_name_after_adding
|
&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| {
|
||||||
let nt = col.get_or_create_io_notetype()?;
|
let nt = if notetype_id.0 == 0 {
|
||||||
|
// when testing via .html page, use first available notetype
|
||||||
|
col.add_image_occlusion_notetype_inner()?;
|
||||||
|
col.get_first_io_notetype()?
|
||||||
|
.or_invalid("expected an i/o notetype to exist")?
|
||||||
|
} else {
|
||||||
|
col.get_io_notetype_by_id(notetype_id)?
|
||||||
|
};
|
||||||
|
|
||||||
let mut note = nt.new_note();
|
let mut note = nt.new_note();
|
||||||
note.set_field(0, occlusions)?;
|
note.set_field(0, occlusions)?;
|
||||||
|
@ -76,47 +79,18 @@ impl Collection {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_or_create_io_notetype(&mut self) -> Result<Arc<Notetype>> {
|
pub fn get_image_occlusion_note(
|
||||||
let tr = &self.tr;
|
&mut self,
|
||||||
let name = format!("{}", tr.notetypes_image_occlusion_name());
|
note_id: NoteId,
|
||||||
let nt = match self.get_notetype_by_name(&name)? {
|
) -> Result<GetImageOcclusionNoteResponse> {
|
||||||
Some(nt) => nt,
|
let value = match self.get_image_occlusion_note_inner(note_id) {
|
||||||
None => {
|
|
||||||
self.add_io_notetype()?;
|
|
||||||
if let Some(nt) = self.get_notetype_by_name(&name)? {
|
|
||||||
nt
|
|
||||||
} else {
|
|
||||||
return Err(AnkiError::TemplateError {
|
|
||||||
info: "IO notetype not found".to_string(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if nt.fields.len() < 4 {
|
|
||||||
Err(AnkiError::TemplateError {
|
|
||||||
info: "IO notetype must have 4+ fields".to_string(),
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
Ok(nt)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_io_notetype(&mut self) -> Result<()> {
|
|
||||||
let usn = self.usn()?;
|
|
||||||
let mut nt = image_occlusion_notetype(&self.tr);
|
|
||||||
self.add_notetype_inner(&mut nt, usn, false)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_image_cloze_note(&mut self, note_id: NoteId) -> Result<ImageClozeNoteResponse> {
|
|
||||||
let value = match self.get_image_cloze_note_inner(note_id) {
|
|
||||||
Ok(note) => Value::Note(note),
|
Ok(note) => Value::Note(note),
|
||||||
Err(err) => Value::Error(format!("{:?}", err)),
|
Err(err) => Value::Error(format!("{:?}", err)),
|
||||||
};
|
};
|
||||||
Ok(ImageClozeNoteResponse { value: Some(value) })
|
Ok(GetImageOcclusionNoteResponse { value: Some(value) })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_image_cloze_note_inner(&mut self, note_id: NoteId) -> Result<ImageClozeNote> {
|
pub fn get_image_occlusion_note_inner(&mut self, note_id: NoteId) -> Result<ImageClozeNote> {
|
||||||
let note = self.storage.get_note(note_id)?.or_not_found(note_id)?;
|
let note = self.storage.get_note(note_id)?.or_not_found(note_id)?;
|
||||||
let mut cloze_note = ImageClozeNote::default();
|
let mut cloze_note = ImageClozeNote::default();
|
||||||
|
|
||||||
|
@ -131,9 +105,9 @@ impl Collection {
|
||||||
cloze_note.image_data = "".into();
|
cloze_note.image_data = "".into();
|
||||||
cloze_note.tags = note.tags.clone();
|
cloze_note.tags = note.tags.clone();
|
||||||
|
|
||||||
let image_file_name = fields[1].clone();
|
let image_file_name = &fields[1];
|
||||||
let src = self
|
let src = self
|
||||||
.extract_img_src(&image_file_name)
|
.extract_img_src(image_file_name)
|
||||||
.unwrap_or_else(|| "".to_owned());
|
.unwrap_or_else(|| "".to_owned());
|
||||||
let final_path = self.media_folder.join(src);
|
let final_path = self.media_folder.join(src);
|
||||||
|
|
||||||
|
@ -190,58 +164,3 @@ impl Collection {
|
||||||
Ok(false)
|
Ok(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn image_occlusion_notetype(tr: &I18n) -> Notetype {
|
|
||||||
const IMAGE_CLOZE_CSS: &str = include_str!("image_occlusion_styling.css");
|
|
||||||
let mut nt = empty_stock(
|
|
||||||
NotetypeKind::Cloze,
|
|
||||||
OriginalStockKind::ImageOcclusion,
|
|
||||||
tr.notetypes_image_occlusion_name(),
|
|
||||||
);
|
|
||||||
nt.config.css = IMAGE_CLOZE_CSS.to_string();
|
|
||||||
let occlusion = tr.notetypes_occlusion();
|
|
||||||
nt.add_field(occlusion.as_ref());
|
|
||||||
let image = tr.notetypes_image();
|
|
||||||
nt.add_field(image.as_ref());
|
|
||||||
let header = tr.notetypes_header();
|
|
||||||
nt.add_field(header.as_ref());
|
|
||||||
let back_extra = tr.notetypes_back_extra_field();
|
|
||||||
nt.add_field(back_extra.as_ref());
|
|
||||||
let comments = tr.notetypes_comments_field();
|
|
||||||
nt.add_field(comments.as_ref());
|
|
||||||
let qfmt = format!(
|
|
||||||
"<div style=\"display: none\">{{{{cloze:{}}}}}</div>
|
|
||||||
<div id=container>
|
|
||||||
{{{{{}}}}}
|
|
||||||
<canvas id=\"canvas\" class=\"image-occlusion-canvas\"></canvas>
|
|
||||||
</div>
|
|
||||||
<div id=\"err\"></div>
|
|
||||||
<script>
|
|
||||||
try {{
|
|
||||||
anki.setupImageCloze();
|
|
||||||
}} catch (exc) {{
|
|
||||||
document.getElementById(\"err\").innerHTML = `{}<br><br>${{exc}}`;
|
|
||||||
}}
|
|
||||||
</script>
|
|
||||||
",
|
|
||||||
occlusion,
|
|
||||||
image,
|
|
||||||
tr.notetypes_error_loading_image_occlusion(),
|
|
||||||
);
|
|
||||||
let afmt = format!(
|
|
||||||
"{{{{{}}}}}
|
|
||||||
{}
|
|
||||||
<button id=\"toggle\">{}</button>
|
|
||||||
<br>
|
|
||||||
{{{{{}}}}}
|
|
||||||
<br>
|
|
||||||
{{{{{}}}}}",
|
|
||||||
header,
|
|
||||||
qfmt,
|
|
||||||
tr.notetypes_toggle_masks(),
|
|
||||||
back_extra,
|
|
||||||
comments,
|
|
||||||
);
|
|
||||||
nt.add_template(nt.name.clone(), qfmt, afmt);
|
|
||||||
nt
|
|
||||||
}
|
|
||||||
|
|
|
@ -3,3 +3,4 @@
|
||||||
|
|
||||||
pub mod imagedata;
|
pub mod imagedata;
|
||||||
pub mod imageocclusion;
|
pub mod imageocclusion;
|
||||||
|
pub(crate) mod notetype;
|
||||||
|
|
114
rslib/src/image_occlusion/notetype.rs
Normal file
114
rslib/src/image_occlusion/notetype.rs
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
// Copyright: Ankitects Pty Ltd and contributors
|
||||||
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use crate::notetype::stock::empty_stock;
|
||||||
|
use crate::notetype::Notetype;
|
||||||
|
use crate::notetype::NotetypeKind;
|
||||||
|
use crate::pb::notetypes::stock_notetype::OriginalStockKind;
|
||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
impl Collection {
|
||||||
|
pub fn add_image_occlusion_notetype(&mut self) -> Result<OpOutput<()>> {
|
||||||
|
self.transact(Op::UpdateNotetype, |col| {
|
||||||
|
col.add_image_occlusion_notetype_inner()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_image_occlusion_notetype_inner(&mut self) -> Result<()> {
|
||||||
|
if self.get_first_io_notetype()?.is_none() {
|
||||||
|
let mut nt = image_occlusion_notetype(&self.tr);
|
||||||
|
let current_id = self.get_current_notetype_id();
|
||||||
|
self.add_notetype_inner(&mut nt, self.usn()?, false)?;
|
||||||
|
if let Some(current_id) = current_id {
|
||||||
|
// preserve previous default
|
||||||
|
self.set_current_notetype_id(current_id)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the I/O notetype with the provided id, checking to make sure it
|
||||||
|
/// is valid.
|
||||||
|
pub(crate) fn get_io_notetype_by_id(
|
||||||
|
&mut self,
|
||||||
|
notetype_id: NotetypeId,
|
||||||
|
) -> Result<Arc<Notetype>> {
|
||||||
|
let nt = self.get_notetype(notetype_id)?.or_not_found(notetype_id)?;
|
||||||
|
io_notetype_if_valid(nt)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_first_io_notetype(&mut self) -> Result<Option<Arc<Notetype>>> {
|
||||||
|
for (_, nt) in self.get_all_notetypes()? {
|
||||||
|
if nt.config.original_stock_kind() == OriginalStockKind::ImageOcclusion {
|
||||||
|
return Some(io_notetype_if_valid(nt)).transpose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn image_occlusion_notetype(tr: &I18n) -> Notetype {
|
||||||
|
const IMAGE_CLOZE_CSS: &str = include_str!("image_occlusion_styling.css");
|
||||||
|
let mut nt = empty_stock(
|
||||||
|
NotetypeKind::Cloze,
|
||||||
|
OriginalStockKind::ImageOcclusion,
|
||||||
|
tr.notetypes_image_occlusion_name(),
|
||||||
|
);
|
||||||
|
nt.config.css = IMAGE_CLOZE_CSS.to_string();
|
||||||
|
let occlusion = tr.notetypes_occlusion();
|
||||||
|
nt.add_field(occlusion.as_ref());
|
||||||
|
let image = tr.notetypes_image();
|
||||||
|
nt.add_field(image.as_ref());
|
||||||
|
let header = tr.notetypes_header();
|
||||||
|
nt.add_field(header.as_ref());
|
||||||
|
let back_extra = tr.notetypes_back_extra_field();
|
||||||
|
nt.add_field(back_extra.as_ref());
|
||||||
|
let comments = tr.notetypes_comments_field();
|
||||||
|
nt.add_field(comments.as_ref());
|
||||||
|
|
||||||
|
let err_loading = tr.notetypes_error_loading_image_occlusion();
|
||||||
|
let qfmt = format!(
|
||||||
|
"\
|
||||||
|
{{{{#{header}}}}}<div>{{{{{header}}}}}</div>{{{{/{header}}}}}
|
||||||
|
<div style=\"display: none\">{{{{cloze:{occlusion}}}}}</div>
|
||||||
|
<div id=\"err\"></div>
|
||||||
|
<div id=container>
|
||||||
|
{{{{{image}}}}}
|
||||||
|
<canvas id=\"canvas\" class=\"image-occlusion-canvas\"></canvas>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
try {{
|
||||||
|
anki.setupImageCloze();
|
||||||
|
}} catch (exc) {{
|
||||||
|
document.getElementById(\"err\").innerHTML = `{err_loading}<br><br>${{exc}}`;
|
||||||
|
}}
|
||||||
|
</script>
|
||||||
|
"
|
||||||
|
);
|
||||||
|
|
||||||
|
let toggle_masks = tr.notetypes_toggle_masks();
|
||||||
|
let afmt = format!(
|
||||||
|
"\
|
||||||
|
{qfmt}
|
||||||
|
<div><button id=\"toggle\">{toggle_masks}</button></div>
|
||||||
|
{{{{#{back_extra}}}}}<div>{{{{{back_extra}}}}}</div>{{{{/{back_extra}}}}}
|
||||||
|
",
|
||||||
|
);
|
||||||
|
nt.add_template(nt.name.clone(), qfmt, afmt);
|
||||||
|
nt
|
||||||
|
}
|
||||||
|
|
||||||
|
fn io_notetype_if_valid(nt: Arc<Notetype>) -> Result<Arc<Notetype>> {
|
||||||
|
if nt.config.original_stock_kind() != OriginalStockKind::ImageOcclusion {
|
||||||
|
invalid_input!("Not an image occlusion notetype");
|
||||||
|
}
|
||||||
|
if nt.fields.len() < 4 {
|
||||||
|
return Err(AnkiError::TemplateError {
|
||||||
|
info: "IO notetype must have 4+ fields".to_string(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Ok(nt)
|
||||||
|
}
|
|
@ -6,7 +6,7 @@ use crate::config::ConfigEntry;
|
||||||
use crate::config::ConfigKey;
|
use crate::config::ConfigKey;
|
||||||
use crate::error::Result;
|
use crate::error::Result;
|
||||||
use crate::i18n::I18n;
|
use crate::i18n::I18n;
|
||||||
use crate::image_occlusion::imagedata::image_occlusion_notetype;
|
use crate::image_occlusion::notetype::image_occlusion_notetype;
|
||||||
use crate::invalid_input;
|
use crate::invalid_input;
|
||||||
use crate::notetype::Notetype;
|
use crate::notetype::Notetype;
|
||||||
use crate::pb::notetypes::notetype::config::Kind as NotetypeKind;
|
use crate::pb::notetypes::notetype::config::Kind as NotetypeKind;
|
||||||
|
|
|
@ -6,20 +6,20 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
import * as tr from "@tslib/ftl";
|
import * as tr from "@tslib/ftl";
|
||||||
|
|
||||||
import Container from "../components/Container.svelte";
|
import Container from "../components/Container.svelte";
|
||||||
import { saveImageNotes } from "./generate";
|
import { addOrUpdateNote } from "./generate";
|
||||||
|
import type { IOMode } from "./lib";
|
||||||
import MasksEditor from "./MaskEditor.svelte";
|
import MasksEditor from "./MaskEditor.svelte";
|
||||||
import Notes from "./Notes.svelte";
|
import Notes from "./Notes.svelte";
|
||||||
import StickyFooter from "./StickyFooter.svelte";
|
import StickyFooter from "./StickyFooter.svelte";
|
||||||
|
|
||||||
export let path: string | null;
|
export let mode: IOMode;
|
||||||
export let noteId: number | null;
|
|
||||||
|
|
||||||
async function hideAllGuessOne(): Promise<void> {
|
async function hideAllGuessOne(): Promise<void> {
|
||||||
saveImageNotes(path!, noteId!, false);
|
addOrUpdateNote(mode, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function hideOneGuessOne(): Promise<void> {
|
async function hideOneGuessOne(): Promise<void> {
|
||||||
saveImageNotes(path!, noteId!, true);
|
addOrUpdateNote(mode, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
const items = [
|
const items = [
|
||||||
|
@ -41,12 +41,16 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<div hidden={activeTabValue != 1}>
|
<div hidden={activeTabValue != 1}>
|
||||||
<MasksEditor {path} {noteId} />
|
<MasksEditor {mode} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div hidden={activeTabValue != 2}>
|
<div hidden={activeTabValue != 2}>
|
||||||
<div class="notes-page"><Notes /></div>
|
<div class="notes-page"><Notes /></div>
|
||||||
<StickyFooter {hideAllGuessOne} {hideOneGuessOne} {noteId} />
|
<StickyFooter
|
||||||
|
{hideAllGuessOne}
|
||||||
|
{hideOneGuessOne}
|
||||||
|
editing={mode.kind == "edit"}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Container>
|
</Container>
|
||||||
|
|
||||||
|
|
|
@ -6,11 +6,11 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
import type { PanZoom } from "panzoom";
|
import type { PanZoom } from "panzoom";
|
||||||
import panzoom from "panzoom";
|
import panzoom from "panzoom";
|
||||||
|
|
||||||
|
import type { IOMode } from "./lib";
|
||||||
import { setupMaskEditor, setupMaskEditorForEdit } from "./mask-editor";
|
import { setupMaskEditor, setupMaskEditorForEdit } from "./mask-editor";
|
||||||
import SideToolbar from "./SideToolbar.svelte";
|
import SideToolbar from "./SideToolbar.svelte";
|
||||||
|
|
||||||
export let path: string | null;
|
export let mode: IOMode;
|
||||||
export let noteId: number | null;
|
|
||||||
|
|
||||||
let instance: PanZoom;
|
let instance: PanZoom;
|
||||||
let innerWidth = 0;
|
let innerWidth = 0;
|
||||||
|
@ -26,14 +26,12 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
});
|
});
|
||||||
instance.pause();
|
instance.pause();
|
||||||
|
|
||||||
if (path) {
|
if (mode.kind == "add") {
|
||||||
setupMaskEditor(path, instance).then((canvas1) => {
|
setupMaskEditor(mode.imagePath, instance).then((canvas1) => {
|
||||||
canvas = canvas1;
|
canvas = canvas1;
|
||||||
});
|
});
|
||||||
}
|
} else {
|
||||||
|
setupMaskEditorForEdit(mode.noteId, instance).then((canvas1) => {
|
||||||
if (noteId) {
|
|
||||||
setupMaskEditorForEdit(noteId, instance).then((canvas1) => {
|
|
||||||
canvas = canvas1;
|
canvas = canvas1;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,12 +10,12 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
export let hideAllGuessOne: () => void;
|
export let hideAllGuessOne: () => void;
|
||||||
export let hideOneGuessOne: () => void;
|
export let hideOneGuessOne: () => void;
|
||||||
export let noteId: number | null;
|
export let editing: boolean;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div style:flex-grow="1" />
|
<div style:flex-grow="1" />
|
||||||
<div class="sticky-footer">
|
<div class="sticky-footer">
|
||||||
{#if noteId}
|
{#if editing}
|
||||||
<div class="update-note-text">
|
<div class="update-note-text">
|
||||||
{tr.actionsUpdateNote()}
|
{tr.actionsUpdateNote()}
|
||||||
</div>
|
</div>
|
||||||
|
@ -49,7 +49,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
z-index: 99;
|
z-index: 99;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0.25rem;
|
padding: 0.25rem;
|
||||||
background: var(--window-bg);
|
background: var(--canvas);
|
||||||
border-style: solid none none;
|
border-style: solid none none;
|
||||||
border-color: var(--border);
|
border-color: var(--border);
|
||||||
border-width: thin;
|
border-width: thin;
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { fabric } from "fabric";
|
||||||
import { get } from "svelte/store";
|
import { get } from "svelte/store";
|
||||||
|
|
||||||
import type { Collection } from "../lib/proto";
|
import type { Collection } from "../lib/proto";
|
||||||
|
import type { IOMode } from "./lib";
|
||||||
import { addImageOcclusionNote, updateImageOcclusionNote } from "./lib";
|
import { addImageOcclusionNote, updateImageOcclusionNote } from "./lib";
|
||||||
import { notesDataStore, tagsWritable } from "./store";
|
import { notesDataStore, tagsWritable } from "./store";
|
||||||
import Toast from "./Toast.svelte";
|
import Toast from "./Toast.svelte";
|
||||||
|
@ -107,9 +108,8 @@ const getObjectPositionInGroup = (group, object): { top: number; left: number }
|
||||||
return { top, left };
|
return { top, left };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const saveImageNotes = async function(
|
export const addOrUpdateNote = async function(
|
||||||
imagePath: string,
|
mode: IOMode,
|
||||||
noteId: number,
|
|
||||||
hideInactive: boolean,
|
hideInactive: boolean,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const { occlusionCloze, noteCount } = generate(hideInactive);
|
const { occlusionCloze, noteCount } = generate(hideInactive);
|
||||||
|
@ -125,29 +125,30 @@ export const saveImageNotes = async function(
|
||||||
header = header ? `<div>${header}</div>` : "";
|
header = header ? `<div>${header}</div>` : "";
|
||||||
backExtra = header ? `<div>${backExtra}</div>` : "";
|
backExtra = header ? `<div>${backExtra}</div>` : "";
|
||||||
|
|
||||||
if (noteId) {
|
if (mode.kind == "edit") {
|
||||||
const result = await updateImageOcclusionNote(
|
const result = await updateImageOcclusionNote(
|
||||||
noteId,
|
mode.noteId,
|
||||||
occlusionCloze,
|
occlusionCloze,
|
||||||
header,
|
header,
|
||||||
backExtra,
|
backExtra,
|
||||||
tags,
|
tags,
|
||||||
);
|
);
|
||||||
showResult(noteId, result, noteCount);
|
showResult(mode.noteId, result, noteCount);
|
||||||
} else {
|
} else {
|
||||||
const result = await addImageOcclusionNote(
|
const result = await addImageOcclusionNote(
|
||||||
imagePath,
|
mode.notetypeId,
|
||||||
|
mode.imagePath,
|
||||||
occlusionCloze,
|
occlusionCloze,
|
||||||
header,
|
header,
|
||||||
backExtra,
|
backExtra,
|
||||||
tags,
|
tags,
|
||||||
);
|
);
|
||||||
showResult(noteId, result, noteCount);
|
showResult(null, result, noteCount);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// show toast message
|
// show toast message
|
||||||
const showResult = (noteId: number, result: Collection.OpChanges, count: number) => {
|
const showResult = (noteId: number | null, result: Collection.OpChanges, count: number) => {
|
||||||
const toastComponent = new Toast({
|
const toastComponent = new Toast({
|
||||||
target: document.body,
|
target: document.body,
|
||||||
props: {
|
props: {
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { ModuleName, setupI18n } from "@tslib/i18n";
|
||||||
|
|
||||||
import { checkNightMode } from "../lib/nightmode";
|
import { checkNightMode } from "../lib/nightmode";
|
||||||
import ImageOcclusionPage from "./ImageOcclusionPage.svelte";
|
import ImageOcclusionPage from "./ImageOcclusionPage.svelte";
|
||||||
|
import type { IOMode } from "./lib";
|
||||||
|
|
||||||
const i18n = setupI18n({
|
const i18n = setupI18n({
|
||||||
modules: [
|
modules: [
|
||||||
|
@ -19,38 +20,24 @@ const i18n = setupI18n({
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
export async function setupImageOcclusion(path: string): Promise<ImageOcclusionPage> {
|
export async function setupImageOcclusion(mode: IOMode): Promise<ImageOcclusionPage> {
|
||||||
checkNightMode();
|
checkNightMode();
|
||||||
await i18n;
|
await i18n;
|
||||||
|
|
||||||
return new ImageOcclusionPage({
|
return new ImageOcclusionPage({
|
||||||
target: document.body,
|
target: document.body,
|
||||||
props: {
|
props: {
|
||||||
path: path,
|
mode,
|
||||||
noteId: null,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function setupImageOcclusionForEdit(noteId: number): Promise<ImageOcclusionPage> {
|
|
||||||
checkNightMode();
|
|
||||||
await i18n;
|
|
||||||
|
|
||||||
return new ImageOcclusionPage({
|
|
||||||
target: document.body,
|
|
||||||
props: {
|
|
||||||
path: null,
|
|
||||||
noteId: noteId,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (window.location.hash.startsWith("#test-")) {
|
if (window.location.hash.startsWith("#test-")) {
|
||||||
const path = window.location.hash.replace("#test-", "");
|
const imagePath = window.location.hash.replace("#test-", "");
|
||||||
setupImageOcclusion(path);
|
setupImageOcclusion({ kind: "add", imagePath, notetypeId: 0 });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (window.location.hash.startsWith("#testforedit-")) {
|
if (window.location.hash.startsWith("#testforedit-")) {
|
||||||
const noteId = parseInt(window.location.hash.replace("#testforedit-", ""));
|
const noteId = parseInt(window.location.hash.replace("#testforedit-", ""));
|
||||||
setupImageOcclusionForEdit(noteId);
|
setupImageOcclusion({ kind: "edit", noteId });
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,9 +4,22 @@
|
||||||
import type { Collection } from "../lib/proto";
|
import type { Collection } from "../lib/proto";
|
||||||
import { ImageOcclusion, imageOcclusion } from "../lib/proto";
|
import { ImageOcclusion, imageOcclusion } from "../lib/proto";
|
||||||
|
|
||||||
|
export interface IOAddingMode {
|
||||||
|
kind: "add";
|
||||||
|
notetypeId: number;
|
||||||
|
imagePath: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IOEditingMode {
|
||||||
|
kind: "edit";
|
||||||
|
noteId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type IOMode = IOAddingMode | IOEditingMode;
|
||||||
|
|
||||||
export async function getImageForOcclusion(
|
export async function getImageForOcclusion(
|
||||||
path: string,
|
path: string,
|
||||||
): Promise<ImageOcclusion.ImageData> {
|
): Promise<ImageOcclusion.GetImageForOcclusionResponse> {
|
||||||
return imageOcclusion.getImageForOcclusion(
|
return imageOcclusion.getImageForOcclusion(
|
||||||
ImageOcclusion.GetImageForOcclusionRequest.create({
|
ImageOcclusion.GetImageForOcclusionRequest.create({
|
||||||
path,
|
path,
|
||||||
|
@ -15,6 +28,7 @@ export async function getImageForOcclusion(
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function addImageOcclusionNote(
|
export async function addImageOcclusionNote(
|
||||||
|
notetypeId: number,
|
||||||
imagePath: string,
|
imagePath: string,
|
||||||
occlusions: string,
|
occlusions: string,
|
||||||
header: string,
|
header: string,
|
||||||
|
@ -23,6 +37,7 @@ export async function addImageOcclusionNote(
|
||||||
): Promise<Collection.OpChanges> {
|
): Promise<Collection.OpChanges> {
|
||||||
return imageOcclusion.addImageOcclusionNote(
|
return imageOcclusion.addImageOcclusionNote(
|
||||||
ImageOcclusion.AddImageOcclusionNoteRequest.create({
|
ImageOcclusion.AddImageOcclusionNoteRequest.create({
|
||||||
|
notetypeId,
|
||||||
imagePath,
|
imagePath,
|
||||||
occlusions,
|
occlusions,
|
||||||
header,
|
header,
|
||||||
|
@ -32,10 +47,10 @@ export async function addImageOcclusionNote(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getImageClozeNote(
|
export async function getImageOcclusionNote(
|
||||||
noteId: number,
|
noteId: number,
|
||||||
): Promise<ImageOcclusion.ImageClozeNoteResponse> {
|
): Promise<ImageOcclusion.GetImageOcclusionNoteResponse> {
|
||||||
return imageOcclusion.getImageClozeNote(
|
return imageOcclusion.getImageOcclusionNote(
|
||||||
ImageOcclusion.GetImageOcclusionNoteRequest.create({
|
ImageOcclusion.GetImageOcclusionNoteRequest.create({
|
||||||
noteId,
|
noteId,
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -8,7 +8,7 @@ import type { PanZoom } from "panzoom";
|
||||||
import protobuf from "protobufjs";
|
import protobuf from "protobufjs";
|
||||||
import { get } from "svelte/store";
|
import { get } from "svelte/store";
|
||||||
|
|
||||||
import { getImageClozeNote, getImageForOcclusion } from "./lib";
|
import { getImageForOcclusion, getImageOcclusionNote } from "./lib";
|
||||||
import { notesDataStore, tagsWritable, zoomResetValue } from "./store";
|
import { notesDataStore, tagsWritable, zoomResetValue } from "./store";
|
||||||
import Toast from "./Toast.svelte";
|
import Toast from "./Toast.svelte";
|
||||||
import { enableSelectable, moveShapeToCanvasBoundaries } from "./tools/lib";
|
import { enableSelectable, moveShapeToCanvasBoundaries } from "./tools/lib";
|
||||||
|
@ -35,7 +35,7 @@ export const setupMaskEditor = async (path: string, instance: PanZoom): Promise<
|
||||||
};
|
};
|
||||||
|
|
||||||
export const setupMaskEditorForEdit = async (noteId: number, instance: PanZoom): Promise<fabric.Canvas> => {
|
export const setupMaskEditorForEdit = async (noteId: number, instance: PanZoom): Promise<fabric.Canvas> => {
|
||||||
const clozeNoteResponse: ImageOcclusion.ImageClozeNoteResponse = await getImageClozeNote(noteId);
|
const clozeNoteResponse: ImageOcclusion.GetImageOcclusionNoteResponse = await getImageOcclusionNote(noteId);
|
||||||
if (clozeNoteResponse.error) {
|
if (clozeNoteResponse.error) {
|
||||||
new Toast({
|
new Toast({
|
||||||
target: document.body,
|
target: document.body,
|
||||||
|
|
|
@ -11,13 +11,18 @@ export function setupImageCloze(): void {
|
||||||
canvas.style.maxHeight = "95vh";
|
canvas.style.maxHeight = "95vh";
|
||||||
|
|
||||||
const ctx: CanvasRenderingContext2D = canvas.getContext("2d")!;
|
const ctx: CanvasRenderingContext2D = canvas.getContext("2d")!;
|
||||||
|
const container = document.getElementById("container") as HTMLDivElement;
|
||||||
const image = document.getElementById("img") as HTMLImageElement;
|
const image = document.getElementById("img") as HTMLImageElement;
|
||||||
|
if (image == null) {
|
||||||
|
container.innerText = "No image to show.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const size = limitSize({ width: image.naturalWidth, height: image.naturalHeight });
|
const size = limitSize({ width: image.naturalWidth, height: image.naturalHeight });
|
||||||
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)
|
// set height for div container (used 'relative' in css)
|
||||||
const container = document.getElementById("container") as HTMLDivElement;
|
|
||||||
container.style.height = `${image.height}px`;
|
container.style.height = `${image.height}px`;
|
||||||
|
|
||||||
// setup button for toggle image occlusion
|
// setup button for toggle image occlusion
|
||||||
|
|
Loading…
Reference in a new issue