Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature image occlusion #2367

Merged
merged 39 commits into from
Mar 29, 2023
Merged

Feature image occlusion #2367

merged 39 commits into from
Mar 29, 2023

Conversation

krmanik
Copy link
Contributor

@krmanik krmanik commented Feb 6, 2023

This is most requested feature for AnkiDroid and AnkiMobile users. This feature is implemented like import-csv features.

Only image is stored in collection. Media, the question & answer masks are stored in notes itself, using data-* attributes and drawn using canvas api in reviewer.

Mask Editor

  • It is implemented using fabricjs and panzoom.
  • Currently following shape tools are supported
    • Rectangle
    • Ellipse
    • Polygon (draw using mouse pointer)
  • Following tools are available to customize shapes
    • Generate group and individual cloze
    • Group and ungroup shapes
    • Delete shapes
    • Copy and paste selected shape
    • Undo and redo

Note editor

Very simple text editor is implemented (still needs improvement)

  • Types of notes generated
    • hide all, guess one
    • hide one, guess one

Image

@glutanimate
Copy link
Contributor

Nice work, Mani! Will take a closer look at this later today/tomorrow, but just wanted to say for now: Glad to see you back!

For continuity's sake, here are the discussions/iterations up to this point: krmanik#1

@krmanik
Copy link
Contributor Author

krmanik commented Feb 6, 2023

Trying to fix these errors but I am not able to figure out the issues.
The backend have input: pb::image_occlusion::ImageClozeMetadataRequest, and input: pb::image_occlusion::AddImageOcclusionNotesRequest, but still it showing errors. What should be possible fix?

~/anki$ ./ninja pylib/anki qt/aqt check
    Finished dev [unoptimized + debuginfo] target(s) in 0.17s
[50/54; 5 active; 99.734s] check:mypy
FAILED: /home/mani/anki/out/tests/python_typecheck.10916796379318679380 
/home/mani/anki/out/rust/debug/runner run --stamp=/home/mani/anki/out/tests/python_typecheck.10916796379318679380 /home/mani/anki/out/pyenv/bin/mypy pylib ts/lib qt/aqt qt/tools out/pylib/anki out/qt/_aqt ftl python tools
Command failed: 
pylib/anki/collection.py:464: error: Argument 1 to "get_image_cloze_metadata" of "RustBackendGenerated" has incompatible type "ImageClozeMetadataRequest"; expected "str"  [arg-type]
pylib/anki/collection.py:483: error: Too many positional arguments for "add_image_occlusion_notes" of "RustBackendGenerated"  [misc]
pylib/anki/collection.py:483: error: Missing named argument "deck_id" for "add_image_occlusion_notes" of "RustBackendGenerated"  [call-arg]
pylib/anki/collection.py:483: error: Missing named argument "occlusions" for "add_image_occlusion_notes" of "RustBackendGenerated"  [call-arg]
pylib/anki/collection.py:483: error: Missing named argument "header" for "add_image_occlusion_notes" of "RustBackendGenerated"  [call-arg]
pylib/anki/collection.py:483: error: Missing named argument "notes" for "add_image_occlusion_notes" of "RustBackendGenerated"  [call-arg]
pylib/anki/collection.py:483: error: Missing named argument "tags" for "add_image_occlusion_notes" of "RustBackendGenerated"  [call-arg]
pylib/anki/collection.py:483: error: Argument 1 to "add_image_occlusion_notes" of "RustBackendGenerated" has incompatible type "AddImageOcclusionNotesRequest"; expected "str"  [arg-type]
Found 8 errors in 1 file (checked 345 source files)

@RumovZ
Copy link
Collaborator

RumovZ commented Feb 6, 2023

Wow, great job!

You usually don't need to construct those request types when calling backend routines. So instead of:

request = image_occlusion_pb2.ImageClozeMetadataRequest(path=path)
return self._backend.get_image_cloze_metadata(request)

Try:

return self._backend.get_image_cloze_metadata(path=path)

I assume you modeled your code after get_csv_metadata(), but that seems to be a special case, presumably because of the optionals. You can check what methods are generated exactly in out/pylib/anki/_backend_generated.py.

@dae
Copy link
Member

