mirror of
https://github.com/ankitects/anki.git
synced 2025-11-06 12:47:11 -05:00
Handle undo for all RPCs that return OpChanges
This commit is contained in:
parent
6165567238
commit
f10d48db47
6 changed files with 62 additions and 85 deletions
|
|
@ -32,9 +32,6 @@ service FrontendService {
|
|||
rpc deckOptionsReady(generic.Empty) returns (generic.Empty);
|
||||
|
||||
// Editor
|
||||
rpc UpdateEditorNote(notes.UpdateNotesRequest) returns (generic.Empty);
|
||||
rpc UpdateEditorNotetype(notetypes.Notetype) returns (generic.Empty);
|
||||
rpc AddEditorNote(notes.AddNoteRequest) returns (notes.AddNoteResponse);
|
||||
rpc ConvertPastedImage(ConvertPastedImageRequest)
|
||||
returns (ConvertPastedImageResponse);
|
||||
rpc OpenFilePicker(openFilePickerRequest) returns (generic.String);
|
||||
|
|
|
|||
|
|
@ -562,36 +562,6 @@ def import_done() -> bytes:
|
|||
return b""
|
||||
|
||||
|
||||
def import_request(endpoint: str) -> bytes:
|
||||
output = raw_backend_request(endpoint)()
|
||||
response = OpChangesOnly()
|
||||
response.ParseFromString(output)
|
||||
|
||||
def handle_on_main() -> None:
|
||||
window = aqt.mw.app.activeModalWidget()
|
||||
on_op_finished(aqt.mw, response, window)
|
||||
|
||||
aqt.mw.taskman.run_on_main(handle_on_main)
|
||||
|
||||
return output
|
||||
|
||||
|
||||
def import_csv() -> bytes:
|
||||
return import_request("import_csv")
|
||||
|
||||
|
||||
def import_anki_package() -> bytes:
|
||||
return import_request("import_anki_package")
|
||||
|
||||
|
||||
def import_json_file() -> bytes:
|
||||
return import_request("import_json_file")
|
||||
|
||||
|
||||
def import_json_string() -> bytes:
|
||||
return import_request("import_json_string")
|
||||
|
||||
|
||||
def search_in_browser() -> bytes:
|
||||
node = SearchNode()
|
||||
node.ParseFromString(request.data)
|
||||
|
|
@ -638,36 +608,6 @@ def deck_options_ready() -> bytes:
|
|||
return b""
|
||||
|
||||
|
||||
def editor_op_changes_request(endpoint: str) -> bytes:
|
||||
output = raw_backend_request(endpoint)()
|
||||
response = OpChanges()
|
||||
response.ParseFromString(output)
|
||||
|
||||
def handle_on_main() -> None:
|
||||
from aqt.editor import NewEditor
|
||||
|
||||
handler = aqt.mw.app.activeWindow()
|
||||
if handler and isinstance(getattr(handler, "editor", None), NewEditor):
|
||||
handler = handler.editor # type: ignore
|
||||
on_op_finished(aqt.mw, response, handler)
|
||||
|
||||
aqt.mw.taskman.run_on_main(handle_on_main)
|
||||
|
||||
return output
|
||||
|
||||
|
||||
def update_editor_note() -> bytes:
|
||||
return editor_op_changes_request("update_notes")
|
||||
|
||||
|
||||
def update_editor_notetype() -> bytes:
|
||||
return editor_op_changes_request("update_notetype")
|
||||
|
||||
|
||||
def add_editor_note() -> bytes:
|
||||
return editor_op_changes_request("add_note")
|
||||
|
||||
|
||||
def get_setting_json(getter: Callable[[str], Any]) -> bytes:
|
||||
req = generic_pb2.String()
|
||||
req.ParseFromString(request.data)
|
||||
|
|
@ -953,16 +893,9 @@ post_handler_list = [
|
|||
set_scheduling_states,
|
||||
change_notetype,
|
||||
import_done,
|
||||
import_csv,
|
||||
import_anki_package,
|
||||
import_json_file,
|
||||
import_json_string,
|
||||
search_in_browser,
|
||||
deck_options_require_close,
|
||||
deck_options_ready,
|
||||
update_editor_note,
|
||||
update_editor_notetype,
|
||||
add_editor_note,
|
||||
get_profile_config_json,
|
||||
set_profile_config_json,
|
||||
get_meta_json,
|
||||
|
|
@ -995,6 +928,10 @@ exposed_backend_list = [
|
|||
# ImportExportService
|
||||
"get_csv_metadata",
|
||||
"get_import_anki_package_presets",
|
||||
"import_csv",
|
||||
"import_anki_package",
|
||||
"import_json_file",
|
||||
"import_json_string",
|
||||
# NotesService
|
||||
"get_field_names",
|
||||
"get_note",
|
||||
|
|
@ -1002,6 +939,9 @@ exposed_backend_list = [
|
|||
"note_fields_check",
|
||||
"defaults_for_adding",
|
||||
"default_deck_for_notetype",
|
||||
"add_note",
|
||||
"update_notes",
|
||||
"update_notetype",
|
||||
# NotetypesService
|
||||
"get_notetype",
|
||||
"get_notetype_names",
|
||||
|
|
@ -1056,7 +996,25 @@ def raw_backend_request(endpoint: str) -> Callable[[], bytes]:
|
|||
|
||||
assert hasattr(RustBackend, f"{endpoint}_raw")
|
||||
|
||||
return lambda: getattr(aqt.mw.col._backend, f"{endpoint}_raw")(request.data)
|
||||
def wrapped() -> bytes:
|
||||
output = getattr(aqt.mw.col._backend, f"{endpoint}_raw")(request.data)
|
||||
if "Has-Op-Changes" in request.headers:
|
||||
response = OpChangesOnly()
|
||||
response.ParseFromString(output)
|
||||
|
||||
def handle_on_main() -> None:
|
||||
from aqt.editor import NewEditor
|
||||
|
||||
handler = aqt.mw.app.activeModalWidget()
|
||||
if handler and isinstance(getattr(handler, "editor", None), NewEditor):
|
||||
handler = handler.editor # type: ignore
|
||||
on_op_finished(aqt.mw, response, handler)
|
||||
|
||||
aqt.mw.taskman.run_on_main(handle_on_main)
|
||||
|
||||
return output
|
||||
|
||||
return wrapped
|
||||
|
||||
|
||||
# all methods in here require a collection
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ use anki_proto_gen::Method;
|
|||
use anyhow::Result;
|
||||
use inflections::Inflect;
|
||||
use itertools::Itertools;
|
||||
use prost_reflect::MessageDescriptor;
|
||||
|
||||
pub(crate) fn write_ts_interface(services: &[BackendService]) -> Result<()> {
|
||||
let root = Path::new("../../out/ts/lib/generated");
|
||||
|
|
@ -73,6 +74,7 @@ fn write_ts_method(
|
|||
input_type,
|
||||
output_type,
|
||||
comments,
|
||||
has_op_changes,
|
||||
}: &MethodDetails,
|
||||
out: &mut String,
|
||||
) {
|
||||
|
|
@ -80,7 +82,7 @@ fn write_ts_method(
|
|||
writeln!(
|
||||
out,
|
||||
r#"{comments}export async function {method_name}(input: PlainMessage<{input_type}>, options?: PostProtoOptions): Promise<{output_type}> {{
|
||||
return await postProto("{method_name}", new {input_type}(input), {output_type}, options);
|
||||
return await postProto("{method_name}", new {input_type}(input), {output_type}, options, {has_op_changes});
|
||||
}}"#
|
||||
).unwrap()
|
||||
}
|
||||
|
|
@ -97,6 +99,7 @@ struct MethodDetails {
|
|||
input_type: String,
|
||||
output_type: String,
|
||||
comments: Option<String>,
|
||||
has_op_changes: bool,
|
||||
}
|
||||
|
||||
impl MethodDetails {
|
||||
|
|
@ -105,15 +108,31 @@ impl MethodDetails {
|
|||
let input_type = full_name_to_imported_reference(method.proto.input().full_name());
|
||||
let output_type = full_name_to_imported_reference(method.proto.output().full_name());
|
||||
let comments = method.comments.clone();
|
||||
let has_op_changes = has_op_changes(&method.proto.output());
|
||||
Self {
|
||||
method_name: name,
|
||||
input_type,
|
||||
output_type,
|
||||
comments,
|
||||
has_op_changes,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn has_op_changes(message: &MessageDescriptor) -> bool {
|
||||
if message.full_name() == "anki.collection.OpChanges" {
|
||||
true
|
||||
} else if let Some(field) = message.get_field(1) {
|
||||
if let Some(field_message) = field.kind().as_message() {
|
||||
has_op_changes(field_message)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn record_referenced_type(referenced_packages: &mut HashSet<String>, type_name: &str) {
|
||||
referenced_packages.insert(type_name.split('.').next().unwrap().to_string());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,11 +11,12 @@ export async function postProto<T>(
|
|||
input: { toBinary(): Uint8Array; getType(): { typeName: string } },
|
||||
outputType: { fromBinary(arr: Uint8Array): T },
|
||||
options: PostProtoOptions = {},
|
||||
hasOpChanges = false,
|
||||
): Promise<T> {
|
||||
try {
|
||||
const inputBytes = input.toBinary();
|
||||
const path = `/_anki/${method}`;
|
||||
const outputBytes = await postProtoInner(path, inputBytes);
|
||||
const outputBytes = await postProtoInner(path, inputBytes, hasOpChanges);
|
||||
return outputType.fromBinary(outputBytes);
|
||||
} catch (err) {
|
||||
const { alertOnError = true } = options;
|
||||
|
|
@ -26,12 +27,14 @@ export async function postProto<T>(
|
|||
}
|
||||
}
|
||||
|
||||
async function postProtoInner(url: string, body: Uint8Array): Promise<Uint8Array> {
|
||||
async function postProtoInner(url: string, body: Uint8Array, hasOpChanges: boolean): Promise<Uint8Array> {
|
||||
const headers = { "Content-Type": "application/binary" };
|
||||
if (hasOpChanges) {
|
||||
headers["Has-Op-Changes"] = "1";
|
||||
}
|
||||
const result = await fetch(url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/binary",
|
||||
},
|
||||
headers,
|
||||
body,
|
||||
});
|
||||
if (!result.ok) {
|
||||
|
|
|
|||
|
|
@ -247,7 +247,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
}
|
||||
values.push(field.config!.sticky);
|
||||
}
|
||||
await updateEditorNotetype(notetype);
|
||||
await updateNotetype(notetype);
|
||||
setSticky(values);
|
||||
}
|
||||
}
|
||||
|
|
@ -405,7 +405,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
async function updateCurrentNote() {
|
||||
if (mode !== "add") {
|
||||
try {
|
||||
await updateEditorNote(
|
||||
await updateNotes(
|
||||
{
|
||||
notes: [note!],
|
||||
skipUndoEntry: false,
|
||||
|
|
@ -586,7 +586,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
if (!(await noteCanBeAdded())) {
|
||||
return;
|
||||
}
|
||||
const response = await addEditorNote({
|
||||
const response = await addNote({
|
||||
note: note!,
|
||||
deckId,
|
||||
});
|
||||
|
|
@ -723,12 +723,12 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
getNotetype,
|
||||
encodeIriPaths,
|
||||
newNote,
|
||||
updateEditorNote,
|
||||
updateNotes,
|
||||
decodeIriPaths,
|
||||
noteFieldsCheck,
|
||||
addEditorNote,
|
||||
addNote,
|
||||
addMediaFromPath,
|
||||
updateEditorNotetype,
|
||||
updateNotetype,
|
||||
closeAddCards as closeAddCardsBackend,
|
||||
closeEditCurrent as closeEditCurrentBackend,
|
||||
htmlToTextLine,
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
|
||||
import { context as editorFieldContext } from "./EditorField.svelte";
|
||||
import type { Note } from "@generated/anki/notes_pb";
|
||||
import { getNotetype, updateEditorNotetype } from "@generated/backend";
|
||||
import { getNotetype, updateNotetype } from "@generated/backend";
|
||||
import { bridgeCommand } from "@tslib/bridgecommand";
|
||||
|
||||
const animated = !document.body.classList.contains("reduce-motion");
|
||||
|
|
@ -39,7 +39,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
active = !active;
|
||||
const notetype = await getNotetype({ ntid: note.notetypeId });
|
||||
notetype.fields[index].config!.sticky = active;
|
||||
await updateEditorNotetype(notetype);
|
||||
await updateNotetype(notetype);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue