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
1 change: 1 addition & 0 deletions packages/web-app-office-settings/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"@opencloud-eu/web-pkg": "workspace:*",
"@vueuse/core": "^14.0.0",
"vue-concurrency": "5.0.3",
"vue-router": "5.3.1",
"vue3-gettext": "^4.0.0-beta.1"
}
}
108 changes: 51 additions & 57 deletions packages/web-app-office-settings/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,44 +9,51 @@
<div class="flex justify-between items-center h-12">
<oc-breadcrumb id="admin-settings-breadcrumb" :items="breadcrumbs" />
</div>
<div class="flex">
<h1>{{ $gettext('Manage Fonts') }}</h1>
<div class="mt-2">
<h1 class="text-2xl my-0" v-text="$gettext('Manage Fonts')" />
<p
class="text-sm text-role-on-surface-variant mt-1 mb-0"
v-text="$gettext('Fonts available in the office documents editor.')"
/>
</div>
<div class="flex">
<div class="flex justify-between items-center my-6 gap-4">
<oc-file-input
v-model="files"
file-types=".ttf,.otf"
:multiple="true"
:label="$gettext('Select font')"
class="my-6"
:description-message="$gettext('Allowed file types: .ttf, .otf')"
/>
<oc-search-bar
v-model="filterTerm"
class="w-3xs"
:label="$gettext('Search')"
:placeholder="$gettext('Search for fonts')"
button-hidden
:is-rounded="false"
/>
</div>
</div>
<oc-table-simple>
<oc-table-head>
<oc-table-tr>
<oc-table-th>{{ $gettext('Name') }}</oc-table-th>
<oc-table-th>{{ $gettext('Preview') }}</oc-table-th>
<oc-table-th class="hidden md:table-cell">{{ $gettext('Version') }}</oc-table-th>
<oc-table-th class="hidden md:table-cell">{{ $gettext('Designer') }}</oc-table-th>
</oc-table-tr>
</oc-table-head>
<oc-table-body>
<oc-table-tr v-for="font in fontsData" :key="font.family">
<oc-table-td>{{ font.family }}</oc-table-td>
<oc-table-td>
<img v-if="previewUrls[font.file]" :src="previewUrls[font.file]" alt="" />
</oc-table-td>
<oc-table-td class="hidden md:table-cell">{{ font.version }}</oc-table-td>
<oc-table-td class="hidden md:table-cell">{{ font.designer }}</oc-table-td>
<oc-table-td class="text-right">
<oc-button :aria-label="$gettext('Delete')" @click="deleteFont(font)">
<oc-icon name="delete-bin" fill-type="line" size-class="size-4" />
</oc-button>
</oc-table-td>
</oc-table-tr>
</oc-table-body>
</oc-table-simple>
<no-content-message
v-if="!fontsData?.length"
id="office-settings-fonts-empty"
icon="font-size"
icon-fill-type="none"
>
<template #message>
<span v-text="$gettext('No fonts found')" />
</template>
<template #callToAction>
<span v-text="$gettext('Upload a font and it will show up here')" />
</template>
</no-content-message>
<fonts-list
v-else
:fonts="fontsData"
:preview-urls="previewUrls"
:filter-term="filterTerm"
@delete="deleteFont"
/>
</div>
</div>
</div>
Expand All @@ -59,36 +66,24 @@ import {
useClientService,
AppLoadingSpinner,
useMessages,
useIsTopBarSticky
useIsTopBarSticky,
NoContentMessage
} from '@opencloud-eu/web-pkg'
import { useAsyncState } from '@vueuse/core'
import { computed, onBeforeUnmount, ref, unref, watch } from 'vue'
import { useTask } from 'vue-concurrency'
import { useGettext } from 'vue3-gettext'
import { BreadcrumbItem } from '@opencloud-eu/design-system/helpers'

interface Font {
file: string
family: string
copyright: string
version: string
trademark: string
manufacturer: string
designer: string
description: string
vendor_url: string
designer_url: string
license: string
license_url: string
uri: string
}
import FontsList from './components/FontsList.vue'
import { Font } from './types'

const { $gettext } = useGettext()
const { showErrorMessage } = useMessages()
const { isSticky } = useIsTopBarSticky()
const breadcrumbs = computed<BreadcrumbItem[]>(() => [
{
text: $gettext('Office')
text: $gettext('Office'),
to: { path: '/admin-settings/office' }
}
])

Expand Down Expand Up @@ -153,8 +148,14 @@ watch(fontsData, (fonts) => {

onBeforeUnmount(revokePreviewUrls)

const files = ref<FileList>()
const filterTerm = ref('')

const files = ref<FileList>(null)
watch(files, async (newFiles) => {
if (!newFiles?.length) {
return
}

await Promise.all(
Array.from(newFiles).map(async (file) => {
const formData = new FormData()
Expand All @@ -172,6 +173,8 @@ watch(files, async (newFiles) => {
await refreshFonts()
})
)

files.value = null
})

const deleteFont = async (font: Font) => {
Expand All @@ -187,12 +190,3 @@ const deleteFont = async (font: Font) => {
await refreshFonts()
}
</script>

<style scoped>
@reference '@opencloud-eu/design-system/tailwind';

th,
td {
@apply p-4;
}
</style>
138 changes: 138 additions & 0 deletions packages/web-app-office-settings/src/components/FontsList.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
<template>
<no-content-message
v-if="!filteredFonts.length"
id="office-settings-fonts-empty-filtered"
icon="font-size"
icon-fill-type="none"
>
<template #message>
<span v-text="$gettext('No fonts found')" />
</template>
<template #callToAction>
<span v-text="$gettext('Try refining the search term to get results')" />
</template>
</no-content-message>
<oc-table
v-else
data-testid="fonts-table"
id-key="file"
:sort-by="sortBy"
:sort-dir="sortDir"
:fields="fields"
:data="items"
:hover="true"
class="fonts-table"
@sort="handleSort"
>
<template #family="{ item }">
<oc-filter-highlight :text="item.family" :term="filterTerm" />
</template>
<template #preview="{ item }">
<img v-if="previewUrls[item.file]" :src="previewUrls[item.file]" alt="" class="max-h-8" />
</template>
<template #version="{ item }">
<span v-text="item.version || '—'" />
</template>
<template #designer="{ item }">
<span v-text="item.designer || '—'" />
</template>
<template #actions="{ item }">
<oc-button
v-oc-tooltip="$gettext('Delete')"
:aria-label="$gettext('Delete')"
appearance="raw"
@click="emit('delete', item)"
>
<oc-icon name="delete-bin" fill-type="line" size-class="size-4" />
</oc-button>
</template>
</oc-table>
</template>

<script setup lang="ts">
import { NoContentMessage, SortField, useSort } from '@opencloud-eu/web-pkg'
import { SortDir } from '@opencloud-eu/design-system/helpers'
import { OcFilterHighlight } from '@opencloud-eu/design-system/components'
import { computed } from 'vue'
import { useGettext } from 'vue3-gettext'
import { Font } from '../types'

const {
fonts,
previewUrls,
filterTerm = ''
} = defineProps<{
fonts: Font[]
previewUrls: Record<string, string>
filterTerm?: string
}>()

const emit = defineEmits<{
(e: 'delete', font: Font): void
}>()

const { $gettext } = useGettext()

const filteredFonts = computed(() => {
const term = filterTerm.toLowerCase()
if (!term) {
return fonts
}

return fonts.filter((font) =>
String(font.family || '')
.toLowerCase()
.includes(term)
)
})

const sortFields: SortField[] = [{ name: 'family', sortable: true, sortDir: SortDir.Asc }]
const { sortBy, sortDir, items, handleSort } = useSort<Font>({
items: filteredFonts,
fields: sortFields
})

const fields = computed(() => [
{
name: 'family',
title: $gettext('Name'),
type: 'slot',
sortable: true,
thClass: 'pl-4',
tdClass: 'pl-4'
},
{
name: 'preview',
title: $gettext('Preview'),
type: 'slot',
width: 'expand' as const
},
{
name: 'version',
title: $gettext('Version'),
type: 'slot',
width: 'shrink' as const,
wrap: 'nowrap' as const,
thClass: 'hidden md:table-cell',
tdClass: 'hidden md:table-cell'
},
{
name: 'designer',
title: $gettext('Designer'),
type: 'slot',
width: 'shrink' as const,
wrap: 'nowrap' as const,
thClass: 'hidden md:table-cell',
tdClass: 'hidden md:table-cell'
},
{
name: 'actions',
title: $gettext('Actions'),
type: 'slot',
alignH: 'right' as const,
width: 'shrink' as const,
thClass: 'pr-4',
tdClass: 'pr-4'
}
])
</script>
15 changes: 15 additions & 0 deletions packages/web-app-office-settings/src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export interface Font {
file: string
family: string
copyright: string
version: string
trademark: string
manufacturer: string
designer: string
description: string
vendor_url: string
designer_url: string
license: string
license_url: string
uri: string
}
19 changes: 19 additions & 0 deletions packages/web-app-office-settings/tests/unit/App.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ type AppVm = {
previewUrls: Record<string, string>
loadPreviewsTask: { perform: (fonts: Font[]) => Promise<void>; last: Promise<unknown> }
deleteFont: (font: Font) => Promise<void>
files: FileList | null
}

const font = (file: string): Font => ({ file, family: file.replace(/\.[^.]+$/, '') })
Expand Down Expand Up @@ -92,6 +93,24 @@ describe('App', () => {
expect(URL.revokeObjectURL).toHaveBeenCalledWith('blob:2')
})

it('uploads the selected fonts and clears the input afterwards', async () => {
const { vm, clientService } = getWrapper([])
const file = new File([''], 'new.ttf')

vm.files = [file] as unknown as FileList
await flushPromises()
// the font list refresh is delayed by the anti-flicker timer
await vi.advanceTimersByTimeAsync(500)
await flushPromises()

expect(clientService.httpAuthenticated.post).toHaveBeenCalledTimes(1)
expect(clientService.httpAuthenticated.post).toHaveBeenCalledWith(
'/collaboration/fonts/manage/',
expect.any(FormData)
)
expect(vm.files).toBeNull()
})

it('encodes the file name when deleting a font', async () => {
const { vm, clientService } = getWrapper([])

Expand Down
Loading
Loading