dae commented Feb 7, 2023

Thank you so much for working on this Mani!

I've only had a brief play with it so far, but some initial thoughts:

  • There's a lot of great stuff here - undo, object selection/transformation, a variety of tools, the fact that occlusions are in code instead of generated images, etc.
  • I wonder if we really need all those tools? The triangle, square and circle tools feel a bit redundant given there's a path tool, rectangle tool and oval tool as well. I suspect the rectangle tool alone is going to be the most-used one by far. But @glutanimate would have a better idea here.
  • For the Notes tab, I'd be tempted to leave the formatting buttons out for now, or limit them to just B/I/U. Eventually we'll want to be able to create occlusions directly inside our normal editor instead of having a separate window (point 4 on Feature: Image occlusion krmanik/anki#1 (comment))
  • Is there a way to edit previously added cards at the moment?
  • It took me a while to find the zoom tools. Would it perhaps make more sense to always show the zoom icons, and get rid of the separate zoom mode?
  • I wasn't able to figure out how the fill tool works.

Some thoughts on design decisions:

Storing the occlusion data in the fields instead of images is great. Storing them as HTML, I'm a little less sure about. Because users can't see the paths unless they enable the HTML editor, I fear some users may accidentally damage or delete their occlusions when they edit the text (point 1 on krmanik#1 (comment)). We could partly avoid that by changing the editor so that users aren't able to edit the field, though that won't solve the problem for older clients/AnkiWeb/etc.

Would it make sense to make the paths visible instead? Eg instead of something like

<div data-type="rect" data-left="399.01" data-top="99.52" data-width="167.09" data-height="33.78" data-fill="#0a2cee" data-stroke="null" data-strokewidth="1" data-angle="0" data-rx="0" data-ry="0">{{c5::hidden::prompted}}</div>

What if we used something like this instead?

{{c5::image-occlusion:rect:399.01,99.52,167.09,33.78:fill=#0a2cee:stroke=1}}

(omitting values if they're the default)

We could then adjust cloze.rs to look for image-occlusion as a prefix, and transform the text into the desired HTML for both the question and answer side.

What do people think? Would that be an improvement, or make things worse?

One other thing - the script in the notetype. It's impressive how simple it is, but I think storing it in the notetype will make it hard for us to make changes in the future if we have old notetypes to worry about, and it increases the chances the user will make accidental changes. Maybe our reviewer TypeScript could export the required function(s) instead, so we could keep the code in the notetype to the minimum?

@glutanimate
Copy link
Contributor

glutanimate commented Feb 7, 2023

@dae I started jotting down some notes yesterday, and your points echo a lot of my thoughts 👍

The triangle, square and circle tools feel a bit redundant given there's a path tool, rectangle tool and oval tool as well

Agreed, yes. I appreciate the effort you went through in adding all of these, Mani, but I do think we should keep the UI complexity down. Even the rectangle tool by its own will likely cover 90% of all IO use cases. With the ellipse tool and path tool by its side, I think we have all bases covered. Being lean here is particularly important on mobile I would argue, where selecting the right tool via touches is likely more of a challenge than on desktop (though even on desktop, in playing around with the PR over the past couple of days I've already had multiple instances where I ended up confusing square vs rectangle and circle vs ellipse, leading to momentary confusion).

So from a UX and maintenance standpoint, I would vote to not include triangle, square, and circle. Though again, thanks a lot for your work on these, Mani!

For the rest, focusing on note type design decisions for now as I don't want to make this comment overlong:

  • @dae user modifiability across the card template, field content, and even field set-up also remains my main concern here, in particular in the context of future iterations and Anki version compatibility. We definitely want to avoid a situation where users have to deal with similar compatibility woes for note types as for add-ons.

  • For the editor, I agree and still think it would be crucial to make the Occlusions field immutable, but I would even go one step beyond that and do the same for the Image field to e.g. avoid accidental mishaps by attempting to resize the image. Basically, any programmatically managed field should not be user-editable outside of the IO editor.

  • Regarding additional protections for field content: I do find the idea interesting to be more transparent about the actual data contained in the field. I could see this leading to less edits made out of confusion where users do not understand that keeping {{c1::hidden::prompted}} verbatim is actually essential for IO to work. It would also kill two birds with one stone, as I noticed that we currently trigger Anki's dupe detection on the Occlusions field, likely because the logic is comparing the plain text values of the fields (or at least ignoring data attributes).

    At the same time, storing the data as plain text clozes might encourage users to attempt to manually adjust the values. It also seems more fickle to a degree as we would have to consider HTML escaping, etc. So I'm a bit undecided here still.

    I would actually also like to revisit whether we still need the shown::prompted approach to control mask visibility. With the improved cloze metadata on 2.1.57+, the cloze::hint system primarily only serves to distinguish between different mask reveal modes (Hide All vs Hide One). However, as we have to split these into separate note types anyways in order to provide different sibling burying behavior, there really isn't any need for this system anymore as the behavior would be managed by the template, which might allow us to simplify the field data format further.

    I'll have to play around with this a bit more.

  • Regarding the card template: I do like the idea of moving the core logic into reviewer.ts and thus out of reach of accidental user edits. I would even vote to compress this down to a single entry point function called in the template. To override Anki default behavior, user templates could then simply forfeit calling the entry point and implementing their own mask composition logic. Or, in a second iteration, we could introduce a JS hook API that would allow template-residing code or add-ons to customize Anki's behavior in a more fine-grained way.

  • Regarding the field set-up: I would also strongly vote to treat the IO note type specially here and add some protections that prevent users from deleting or otherwise modifying the Image and Occlusions fields. While less common than accidental edits to field content and templates, this is yet another issue I've seen users run into many times over the years with IOE. Basically, I would see us adding a field-specific flag that would mark them as protected both in the Fields dialog and Editor.

Last, some considerations towards editing existing notes and handling multiple IO note types:

  • I do think it's essential that we provide users with a way to edit existing notes.
  • This is not the only place where we have to take this into consideration, but it's probably where the need for this is the most clear: Given that users are likely to end up with multiple copies of the IO note type as they traverse shared decks, etc., the IO editor has to be capable of handling more than one note type. This is of course also essential to enable users to create their own custom IO note types, which they will expect to be able to do similar to any other note type.
  • However, decoupling the native IO implementation from one hard-coded note type has a number of implications:
    • On the UX side, users would have to be able to select the IO note type to use when creating image occlusions. While we're still following the separate IO editor window paradigm, the most reasonable place to perform this selection would likely be in the IO editor window (next to the deck selector, probably)
    • On the backend side, we would need to have a concept of what constitutes a valid IO note type (so that users don't e.g. attempt to select Basic). We would probably want to handle this in the same way as we currently do for Cloze note types.

@krmanik
Copy link
Contributor Author

krmanik commented Feb 7, 2023

I assume you modeled your code after get_csv_metadata(), but that seems to be a special case, presumably because of the optionals. You can check what methods are generated exactly in out/pylib/anki/_backend_generated.py.

It works. Thanks.


I wonder if we really need all those tools? The triangle, square and circle tools feel a bit redundant given there's a path tool, rectangle tool and oval tool as well. I suspect the rectangle tool alone is going to be the most-used one by far. But @glutanimate would have a better idea here.

Some of the tools will be removed and only selected tools be available.

For the Notes tab, I'd be tempted to leave the formatting buttons out for now, or limit them to just B/I/U. Eventually we'll want to be able to create occlusions directly inside our normal editor instead of having a separate window (point 4 on krmanik#1 (comment))

I have implementing considering mobile version, but notes editor will be also ported similar to other web pages. So, I think I should look into integrating into the existing note editor(but currently separate note editor for mobile version should be used).

Is there a way to edit previously added cards at the moment?

The editing of card is not available, the edit button should open mask editor and generate fabric.Canvas objects and place on mask editor. After finish of edit, the data in cloze re-adjusted. (design idea)

It took me a while to find the zoom tools. Would it perhaps make more sense to always show the zoom icons, and get rid of the separate zoom mode?

I will change the zoom tools to always show.

I wasn't able to figure out how the fill tool works.

It is reverse of what fill tools works. In my (incorrect) implementation first select shapes and then select fill tools to change the color of shapes. But it should work like select fill tools and select any shapes and change their fill-color. It will be fixed in next commits.

What if we used something like this instead?

{{c5::image-occlusion:rect:399.01,99.52,167.09,33.78:fill=#0a2cee:stroke=1}}

I think it will reduce the html in the notes. This is information visible to users. I will look into cloze.rs for implementations.

As pointed by glutanimate,

keeping {{c1::hidden::prompted}} verbatim is actually essential for IO to work.

Because it checks for hidden and cloze (css) class to show and hide the shapes.

So the following cloze should be

{{c5::image-occlusion:rect:399.01,99.52,167.09,33.78:fill=#0a2cee:stroke=1}}

replaced with

{{c5::hidden::prompted::image-occlusion:rect:399.01,99.52,167.09,33.78:fill=#0a2cee:stroke=1}}

And generated html using later part image-occlusion:rect:399.01,99.52,167.09,33.78:fill=#0a2cee:stroke=1.

Or may be other cloze generation should be used.

Maybe our reviewer TypeScript could export the required function(s) instead, so we could keep the code in the notetype to the minimum?

It will be best to export this to reviewer TypeScript, because minimum changes in image occlusion note type make it easier to updates.

However, decoupling the native IO implementation from one hard-coded note type has a number of implications:
On the backend side, we would need to have a concept of what constitutes a valid IO note type (so that users don't e.g. attempt to select Basic). We would probably want to handle this in the same way as we currently do for Cloze note types.

This can be implemented similar to cloze, when cloze note types selected then other cloze button are shown. So, here the image selection to generate image occlusion should show/hide the button depending on note types. If note types are image occlusion then show the button to select image and show mask editor otherwise hide it.

Also three icon button will be shown when note types Image Occlusion selected.
| Select Image | Mask Editor | Notes Editor |

| Select Image |

  • Open file manager, select image and load mask editor in native note editor
  • If already image selected then prompt for removing image generating again

| Mask Editor |

  • The mask editor will be shown only shown after image selected.
  • The mask editor will be shown when mask editor button clicked

| Notes Editor |

  • The note editor will be shown by default but when user select image, it will be replaced with mask editor
  • The fields in note editor will be shown when mask editor button clicked

Edit scenario

  • When user click edits then it load the canvas objects and place mask editor. After edit update notes.
  • It will handle the immutability of Occlusions and Image fields.

@krmanik
Copy link
Contributor Author

krmanik commented Feb 7, 2023

The latest commit is not complete. I have tried to filter out the data-cloze, but need help to finish it.

I want the data-cloze should only contains image-occlusion, but filtering is not done.

thread 'cloze::test::image_cloze' panicked at 'assertion failed: `(left == right)`
  left: `"<span class=\"cloze\" data-cloze=\"image&#x2D;occlusion&#x3A;rect&#x3A;left&#x3D;10&#x2E;0&#x3A;top&#x3D;20&#x3A;width&#x3D;30&#x3A;height&#x3D;10&#x3A;fill&#x3D;&#x23;ffe34d&#x3A;stroke&#x3D;5\" data-shape=\"rect\" data-left=\"10.0\" data-top=\"20\" data-width=\"30\" data-height=\"10\" data-fill=\"#ffe34d\" data-stroke=\"5\"  data-ordinal=\"1\">[...]</span>"`,
 right: `"<span class=\"cloze\" data-cloze=\"image-occlusion\" data-shape=\"rect\" data-left=\"10.0\" data-top=\"20\" data-width=\"30\" data-height=\"10\" data-fill=\"#ffe34d\" data-stroke=\"5\" data-ordinal=\"1\">[...]</span>"`', rslib/src/cloze.rs:544:9

@dae
Copy link
Member

dae commented Feb 8, 2023

We can bypass all the usual cloze rendering and have complete control over what is output for image occlusion. Eg:

diff --git a/rslib/src/cloze.rs b/rslib/src/cloze.rs
index 720f89e74..158cd2f2d 100644
--- a/rslib/src/cloze.rs
+++ b/rslib/src/cloze.rs
@@ -140,9 +140,12 @@ impl ExtractedCloze<'_> {
         buf.into()
     }
 
-    fn image_occlusion(&self) -> Cow<str> {
-        get_image_cloze_data(&self.clozed_text()).into()
-    } 
+    /// If cloze starts with image-occlusion:, return the text following that.
+    fn image_occlusion(&self) -> Option<&str> {
+        let Some(first_node) = self.nodes.get(0) else { return None };
+        let TextOrCloze::Text(text) = first_node else { return None };
+        text.strip_prefix("image-occlusion:")
+    }
 }
 
 fn parse_text_with_clozes(text: &str) -> Vec<TextOrCloze<'_>> {
@@ -217,6 +220,10 @@ fn reveal_cloze(
 ) {
     let active = cloze.ordinal == cloze_ord;
     *active_cloze_found_in_text |= active;
+    if let Some(image_occlusion_text) = cloze.image_occlusion() {
+        buf.push_str(&render_image_occlusion(image_occlusion_text, question, active));
+        return;
+    }
     match (question, active) {
         (true, true) => {
             // question side with active cloze; all inner content is elided
@@ -235,9 +242,8 @@ fn reveal_cloze(
             }
             write!(
                 buf,
-                r#"<span class="cloze" data-cloze="{}" {} data-ordinal="{}">[{}]</span>"#,
+                r#"<span class="cloze" data-cloze="{}" data-ordinal="{}">[{}]</span>"#,
                 encode_attribute(&content_buf),
-                cloze.image_occlusion(),
                 cloze.ordinal,
                 cloze.hint()
             )
@@ -246,8 +252,7 @@ fn reveal_cloze(
         (false, true) => {
             write!(
                 buf,
-                r#"<span class="cloze" {} data-ordinal="{}">"#,
-                cloze.image_occlusion(),
+                r#"<span class="cloze" data-ordinal="{}">"#,
                 cloze.ordinal
             )
             .unwrap();
@@ -265,8 +270,7 @@ fn reveal_cloze(
             // question or answer side inactive cloze; text shown, children may be active
             write!(
                 buf,
-                r#"<span class="cloze-inactive" {} data-ordinal="{}">"#,
-                cloze.image_occlusion(),
+                r#"<span class="cloze-inactive" data-ordinal="{}">"#,
                 cloze.ordinal
             )
             .unwrap();
@@ -283,6 +287,11 @@ fn reveal_cloze(
     }
 }
 
+fn render_image_occlusion(text: &str, question_side: bool, active: bool) -> String {
+    // todo: alter HTML depending on question/answer side, active/non-active cloze
+    get_image_cloze_data(text)
+}
+
 pub fn reveal_cloze_text(text: &str, cloze_ord: u16, question: bool) -> Cow<str> {
     let mut buf = String::new();
     let mut active_cloze_found_in_text = false;

Does that make things easier?

@dae
Copy link
Member

dae commented Feb 8, 2023

(we don't even need to use <span class=cloze ...> - you could output something completely different if that was easier)

@krmanik
Copy link
Contributor Author

krmanik commented Feb 8, 2023

Thanks I have used the patch and it is easier then earlier implementation.

I have added new notes type for image cloze in stock.rs, also moved the image shape generation to canvas to reviewer ts.

Now adding the following to Occlusion fields and image in Image tag with id="img" generate the image occlusion for now.

{{c1::image-occlusion:rect:left=19.54:top=46.96:width=126.13:height=53.29:fill=#55aaff:quesmaskcolor=#f06}}
{{c2::image-occlusion:ellipse:left=46.83:top=188.56:width=166.65:height=66.51:fill=#55aa6f:rx=88.82324548896866:ry=33.65283560259413:quesmaskcolor=#f06}}
{{c3::image-occlusion:polygon:left=3.05:top=353.02:width=94.15:height=36.31:fill=#ffaa00:points=3.55,353.51 96.60,355.29 95.92,390.82 3.55,389.04:quesmaskcolor=#f06}}

@glutanimate
Copy link
Contributor

The new card rendering approach and note type format look clean, nice work!

A couple of thoughts regarding setupImageCloze():

  • How were you thinking of encoding the "Hide all" vs "Hide one" behavior? I was thinking that this is something that we could specify as an argument to setupImageCloze(), e.g. a hideAll bool flag. To make the interface extensible and expressive, we could group all customizable properties in a single-argument object, i.e.:

    interface SetupImageClozeOptions {
      hideAll?: bool;
    }
    
    export function setupImageCloze(options: SetupImageClozeOptions) {
        const hideAll = options.hideAll || true;
        ...
    }
  • Given that the API and behavior of setupImageCloze() might change in the future, perhaps it would then also make sense to also equip SetupImageClozeOptions with an apiVersion parameter or something similar?

@krmanik
Copy link
Contributor Author

krmanik commented Feb 9, 2023

Included the mask editor in note editor. The editor have toggle button to switch between note editor and mask editor.

Next implementation todo, add a button in mask editor's top toolbar [generate]. When the generate button clicked then it generate cloze using generate.ts and set the data to the fields in note editor. When Add at button clicked it add the notes.

ts/editor/NoteEditor.svelte Outdated Show resolved Hide resolved
@dae
Copy link
Member

dae commented Feb 9, 2023

I'll have more time to return to this in a day or so, but just a quick note for now: it might be nicer to keep I/O in a separate screen/page for the initial implementation, as that will make it easier to integrate into the mobile clients. There's a bunch of work that needs to be done before we can integrate the editor into the mobile clients, so combining the two would mean there'd be a longer wait for mobile users to get access to this.

@krmanik
Copy link
Contributor Author

krmanik commented Feb 9, 2023

The implementation for mobile will be done using ImageOcclusionPage.svelete. The page will be built for mobile device from web.rs. On mobile device we have to just show the show the hidden buttons for mask edit, note edit and note add. I will push the change how it can be used in anki note editor as well as on mobile client. Because the getting image and adding notes is already implemented in backend in previous commits.

@dae
Copy link
Member

dae commented Feb 10, 2023

Just trying to keep things simple to start with :-) If we have two different ways of invoking/using the I/O editor depending on platform, that's more variables we need to test. Integration inside the editor is the best way in the long run, but I just thought it might make more sense to focus on the external-case for now, and then when the mobile clients have adopted the new editor, we can then switch the implementation over. But I don't have strong feelings here if you're keen to work on both implementations.

ftl/core/editing.ftl Outdated Show resolved Hide resolved
ftl/core/editing.ftl Outdated Show resolved Hide resolved
rslib/src/image_occlusion/imagedata.rs Outdated Show resolved Hide resolved
@krmanik
Copy link
Contributor Author

krmanik commented Feb 10, 2023

I will separate it into two PR, in this PR only for mobile client change will be added. In other PR, I will add the changes for adding into note editor (and works on the implementation).

rslib/src/notetype/stock.rs Outdated Show resolved Hide resolved
@dae
Copy link
Member

dae commented Feb 11, 2023

Is this the difference between c1,c2,c3 vs c1,c1,c1? Could you perhaps avoid any extra text on the field and just guess which type it is based on the cloze numbers?

@krmanik
Copy link
Contributor Author

krmanik commented Feb 11, 2023

Is this the difference between c1,c2,c3 vs c1,c1,c1? Could you perhaps avoid any extra text on the field and just guess which type it is based on the cloze numbers?

It is different because in first case c1,c2,c3 is individual shapes and three notes. In later case c1,c1,c1 is group cloze, one note, all shapes are combined into one group and all shapes drawn once.

For, e.g.

  • If there are three label on image and users have to hide and show on front side and back side of note one by one then c1,c2,c3
  • If there are three label on image and users have to hide and show on front side and back side of note all at once then c1,c1,c1.

For guessing part I don't know it can be done without storing the hideall value somewhere. Then values may be added to first cloze.

-{{c1::image-occlusion:rect:left=19.54:top=46.96:width=126.13:height=53.29:fill=#55aaff:quesmaskcolor=#f06}}
+{{c1::image-occlusion:rect:left=19.54:top=46.96:width=126.13:height=53.29:fill=#55aaff:quesmaskcolor=#f06:hideall=true}}

@dae
Copy link
Member

dae commented Feb 11, 2023

Ok, I think I get it. We want to change whether inactive clozes are hidden or not on the question side, but not change how many cards are generated? And c1,c1,c1 would be equivalent to "hide all, guess all", not "hide all, guess one".

What if we put a hideinactive property on every cloze, and not just the first one? That would allow for a 'hide some, guess one' option in the future if we ever wanted to add such a feature, and it simplifies the cloze rendering, since all the information is available directly inside each cloze. Would that make sense?

(@glutanimate may have further thoughts)

@krmanik
Copy link
Contributor Author

krmanik commented Feb 11, 2023

What if we put a hideinactive property on every cloze, and not just the first one? That would allow for a 'hide some, guess one' option in the future if we ever wanted to add such a feature, and it simplifies the cloze rendering, since all the information is available directly inside each cloze. Would that make sense?

This approach is good and easy. Thanks

@krmanik
Copy link
Contributor Author

krmanik commented Feb 11, 2023

Is there a way to edit previously added cards at the moment?

Now existing notes can be edited. For now only shapes and notes can be edited. Deck selection and image changes can not be done. On mobile client before loading web pages the path to image or note id can be passed.

The page is built for mobile client so may be following steps can be used to test it.
For anyone to test it,

  1. run anki
./tools/ts-run
  1. to test add notes, pass path to image
http://127.0.0.1:40000/_anki/pages/image-occlusion.html#test-/path/to/image.png
  1. to test update notes, pass note id
http://127.0.0.1:40000/_anki/pages/image-occlusion.html#testforedit-123123123

Copy link
Member

@dae dae left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like it's coming together really well Mani :-)

I was initially confused that there's no icon in the editor anymore, then I figured out you meant that you can only test via the external browser/that code currently. I also initially thought testing on mobile would be hard due to sandboxing, then realised the path to the image is on the computer host :-)

A few things I noticed:

  • I used 'hide all, guess one', and both of the rects I created had a quesmarkcolor set. I played around with color setting in the past, so not sure if that's being remembered or is the default. But when it comes time to review, both rectangles have the same color, so you can't tell which one is being asked. If I remove quesmaskcolor manually, the active rect changes color.
  • I tried editing a note, and got:
lib.ts:199 Uncaught TypeError: import_fabric.default.Rect is not a constructor
    at lib.ts:199:34
    at Array.forEach (<anonymous>)
    at generateShapeFromCloze (lib.ts:173:15)
    at mask-editor.ts:78:13
    at fabric.js:21623:19
    at HTMLImageElement.onLoadCallback (fabric.js:1100:30)
  • Would it make sense to also show hide one/all buttons in the image tab, and not just the notes tab? @glutanimate what percentage of occlusions would you say have added text content?
  • I find myself wanting a 'resize to fit' button more than 'show at 100% size' - maybe it could toggle, or there could be both?
  • If you add 4 rects, it says '4 notes added' - perhaps it should say '4 cards added' instead?
  • I tested this in Mobile Safari, and was successfully able to add an occlusion. The basic creation of shapes, zooming, panning using the pan tool, etc all worked for me - it's already looking great. Pinching to zoom created a shape instead of zooming, which was a little surprising, but that may be because I was testing with a mouse and an emulated pinch gesture.
  • When I tested with a larger image, I unfortunately hit a problem: the screen fails to load, and Safari prints the following warning in the console: Canvas area exceeds the maximum limit (width * height > 16777216) (more info: https://pqina.nl/blog/canvas-area-exceeds-the-maximum-limit/). Presumably it can be worked around by applying a constant scaling factor to the image or canvas coordinates or enforcing a maximum height/width on images prior to editing, but it unfortunately means a more complicated implementation.
  • In some of the other screens like the deck options, they look for a #test suffix on the page name to automatically set up for testing, so you don't need to run any commands from the console. You could optionally do something similar here - for example image-occlusion.html#test-/home/user/foo.png could automatically call anki.setupImageOcclusion("/home/user/foo.png"). That makes it a bit easier to test, as you don't need to run console commands, especially on mobile clients. Could also optionally do #testedit-123123123 for the editing case.

rslib/src/notetype/stock.rs Outdated Show resolved Hide resolved
@krmanik
Copy link
Contributor Author

krmanik commented Feb 12, 2023

I used 'hide all, guess one', and both of the rects I created had a quesmarkcolor set. I played around with color setting in the past, so not sure if that's being remembered or is the default. But when it comes time to review, both rectangles have the same color, so you can't tell which one is being asked. If I remove quesmaskcolor manually, the active rect changes color.

Here are two things to consider, during review of image cloze notes

  • the question mask color is for active shapes and fill color for inactive shapes.
  • in mask editor is previewing only shape color change (I will update for previewing question mask color by toggle or some other method).

I tried editing a note, and got:
lib.ts:199 Uncaught TypeError: import_fabric.default.Rect is not a constructor

Instead importing import { fabric } from "fabric";, imported import fabric from "fabric";. It is fixed now.

I find myself wanting a 'resize to fit' button more than 'show at 100% size' - maybe it could toggle, or there could be both?

I have added option as drop down menu to change canvas size from 25% to 300%;

If you add 4 rects, it says '4 notes added' - perhaps it should say '4 cards added' instead?

I have added strings to ftl and implemented it.

When I tested with a larger image, I unfortunately hit a problem: the screen fails to load, and Safari prints the following warning in the console: Canvas area exceeds the maximum limit (width * height > 16777216) (more info: https://pqina.nl/blog/canvas-area-exceeds-the-maximum-limit/). Presumably it can be worked around by applying a constant scaling factor to the image or canvas coordinates or enforcing a maximum height/width on images prior to editing, but it unfortunately means a more complicated implementation.

It is fixed now. I have used suggested implementation. Thanks

In some of the other screens like the deck options, they look for a #test suffix on the page name to automatically set up for testing, so you don't need to run any commands from the console.

I have updated it, so to test we have to just load test url now now.

Also some changes needs to do for smaller screen size ( I will update it).

krmanik and others added 22 commits March 28, 2023 23:19
Makes it clearer that only one of them can be returned
The second unwrap should be ok, as the input is utf8
- fixes crash when note doesn't exist - Ok(None) case was not covered
- decouples business logic from native error->proto error conversion
- no need for original copy
- field[x] is more idiomatic than field.get(x).unwrap()
- don't need mutable access to fields
+ Use our read_file helper for better error context
- remove strings from ftl
- remove color picker component
- remove from cloze generation
- remove icons for two buttons
- use constant color for shapes
- rename mask to inactive shape and active shape color
- border witdth and border color
- change decimal point deserializing string and toFixed(2)
- add thin border in mask editor, may be image background was transparent
- do not draw fixed ratio shapes by turn of uniformScaling
- fix rectangle width,height
- fix ellipse rx,ry,width,height
- fix polygon postion and points
- draw outside of canvas also
- move shapes at boundry when pointer is outside of canvas
- include rx, ry for ellipse only
- include points for polygon only
- implemented undo redo using fabric canvas events
- polygon is special case and implemented only added and modified event
- rectangle and ellipse have object:added, object:modified and object:removed case
- change id to undo and redo
…ow canvas editor

- set image width and height after adding image
- fix shapes at edges
- toggle masks button to show/hide masks
- hide clozes string, it contains <br>
- set height for div container (used 'relative' in css)
- rename cursor tools
- add left and right border
@krmanik
Copy link
Contributor Author

krmanik commented Mar 28, 2023

Thanks for reviewing.

It seems like performing an undo once will prevent the affected shapes from being properly tracked in the edit history from that point on.

When shapes are created an id are assigned but when shapes are added after removing using undo, the id are not assigned so the function returned false, and undo/redo was not working. But I have fixed the issues.

Given that all shapes now have borders by default, we could consider dropping the gray/green styling that appears when drawing a shape.

I have used transparent color in draw mode and in other mode default color.

@dae
Copy link
Member

dae commented Mar 29, 2023

Thanks for all you work on this Mani, and for your input Aristotelis. It feels polished, and this PR has waited long enough, so I think it's time to merge. I'll start looking into the notetype changes I mentioned above in early April.

@dae dae merged commit 2bf134d into ankitects:main Mar 29, 2023
@krmanik
Copy link
Contributor Author

krmanik commented Mar 31, 2023

I will start implementing the button to editor toolbar.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants