Skip to content

Commit

Permalink
[2] Webcard update (#10)
Browse files Browse the repository at this point in the history
* Add more options to web filters

* format code

remove unused methods

* change if logic

* add missing field

* restore original field names

* extract function

* rework tag previews

* update ajt common

* control field mapping in config

* add widgets for setting up remote field names

* rename sent to sentence

---------

Co-authored-by: FileX <filex.stuff@proton.me>
Co-authored-by: Ren Tatsumoto <tatsu@autistici.org>
  • Loading branch information
3 people committed May 15, 2024
1 parent c41d858 commit c13eeea
Show file tree
Hide file tree
Showing 7 changed files with 117 additions and 22 deletions.
2 changes: 1 addition & 1 deletion ajt_common
Submodule ajt_common updated 5 files
+8 −0 .gitattributes
+12 −0 .gitignore
+26 −3 addon_config.py
+32 −6 package.sh
+56 −0 utils.py
10 changes: 9 additions & 1 deletion config.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,13 @@
"search_the_web": false,
"sentence_min_length": 0,
"sentence_max_length": 0,
"timeout_seconds": 60
"timeout_seconds": 60,
"remote_fields": {
"sentence_kanji": "SentKanji",
"sentence_furigana": "SentFurigana",
"sentence_eng": "SentEng",
"sentence_audio": "SentAudio",
"image": "Image",
"notes": "Notes"
}
}
38 changes: 37 additions & 1 deletion config.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,46 @@

from typing import Optional

from .ajt_common.addon_config import AddonConfigManager
from .ajt_common.addon_config import AddonConfigManager, ConfigSubViewBase


class RemoteFieldsConfig(ConfigSubViewBase):
_view_key: str = "remote_fields"

@property
def sentence_kanji(self) -> str:
return self["sentence_kanji"]

@property
def sentence_furigana(self) -> str:
return self["sentence_furigana"]

@property
def sentence_eng(self) -> str:
return self["sentence_eng"]

@property
def sentence_audio(self) -> str:
return self["sentence_audio"]

@property
def image(self) -> str:
return self["image"]

@property
def notes(self) -> str:
return self["notes"]


class CroProConfig(AddonConfigManager):
def __init__(self, default: bool = False):
super().__init__(default)
self._remote_fields = RemoteFieldsConfig(default)

@property
def remote_fields(self) -> RemoteFieldsConfig:
return self._remote_fields

