Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions build/configure/src/web.rs
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,18 @@ fn build_and_check_pages(build: &mut Build) -> Result<()> {
":sass"
],
)?;
build_page(
"image-occlusion",
true,
inputs![
//
":ts:lib",
":ts:components",
":ts:sveltelib",
":ts:tag-editor",
":sass"
],
)?;

Ok(())
}
Expand Down
10 changes: 10 additions & 0 deletions ftl/core/editing.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,13 @@ editing-close-html-tags = Auto-close HTML tags
## You don't need to translate these strings, as they will be replaced with different ones soon.

editing-html-editor = HTML Editor

## Image Occlusion editing

editing-image-occlusion = Image Occlusion
editing-masks = Masks
editing-notes = Notes
editing-hide-all-guess-one = Hide All, Guess One
editing-hide-one-guess-one = Hide One, Guess One
editing-question-mask-color = Question Mask Color
editing-answer-mask-color = Answer Mask Color
6 changes: 6 additions & 0 deletions ftl/core/notetypes.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,9 @@ notetypes-note-types = Note Types
notetypes-options = Options
notetypes-please-add-another-note-type-first = Please add another note type first.
notetypes-type = Type

## Image Occlusion

notetypes-occlusions-field = Occlusions
notetypes-image-field = Image
notetypes-notes-field = Notes
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
"codemirror": "^5.63.1",
"css-browser-selector": "^0.6.5",
"d3": "^7.0.0",
"fabric": "^5.3.0",
"fuse.js": "^6.6.2",
"gemoji": "^7.1.0",
"intl-pluralrules": "^1.2.2",
Expand All @@ -83,6 +84,7 @@
"lodash-es": "^4.17.21",
"marked": "^4.0.0",
"mathjax": "^3.1.2",
"panzoom": "^9.4.3",
"protobufjs": "^7"
},
"resolutions": {
Expand Down
1 change: 1 addition & 0 deletions proto/anki/backend.proto
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ enum ServiceIndex {
SERVICE_INDEX_LINKS = 15;
SERVICE_INDEX_IMPORT_EXPORT = 16;
SERVICE_INDEX_ANKIDROID = 17;
SERVICE_INDEX_IMAGE_OCCLUSION = 18;
}

message BackendInit {
Expand Down
37 changes: 37 additions & 0 deletions proto/anki/image_occlusion.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html

syntax = "proto3";

option java_multiple_files = true;

package anki.image_occlusion;

import "anki/cards.proto";
import "anki/collection.proto";
import "anki/notes.proto";
import "anki/generic.proto";

service ImageOcclusionService {
rpc GetImageClozeMetadata(ImageClozeMetadataRequest) returns (ImageClozeMetadata);
rpc AddImageOcclusionNotes(AddImageOcclusionNotesRequest) returns (collection.OpChanges);
}

message ImageClozeMetadataRequest {
string path = 1;
}

message ImageClozeMetadata {
bytes data = 1;
string name = 2;
int64 deck_id = 3;
}

message AddImageOcclusionNotesRequest {
string image_path = 1;
int64 deck_id = 2;
string occlusions = 3;
string header = 4;
string notes = 5;
repeated string tags = 6;
}
29 changes: 29 additions & 0 deletions pylib/anki/collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
config_pb2,
generic_pb2,
import_export_pb2,
image_occlusion_pb2,
links_pb2,
search_pb2,
stats_pb2,
Expand Down Expand Up @@ -39,6 +40,8 @@
CsvMetadata = import_export_pb2.CsvMetadata
DupeResolution = CsvMetadata.DupeResolution
Delimiter = import_export_pb2.CsvMetadata.Delimiter
ImageClozeMetadata = image_occlusion_pb2.ImageClozeMetadata
AddImageOcclusionNotesRequest = image_occlusion_pb2.AddImageOcclusionNotesRequest

import copy
import os
Expand Down Expand Up @@ -455,6 +458,32 @@ def import_json_file(self, path: str) -> ImportLogWithChanges:
def import_json_string(self, json: str) -> ImportLogWithChanges:
return self._backend.import_json_string(json)

# Image Occlusion
def get_image_cloze_metadata(self, path: str | None) -> ImageClozeMetadata:
request = image_occlusion_pb2.ImageClozeMetadataRequest(path=path)
return self._backend.get_image_cloze_metadata(request)

def add_image_occlusion_notes(
self,
image_path: str | None,
deck_id: int | None,
notes_data: bytes | None,
occlusions: str | None,
header: str | None,
notes: str | None,
tags: list[str] | None
) -> bool:
request = image_occlusion_pb2.AddImageOcclusionNotesRequest(
image_path=image_path,
deck_id=deck_id,
notes_data=notes_data,
occlusions=occlusions,
header=header,
notes=notes,
tags=tags
)
return self._backend.add_image_occlusion_notes(request)

# Object helpers
##########################################################################

Expand Down
3 changes: 3 additions & 0 deletions pylib/tools/genbackend.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import anki.decks_pb2
import anki.i18n_pb2
import anki.import_export_pb2
import anki.image_occlusion_pb2
import anki.links_pb2
import anki.media_pb2
import anki.notes_pb2
Expand Down Expand Up @@ -187,6 +188,7 @@ def render_service(
MEDIA=anki.media_pb2,
LINKS=anki.links_pb2,
IMPORT_EXPORT=anki.import_export_pb2,
IMAGE_OCCLUSION=anki.image_occlusion_pb2,
)

for service in anki.backend_pb2.ServiceIndex.DESCRIPTOR.values:
Expand Down Expand Up @@ -238,6 +240,7 @@ def render_service(
import anki.tags_pb2
import anki.media_pb2
import anki.import_export_pb2
import anki.image_occlusion_pb2

class RustBackendGenerated:
def _run_command(self, service: int, method: int, input: Any) -> bytes:
Expand Down
23 changes: 23 additions & 0 deletions qt/aqt/editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
tr,
)
from aqt.webview import AnkiWebView
from aqt.imageocclusion import ImageOcclusionDialog

pics = ("jpg", "jpeg", "png", "tif", "tiff", "gif", "svg", "webp", "ico")
audio = (
Expand Down Expand Up @@ -1171,6 +1172,27 @@ def collapseTags(self) -> None:
def expandTags(self) -> None:
aqt.mw.pm.set_tags_collapsed(self.editorMode, False)

def onImageOcclusion(self) -> None:
"""Show a file selection screen, then get selected image path."""
extension_filter = " ".join(
f"*.{extension}" for extension in sorted(itertools.chain(pics))
)
filter = f"{tr.editing_media()} ({extension_filter})"

def accept(file: str) -> None:
diag = ImageOcclusionDialog(self.mw, file)


file = getFile(
parent=self.widget,
title=tr.editing_add_media(),
cb=cast(Callable[[Any], None], accept),
filter=filter,
key="media",
)

self.parentWindow.activateWindow()

# Links from HTML
######################################################################

Expand Down Expand Up @@ -1202,6 +1224,7 @@ def _init_links(self) -> None:
toggleCloseHTMLTags=Editor.toggleCloseHTMLTags,
expandTags=Editor.expandTags,
collapseTags=Editor.collapseTags,
imageOcclusion=Editor.onImageOcclusion,
)


Expand Down
53 changes: 53 additions & 0 deletions qt/aqt/imageocclusion.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Copyright: Ankitects Pty Ltd and contributors
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html

from __future__ import annotations

import aqt
import aqt.deckconf
import aqt.main
import aqt.operations
from aqt.qt import *
from aqt.utils import addCloseShortcut, disable_help_button, restoreGeom, saveGeom, tr
from aqt.webview import AnkiWebView


class ImageOcclusionDialog(QDialog):

TITLE = "image occlusion"
silentlyClose = True

def __init__(
self,
mw: aqt.main.AnkiQt,
path: str,
) -> None:
QDialog.__init__(self, mw)
self.mw = mw
self._setup_ui(path)
self.show()

def _setup_ui(self, path: str) -> None:
self.setWindowModality(Qt.WindowModality.ApplicationModal)
self.mw.garbage_collect_on_dialog_finish(self)
self.setMinimumSize(400, 300)
disable_help_button(self)
restoreGeom(self, self.TITLE)
addCloseShortcut(self)

self.web = AnkiWebView(title=self.TITLE)
self.web.setVisible(False)
self.web.load_ts_page("image-occlusion")
layout = QVBoxLayout()
layout.setContentsMargins(0, 0, 0, 0)
layout.addWidget(self.web)
self.setLayout(layout)

self.web.eval(f"anki.setupImageOcclusion('{path}');")
self.setWindowTitle(self.TITLE)

def reject(self) -> None:
self.web.cleanup()
self.web = None
saveGeom(self, self.TITLE)
QDialog.reject(self)
15 changes: 15 additions & 0 deletions qt/aqt/mediasrv.py
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,17 @@ def handle_on_main() -> None:
return b""


def image_occlusion() -> bytes:
data = request.data

def handle_on_main() -> None:
window = aqt.mw.app.activeWindow()
if isinstance(window, ImageOcclusionDialog):
window.do_image_occlusion(data)

aqt.mw.taskman.run_on_main(handle_on_main)
return b""

post_handler_list = [
congrats_info,
get_deck_configs_for_update,
Expand All @@ -455,6 +466,7 @@ def handle_on_main() -> None:
set_scheduling_states,
change_notetype,
import_csv,
image_occlusion,
]


Expand All @@ -478,6 +490,9 @@ def handle_on_main() -> None:
"set_graph_preferences",
# TagsService
"complete_tag",
# ImageOcclusionService
"get_image_cloze_metadata",
"add_image_occlusion_notes"
]


Expand Down
35 changes: 35 additions & 0 deletions rslib/src/backend/image_occlusion.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html

use super::Backend;
pub(super) use crate::pb::image_occlusion::imageocclusion_service::Service as ImageOcclusionService;
use crate::{
pb::{self as pb},
prelude::*,
};

impl ImageOcclusionService for Backend {
fn get_image_cloze_metadata(
&self,
input: pb::image_occlusion::ImageClozeMetadataRequest,
) -> Result<pb::image_occlusion::ImageClozeMetadata> {
self.with_col(|col| col.get_image_cloze_metadata(&input.path))
}

fn add_image_occlusion_notes(
&self,
input: pb::image_occlusion::AddImageOcclusionNotesRequest,
) -> Result<pb::collection::OpChanges> {
self.with_col(|col| {
col.add_image_occlusion_notes(
&input.image_path,
input.deck_id.into(),
&input.occlusions,
&input.header,
&input.notes,
input.tags,
)
})
.map(Into::into)
}
}
3 changes: 3 additions & 0 deletions rslib/src/backend/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ mod decks;
mod error;
mod generic;
mod i18n;
mod image_occlusion;
mod import_export;
mod links;
mod media;
Expand Down Expand Up @@ -48,6 +49,7 @@ use self::config::ConfigService;
use self::deckconfig::DeckConfigService;
use self::decks::DecksService;
use self::i18n::I18nService;
use self::image_occlusion::ImageOcclusionService;
use self::import_export::ImportExportService;
use self::links::LinksService;
use self::media::MediaService;
Expand Down Expand Up @@ -142,6 +144,7 @@ impl Backend {
ServiceIndex::Collection => CollectionService::run_method(self, method, input),
ServiceIndex::Cards => CardsService::run_method(self, method, input),
ServiceIndex::ImportExport => ImportExportService::run_method(self, method, input),
ServiceIndex::ImageOcclusion => ImageOcclusionService::run_method(self, method, input),
})
.map_err(|err| {
let backend_err = err.into_protobuf(&self.tr);
Expand Down
Loading