mirror of
https://github.com/ankitects/anki.git
synced 2025-09-19 22:42:25 -04:00
Complete noteCanBeAdded()
This commit is contained in:
parent
0c98a68528
commit
9a054d6924
3 changed files with 159 additions and 22 deletions
|
@ -12,6 +12,7 @@ import "anki/generic.proto";
|
||||||
import "anki/search.proto";
|
import "anki/search.proto";
|
||||||
import "anki/notes.proto";
|
import "anki/notes.proto";
|
||||||
import "anki/notetypes.proto";
|
import "anki/notetypes.proto";
|
||||||
|
import "anki/links.proto";
|
||||||
|
|
||||||
service FrontendService {
|
service FrontendService {
|
||||||
// Returns values from the reviewer
|
// Returns values from the reviewer
|
||||||
|
@ -44,6 +45,8 @@ service FrontendService {
|
||||||
rpc CloseAddCards(generic.Bool) returns (generic.Empty);
|
rpc CloseAddCards(generic.Bool) returns (generic.Empty);
|
||||||
rpc CloseEditCurrent(generic.Empty) returns (generic.Empty);
|
rpc CloseEditCurrent(generic.Empty) returns (generic.Empty);
|
||||||
rpc OpenLink(generic.String) returns (generic.Empty);
|
rpc OpenLink(generic.String) returns (generic.Empty);
|
||||||
|
rpc AskUser(AskUserRequest) returns (generic.Bool);
|
||||||
|
rpc ShowMessageBox(ShowMessageBoxRequest) returns (generic.Empty);
|
||||||
|
|
||||||
// Profile config
|
// Profile config
|
||||||
rpc GetProfileConfigJson(generic.String) returns (generic.Json);
|
rpc GetProfileConfigJson(generic.String) returns (generic.Json);
|
||||||
|
@ -107,3 +110,31 @@ message ReadClipboardResponse {
|
||||||
message WriteClipboardRequest {
|
message WriteClipboardRequest {
|
||||||
map<string, bytes> data = 1;
|
map<string, bytes> data = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message Help {
|
||||||
|
oneof value {
|
||||||
|
links.HelpPageLinkRequest.HelpPage help_page = 1;
|
||||||
|
string help_link = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message AskUserRequest {
|
||||||
|
string text = 1;
|
||||||
|
optional Help help = 2;
|
||||||
|
optional string title = 4;
|
||||||
|
optional bool default_no = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum MessageBoxType {
|
||||||
|
INFO = 0;
|
||||||
|
WARNING = 1;
|
||||||
|
CRITICAL = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ShowMessageBoxRequest {
|
||||||
|
string text = 1;
|
||||||
|
MessageBoxType type = 2;
|
||||||
|
optional Help help = 3;
|
||||||
|
optional string title = 4;
|
||||||
|
optional string text_format = 5;
|
||||||
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ from collections.abc import Callable
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from errno import EPROTOTYPE
|
from errno import EPROTOTYPE
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
from typing import Any, cast
|
from typing import Any, Generic, cast
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
import flask_cors
|
import flask_cors
|
||||||
|
@ -40,7 +40,14 @@ from aqt.operations import on_op_finished
|
||||||
from aqt.operations.deck import update_deck_configs as update_deck_configs_op
|
from aqt.operations.deck import update_deck_configs as update_deck_configs_op
|
||||||
from aqt.progress import ProgressUpdate
|
from aqt.progress import ProgressUpdate
|
||||||
from aqt.qt import *
|
from aqt.qt import *
|
||||||
from aqt.utils import aqt_data_path, openLink, show_warning, tr
|
from aqt.utils import (
|
||||||
|
aqt_data_path,
|
||||||
|
askUser,
|
||||||
|
openLink,
|
||||||
|
show_info,
|
||||||
|
show_warning,
|
||||||
|
tr,
|
||||||
|
)
|
||||||
|
|
||||||
# https://forums.ankiweb.net/t/anki-crash-when-using-a-specific-deck/22266
|
# https://forums.ankiweb.net/t/anki-crash-when-using-a-specific-deck/22266
|
||||||
waitress.wasyncore._DISCONNECTED = waitress.wasyncore._DISCONNECTED.union({EPROTOTYPE}) # type: ignore
|
waitress.wasyncore._DISCONNECTED = waitress.wasyncore._DISCONNECTED.union({EPROTOTYPE}) # type: ignore
|
||||||
|
@ -704,18 +711,34 @@ def retrieve_url() -> bytes:
|
||||||
).SerializeToString()
|
).SerializeToString()
|
||||||
|
|
||||||
|
|
||||||
|
AsyncRequestReturnType = TypeVar("AsyncRequestReturnType")
|
||||||
|
|
||||||
|
|
||||||
|
class AsyncRequestHandler(Generic[AsyncRequestReturnType]):
|
||||||
|
def __init__(self, callback: Callable[[AsyncRequestHandler], None]) -> None:
|
||||||
|
self.callback = callback
|
||||||
|
self.loop = asyncio.get_event_loop()
|
||||||
|
self.future = self.loop.create_future()
|
||||||
|
|
||||||
|
def run(self) -> None:
|
||||||
|
aqt.mw.taskman.run_on_main(lambda: self.callback(self))
|
||||||
|
|
||||||
|
def set_result(self, result: AsyncRequestReturnType) -> None:
|
||||||
|
self.loop.call_soon_threadsafe(self.future.set_result, result)
|
||||||
|
|
||||||
|
async def get_result(self) -> AsyncRequestReturnType:
|
||||||
|
return await self.future
|
||||||
|
|
||||||
|
|
||||||
async def open_file_picker() -> bytes:
|
async def open_file_picker() -> bytes:
|
||||||
req = frontend_pb2.openFilePickerRequest()
|
req = frontend_pb2.openFilePickerRequest()
|
||||||
req.ParseFromString(request.data)
|
req.ParseFromString(request.data)
|
||||||
|
|
||||||
loop = asyncio.get_event_loop()
|
def callback(request_handler: AsyncRequestHandler) -> None:
|
||||||
future = loop.create_future()
|
|
||||||
|
|
||||||
def on_main() -> None:
|
|
||||||
from aqt.utils import getFile
|
from aqt.utils import getFile
|
||||||
|
|
||||||
def cb(filename: str | None) -> None:
|
def cb(filename: str | None) -> None:
|
||||||
loop.call_soon_threadsafe(future.set_result, filename)
|
request_handler.set_result(filename)
|
||||||
|
|
||||||
window = aqt.mw.app.activeWindow()
|
window = aqt.mw.app.activeWindow()
|
||||||
assert window is not None
|
assert window is not None
|
||||||
|
@ -727,9 +750,9 @@ async def open_file_picker() -> bytes:
|
||||||
key=req.key,
|
key=req.key,
|
||||||
)
|
)
|
||||||
|
|
||||||
aqt.mw.taskman.run_on_main(on_main)
|
request_handler: AsyncRequestHandler[str | None] = AsyncRequestHandler(callback)
|
||||||
|
request_handler.run()
|
||||||
filename = await future
|
filename = await request_handler.get_result()
|
||||||
|
|
||||||
return generic_pb2.String(val=filename if filename else "").SerializeToString()
|
return generic_pb2.String(val=filename if filename else "").SerializeToString()
|
||||||
|
|
||||||
|
@ -757,22 +780,19 @@ def show_in_media_folder() -> bytes:
|
||||||
|
|
||||||
|
|
||||||
async def record_audio() -> bytes:
|
async def record_audio() -> bytes:
|
||||||
loop = asyncio.get_event_loop()
|
def callback(request_handler: AsyncRequestHandler) -> None:
|
||||||
future = loop.create_future()
|
|
||||||
|
|
||||||
def on_main() -> None:
|
|
||||||
from aqt.sound import record_audio
|
from aqt.sound import record_audio
|
||||||
|
|
||||||
def cb(path: str | None) -> None:
|
def cb(path: str | None) -> None:
|
||||||
loop.call_soon_threadsafe(future.set_result, path)
|
request_handler.set_result(path)
|
||||||
|
|
||||||
window = aqt.mw.app.activeWindow()
|
window = aqt.mw.app.activeWindow()
|
||||||
assert window is not None
|
assert window is not None
|
||||||
record_audio(window, aqt.mw, True, cb)
|
record_audio(window, aqt.mw, True, cb)
|
||||||
|
|
||||||
aqt.mw.taskman.run_on_main(on_main)
|
request_handler: AsyncRequestHandler[str | None] = AsyncRequestHandler(callback)
|
||||||
|
request_handler.run()
|
||||||
path = await future
|
path = await request_handler.get_result()
|
||||||
|
|
||||||
return generic_pb2.String(val=path if path else "").SerializeToString()
|
return generic_pb2.String(val=path if path else "").SerializeToString()
|
||||||
|
|
||||||
|
@ -838,6 +858,67 @@ def open_link() -> bytes:
|
||||||
return b""
|
return b""
|
||||||
|
|
||||||
|
|
||||||
|
async def ask_user() -> bytes:
|
||||||
|
req = frontend_pb2.AskUserRequest()
|
||||||
|
req.ParseFromString(request.data)
|
||||||
|
|
||||||
|
def callback(request_handler: AsyncRequestHandler) -> None:
|
||||||
|
kwargs: dict[str, Any] = dict(text=req.text)
|
||||||
|
if req.HasField("help"):
|
||||||
|
help_arg: Any
|
||||||
|
if req.help.WhichOneof("value") == "help_page":
|
||||||
|
help_arg = req.help.help_page
|
||||||
|
else:
|
||||||
|
help_arg = req.help.help_link
|
||||||
|
kwargs["help"] = help_arg
|
||||||
|
if req.HasField("title"):
|
||||||
|
kwargs["title"] = req.title
|
||||||
|
if req.HasField("default_no"):
|
||||||
|
kwargs["defaultno"] = req.default_no
|
||||||
|
answer = askUser(**kwargs)
|
||||||
|
request_handler.set_result(answer)
|
||||||
|
|
||||||
|
request_handler: AsyncRequestHandler[bool] = AsyncRequestHandler(callback)
|
||||||
|
request_handler.run()
|
||||||
|
answer = await request_handler.get_result()
|
||||||
|
|
||||||
|
return generic_pb2.Bool(val=answer).SerializeToString()
|
||||||
|
|
||||||
|
|
||||||
|
async def show_message_box() -> bytes:
|
||||||
|
req = frontend_pb2.ShowMessageBoxRequest()
|
||||||
|
req.ParseFromString(request.data)
|
||||||
|
|
||||||
|
def callback(request_handler: AsyncRequestHandler) -> None:
|
||||||
|
kwargs: dict[str, Any] = dict(text=req.text)
|
||||||
|
if req.type == frontend_pb2.MessageBoxType.INFO:
|
||||||
|
icon = QMessageBox.Icon.Information
|
||||||
|
elif req.type == frontend_pb2.MessageBoxType.WARNING:
|
||||||
|
icon = QMessageBox.Icon.Warning
|
||||||
|
elif req.type == frontend_pb2.MessageBoxType.CRITICAL:
|
||||||
|
icon = QMessageBox.Icon.Critical
|
||||||
|
kwargs["icon"] = icon
|
||||||
|
if req.HasField("help"):
|
||||||
|
help_arg: Any
|
||||||
|
if req.help.WhichOneof("value") == "help_page":
|
||||||
|
help_arg = req.help.help_page
|
||||||
|
else:
|
||||||
|
help_arg = req.help.help_link
|
||||||
|
kwargs["help"] = help_arg
|
||||||
|
if req.HasField("title"):
|
||||||
|
kwargs["title"] = req.title
|
||||||
|
if req.HasField("text_format"):
|
||||||
|
kwargs["text_format"] = req.text_format
|
||||||
|
show_info(**kwargs)
|
||||||
|
request_handler.set_result(True)
|
||||||
|
|
||||||
|
request_handler: AsyncRequestHandler[bool] = AsyncRequestHandler(callback)
|
||||||
|
request_handler.run()
|
||||||
|
answer = await request_handler.get_result()
|
||||||
|
|
||||||
|
return generic_pb2.Bool(val=answer).SerializeToString()
|
||||||
|
|
||||||
|
|
||||||
post_handler_list = [
|
post_handler_list = [
|
||||||
congrats_info,
|
congrats_info,
|
||||||
get_deck_configs_for_update,
|
get_deck_configs_for_update,
|
||||||
|
@ -872,6 +953,8 @@ post_handler_list = [
|
||||||
close_add_cards,
|
close_add_cards,
|
||||||
close_edit_current,
|
close_edit_current,
|
||||||
open_link,
|
open_link,
|
||||||
|
ask_user,
|
||||||
|
show_message_box,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -518,16 +518,35 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (result.state === NoteFieldsCheckResponse_State.MISSING_CLOZE) {
|
if (result.state === NoteFieldsCheckResponse_State.MISSING_CLOZE) {
|
||||||
// TODO: askUser(tr.addingYouHaveAClozeDeletionNote())
|
const answer = (
|
||||||
|
await askUser({
|
||||||
|
text: tr.addingYouHaveAClozeDeletionNote(),
|
||||||
|
})
|
||||||
|
).val;
|
||||||
|
if (!answer) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (result.state === NoteFieldsCheckResponse_State.NOTETYPE_NOT_CLOZE) {
|
if (result.state === NoteFieldsCheckResponse_State.NOTETYPE_NOT_CLOZE) {
|
||||||
problem = tr.addingClozeOutsideClozeNotetype();
|
problem = tr.addingClozeOutsideClozeNotetype();
|
||||||
}
|
}
|
||||||
if (result.state === NoteFieldsCheckResponse_State.FIELD_NOT_CLOZE) {
|
if (result.state === NoteFieldsCheckResponse_State.FIELD_NOT_CLOZE) {
|
||||||
problem = tr.addingClozeOutsideClozeField();
|
problem = tr.addingClozeOutsideClozeField();
|
||||||
}
|
}
|
||||||
return problem ? false : true;
|
if (problem) {
|
||||||
|
showMessageBox({
|
||||||
|
text: problem,
|
||||||
|
type: MessageBoxType.WARNING,
|
||||||
|
help: {
|
||||||
|
value: {
|
||||||
|
case: "helpPage",
|
||||||
|
value: HelpPageLinkRequest_HelpPage.ADDING_CARD_AND_NOTE,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function addCurrentNoteInner(deckId: bigint) {
|
async function addCurrentNoteInner(deckId: bigint) {
|
||||||
|
@ -676,6 +695,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
closeAddCards as closeAddCardsBackend,
|
closeAddCards as closeAddCardsBackend,
|
||||||
closeEditCurrent as closeEditCurrentBackend,
|
closeEditCurrent as closeEditCurrentBackend,
|
||||||
htmlToTextLine,
|
htmlToTextLine,
|
||||||
|
askUser,
|
||||||
|
showMessageBox,
|
||||||
} from "@generated/backend";
|
} from "@generated/backend";
|
||||||
import { wrapInternal } from "@tslib/wrap";
|
import { wrapInternal } from "@tslib/wrap";
|
||||||
import { getProfileConfig, getMeta, setMeta, getColConfig } from "@tslib/profile";
|
import { getProfileConfig, getMeta, setMeta, getColConfig } from "@tslib/profile";
|
||||||
|
@ -701,6 +722,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
import { registerShortcut } from "@tslib/shortcuts";
|
import { registerShortcut } from "@tslib/shortcuts";
|
||||||
import ActionButtons from "./ActionButtons.svelte";
|
import ActionButtons from "./ActionButtons.svelte";
|
||||||
import HistoryModal from "./HistoryModal.svelte";
|
import HistoryModal from "./HistoryModal.svelte";
|
||||||
|
import { HelpPageLinkRequest_HelpPage } from "@generated/anki/links_pb";
|
||||||
|
import { MessageBoxType } from "@generated/anki/frontend_pb";
|
||||||
|
|
||||||
$: isIOImageLoaded = false;
|
$: isIOImageLoaded = false;
|
||||||
$: ioImageLoadedStore.set(isIOImageLoaded);
|
$: ioImageLoadedStore.set(isIOImageLoaded);
|
||||||
|
|
Loading…
Reference in a new issue