@property
def exported_tag(self) -> Optional[str]:
"""
Expand Down
50 changes: 31 additions & 19 deletions remote_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,8 @@

import anki.httpclient
import requests
from .config import config

IMAGE_FIELD_NAME = "Image"
AUDIO_FIELD_NAME = "SentAudio"
API_URL = "https://api.immersionkit.com/look_up_dictionary?"


Expand All @@ -28,6 +27,7 @@ class ApiReturnExampleDict(TypedDict):
sentence_with_furigana: str
translation: str
sentence_id: str
category: str


@enum.unique
Expand Down Expand Up @@ -61,41 +61,53 @@ def as_anki_ref(self):
raise NotImplementedError(f"not implemented: {self.type}")


def remote_tags_as_list(json_dict: ApiReturnExampleDict) -> list[str]:
return [tag.replace(r"\s:", "_") for tag in [*json_dict["tags"], json_dict["category"]]]


@dataclasses.dataclass
class RemoteNote:
"""
Packs the response from the API into a familiar interface.
"""

sent_kanji: str
sent_furigana: str
sent_eng: str
sentence_kanji: str
sentence_furigana: str
sentence_eng: str
image_url: str
sound_url: str
notes: str
tags: list[str]

def __post_init__(self):
self._media = {
IMAGE_FIELD_NAME: RemoteMediaInfo(IMAGE_FIELD_NAME, self.image_url, MediaType.image),
AUDIO_FIELD_NAME: RemoteMediaInfo(AUDIO_FIELD_NAME, self.sound_url, MediaType.sound),
config.remote_fields.image: RemoteMediaInfo(
config.remote_fields.image,
self.image_url,
MediaType.image,
),
config.remote_fields.sentence_audio: RemoteMediaInfo(
config.remote_fields.sentence_audio,
self.sound_url,
MediaType.sound,
),
}
self._mapping = {
"SentKanji": self.sent_kanji,
"SentFurigana": self.sent_furigana,
"SentEng": self.sent_eng,
AUDIO_FIELD_NAME: self.audio.as_anki_ref(),
IMAGE_FIELD_NAME: self.image.as_anki_ref(),
"Notes": self.notes,
config.remote_fields.sentence_kanji: self.sentence_kanji,
config.remote_fields.sentence_furigana: self.sentence_furigana,
config.remote_fields.sentence_eng: self.sentence_eng,
config.remote_fields.sentence_audio: self.audio.as_anki_ref(),
config.remote_fields.image: self.image.as_anki_ref(),
config.remote_fields.notes: self.notes,
}

@property
def image(self) -> RemoteMediaInfo:
return self._media[IMAGE_FIELD_NAME]
return self._media[config.remote_fields.image]

@property
def audio(self) -> RemoteMediaInfo:
return self._media[AUDIO_FIELD_NAME]
return self._media[config.remote_fields.sentence_audio]

def __contains__(self, item) -> bool:
return item in self._mapping
Expand Down Expand Up @@ -125,12 +137,12 @@ def items(self):
@classmethod
def from_json(cls, json_dict: ApiReturnExampleDict):
return RemoteNote(
tags=json_dict["tags"],
tags=remote_tags_as_list(json_dict),
image_url=json_dict["image_url"],
sound_url=json_dict["sound_url"],
sent_kanji=json_dict["sentence"],
sent_furigana=json_dict["sentence_with_furigana"],
sent_eng=json_dict["translation"],
sentence_kanji=json_dict["sentence"],
sentence_furigana=json_dict["sentence_with_furigana"],
sentence_eng=json_dict["translation"],
notes=json_dict["sentence_id"],
)

Expand Down
14 changes: 14 additions & 0 deletions settings_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from aqt.webview import AnkiWebView

from .ajt_common.about_menu import tweak_window
from .ajt_common.anki_field_selector import AnkiFieldSelector
from .ajt_common.utils import ui_translate
from .common import ADDON_NAME, LogDebug, CONFIG_MD_PATH
from .config import config
Expand All @@ -31,6 +32,14 @@ def make_checkboxes() -> dict[str, QCheckBox]:
BUT_CANCEL = QDialogButtonBox.StandardButton.Cancel


def make_remote_field_combos() -> dict[str, QComboBox]:
d = {}
for field_key, field_name in config.remote_fields.items():
d[field_key] = AnkiFieldSelector()
d[field_key].setCurrentText(field_name)
return d


class CroProSettingsDialog(QDialog):
name = "cropro_settings_dialog"

Expand All @@ -46,6 +55,7 @@ def __init__(self, *args, **kwargs) -> None:
# Currently, the longest sentence has a length of 196 letters (Shirokuma Cafe Outro full sub).
self.sentence_min_length = CroProSpinBox(min_val=0, max_val=500, step=1, value=config.sentence_min_length)
self.sentence_max_length = CroProSpinBox(min_val=0, max_val=999, step=1, value=config.sentence_max_length)
self._remote_fields = make_remote_field_combos()
self.button_box = QDialogButtonBox(BUT_HELP | BUT_OK | BUT_CANCEL)
self._create_tabs()
self._setup_ui()
Expand Down Expand Up @@ -101,6 +111,8 @@ def _make_web_tab(self) -> QWidget:
length_layout.addWidget(self.sentence_max_length)
length_layout.setAlignment(Qt.AlignmentFlag.AlignRight)
layout.addRow("Sentence Length", length_layout)
for field_key, edit_widget in self._remote_fields.items():
layout.addRow(ui_translate(field_key).title(), edit_widget)
return widget

def _make_hl_tab(self) -> QWidget:
Expand Down Expand Up @@ -216,6 +228,8 @@ def accept(self) -> None:
)
for key, checkbox in self.checkboxes.items():
config[key] = checkbox.isChecked()
for field_key, edit_widget in self._remote_fields.items():
config.remote_fields[field_key] = edit_widget.currentText()
config.write_config()
return super().accept()

Expand Down
18 changes: 18 additions & 0 deletions web/previewer.css
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
--content-bg: hsl(0, 0%, 97%);
--name-color: hsl(0, 0%, 25%);
--content-color: hsl(0, 0%, 1%);
--tag-bg: hsl(0, 0%, 100%);
--tag-border: hsl(0, 0%, 65%);
}

:root[class*="night-mode"] {
Expand All @@ -13,6 +15,8 @@
--content-bg: hsl(0, 0%, 3%);
--name-color: hsl(0, 0%, 75%);
--content-color: hsl(0, 0%, 90%);
--tag-bg: hsl(0, 0%, 7%);
--tag-border: hsl(0, 0%, 50%);
}

body,
Expand Down Expand Up @@ -107,3 +111,17 @@ button.cropro__play_button {
button.cropro__play_button:hover {
background-color: #ced0d2;
}

.cropro__note_tag {
/* Each tag is a <span> */
display: inline-block;
background-color: var(--tag-bg);
border: 1px solid var(--tag-border);
border-radius: 0.4rem;
padding: 0.1rem 0.3rem;
color: var(--content-color);
}

.cropro__note_tag:not(:first-child) {
margin-left: 0.3rem;
}
7 changes: 7 additions & 0 deletions widgets/note_previewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ def format_local_audio(audio_files: Iterable[str]) -> str:
)


def format_tags_as_html(tags: list[str]) -> str:
return "".join(f'<span class="cropro__note_tag">{tag}</span>' for tag in tags)


class NotePreviewer(AnkiWebView):
"""Previews a note in a Form Layout using a webview."""

Expand Down Expand Up @@ -143,6 +147,9 @@ def _generate_html_for_note(self, note: Union[Note, RemoteNote]) -> str:
else:
markup.write(self._create_html_for_field(field_content))
markup.write("</div>")
if note.tags:
markup.write('<div class="name">Tags</div>')
markup.write(f'<div class="content">{format_tags_as_html(note.tags)}</div>')
return markup.getvalue()

def _create_html_for_remote_field(self, field_name: str, field_content: str) -> str:
Expand Down

0 comments on commit c13eeea

Please sign in to comment.