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);
|
rpc deckOptionsReady(generic.Empty) returns (generic.Empty);
|
||||||
|
|
||||||
// Editor
|
// 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)
|
rpc ConvertPastedImage(ConvertPastedImageRequest)
|
||||||
returns (ConvertPastedImageResponse);
|
returns (ConvertPastedImageResponse);
|
||||||
rpc OpenFilePicker(openFilePickerRequest) returns (generic.String);
|
rpc OpenFilePicker(openFilePickerRequest) returns (generic.String);
|
||||||
|
|
|
||||||
|
|
@ -562,36 +562,6 @@ def import_done() -> bytes:
|
||||||
return b""
|
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:
|
def search_in_browser() -> bytes:
|
||||||
node = SearchNode()
|
node = SearchNode()
|
||||||
node.ParseFromString(request.data)
|
node.ParseFromString(request.data)
|
||||||
|
|
@ -638,36 +608,6 @@ def deck_options_ready() -> bytes:
|
||||||
return b""
|
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:
|
def get_setting_json(getter: Callable[[str], Any]) -> bytes:
|
||||||
req = generic_pb2.String()
|
req = generic_pb2.String()
|
||||||
req.ParseFromString(request.data)
|
req.ParseFromString(request.data)
|
||||||
|
|
@ -953,16 +893,9 @@ post_handler_list = [
|
||||||
set_scheduling_states,
|
set_scheduling_states,
|
||||||
change_notetype,
|
change_notetype,
|
||||||
import_done,
|
import_done,
|
||||||
import_csv,
|
|
||||||
import_anki_package,
|
|
||||||
import_json_file,
|
|
||||||
import_json_string,
|
|
||||||
search_in_browser,
|
search_in_browser,
|
||||||
deck_options_require_close,
|
deck_options_require_close,
|
||||||
deck_options_ready,
|
deck_options_ready,
|
||||||
update_editor_note,
|
|
||||||
update_editor_notetype,
|
|
||||||
add_editor_note,
|
|
||||||
get_profile_config_json,
|
get_profile_config_json,
|
||||||
set_profile_config_json,
|
set_profile_config_json,
|
||||||
get_meta_json,
|
get_meta_json,
|
||||||
|
|
@ -995,6 +928,10 @@ exposed_backend_list = [
|
||||||
# ImportExportService
|
# ImportExportService
|
||||||
"get_csv_metadata",
|
"get_csv_metadata",
|
||||||
"get_import_anki_package_presets",
|
"get_import_anki_package_presets",
|
||||||
|
"import_csv",
|
||||||
|
"import_anki_package",
|
||||||
|
"import_json_file",
|
||||||
|
"import_json_string",
|
||||||
# NotesService
|
# NotesService
|
||||||
"get_field_names",
|
"get_field_names",
|
||||||
"get_note",
|
"get_note",
|
||||||
|
|
@ -1002,6 +939,9 @@ exposed_backend_list = [
|
||||||
"note_fields_check",
|
"note_fields_check",
|
||||||
"defaults_for_adding",
|
"defaults_for_adding",
|
||||||
"default_deck_for_notetype",
|
"default_deck_for_notetype",
|
||||||
|
"add_note",
|
||||||
|
"update_notes",
|
||||||
|
"update_notetype",
|
||||||
# NotetypesService
|
# NotetypesService
|
||||||
"get_notetype",
|
"get_notetype",
|
||||||
"get_notetype_names",
|
"get_notetype_names",
|
||||||
|
|
@ -1056,7 +996,25 @@ def raw_backend_request(endpoint: str) -> Callable[[], bytes]:
|
||||||
|
|
||||||
assert hasattr(RustBackend, f"{endpoint}_raw")
|
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
|
# all methods in here require a collection
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ use anki_proto_gen::Method;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use inflections::Inflect;
|
use inflections::Inflect;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
use prost_reflect::MessageDescriptor;
|
||||||
|
|
||||||
pub(crate) fn write_ts_interface(services: &[BackendService]) -> Result<()> {
|
pub(crate) fn write_ts_interface(services: &[BackendService]) -> Result<()> {
|
||||||
let root = Path::new("../../out/ts/lib/generated");
|
let root = Path::new("../../out/ts/lib/generated");
|
||||||
|
|
@ -73,6 +74,7 @@ fn write_ts_method(
|
||||||
input_type,
|
input_type,
|
||||||
output_type,
|
output_type,
|
||||||
comments,
|
comments,
|
||||||
|
has_op_changes,
|
||||||
}: &MethodDetails,
|
}: &MethodDetails,
|
||||||
out: &mut String,
|
out: &mut String,
|
||||||
) {
|
) {
|
||||||
|
|
@ -80,7 +82,7 @@ fn write_ts_method(
|
||||||
writeln!(
|
writeln!(
|
||||||
out,
|
out,
|
||||||
r#"{comments}export async function {method_name}(input: PlainMessage<{input_type}>, options?: PostProtoOptions): Promise<{output_type}> {{
|
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()
|
).unwrap()
|
||||||
}
|
}
|
||||||
|
|
@ -97,6 +99,7 @@ struct MethodDetails {
|
||||||
input_type: String,
|
input_type: String,
|
||||||
output_type: String,
|
output_type: String,
|
||||||
comments: Option<String>,
|
comments: Option<String>,
|
||||||
|
has_op_changes: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MethodDetails {
|
impl MethodDetails {
|
||||||
|
|
@ -105,15 +108,31 @@ impl MethodDetails {
|
||||||
let input_type = full_name_to_imported_reference(method.proto.input().full_name());
|
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 output_type = full_name_to_imported_reference(method.proto.output().full_name());
|
||||||
let comments = method.comments.clone();
|
let comments = method.comments.clone();
|
||||||
|
let has_op_changes = has_op_changes(&method.proto.output());
|
||||||
Self {
|
Self {
|
||||||
method_name: name,
|
method_name: name,
|
||||||
input_type,
|
input_type,
|
||||||
output_type,
|
output_type,
|
||||||
comments,
|
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) {
|
fn record_referenced_type(referenced_packages: &mut HashSet<String>, type_name: &str) {
|
||||||
referenced_packages.insert(type_name.split('.').next().unwrap().to_string());
|
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 } },
|
input: { toBinary(): Uint8Array; getType(): { typeName: string } },
|
||||||
outputType: { fromBinary(arr: Uint8Array): T },
|
outputType: { fromBinary(arr: Uint8Array): T },
|
||||||
options: PostProtoOptions = {},
|
options: PostProtoOptions = {},
|
||||||
|
hasOpChanges = false,
|
||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
try {
|
try {
|
||||||
const inputBytes = input.toBinary();
|
const inputBytes = input.toBinary();
|
||||||
const path = `/_anki/${method}`;
|
const path = `/_anki/${method}`;
|
||||||
const outputBytes = await postProtoInner(path, inputBytes);
|
const outputBytes = await postProtoInner(path, inputBytes, hasOpChanges);
|
||||||
return outputType.fromBinary(outputBytes);
|
return outputType.fromBinary(outputBytes);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const { alertOnError = true } = options;
|
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, {
|
const result = await fetch(url, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers,
|
||||||
"Content-Type": "application/binary",
|
|
||||||
},
|
|
||||||
body,
|
body,
|
||||||
});
|
});
|
||||||
if (!result.ok) {
|
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);
|
values.push(field.config!.sticky);
|
||||||
}
|
}
|
||||||
await updateEditorNotetype(notetype);
|
await updateNotetype(notetype);
|
||||||
setSticky(values);
|
setSticky(values);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -405,7 +405,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
async function updateCurrentNote() {
|
async function updateCurrentNote() {
|
||||||
if (mode !== "add") {
|
if (mode !== "add") {
|
||||||
try {
|
try {
|
||||||
await updateEditorNote(
|
await updateNotes(
|
||||||
{
|
{
|
||||||
notes: [note!],
|
notes: [note!],
|
||||||
skipUndoEntry: false,
|
skipUndoEntry: false,
|
||||||
|
|
@ -586,7 +586,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
if (!(await noteCanBeAdded())) {
|
if (!(await noteCanBeAdded())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const response = await addEditorNote({
|
const response = await addNote({
|
||||||
note: note!,
|
note: note!,
|
||||||
deckId,
|
deckId,
|
||||||
});
|
});
|
||||||
|
|
@ -723,12 +723,12 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
getNotetype,
|
getNotetype,
|
||||||
encodeIriPaths,
|
encodeIriPaths,
|
||||||
newNote,
|
newNote,
|
||||||
updateEditorNote,
|
updateNotes,
|
||||||
decodeIriPaths,
|
decodeIriPaths,
|
||||||
noteFieldsCheck,
|
noteFieldsCheck,
|
||||||
addEditorNote,
|
addNote,
|
||||||
addMediaFromPath,
|
addMediaFromPath,
|
||||||
updateEditorNotetype,
|
updateNotetype,
|
||||||
closeAddCards as closeAddCardsBackend,
|
closeAddCards as closeAddCardsBackend,
|
||||||
closeEditCurrent as closeEditCurrentBackend,
|
closeEditCurrent as closeEditCurrentBackend,
|
||||||
htmlToTextLine,
|
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 { context as editorFieldContext } from "./EditorField.svelte";
|
||||||
import type { Note } from "@generated/anki/notes_pb";
|
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";
|
import { bridgeCommand } from "@tslib/bridgecommand";
|
||||||
|
|
||||||
const animated = !document.body.classList.contains("reduce-motion");
|
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;
|
active = !active;
|
||||||
const notetype = await getNotetype({ ntid: note.notetypeId });
|
const notetype = await getNotetype({ ntid: note.notetypeId });
|
||||||
notetype.fields[index].config!.sticky = active;
|
notetype.fields[index].config!.sticky = active;
|
||||||
await updateEditorNotetype(notetype);
|
await updateNotetype(notetype);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue