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-shrink-images = Shrink Images
|
||||
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.
|
||||
|
||||
|
|
|
@ -13,20 +13,23 @@ import "anki/notes.proto";
|
|||
import "anki/generic.proto";
|
||||
|
||||
service ImageOcclusionService {
|
||||
rpc GetImageForOcclusion(GetImageForOcclusionRequest) returns (ImageData);
|
||||
rpc GetImageForOcclusion(GetImageForOcclusionRequest)
|
||||
returns (GetImageForOcclusionResponse);
|
||||
rpc AddImageOcclusionNote(AddImageOcclusionNoteRequest)
|
||||
returns (collection.OpChanges);
|
||||
rpc GetImageClozeNote(GetImageOcclusionNoteRequest)
|
||||
returns (ImageClozeNoteResponse);
|
||||
rpc GetImageOcclusionNote(GetImageOcclusionNoteRequest)
|
||||
returns (GetImageOcclusionNoteResponse);
|
||||
rpc UpdateImageOcclusionNote(UpdateImageOcclusionNoteRequest)
|
||||
returns (collection.OpChanges);
|
||||
// Adds an I/O notetype if none exists in the collection.
|
||||
rpc AddImageOcclusionNotetype(generic.Empty) returns (collection.OpChanges);
|
||||
}
|
||||
|
||||
message GetImageForOcclusionRequest {
|
||||
string path = 1;
|
||||
}
|
||||
|
||||
message ImageData {
|
||||
message GetImageForOcclusionResponse {
|
||||
bytes data = 1;
|
||||
string name = 2;
|
||||
}
|
||||
|
@ -37,20 +40,28 @@ message AddImageOcclusionNoteRequest {
|
|||
string header = 3;
|
||||
string back_extra = 4;
|
||||
repeated string tags = 5;
|
||||
}
|
||||
|
||||
message ImageClozeNote {
|
||||
bytes image_data = 1;
|
||||
string occlusions = 2;
|
||||
string header = 3;
|
||||
string back_extra = 4;
|
||||
repeated string tags = 5;
|
||||
int64 notetype_id = 6;
|
||||
}
|
||||
|
||||
message GetImageOcclusionNoteRequest {
|
||||
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 {
|
||||
int64 note_id = 1;
|
||||
string occlusions = 2;
|
||||
|
@ -58,10 +69,3 @@ message UpdateImageOcclusionNoteRequest {
|
|||
string back_extra = 4;
|
||||
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
|
||||
Delimiter = import_export_pb2.CsvMetadata.Delimiter
|
||||
TtsVoice = card_rendering_pb2.AllTtsVoicesResponse.TtsVoice
|
||||
ImageData = image_occlusion_pb2.ImageData
|
||||
GetImageForOcclusionResponse = image_occlusion_pb2.GetImageForOcclusionResponse
|
||||
AddImageOcclusionNoteRequest = image_occlusion_pb2.AddImageOcclusionNoteRequest
|
||||
ImageClozeNoteResponse = image_occlusion_pb2.ImageClozeNoteResponse
|
||||
GetImageOcclusionNoteResponse = image_occlusion_pb2.GetImageOcclusionNoteResponse
|
||||
|
||||
import copy
|
||||
import os
|
||||
|
@ -462,18 +462,21 @@ class Collection(DeprecatedNamesMixin):
|
|||
|
||||
# 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)
|
||||
|
||||
def add_image_occlusion_note(
|
||||
self,
|
||||
image_path: str | None,
|
||||
occlusions: str | None,
|
||||
header: str | None,
|
||||
back_extra: str | None,
|
||||
tags: list[str] | None,
|
||||
notetype_id: int,
|
||||
image_path: str,
|
||||
occlusions: str,
|
||||
header: str,
|
||||
back_extra: str,
|
||||
tags: list[str],
|
||||
) -> OpChanges:
|
||||
return self._backend.add_image_occlusion_note(
|
||||
notetype_id=notetype_id,
|
||||
image_path=image_path,
|
||||
occlusions=occlusions,
|
||||
header=header,
|
||||
|
@ -481,8 +484,10 @@ class Collection(DeprecatedNamesMixin):
|
|||
tags=tags,
|
||||
)
|
||||
|
||||
def get_image_cloze_note(self, note_id: int | None) -> ImageClozeNoteResponse:
|
||||
return self._backend.get_image_cloze_note(note_id=note_id)
|
||||
def get_image_occlusion_note(
|
||||
self, note_id: int | None
|
||||
) -> GetImageOcclusionNoteResponse:
|
||||
return self._backend.get_image_occlusion_note(note_id=note_id)
|
||||
|
||||
def update_image_occlusion_note(
|
||||
self,
|
||||
|
|
|
@ -481,7 +481,7 @@ exposed_backend_list = [
|
|||
# ImageOcclusionService
|
||||
"get_image_for_occlusion",
|
||||
"add_image_occlusion_note",
|
||||
"get_image_cloze_note",
|
||||
"get_image_occlusion_note",
|
||||
"update_image_occlusion_note",
|
||||
]
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ impl ImageOcclusionService for Backend {
|
|||
fn get_image_for_occlusion(
|
||||
&self,
|
||||
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))
|
||||
}
|
||||
|
||||
|
@ -20,6 +20,7 @@ impl ImageOcclusionService for Backend {
|
|||
) -> Result<pb::collection::OpChanges> {
|
||||
self.with_col(|col| {
|
||||
col.add_image_occlusion_note(
|
||||
input.notetype_id.into(),
|
||||
&input.image_path,
|
||||
&input.occlusions,
|
||||
&input.header,
|
||||
|
@ -30,11 +31,11 @@ impl ImageOcclusionService for Backend {
|
|||
.map(Into::into)
|
||||
}
|
||||
|
||||
fn get_image_cloze_note(
|
||||
fn get_image_occlusion_note(
|
||||
&self,
|
||||
input: pb::image_occlusion::GetImageOcclusionNoteRequest,
|
||||
) -> Result<pb::image_occlusion::ImageClozeNoteResponse> {
|
||||
self.with_col(|col| col.get_image_cloze_note(input.note_id.into()))
|
||||
) -> Result<pb::image_occlusion::GetImageOcclusionNoteResponse> {
|
||||
self.with_col(|col| col.get_image_occlusion_note(input.note_id.into()))
|
||||
}
|
||||
|
||||
fn update_image_occlusion_note(
|
||||
|
@ -52,4 +53,12 @@ impl ImageOcclusionService for Backend {
|
|||
})
|
||||
.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::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use regex::Regex;
|
||||
|
||||
use crate::io::metadata;
|
||||
use crate::io::read_file;
|
||||
use crate::media::MediaManager;
|
||||
use crate::notetype::stock::empty_stock;
|
||||
use crate::notetype::CardGenContext;
|
||||
use crate::notetype::Notetype;
|
||||
use crate::notetype::NotetypeKind;
|
||||
use crate::pb::image_occlusion::image_cloze_note_response::Value;
|
||||
use crate::pb::image_occlusion::ImageClozeNote;
|
||||
use crate::pb::image_occlusion::ImageClozeNoteResponse;
|
||||
pub use crate::pb::image_occlusion::ImageData;
|
||||
use crate::pb::notetypes::stock_notetype::OriginalStockKind;
|
||||
use crate::pb::image_occlusion::get_image_occlusion_note_response::ImageClozeNote;
|
||||
use crate::pb::image_occlusion::get_image_occlusion_note_response::Value;
|
||||
use crate::pb::image_occlusion::GetImageForOcclusionResponse;
|
||||
use crate::pb::image_occlusion::GetImageOcclusionNoteResponse;
|
||||
use crate::prelude::*;
|
||||
|
||||
impl Collection {
|
||||
pub fn get_image_for_occlusion(&mut self, path: &str) -> Result<ImageData> {
|
||||
let mut metadata = ImageData {
|
||||
pub fn get_image_for_occlusion(&mut self, path: &str) -> Result<GetImageForOcclusionResponse> {
|
||||
let mut metadata = GetImageForOcclusionResponse {
|
||||
..Default::default()
|
||||
};
|
||||
metadata.data = read_file(path)?;
|
||||
|
@ -33,6 +28,7 @@ impl Collection {
|
|||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn add_image_occlusion_note(
|
||||
&mut self,
|
||||
notetype_id: NotetypeId,
|
||||
image_path: &str,
|
||||
occlusions: &str,
|
||||
header: &str,
|
||||
|
@ -52,13 +48,20 @@ impl Collection {
|
|||
let actual_image_name_after_adding = mgr.add_file(&image_filename, &image_bytes)?;
|
||||
|
||||
let image_tag = format!(
|
||||
"<img id='img' src='{}'></img>",
|
||||
r#"<img id="img" src="{}">"#,
|
||||
&actual_image_name_after_adding
|
||||
);
|
||||
|
||||
let current_deck = self.get_current_deck()?;
|
||||
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();
|
||||
note.set_field(0, occlusions)?;
|
||||
|
@ -76,47 +79,18 @@ impl Collection {
|
|||
})
|
||||
}
|
||||
|
||||
fn get_or_create_io_notetype(&mut self) -> Result<Arc<Notetype>> {
|
||||
let tr = &self.tr;
|
||||
let name = format!("{}", tr.notetypes_image_occlusion_name());
|
||||
let nt = match self.get_notetype_by_name(&name)? {
|
||||
Some(nt) => nt,
|
||||
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) {
|
||||
pub fn get_image_occlusion_note(
|
||||
&mut self,
|
||||
note_id: NoteId,
|
||||
) -> Result<GetImageOcclusionNoteResponse> {
|
||||
let value = match self.get_image_occlusion_note_inner(note_id) {
|
||||
Ok(note) => Value::Note(note),
|
||||
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 mut cloze_note = ImageClozeNote::default();
|
||||
|
||||
|
@ -131,9 +105,9 @@ impl Collection {
|
|||
cloze_note.image_data = "".into();
|
||||
cloze_note.tags = note.tags.clone();
|
||||
|
||||
let image_file_name = fields[1].clone();
|
||||
let image_file_name = &fields[1];
|
||||
let src = self
|
||||
.extract_img_src(&image_file_name)
|
||||
.extract_img_src(image_file_name)
|
||||
.unwrap_or_else(|| "".to_owned());
|
||||
let final_path = self.media_folder.join(src);
|
||||
|
||||
|
@ -190,58 +164,3 @@ impl Collection {
|
|||
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 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::error::Result;
|
||||
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::notetype::Notetype;
|
||||
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 Container from "../components/Container.svelte";
|
||||
import { saveImageNotes } from "./generate";
|
||||
import { addOrUpdateNote } from "./generate";
|
||||
import type { IOMode } from "./lib";
|
||||
import MasksEditor from "./MaskEditor.svelte";
|
||||
import Notes from "./Notes.svelte";
|
||||
import StickyFooter from "./StickyFooter.svelte";
|
||||
|
||||
export let path: string | null;
|
||||
export let noteId: number | null;
|
||||
export let mode: IOMode;
|
||||
|
||||
async function hideAllGuessOne(): Promise<void> {
|
||||
saveImageNotes(path!, noteId!, false);
|
||||
addOrUpdateNote(mode, false);
|
||||
}
|
||||
|
||||
async function hideOneGuessOne(): Promise<void> {
|
||||
saveImageNotes(path!, noteId!, true);
|
||||
addOrUpdateNote(mode, true);
|
||||
}
|
||||
|
||||
const items = [
|
||||
|
@ -41,12 +41,16 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
</ul>
|
||||
|
||||
<div hidden={activeTabValue != 1}>
|
||||
<MasksEditor {path} {noteId} />
|
||||
<MasksEditor {mode} />
|
||||
</div>
|
||||
|
||||
<div hidden={activeTabValue != 2}>
|
||||
<div class="notes-page"><Notes /></div>
|
||||
<StickyFooter {hideAllGuessOne} {hideOneGuessOne} {noteId} />
|
||||
<StickyFooter
|
||||
{hideAllGuessOne}
|
||||
{hideOneGuessOne}
|
||||
editing={mode.kind == "edit"}
|
||||
/>
|
||||
</div>
|
||||
</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 panzoom from "panzoom";
|
||||
|
||||
import type { IOMode } from "./lib";
|
||||
import { setupMaskEditor, setupMaskEditorForEdit } from "./mask-editor";
|
||||
import SideToolbar from "./SideToolbar.svelte";
|
||||
|
||||
export let path: string | null;
|
||||
export let noteId: number | null;
|
||||
export let mode: IOMode;
|
||||
|
||||
let instance: PanZoom;
|
||||
let innerWidth = 0;
|
||||
|
@ -26,14 +26,12 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
});
|
||||
instance.pause();
|
||||
|
||||
if (path) {
|
||||
setupMaskEditor(path, instance).then((canvas1) => {
|
||||
if (mode.kind == "add") {
|
||||
setupMaskEditor(mode.imagePath, instance).then((canvas1) => {
|
||||
canvas = canvas1;
|
||||
});
|
||||
}
|
||||
|
||||
if (noteId) {
|
||||
setupMaskEditorForEdit(noteId, instance).then((canvas1) => {
|
||||
} else {
|
||||
setupMaskEditorForEdit(mode.noteId, instance).then((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 hideOneGuessOne: () => void;
|
||||
export let noteId: number | null;
|
||||
export let editing: boolean;
|
||||
</script>
|
||||
|
||||
<div style:flex-grow="1" />
|
||||
<div class="sticky-footer">
|
||||
{#if noteId}
|
||||
{#if editing}
|
||||
<div class="update-note-text">
|
||||
{tr.actionsUpdateNote()}
|
||||
</div>
|
||||
|
@ -49,7 +49,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
z-index: 99;
|
||||
margin: 0;
|
||||
padding: 0.25rem;
|
||||
background: var(--window-bg);
|
||||
background: var(--canvas);
|
||||
border-style: solid none none;
|
||||
border-color: var(--border);
|
||||
border-width: thin;
|
||||
|
|
|
@ -6,6 +6,7 @@ import { fabric } from "fabric";
|
|||
import { get } from "svelte/store";
|
||||
|
||||
import type { Collection } from "../lib/proto";
|
||||
import type { IOMode } from "./lib";
|
||||
import { addImageOcclusionNote, updateImageOcclusionNote } from "./lib";
|
||||
import { notesDataStore, tagsWritable } from "./store";
|
||||
import Toast from "./Toast.svelte";
|
||||
|
@ -107,9 +108,8 @@ const getObjectPositionInGroup = (group, object): { top: number; left: number }
|
|||
return { top, left };
|
||||
};
|
||||
|
||||
export const saveImageNotes = async function(
|
||||
imagePath: string,
|
||||
noteId: number,
|
||||
export const addOrUpdateNote = async function(
|
||||
mode: IOMode,
|
||||
hideInactive: boolean,
|
||||
): Promise<void> {
|
||||
const { occlusionCloze, noteCount } = generate(hideInactive);
|
||||
|
@ -125,29 +125,30 @@ export const saveImageNotes = async function(
|
|||
header = header ? `<div>${header}</div>` : "";
|
||||
backExtra = header ? `<div>${backExtra}</div>` : "";
|
||||
|
||||
if (noteId) {
|
||||
if (mode.kind == "edit") {
|
||||
const result = await updateImageOcclusionNote(
|
||||
noteId,
|
||||
mode.noteId,
|
||||
occlusionCloze,
|
||||
header,
|
||||
backExtra,
|
||||
tags,
|
||||
);
|
||||
showResult(noteId, result, noteCount);
|
||||
showResult(mode.noteId, result, noteCount);
|
||||
} else {
|
||||
const result = await addImageOcclusionNote(
|
||||
imagePath,
|
||||
mode.notetypeId,
|
||||
mode.imagePath,
|
||||
occlusionCloze,
|
||||
header,
|
||||
backExtra,
|
||||
tags,
|
||||
);
|
||||
showResult(noteId, result, noteCount);
|
||||
showResult(null, result, noteCount);
|
||||
}
|
||||
};
|
||||
|
||||
// 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({
|
||||
target: document.body,
|
||||
props: {
|
||||
|
|
|
@ -7,6 +7,7 @@ import { ModuleName, setupI18n } from "@tslib/i18n";
|
|||
|
||||
import { checkNightMode } from "../lib/nightmode";
|
||||
import ImageOcclusionPage from "./ImageOcclusionPage.svelte";
|
||||
import type { IOMode } from "./lib";
|
||||
|
||||
const i18n = setupI18n({
|
||||
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();
|
||||
await i18n;
|
||||
|
||||
return new ImageOcclusionPage({
|
||||
target: document.body,
|
||||
props: {
|
||||
path: path,
|
||||
noteId: null,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function setupImageOcclusionForEdit(noteId: number): Promise<ImageOcclusionPage> {
|
||||
checkNightMode();
|
||||
await i18n;
|
||||
|
||||
return new ImageOcclusionPage({
|
||||
target: document.body,
|
||||
props: {
|
||||
path: null,
|
||||
noteId: noteId,
|
||||
mode,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (window.location.hash.startsWith("#test-")) {
|
||||
const path = window.location.hash.replace("#test-", "");
|
||||
setupImageOcclusion(path);
|
||||
const imagePath = window.location.hash.replace("#test-", "");
|
||||
setupImageOcclusion({ kind: "add", imagePath, notetypeId: 0 });
|
||||
}
|
||||
|
||||
if (window.location.hash.startsWith("#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 { 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(
|
||||
path: string,
|
||||
): Promise<ImageOcclusion.ImageData> {
|
||||
): Promise<ImageOcclusion.GetImageForOcclusionResponse> {
|
||||
return imageOcclusion.getImageForOcclusion(
|
||||
ImageOcclusion.GetImageForOcclusionRequest.create({
|
||||
path,
|
||||
|
@ -15,6 +28,7 @@ export async function getImageForOcclusion(
|
|||
}
|
||||
|
||||
export async function addImageOcclusionNote(
|
||||
notetypeId: number,
|
||||
imagePath: string,
|
||||
occlusions: string,
|
||||
header: string,
|
||||
|
@ -23,6 +37,7 @@ export async function addImageOcclusionNote(
|
|||
): Promise<Collection.OpChanges> {
|
||||
return imageOcclusion.addImageOcclusionNote(
|
||||
ImageOcclusion.AddImageOcclusionNoteRequest.create({
|
||||
notetypeId,
|
||||
imagePath,
|
||||
occlusions,
|
||||
header,
|
||||
|
@ -32,10 +47,10 @@ export async function addImageOcclusionNote(
|
|||
);
|
||||
}
|
||||
|
||||
export async function getImageClozeNote(
|
||||
export async function getImageOcclusionNote(
|
||||
noteId: number,
|
||||
): Promise<ImageOcclusion.ImageClozeNoteResponse> {
|
||||
return imageOcclusion.getImageClozeNote(
|
||||
): Promise<ImageOcclusion.GetImageOcclusionNoteResponse> {
|
||||
return imageOcclusion.getImageOcclusionNote(
|
||||
ImageOcclusion.GetImageOcclusionNoteRequest.create({
|
||||
noteId,
|
||||
}),
|
||||
|
|
|
@ -8,7 +8,7 @@ import type { PanZoom } from "panzoom";
|
|||
import protobuf from "protobufjs";
|
||||
import { get } from "svelte/store";
|
||||
|
||||
import { getImageClozeNote, getImageForOcclusion } from "./lib";
|
||||
import { getImageForOcclusion, getImageOcclusionNote } from "./lib";
|
||||
import { notesDataStore, tagsWritable, zoomResetValue } from "./store";
|
||||
import Toast from "./Toast.svelte";
|
||||
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> => {
|
||||
const clozeNoteResponse: ImageOcclusion.ImageClozeNoteResponse = await getImageClozeNote(noteId);
|
||||
const clozeNoteResponse: ImageOcclusion.GetImageOcclusionNoteResponse = await getImageOcclusionNote(noteId);
|
||||
if (clozeNoteResponse.error) {
|
||||
new Toast({
|
||||
target: document.body,
|
||||
|
|
|
@ -11,13 +11,18 @@ export function setupImageCloze(): void {
|
|||
canvas.style.maxHeight = "95vh";
|
||||
|
||||
const ctx: CanvasRenderingContext2D = canvas.getContext("2d")!;
|
||||
const container = document.getElementById("container") as HTMLDivElement;
|
||||
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 });
|
||||
canvas.width = size.width;
|
||||
canvas.height = size.height;
|
||||
|
||||
// set height for div container (used 'relative' in css)
|
||||
const container = document.getElementById("container") as HTMLDivElement;
|
||||
container.style.height = `${image.height}px`;
|
||||
|
||||
// setup button for toggle image occlusion
|
||||
|
|
Loading…
Reference in a new issue