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:
Damien Elmes 2023-04-19 15:30:18 +10:00 committed by GitHub
parent ed334fa45d
commit f6486da233
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 257 additions and 194 deletions

View file

@ -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.

View file

@ -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;
}
}

View file

@ -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,

View file

@ -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",
]

View file

@ -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)
}
}

View file

@ -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
}

View file

@ -3,3 +3,4 @@
pub mod imagedata;
pub mod imageocclusion;
pub(crate) mod notetype;

View 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)
}

View file

@ -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;

View file

@ -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>

View file

@ -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;
});
}

View file

@ -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;

View file

@ -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: {

View file

@ -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 });
}

View file

@ -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,
}),

View file

@ -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,

View file

@ -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