Skip to content

Commit

Permalink
feat: add basic client side search to app listing
Browse files Browse the repository at this point in the history
  • Loading branch information
kulmann committed Aug 1, 2024
1 parent 762d561 commit 8a49fe3
Show file tree
Hide file tree
Showing 7 changed files with 105 additions and 12 deletions.
5 changes: 5 additions & 0 deletions packages/web-app-app-store/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,18 @@
"description": "ownCloud app store",
"license": "AGPL-3.0",
"devDependencies": {
"@types/mark.js": "8.11.12",
"web-test-helpers": "workspace:*"
},
"dependencies": {
"mark.js": "^8.11.1"
},
"peerDependencies": {
"@ownclouders/web-client": "workspace:*",
"@ownclouders/web-pkg": "workspace:*",
"axios": "1.6.8",
"design-system": "workspace:@ownclouders/design-system@*",
"fuse.js": "6.6.2",
"lodash-es": "4.17.21",
"pinia": "2.1.7",
"vue-concurrency": "5.0.1",
Expand Down
37 changes: 33 additions & 4 deletions packages/web-app-app-store/src/components/AppTile.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,25 @@
</div>
<div class="app-tile-body oc-card-body oc-p">
<div class="app-content">
<h3 class="oc-my-s">{{ app.name }}</h3>
<p class="oc-my-s">{{ app.subtitle }}</p>
<div class="oc-flex oc-flex-middle">
<h3 class="oc-my-s oc-text-truncate mark-element">{{ app.name }}</h3>
<span class="oc-ml-s oc-text-muted oc-text-small">
v{{ app.mostRecentVersion.version }}
</span>
</div>
<p class="oc-my-s mark-element">{{ app.subtitle }}</p>
</div>
<div class="app-tags">
<oc-tag
v-for="tag in app.tags"
:key="`app-tag-${app.id}-${tag}`"
size="small"
class="oc-text-nowrap mark-element"
type="button"
@click="emitSearchTerm(tag)"
>
{{ tag }}
</oc-tag>
</div>
<oc-list class="app-actions">
<action-menu-item
Expand Down Expand Up @@ -36,14 +53,21 @@ export default defineComponent({
required: true
}
},
setup() {
emits: ['search'],
setup(props, { emit }) {
const { downloadAppAction } = useAppActionsDownload()
const actions = computed(() => {
return [downloadAppAction]
})
const emitSearchTerm = (term: string) => {
emit('search', term)
}
return {
actions
actions,
emitSearchTerm
}
}
})
Expand Down Expand Up @@ -73,6 +97,11 @@ export default defineComponent({
justify-content: space-between;
height: 100%;
.app-tags {
display: flex;
gap: 0.5rem;
}
.app-actions {
display: flex;
justify-content: flex-start;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export const useAppActionsDownload = () => {
return $gettext('Download')
},
handler: (options?) => {
const version = options.app.versions[0]
const version = options.app.mostRecentVersion
const filename = version.filename || version.url.split('/').pop()
triggerDownloadWithFilename(version.url, filename)
},
Expand Down
3 changes: 2 additions & 1 deletion packages/web-app-app-store/src/piniaStores/apps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ export const useAppsStore = defineStore(`${APPID}-apps`, () => {
return appsList.apps.map((app) => {
return {
...app,
repository: repo
repository: repo,
mostRecentVersion: app.versions[0]
}
})
} catch (e) {
Expand Down
3 changes: 2 additions & 1 deletion packages/web-app-app-store/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ export const RawAppSchema = z.object({
})

export const AppSchema = RawAppSchema.extend({
repository: AppStoreRepositorySchema
repository: AppStoreRepositorySchema,
mostRecentVersion: AppVersionSchema
})
export type App = z.infer<typeof AppSchema>

Expand Down
58 changes: 53 additions & 5 deletions packages/web-app-app-store/src/views/AppList.vue
Original file line number Diff line number Diff line change
@@ -1,22 +1,36 @@
<template>
<div class="app-list oc-px">
<h2 v-text="$gettext('App Store')" />
<div class="oc-flex oc-flex-middle">
<oc-text-input
id="apps-filter"
v-model.trim="filterTerm"
:label="$gettext('Search')"
:clear-button-enabled="true"
autocomplete="off"
/>
</div>
<oc-list class="app-tiles">
<app-tile
v-for="app in sortedApps"
v-for="app in filteredApps"
:key="`app-${app.repository.name}-${app.id}`"
:app="app"
class="oc-my-m"
@search="setFilterTerm"
/>
</oc-list>
</div>
</template>

<script lang="ts">
import { computed, defineComponent, unref } from 'vue'
import { computed, defineComponent, nextTick, onMounted, ref, unref, watch } from 'vue'
import Mark from 'mark.js'
import Fuse from 'fuse.js'
import { useAppsStore } from '../piniaStores'
import AppTile from '../components/AppTile.vue'
import { storeToRefs } from 'pinia'
import { App } from '../types'
import { defaultFuseOptions } from '@ownclouders/web-pkg'
export default defineComponent({
name: 'AppList',
Expand All @@ -25,12 +39,46 @@ export default defineComponent({
const appsStore = useAppsStore()
const { apps } = storeToRefs(appsStore)
const sortedApps = computed(() => {
return unref(apps).sort((a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase()))
const filterTerm = ref('')
const setFilterTerm = (term: string) => {
filterTerm.value = term.trim()
}
const filter = (apps: App[], filterTerm: string) => {
if (!(filterTerm || '').trim()) {
return apps
}
const searchEngine = new Fuse(apps, {
...defaultFuseOptions,
keys: ['name', 'subtitle', 'tags']
})
return searchEngine.search(filterTerm).map((r) => r.item)
}
const filteredApps = computed(() => {
// TODO: debounce the filtering by 100-300ms
return filter(unref(apps), unref(filterTerm)).sort((a, b) =>
a.name.toLowerCase().localeCompare(b.name.toLowerCase())
)
})
const markInstance = ref(null)
onMounted(async () => {
await nextTick()
markInstance.value = new Mark('.mark-element')
})
watch(filterTerm, () => {
unref(markInstance)?.unmark()
if (unref(filterTerm)) {
unref(markInstance)?.mark(unref(filterTerm), {
element: 'span',
className: 'mark-highlight'
})
}
})
return {
sortedApps
filteredApps,
filterTerm,
setFilterTerm
}
}
})
Expand Down
9 changes: 9 additions & 0 deletions pnpm-lock.yaml

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

0 comments on commit 8a49fe3

Please sign in to comment.