Skip to content
Open
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
65 changes: 62 additions & 3 deletions src/PhotosApp.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,19 @@
<NcContent appName="photos">
<NcAppNavigation :aria-label="t('photos', 'Photos')">
<template v-if="isTimelineView" #search>
<NcTextField
class="app-navigation__search"
:label="t('photos', 'Search photos by filename')"
:modelValue="searchTerm"
trailingButtonIcon="close"
:showTrailingButton="searchTerm.length > 0"
:trailingButtonLabel="t('photos', 'Clear search')"
@update:modelValue="onSearchInput"
@trailingButtonClick="clearSearch">
<template #icon>
<Magnify :size="16" />
</template>
</NcTextField>
<PhotosFiltersInput
:selectedFilters="selectedFilters"
@selectFilter="selectFilter" />
Expand Down Expand Up @@ -99,6 +112,14 @@
<CalendarTodayOutline v-else :size="20" />
</template>
</NcAppNavigationItem>
<NcAppNavigationItem
:to="{ name: 'memories' }"
:name="t('photos', 'Memories')"
data-id-app-nav-item="memories">
<template #icon>
<TimelapseIcon :size="20" />
</template>
</NcAppNavigationItem>
<NcAppNavigationItem
:to="{ name: 'shared' }"
:name="t('photos', 'Shared with you')"
Expand Down Expand Up @@ -128,10 +149,9 @@
</template>
</NcAppNavigationItem>
<NcAppNavigationItem
v-if="showLocationMenuEntry"
:to="{ name: 'maps' }"
:to="{ name: 'map' }"
:name="t('photos', 'Map')"
data-id-app-nav-item="maps">
data-id-app-nav-item="map">
<template #icon="{ active }">
<MapIcon v-if="active" :size="20" />
<MapOutline v-else :size="20" />
Expand Down Expand Up @@ -183,6 +203,7 @@ import NcAppNavigation from '@nextcloud/vue/components/NcAppNavigation'
import NcAppNavigationItem from '@nextcloud/vue/components/NcAppNavigationItem'
import NcButton from '@nextcloud/vue/components/NcButton'
import NcContent from '@nextcloud/vue/components/NcContent'
import NcTextField from '@nextcloud/vue/components/NcTextField'
import AccountBoxMultiple from 'vue-material-design-icons/AccountBoxMultiple.vue'
import AccountBoxMultipleOutline from 'vue-material-design-icons/AccountBoxMultipleOutline.vue'
import AccountGroup from 'vue-material-design-icons/AccountGroup.vue'
Expand All @@ -198,6 +219,7 @@ import ImageIcon from 'vue-material-design-icons/Image.vue'
import ImageMultiple from 'vue-material-design-icons/ImageMultiple.vue'
import ImageMultipleOutline from 'vue-material-design-icons/ImageMultipleOutline.vue'
import ImageOutline from 'vue-material-design-icons/ImageOutline.vue'
import Magnify from 'vue-material-design-icons/Magnify.vue'
import MapIcon from 'vue-material-design-icons/Map.vue'
import MapMarker from 'vue-material-design-icons/MapMarker.vue'
import MapMarkerOutline from 'vue-material-design-icons/MapMarkerOutline.vue'
Expand All @@ -208,6 +230,7 @@ import Star from 'vue-material-design-icons/Star.vue'
import StarOutline from 'vue-material-design-icons/StarOutline.vue'
import Tag from 'vue-material-design-icons/Tag.vue'
import TagOutline from 'vue-material-design-icons/TagOutline.vue'
import TimelapseIcon from 'vue-material-design-icons/Timelapse.vue'
import VideoIcon from 'vue-material-design-icons/Video.vue'
import VideoOutline from 'vue-material-design-icons/VideoOutline.vue'
import PhotosFiltersDisplay from './components/PhotosFilters/PhotosFiltersDisplay.vue'
Expand Down Expand Up @@ -247,6 +270,7 @@ export default {
StarOutline,
Tag,
TagOutline,
TimelapseIcon,
VideoIcon,
VideoOutline,
MapIcon,
Expand All @@ -258,6 +282,8 @@ export default {
NcAppNavigationItem,
NcButton,
NcContent,
NcTextField,
Magnify,
SettingsDialog,
PhotosFiltersInput,
PhotosFiltersDisplay,
Expand Down Expand Up @@ -288,6 +314,9 @@ export default {
: (getCurrentUser().isAdmin && loadState('photos', 'showPeopleMenuEntry', true) && isAppStoreEnabled) || isRecognizeInstalled,

openedSettings: false,

searchTerm: '',
searchDebounceHandle: null as ReturnType<typeof setTimeout> | null,
}
},

Expand Down Expand Up @@ -346,6 +375,31 @@ export default {
}
},

