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
8 changes: 4 additions & 4 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"url": "git+https://github.com/opencor/webapp.git"
},
"type": "module",
"version": "1.20260811.0",
"version": "1.20260813.0",
"engines": {
"bun": ">=1.2.0"
},
Expand Down Expand Up @@ -79,7 +79,7 @@
"@wasm-fmt/clang-format": "^22.1.8",
"autoprefixer": "^10.5.4",
"cmake-js": "^8.0.0",
"electron": "^43.3.0",
"electron": "^43.4.0",
"electron-builder": "^26.15.3",
"electron-conf": "^1.3.0",
"electron-updater": "^6.8.9",
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
},
"./style.css": "./dist/opencor.css"
},
"version": "1.20260811.0",
"version": "1.20260813.0",
"libopencorVersion": "1.20260803.0",
"scripts": {
"build": "vite build && bun scripts/generate.version.ts",
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/src/common/viewRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export const useViewRegistry = vueusecore.createGlobalState(() => {
// Note: the user's chosen view is always returned, whether or not it is applicable to the file (in which case it is
// up to the caller to indicate that the view is not supported for the given file type).

function resolve(activeViewId: string | undefined): IViewDescriptor | null {
function resolve(activeViewId: string): IViewDescriptor | null {
return (
_descriptors.find((descriptor) => {
return descriptor.id === activeViewId;
Expand Down
91 changes: 88 additions & 3 deletions src/renderer/src/components/ViewSwitcherComponent.vue
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
<template>
<nav class="w-14 h-full shrink-0 flex flex-col overflow-y-auto py-1"
:style="{ borderRight: '1px solid var(--p-content-border-color)' }"
@wheel.prevent="onWheel"
>
<template v-for="([category, views], categoryIndex) in categoriesWithViews" :key="category.id">
<div v-if="categoryIndex > 0" class="mx-2 my-1 border-t"
style="border-color: var(--p-content-border-color);"
/>
<button v-for="view in views" :key="view.id"
type="button"
:ref="(element) => setViewButtonRef(view.id, element)"
:class="[
'view-icon flex items-center justify-center mx-1 my-0.5 rounded-r-md transition-colors duration-150',
{ 'view-icon-active': view.id === activeViewId }
Expand All @@ -32,16 +34,18 @@ import {
type ViewCategory as ViewCategoryType
} from '../common/viewRegistry';

defineProps<{
activeViewId?: string;
const props = defineProps<{
activeViewId: string;
}>();

defineEmits<{
const emit = defineEmits<{
selectView: [viewId: string];
}>();

const viewRegistry = useViewRegistry();

// Compute the list of view categories that have at least one view, along with their corresponding views.

const categoriesWithViews = vue.computed<Array<[ViewCategoryType, IViewDescriptor[]]>>(() => {
const res: Array<[ViewCategoryType, IViewDescriptor[]]> = [];

Expand All @@ -55,6 +59,87 @@ const categoriesWithViews = vue.computed<Array<[ViewCategoryType, IViewDescripto

return res;
});

// Compute the list of view IDs in the order they are displayed in the view switcher.

const viewIds = vue.computed<string[]>(() => {
const res: string[] = [];

for (const [, views] of categoriesWithViews.value) {
for (const view of views) {
res.push(view.id);
}
}

return res;
});

// Keep a map of view IDs to their corresponding button elements, so we can scroll the active view's button into view
// when the active view changes.

const viewButtonRefs = new Map<string, HTMLElement>();

const setViewButtonRef = (viewId: string, element: unknown): void => {
if (element instanceof HTMLElement) {
viewButtonRefs.set(viewId, element);
} else {
viewButtonRefs.delete(viewId);
}
};

// Switch views with the mouse wheel.
// Note: we don't wrap around, i.e. we don't go from the last view to the first view, or vice versa. Also, we damp the
// wheel delta to avoid switching views too quickly when the user scrolls fast.

const wheelDelta = vue.ref(0);
const wheelThreshold = 321;

const selectViewByOffset = (offset: number): void => {
const activeViewIndex = viewIds.value.indexOf(props.activeViewId);

if (activeViewIndex === -1) {
return;
}

const newViewIndex = Math.min(Math.max(activeViewIndex + offset, 0), viewIds.value.length - 1);

if (newViewIndex !== activeViewIndex) {
emit('selectView', viewIds.value[newViewIndex]);
}
};

const onWheel = (event: WheelEvent): void => {
if (!viewIds.value.length) {
return;
}

wheelDelta.value += event.deltaY;

while (wheelDelta.value >= wheelThreshold) {
wheelDelta.value -= wheelThreshold;

selectViewByOffset(1);
}

while (wheelDelta.value <= -wheelThreshold) {
wheelDelta.value += wheelThreshold;

selectViewByOffset(-1);
}
};

// Scroll the active view's button into view when the active view changes.

vue.watch(
() => props.activeViewId,
(viewId) => {
viewButtonRefs.get(viewId)?.scrollIntoView({
block: 'nearest',
inline: 'nearest'
});
},
{ immediate: true, flush: 'post' }
);
</script>

<style scoped>
Expand Down
Loading