@@ -83,7 +129,7 @@ const device = computed(() => {
.sidebarItem {
cursor: pointer;
font-size: 14px;
- padding-left: 8px;
+ padding-inline-start: 8px;
display: flex;
align-items: center;
gap: 0 4px;
@@ -98,6 +144,14 @@ const device = computed(() => {
background: var(--background-strong);
}
+ &:has(.checkbox) {
+ padding-inline-start: 0;
+ }
+
+ .checkbox {
+ margin-inline: 4px;
+ }
+
&.selected {
background: var(--background-weak);
}
diff --git a/client/src/components/sidebar/SidebarItemLink.vue b/client/src/components/sidebar/SidebarItemLink.vue
index 4b2827ae..e2e2fc4b 100644
--- a/client/src/components/sidebar/SidebarItemLink.vue
+++ b/client/src/components/sidebar/SidebarItemLink.vue
@@ -28,8 +28,9 @@ const useTextLink = !config.USE_STRAVA_ICONS;
@click.stop
>
- View on
- Strava
+ View on
+
+ Strava

diff --git a/client/src/components/sidebar/SidebarItemList.vue b/client/src/components/sidebar/SidebarItemList.vue
index 09358d2a..eca6cdd8 100644
--- a/client/src/components/sidebar/SidebarItemList.vue
+++ b/client/src/components/sidebar/SidebarItemList.vue
@@ -13,7 +13,6 @@ const selectionStore = useSelectionStore();
const emit = defineEmits<{
zoomToSelected: [];
focusSidebar: [];
- scrollToSelected: [];
}>();
function forceSelect(): void {
@@ -37,6 +36,7 @@ function click(id: string, e: MouseEvent): void {
:key="item.id"
:item
:selected="selectionStore.selected.has(item.id)"
+ :showCheckbox="selectionStore.multiSelectionMode"
@click="click(item.id, $event)"
@dblclick="forceSelect()"
/>
diff --git a/client/src/components/sidebar/SidebarItemStats.vue b/client/src/components/sidebar/SidebarItemStats.vue
index cad50b8b..08f98632 100644
--- a/client/src/components/sidebar/SidebarItemStats.vue
+++ b/client/src/components/sidebar/SidebarItemStats.vue
@@ -14,6 +14,7 @@ import StatsList from './StatsList.vue';
const { item, additionalStats } = defineProps<{
item: MapItemStats;
additionalStats?: (string | false | undefined | null)[];
+ icons?: string[];
}>();
const distanceString = computed(() => formatKilometers(item.distance));
@@ -52,5 +53,5 @@ const stats = computed(() => {
-
+
diff --git a/client/src/components/sidebar/SidebarSelectionControls.vue b/client/src/components/sidebar/SidebarSelectionControls.vue
new file mode 100644
index 00000000..0386ea91
--- /dev/null
+++ b/client/src/components/sidebar/SidebarSelectionControls.vue
@@ -0,0 +1,88 @@
+
+
+
+
+
+ Multiple selection mode
+
+ Clear
+ Focus
+ Release
+
+
+
+
+
diff --git a/client/src/components/sidebar/StatsList.vue b/client/src/components/sidebar/StatsList.vue
index 2d528825..26511700 100644
--- a/client/src/components/sidebar/StatsList.vue
+++ b/client/src/components/sidebar/StatsList.vue
@@ -1,8 +1,11 @@
- User settings
-
{{ fullName }}
diff --git a/client/src/components/sidebar/controls.module.scss b/client/src/components/sidebar/controls.module.scss
new file mode 100644
index 00000000..90668dba
--- /dev/null
+++ b/client/src/components/sidebar/controls.module.scss
@@ -0,0 +1,67 @@
+.buttons {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: space-evenly;
+ align-items: center;
+}
+
+.segmentedControl:only-child {
+ flex-grow: 1;
+}
+
+.grid {
+ display: grid;
+ grid-template-columns: max-content 1fr;
+
+ &.center {
+ justify-content: center;
+ grid-template-columns: max-content max-content;
+ }
+
+ > :is(label, .label) {
+ grid-template-columns: subgrid;
+ display: grid;
+ grid-column: span 2;
+ align-items: center;
+ }
+
+ > .buttons {
+ grid-column: span 2;
+ justify-content: space-between;
+ }
+}
+
+.row,
+.grid {
+ > :is(label, .label) > span {
+ min-height: 1.2em;
+ font-size: 0.9em;
+ font-weight: 600;
+ padding-inline: 0.5rem;
+ display: block;
+ }
+}
+
+.row {
+ min-width: 0;
+ display: flex;
+ padding-top: 1.2em;
+ row-gap: 1.2em;
+ justify-content: space-evenly;
+
+ > label {
+ display: flex;
+ min-width: 0;
+ margin-top: -1.2em;
+ flex-direction: column;
+ align-items: start;
+
+ &.expand {
+ flex: 1;
+ }
+ }
+}
+
+.hidden {
+ visibility: hidden;
+}
diff --git a/client/src/components/sidebar/help.module.scss b/client/src/components/sidebar/help.module.scss
new file mode 100644
index 00000000..adc27474
--- /dev/null
+++ b/client/src/components/sidebar/help.module.scss
@@ -0,0 +1,21 @@
+.helpModal {
+ width: min(35em, calc(100vw - 4rem));
+ max-height: 90vh;
+
+ dl {
+ > dt {
+ font-weight: 600;
+ }
+
+ > dd {
+ margin-left: 1em;
+ margin-block: 0.5em;
+ }
+ }
+
+ kbd {
+ font-family: inherit;
+ font-weight: bold;
+ white-space: nowrap;
+ }
+}
diff --git a/client/src/components/tooltip/ErrorTooltip.vue b/client/src/components/tooltip/ErrorTooltip.vue
index a63b7016..10b17255 100644
--- a/client/src/components/tooltip/ErrorTooltip.vue
+++ b/client/src/components/tooltip/ErrorTooltip.vue
@@ -54,8 +54,8 @@ function adjustMargins() {
}
watch(
- () => errorMessage,
- (errorMessage) => {
+ [() => errorMessage],
+ ([errorMessage]) => {
if (errorMessage) {
errorPosition.value = calculateTooltipPosition(errorMessage);
diff --git a/client/src/components/ui/UIButton.vue b/client/src/components/ui/UIButton.vue
index f0ed5158..6909a1e8 100644
--- a/client/src/components/ui/UIButton.vue
+++ b/client/src/components/ui/UIButton.vue
@@ -18,6 +18,8 @@ const {
loadingText,
invertColor = false,
disabled = false,
+ ghost = false,
+ light = false,
onClick,
onDblClick,
onRejection,
@@ -27,6 +29,8 @@ const {
invertColor?: boolean;
loadingText?: string;
disabled?: boolean;
+ light?: boolean;
+ ghost?: boolean;
onClick?: () => void | Promise
;
onDblClick?: () => void | Promise;
onRejection?: (value: ButtonError) => string | void;
@@ -47,7 +51,7 @@ async function clickHandler(e: MouseEvent) {
}
runningClickHandler.value = true;
- Promise.resolve(handler?.())
+ Promise.try(() => handler?.())
.catch((error: unknown) => {
if (onRejection) {
onRejection({ error, showError });
@@ -65,7 +69,12 @@ async function clickHandler(e: MouseEvent) {