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
21 changes: 3 additions & 18 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
<script>
import { mapActions, mapState } from 'vuex'
import { notificationState, hideNotification } from '@/util/notificationStore.js'
import { resetPageScroll, resetPageScrollDeferred } from '@/util/scrollUtils.js'
import QRCodeDialog from './components/ui/QRCodeDialog.vue'
import ProfileDropdown from './components/ui/ProfileDropDown.vue';

Expand Down Expand Up @@ -85,26 +86,10 @@ export default {
this.logout()
},
resetMainScroll () {
window.scrollTo(0, 0)
document.documentElement.scrollTop = 0
document.body.scrollTop = 0

const vMain = this.$el && this.$el.querySelector('.v-main')
if (vMain) {
vMain.scrollTop = 0
}

resetPageScroll()
// Mobile Safari can apply scroll restoration after the route render.
// Repeat reset on next frames so the new page always starts below navbar.
this.$nextTick(() => {
window.requestAnimationFrame(() => {
window.scrollTo(0, 0)
document.documentElement.scrollTop = 0
document.body.scrollTop = 0
const nextVMain = this.$el && this.$el.querySelector('.v-main')
if (nextVMain) nextVMain.scrollTop = 0
})
})
resetPageScrollDeferred(this)
},
onNotificationAction () {
if (notificationState.actionOnClick) {
Expand Down
14 changes: 10 additions & 4 deletions src/assets/scss/main.scss
Original file line number Diff line number Diff line change
Expand Up @@ -533,11 +533,17 @@ body,
========================================================================== */

.compact-rename-dialog {
/* Single card only: no extra background on the dialog wrapper */
background: transparent !important;
box-shadow: none !important;

/* Card fills wrapper so the outer div is never visible */
.v-card {
width: fit-content;
min-width: min(320px, calc(100vw - 48px));
max-width: min(420px, calc(100vw - 48px));
margin: 0 auto;
width: 100%;
min-width: 100%;
max-width: 100%;
min-height: 100%;
margin: 0;
}
}

Expand Down
21 changes: 13 additions & 8 deletions src/components/pages/Calibration.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,19 @@
column
:step="2">

<template v-slot:left>
<v-btn text @click="$router.push(`/${session.id}/connect-devices`)">
<v-icon left>mdi-arrow-left</v-icon>
{{ backLabel }}
</v-btn>
</template>

<template v-slot:right>
<v-btn class="calibration-nav-btn" :disabled="busy" @click="onNext">
Calibrate
</v-btn>
</template>

<v-card v-if="!$vuetify.breakpoint.smAndDown" class="step-2-1">
<v-card-text class="d-flex align-center">
<p style="margin-bottom: 0">{{ n_videos_uploaded }} of {{ n_cameras_connected }} videos uploaded.</p>
Expand Down Expand Up @@ -98,14 +111,6 @@
</v-card-text>
</v-card>

<div class="navigation page-navigation d-flex justify-space-between align-center w-100 flex-nowrap">
<v-btn text @click="$router.push(`/${session.id}/connect-devices`)">
<v-icon left>mdi-arrow-left</v-icon>
{{ backLabel }}
</v-btn>
<v-btn class="calibration-nav-btn" :disabled="busy" @click="onNext">Calibrate</v-btn>
</div>

</MainLayout>
</template>

Expand Down
7 changes: 7 additions & 0 deletions src/components/pages/ConnectDevices.vue
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@
import { mapMutations, mapActions, mapState } from 'vuex'
import { apiInfo, clearToastMessages} from "@/util/ErrorMessage.js";
import { getSessionDeepLink } from '@/util/SessionDeepLink.js'
import { resetPageScroll, resetPageScrollDeferred } from '@/util/scrollUtils.js'
import MainLayout from '@/layout/MainLayout'

export default {
Expand All @@ -104,6 +105,10 @@ export default {
}
},
async mounted () {
// Ensure content starts below navbar (fixes content hidden behind navbar on navigation).
resetPageScroll()
this.$nextTick(() => resetPageScrollDeferred(this))

if (!localStorage.getItem('iosAppNotificationShown')) {
apiInfo("The new iOS app (2.0) is available on the App Store.", 20000, {text : "Go to App Store", onClick : () => {window.open("https://apps.apple.com/us/app/opencap/id1630513242", "_blank");}, position: 'top-center'});
localStorage.setItem('iosAppNotificationShown', 'true');
Expand All @@ -118,6 +123,8 @@ export default {
this.loading = false
}
}
// Reset scroll again after async content loads (spinner -> QR code) to fix layout-shift scroll.
this.$nextTick(() => resetPageScrollDeferred(this))
},
computed: {
...mapState({
Expand Down
92 changes: 84 additions & 8 deletions src/components/pages/Dashboard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -106,14 +106,60 @@
@change="onYQuantitySelected"
/>

<v-btn block class="mt-4" @click="onChartDownload">
<v-icon left small>mdi-download</v-icon>
Download Chart
</v-btn>
<v-btn block class="mt-2" @click="onChartDownloadWhite">
<v-icon left small>mdi-download</v-icon>
Download Chart (White)
</v-btn>
<v-dialog
v-model="downloadDialog"
max-width="400"
content-class="app-dialog"
>
<template v-slot:activator="{ on, attrs }">
<v-btn
ref="downloadDialogActivator"
block
class="mt-4"
v-bind="attrs"
v-on="on"
>
<v-icon left small>mdi-download</v-icon>
Download Chart
</v-btn>
</template>

<v-card>
<v-card-title class="text-h6">
Choose background
</v-card-title>
<v-card-text>
<v-radio-group v-model="downloadBackground">
<v-radio label="Black background" value="black" />
<v-radio label="White background" value="white" />
</v-radio-group>
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn text @click="downloadDialog = false">
Cancel
</v-btn>
<v-btn color="primary" text @click="onConfirmDownload">
Download
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>

<div class="left d-flex flex-column pa-2" v-if="loggedIn">
<v-btn class="w-100 mt-4 sidebar-action-btn" :to="{ name: 'SelectSession' }">
<v-icon left small>mdi-arrow-left</v-icon>
Back to session list
</v-btn>

<v-btn
v-show="!showEmptyStateMessage"
class="w-100 mt-4 sidebar-action-btn"
@click="$router.push({ name: 'Session', params: { id: current_session_id } })">
<v-icon left small>mdi-play-circle-outline</v-icon>
Go to Visualizer
</v-btn>
</div>
</v-card-text>
</v-card>

Expand Down Expand Up @@ -306,6 +352,9 @@ export default {
{ text: 'Yellow-Blue', value: ['yellow', 'blue'] }
],

downloadDialog: false,
downloadBackground: 'black',

chartData: {
datasets: []
},
Expand Down Expand Up @@ -416,6 +465,18 @@ export default {
isMobile (val) {
this.chartOptions.plugins.legend.display = !val
this.chartOptions.plugins.title.font.size = val ? 18 : 28
},

downloadDialog (isOpen) {
if (!isOpen) {
this.$nextTick(() => {
const activator = this.$refs.downloadDialogActivator
const el = activator && (activator.$el || activator)
if (el && typeof el.blur === 'function') {
el.blur()
}
})
}
}
},

Expand Down Expand Up @@ -626,6 +687,9 @@ export default {

onChartDownload () {
const canvas = document.querySelector('#chart canvas')
if (!canvas) {
return
}
const link = document.createElement('a')
link.download = 'chart.png'
link.href = canvas.toDataURL('image/png')
Expand Down Expand Up @@ -719,6 +783,18 @@ export default {
return clone
},

onConfirmDownload () {
const choice = this.downloadBackground
this.downloadDialog = false
this.$nextTick(async () => {
if (choice === 'white') {
await this.onChartDownloadWhite()
} else {
this.onChartDownload()
}
})
},

async fetchResultText (url) {
if (url.startsWith(axios.defaults.baseURL)) {
const res = await axios.get(url)
Expand Down
17 changes: 13 additions & 4 deletions src/components/pages/RecycleBin.vue
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
<template>
<div class="recycle-bin d-flex flex-column">
<h1 class="page-title">Recycle Bin</h1>
<div class="d-flex flex-wrap align-center recycle-toolbar">
<h1 class="page-title recycle-toolbar-layer">Recycle Bin</h1>
<div class="d-flex flex-wrap align-center recycle-toolbar recycle-toolbar-layer">
<v-btn
class="recycle-toolbar-button"
text
@click="$router.push({ name: 'SelectSession' })">
@click.prevent="$router.push({ name: 'SelectSession' })">
<v-icon left>mdi-arrow-left</v-icon>
Back to Sessions
</v-btn>
<v-btn
color="grey darken-4"
dark
class="recycle-toolbar-button"
@click="empty_bin_dialog = true">
@click.prevent="empty_bin_dialog = true">
<v-icon left>mdi-delete-sweep</v-icon>
Empty Recycle Bin
</v-btn>
Expand Down Expand Up @@ -615,9 +615,16 @@ export default {
}
}

.recycle-toolbar-layer {
position: relative;
z-index: 2;
isolation: isolate;
}

.recycle-toolbar {
padding: 10px 8px 14px;
gap: 8px;
touch-action: manipulation;

@media (max-width: 599px) {
padding: 8px 6px 10px;
Expand All @@ -641,6 +648,8 @@ export default {
}

.recycle-content {
position: relative;
z-index: 1;
flex: 1 1 auto;
min-height: 0;
overflow-y: auto;
Expand Down
24 changes: 17 additions & 7 deletions src/components/pages/SelectSession.vue
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
<template>
<div class="select-session d-flex flex-column">
<h1 class="page-title">Sessions</h1>
<div class="d-flex flex-wrap align-center toolbar-container">
<h1 class="page-title sessions-toolbar-layer">Sessions</h1>
<div class="d-flex flex-wrap align-center toolbar-container sessions-toolbar-layer">
<v-btn
color="grey darken-4"
dark
@click="$router.push({ name: 'RecordingMode' })"
@click.prevent="$router.push({ name: 'RecordingMode' })"
class="toolbar-button">
<v-icon left>mdi-plus</v-icon>
New session
Expand Down Expand Up @@ -36,7 +36,7 @@
color="grey darken-4"
dark
class="toolbar-button"
@click="$router.push({ name: 'Subjects' })">
@click.prevent="$router.push({ name: 'Subjects' })">
<v-icon left>mdi-account-group-outline</v-icon>
Subjects
</v-btn>
Expand All @@ -45,7 +45,7 @@
color="grey darken-4"
dark
class="toolbar-button"
@click="$router.push({ name: 'RecycleBin' })">
@click.prevent="$router.push({ name: 'RecycleBin' })">
<v-icon left>mdi-delete-outline</v-icon>
Recycle Bin
</v-btn>
Expand All @@ -70,7 +70,7 @@
color="grey darken-4"
dark
class="submit-btn"
@click="onClearSearch()">
@click.prevent="onClearSearch()">
Clear
</v-btn>
</div>
Expand Down Expand Up @@ -804,11 +804,20 @@ export default {
}
}

// Toolbar and title sit above the table in stacking order so taps on "New session"
// (and other toolbar buttons) are never delivered to the table header on touch devices.
.sessions-toolbar-layer {
position: relative;
z-index: 2;
isolation: isolate;
}

.toolbar-container {
padding: 10px 8px 14px;
gap: 8px;
justify-content: flex-start;

touch-action: manipulation;

@media (max-width: 599px) {
padding: 8px 6px 10px;
gap: 6px;
Expand Down Expand Up @@ -857,6 +866,7 @@ export default {

.sessions-table-wrapper {
position: relative;
z-index: 1;
margin: 0 8px 16px 8px;
overflow: hidden;
display: flex;
Expand Down
Loading