// Debounce typing into the timeline search field so we don't
// fire off a DAV REPORT on every keystroke. The user gets ~300ms
// of pause before the server-side filter is updated.
onSearchInput(value: string) {
this.searchTerm = value

if (this.searchDebounceHandle !== null) {
clearTimeout(this.searchDebounceHandle)
}

this.searchDebounceHandle = setTimeout(() => {
this.applySearchTerm(value)
}, 300)
},

applySearchTerm(value: string) {
const trimmed = value.trim()
this.selectedFilters.name = trimmed.length === 0 ? [] : [trimmed]
},

clearSearch() {
this.searchTerm = ''
this.applySearchTerm('')
},

t,
},
}
Expand All @@ -366,4 +420,9 @@ export default {
.app-navigation__footer {
padding: calc(var(--default-grid-baseline) * 2);
}

.app-navigation__search {
margin: calc(var(--default-grid-baseline) * 2);
margin-bottom: calc(var(--default-grid-baseline));
}
</style>
46 changes: 45 additions & 1 deletion src/components/FileComponent.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@
class="file"
:href="file.source"
:aria-label="ariaLabel"
@click.stop.prevent="emitClick">
@click.stop.prevent="onTileClick"
@pointerdown="onTilePointerDown"
@pointerup="onTilePointerUp"
@pointercancel="cancelLongPress"
@pointerleave="cancelLongPress">

<!-- image and loading placeholder -->
<div class="file__images">
Expand Down Expand Up @@ -127,6 +131,12 @@ export default {
loadedLarge: false,
errorLarge: false,
isMobile: useIsMobile(),
// Long-press detection: a press held for >500ms starts a
// selection instead of opening the viewer. The handle is
// kept on `this` (not in data so we don't bother with
// reactivity) and cleared on cancel / release.
longPressTimer: null as ReturnType<typeof setTimeout> | null,
longPressFired: false,
}
},

Expand Down Expand Up @@ -185,6 +195,7 @@ export default {
if (this.$refs.imgLarge !== undefined) {
(this.$refs.imgLarge as HTMLImageElement).src = ''
}
this.cancelLongPress()
},

methods: {
Expand All @@ -205,6 +216,39 @@ export default {
this.$emit('click', this.file.fileid)
},

// Tap = open viewer. Long-press (>500ms) = toggle selection.
// We swallow the click that follows a long-press so a release
// after the timeout doesn't also trigger the open.
onTileClick() {
if (this.longPressFired) {
this.longPressFired = false
return
}
this.emitClick()
},

onTilePointerDown() {
if (!this.allowSelection) {
return
}
this.cancelLongPress()
this.longPressTimer = setTimeout(() => {
this.longPressFired = true
this.onToggle(!this.selected)
}, 500)
},

onTilePointerUp() {
this.cancelLongPress()
},

cancelLongPress() {
if (this.longPressTimer !== null) {
clearTimeout(this.longPressTimer)
this.longPressTimer = null
}
},

onLoadSmall() {
this.loadedSmall = true
},
Expand Down
Loading
Loading