Skip to content

Commit 194314d

Browse files
Manuals: Sync the sidebar to search view and webview (#682)
1 parent a1315a6 commit 194314d

File tree

1 file changed

+97
-21
lines changed

1 file changed

+97
-21
lines changed

src/Manuals/DocumentationViewer.js

Lines changed: 97 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ const DocumentationPage = GObject.registerClass(
5050
class DocumentationPage extends GObject.Object {},
5151
);
5252

53+
const URI_TO_SIDEBAR_PATH = {};
54+
let sync_sidebar = false;
55+
let scrolled_to = false;
56+
5357
export default function DocumentationViewer({ application }) {
5458
const builder = Gtk.Builder.new_from_resource(resource);
5559

@@ -112,10 +116,27 @@ export default function DocumentationViewer({ application }) {
112116
);
113117
user_content_manager.add_style_sheet(stylesheet);
114118

119+
const root_model = Gio.ListStore.new(DocumentationPage);
120+
const browse_selection_model = createBrowseSelectionModel(
121+
root_model,
122+
webview,
123+
);
124+
browse_list_view.model = browse_selection_model;
125+
115126
webview.connect("load-changed", () => {
116127
updateButtons();
117128
});
118129

130+
webview.connect("notify::uri", () => {
131+
const selected_item = browse_selection_model.selected_item.item;
132+
if (webview.uri !== selected_item.uri) {
133+
sync_sidebar = true;
134+
const path = URI_TO_SIDEBAR_PATH[webview.uri];
135+
if (!path) return;
136+
selectSidebarItem(browse_list_view, path);
137+
}
138+
});
139+
119140
webview.get_back_forward_list().connect("changed", () => {
120141
updateButtons();
121142
});
@@ -133,6 +154,23 @@ export default function DocumentationViewer({ application }) {
133154
webview.go_forward();
134155
});
135156

157+
const adj = browse_page.get_vscrollbar().adjustment;
158+
adj.connect("value-changed", () => {
159+
if (scrolled_to) {
160+
const index = browse_selection_model.selected;
161+
const adj = browse_page.get_vscrollbar().adjustment;
162+
const bottom_edge = (index + 1) * 38 - adj.value;
163+
const top_edge = bottom_edge - 38;
164+
// If row is not visible after scroll_to, adjust
165+
if (bottom_edge === 0) {
166+
adj.value -= 38;
167+
} else if (top_edge === adj.page_size) {
168+
adj.value += 38;
169+
}
170+
scrolled_to = false;
171+
}
172+
});
173+
136174
const expr = new Gtk.ClosureExpression(
137175
GObject.TYPE_STRING,
138176
(item) => item.search_name,
@@ -158,11 +196,10 @@ export default function DocumentationViewer({ application }) {
158196
sorter.expression = expr;
159197
search_model.connect("selection-changed", () => {
160198
const uri = search_model.selected_item.uri;
161-
webview.load_uri(uri);
199+
const sidebar_path = URI_TO_SIDEBAR_PATH[uri];
200+
selectSidebarItem(browse_list_view, sidebar_path);
162201
});
163202

164-
const root_model = Gio.ListStore.new(DocumentationPage);
165-
browse_list_view.model = createBrowseSelectionModel(root_model, webview);
166203
let promise_load;
167204
async function load() {
168205
if (!promise_load) {
@@ -198,7 +235,8 @@ export default function DocumentationViewer({ application }) {
198235
load()
199236
.then(() => {
200237
if (!mapped) {
201-
browse_list_view.model.selected = 12;
238+
collapseAllRows(browse_selection_model.model);
239+
browse_selection_model.selected = 12;
202240
search_entry.text = "";
203241
onSearchChanged();
204242
}
@@ -209,6 +247,33 @@ export default function DocumentationViewer({ application }) {
209247
application.set_accels_for_action("app.documentation", ["<Control>M"]);
210248
}
211249

250+
function sortFunc(doc1, doc2) {
251+
return doc1.name.localeCompare(doc2.name);
252+
}
253+
254+
function collapseAllRows(model) {
255+
for (let i = 0; i < model.n_items; i++) {
256+
const row = model.get_row(i);
257+
row.expanded = false;
258+
}
259+
}
260+
261+
function selectSidebarItem(browse_list_view, path) {
262+
const selection_model = browse_list_view.model;
263+
collapseAllRows(selection_model.model);
264+
for (const index of path.slice(0, -1)) {
265+
const row = selection_model.model.get_row(index);
266+
row.expanded = true;
267+
}
268+
const index = path[path.length - 1];
269+
// If possible, overshoot scrolling by one row to ensure selected row is visible
270+
index + 1 === selection_model.n_items
271+
? browse_list_view.scroll_to(index, Gtk.ListScrollFlags.NONE, null)
272+
: browse_list_view.scroll_to(index + 1, Gtk.ListScrollFlags.NONE, null);
273+
selection_model.selected = index;
274+
scrolled_to = true;
275+
}
276+
212277
async function loadLibrary(model, directory) {
213278
try {
214279
const json_file = directory.get_child("index.json");
@@ -225,7 +290,7 @@ async function loadLibrary(model, directory) {
225290
children: getChildren(index, directory),
226291
});
227292

228-
model.append(page);
293+
model.insert_sorted(page, sortFunc);
229294
} catch (error) {
230295
if (!error.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_FOUND)) throw error;
231296
}
@@ -267,10 +332,21 @@ async function scanLibraries(model, base_dir) {
267332
return Promise.allSettled(libraries);
268333
}
269334

270-
function flattenModel(list_store, flattened_model = newListStore()) {
335+
function flattenModel(
336+
list_store,
337+
flattened_model = newListStore(),
338+
path = [0],
339+
) {
271340
for (const item of list_store) {
272341
if (item.search_name) flattened_model.append(item);
273-
if (item.children) flattenModel(item.children, flattened_model);
342+
if (item.children) {
343+
flattenModel(item.children, flattened_model, [
344+
...path,
345+
path[path.length - 1] + 1,
346+
]);
347+
}
348+
URI_TO_SIDEBAR_PATH[item.uri] = path.slice();
349+
path[path.length - 1]++;
274350
}
275351
return flattened_model;
276352
}
@@ -282,16 +358,13 @@ function createBrowseSelectionModel(root_model, webview) {
282358
false,
283359
(item) => item.children,
284360
);
285-
const sorter = Gtk.TreeListRowSorter.new(
286-
Gtk.CustomSorter.new((a, b) => {
287-
const name1 = a.name;
288-
const name2 = b.name;
289-
return name1.localeCompare(name2);
290-
}),
291-
);
292-
const sort_model = Gtk.SortListModel.new(tree_model, sorter);
293-
const selection_model = Gtk.SingleSelection.new(sort_model);
361+
const selection_model = Gtk.SingleSelection.new(tree_model);
294362
selection_model.connect("selection-changed", () => {
363+
// If selection changed to sync the sidebar, dont load_uri again
364+
if (sync_sidebar) {
365+
sync_sidebar = false;
366+
return;
367+
}
295368
const uri = selection_model.selected_item.item.uri;
296369
webview.load_uri(uri);
297370
});
@@ -345,12 +418,13 @@ function getChildren(index, dir) {
345418
location = subsections[symbol.type_name][symbol.type];
346419
}
347420
if (location)
348-
location.append(
421+
location.insert_sorted(
349422
new DocumentationPage({
350423
name: symbol.name,
351424
search_name: getSearchNameForDocument(symbol, index.meta),
352425
uri: `${dir.get_uri()}/${getLinkForDocument(symbol)}`,
353426
}),
427+
sortFunc,
354428
);
355429
}
356430

@@ -359,33 +433,35 @@ function getChildren(index, dir) {
359433
const sections_model = newListStore();
360434
for (const section in sections) {
361435
if (sections[section].get_n_items() > 0)
362-
sections_model.append(
436+
sections_model.insert_sorted(
363437
new DocumentationPage({
364438
name: SECTION_TYPES[section][0],
365439
uri: `${index_html}${SECTION_TYPES[section][1]}`,
366440
children: sections[section],
367441
}),
442+
sortFunc,
368443
);
369444
}
370445
return sections_model;
371446
}
372447

373448
const REQUIRED = ["class", "interface", "record", "domain"];
374-
375449
function createSubsections(subsections, sections) {
376450
for (const type of REQUIRED) {
377451
for (const item of sections[type]) {
378452
const model = newListStore();
379453
const name = item.name;
380454
for (const subsection in subsections[name]) {
381-
if (subsections[name][subsection].get_n_items() > 0)
382-
model.append(
455+
if (subsections[name][subsection].get_n_items() > 0) {
456+
model.insert_sorted(
383457
new DocumentationPage({
384458
name: SUBSECTION_TYPES[subsection][0],
385459
uri: `${item.uri}${SUBSECTION_TYPES[subsection][1]}`,
386460
children: subsections[name][subsection],
387461
}),
462+
sortFunc,
388463
);
464+
}
389465
}
390466
item.children = model;
391467
}

0 commit comments

Comments
 (0)