Skip to content
Merged
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
10 changes: 10 additions & 0 deletions src/common/view.js
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,16 @@ class View {
return sdt ? sdt.mapper.sdtToSourcePosition(sdtAnchor) : null;
}

/**
* Top-level structured-document-text block index currently in view, or null. Used to start Read Aloud playback
* where the reader is.
* @returns {Promise<number | null>}
*/
async getVisibleBlockIndex() {
let sdt = await this._loadSDT();
return sdt ? (this._view.getVisibleBlockIndex?.(sdt.structure) ?? null) : null;
}

async createAnnotationFromSDT({ sdtAnchor, type, color, comment, tags }) {
let sdt = await this._loadSDT();
if (!sdt) {
Expand Down
87 changes: 79 additions & 8 deletions src/index.ios.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,18 @@ function decodeBase64(base64) {
return decoder.decode(base64ToBytes(base64));
}

// Read Aloud annotation preview: while a highlight session is active the previewed annotation is rendered in the reader
// but its saves are withheld from the app, so nothing is written to the database until the session is confirmed.
let readAloudPreviewAnnotation = null;
const readAloudPreviewIds = new Set();

let readAloudPreviewQueue = Promise.resolve();
function enqueueReadAloudPreview(task) {
readAloudPreviewQueue = readAloudPreviewQueue.then(task).catch((error) => {
log("Read Aloud preview operation failed: " + error);
});
}

window.createView = (options) => {
log("Create " + options.type + " view");
const annotations = JSON.parse(decodeBase64(options.annotations));
Expand All @@ -42,10 +54,15 @@ window.createView = (options) => {
postMessage('onViewContentInitialized');
},
onSaveAnnotations: (annotations) => {
postMessage('onSaveAnnotations', { annotations });
// Withhold read-aloud preview annotations (not yet confirmed) so they aren't persisted mid-session.
const saved = annotations.filter(annotation => !readAloudPreviewIds.has(annotation.id));
if (!saved.length) {
return;
}
postMessage('onSaveAnnotations', { annotations: saved });

if (annotations[0].type == "note") {
window._view.selectAnnotations([annotations[0].id]);
if (saved[0].type == "note") {
window._view.selectAnnotations([saved[0].id]);
}
},
onSetOutline: (outline) => {
Expand Down Expand Up @@ -160,11 +177,65 @@ window.getReadAloudSegments = async (options) => {
postMessage('onReadAloudSegments', { requestID: options.requestID, segments });
};

window.setReadAloudAnnotation = async (options) => {
const params = JSON.parse(decodeBase64(options.params));
log("Set Read Aloud annotation: " + params.type);
const annotation = await window._view.setReadAloudAnnotation(params);
postMessage('onReadAloudAnnotation', { requestID: options.requestID, annotation });
window.getReadAloudStartBlockIndex = async (options) => {
let blockIndex = null;
try {
blockIndex = await window._view.getVisibleBlockIndex();
}
catch (error) {
log("Read Aloud start block index unavailable: " + error);
}
postMessage('onReadAloudStartBlockIndex', { requestID: options.requestID, blockIndex });
};

window.setReadAloudAnnotation = (options) => {
enqueueReadAloudPreview(async () => {
const params = JSON.parse(decodeBase64(options.params));
if (readAloudPreviewAnnotation) {
params.id = readAloudPreviewAnnotation.id;
}
log("Set Read Aloud annotation preview: " + params.type);
const annotation = await window._view.setReadAloudAnnotation(params);
if (annotation) {
readAloudPreviewAnnotation = annotation;
readAloudPreviewIds.add(annotation.id);
}
postMessage('onReadAloudAnnotation', { requestID: options.requestID, annotation });
});
};

window.confirmReadAloudAnnotation = () => {
// Confirm the session: stop withholding the preview annotation and report it as a normal save so it is persisted.
enqueueReadAloudPreview(async () => {
if (!readAloudPreviewAnnotation) {
return;
}
const annotation = readAloudPreviewAnnotation;
readAloudPreviewIds.delete(annotation.id);
readAloudPreviewAnnotation = null;
log("Confirm Read Aloud annotation");
postMessage('onSaveAnnotations', { annotations: [annotation] });
});
};

window.cancelReadAloudAnnotation = () => {
enqueueReadAloudPreview(async () => {
if (!readAloudPreviewAnnotation) {
return;
}
const id = readAloudPreviewAnnotation.id;
readAloudPreviewIds.delete(id);
readAloudPreviewAnnotation = null;
log("Cancel Read Aloud annotation");
window._view.unsetAnnotations([id]);
});
};

window.setReadAloudSpotlight = async (options) => {
const anchor = options.anchor ? JSON.parse(decodeBase64(options.anchor)) : null;
log("Set Read Aloud spotlight: " + (anchor ? JSON.stringify(anchor) : "clear"));
const position = anchor ? await window._view.sdtAnchorToPosition(anchor) : null;
window._view.setReadAloudSpotlight(position);
};

// Notify when iframe is loaded
Expand Down
Loading