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
27 changes: 0 additions & 27 deletions packages/web-pkg/src/apps/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,29 +24,8 @@ export interface AppNavigationItem {
priority?: number
}

/**
* ApplicationQuickAction describes an application action that is used in the runtime.
*
* @deprecated Quick actions should be registered as extension via the `files.quick-action` scope.
*/
export interface ApplicationQuickAction {
id?: string
label?: (...args: unknown[]) => string | string
icon?: string
iconFillType?: IconFillType
handler?: (...args: unknown[]) => Promise<void> | void
displayed?: (...args: unknown[]) => boolean | boolean
}

export type AppConfigObject = Record<string, any>

/** @deprecated */
export interface ApplicationMenuItem {
enabled: () => boolean
priority?: number
openAsEditor?: boolean
}

export interface ApplicationFileExtension {
app?: string
extension?: string
Expand Down Expand Up @@ -77,15 +56,9 @@ export interface ApplicationInformation {
meta?: {
fileSizeLimit?: number
}
/** @deprecated */
isFileEditor?: boolean
extensions?: ApplicationFileExtension[]
defaultExtension?: string
/** @deprecated */
type?: string
translations?: Translations
/** @deprecated */
applicationMenu?: ApplicationMenuItem
}

/**
Expand Down
1 change: 0 additions & 1 deletion packages/web-pkg/src/composables/piniaStores/apps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ export const useAppsStore = defineStore('apps', () => {
}

unref(apps)[appInfo.id] = {
applicationMenu: appInfo.applicationMenu || { enabled: () => false },
defaultExtension: appInfo.defaultExtension || '',
icon: 'check_box_outline_blank',
name: appInfo.name || appInfo.id,
Expand Down
194 changes: 58 additions & 136 deletions packages/web-runtime/src/components/Topbar/TopBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -41,175 +41,97 @@
</header>
</template>

<script lang="ts">
<script setup lang="ts">
import { storeToRefs } from 'pinia'
import { computed, unref, PropType, ref, onMounted } from 'vue'
import { computed, unref, ref } from 'vue'
import ApplicationsMenu from './ApplicationsMenu.vue'
import UserMenu from './UserMenu.vue'
import Notifications from './Notifications.vue'
import FeedbackLink from './FeedbackLink.vue'
import SideBarToggle from './SideBarToggle.vue'
import {
ApplicationInformation,
AppMenuItemExtension,
CustomComponentTarget,
useAuthStore,
useCapabilityStore,
useConfigStore,
useEmbedMode,
useExtensionRegistry,
useOpenEmptyEditor,
useRouter,
useThemeStore
} from '@opencloud-eu/web-pkg'
import { routeNames } from '../../router/names'
import { appMenuExtensionPoint, topBarCenterExtensionPoint } from '../../extensionPoints'
import { RouteLocationNormalizedLoaded } from 'vue-router'
import { useGettext } from 'vue3-gettext'

export default {
components: {
ApplicationsMenu,
CustomComponentTarget,
FeedbackLink,
Notifications,
SideBarToggle,
UserMenu
},
props: {
applicationsList: {
type: Array as PropType<ApplicationInformation[]>,
required: false,
default: (): ApplicationInformation[] => []
}
},
setup(props) {
const capabilityStore = useCapabilityStore()
const themeStore = useThemeStore()
const { currentTheme } = storeToRefs(themeStore)
const configStore = useConfigStore()
const { options: configOptions } = storeToRefs(configStore)
const extensionRegistry = useExtensionRegistry()
const { openEmptyEditor } = useOpenEmptyEditor()

const authStore = useAuthStore()
const router = useRouter()
const { isEnabled: isEmbedModeEnabled } = useEmbedMode()
const { $gettext } = useGettext()
const capabilityStore = useCapabilityStore()
const themeStore = useThemeStore()
const { currentTheme } = storeToRefs(themeStore)
const configStore = useConfigStore()
const { options: configOptions } = storeToRefs(configStore)
const extensionRegistry = useExtensionRegistry()

const appMenuExtensions = computed(() => {
return extensionRegistry.requestExtensions(appMenuExtensionPoint)
})
const authStore = useAuthStore()
const router = useRouter()
const { isEnabled: isEmbedModeEnabled } = useEmbedMode()

const logoWidth = ref('150px')
const hideLogo = computed(() => unref(configOptions).hideLogo)
const appMenuExtensions = computed(() => {
return extensionRegistry.requestExtensions(appMenuExtensionPoint)
})

const isNotificationBellEnabled = computed(() => {
return (
authStore.userContextReady && capabilityStore.notificationsOcsEndpoints.includes('list')
)
})
const hideLogo = computed(() => unref(configOptions).hideLogo)

const homeLink = computed(() => {
if (authStore.publicLinkContextReady && !authStore.userContextReady) {
return {
name: 'resolvePublicLink',
params: { token: authStore.publicLinkToken }
}
}
const isNotificationBellEnabled = computed(() => {
return authStore.userContextReady && capabilityStore.notificationsOcsEndpoints.includes('list')
})

return '/'
})

const isRuntimeRoute = (route: RouteLocationNormalizedLoaded) => {
return Object.values(routeNames).includes(route.name.toString())
}
const isSideBarToggleVisible = computed(() => {
return authStore.userContextReady || authStore.publicLinkContextReady
})
const isSideBarToggleDisabled = computed(() => {
return isRuntimeRoute(unref(router.currentRoute))
})

const contentOnLeftPortal = ref(false)
const updateLeftPortal = (newContent: { hasContent: boolean; sources: string[] }) => {
contentOnLeftPortal.value = newContent.hasContent
const homeLink = computed(() => {
if (authStore.publicLinkContextReady && !authStore.userContextReady) {
return {
name: 'resolvePublicLink',
params: { token: authStore.publicLinkToken }
}
}

onMounted(() => {
// FIXME: backwards compatibility for the deprecated applicationMenu prop
const navExtensions = props.applicationsList
.filter((app) => app.applicationMenu?.enabled())
.map((app) => ({
id: app.id,
type: 'appMenuItem',
label: () => app.name,
path: `/${app.id}`,
icon: app.icon,
color: app.color,
extensionPointIds: [appMenuExtensionPoint.id],
priority: app.applicationMenu?.priority || 50,
...((app as any).url && { url: (app as any).url, target: '_blank' }),
...(app.applicationMenu?.openAsEditor && {
handler: () => openEmptyEditor(app.id, app.defaultExtension)
})
})) as AppMenuItemExtension[]
return '/'
})

extensionRegistry.registerExtensions(computed(() => navExtensions))
})
const isRuntimeRoute = (route: RouteLocationNormalizedLoaded) => {
return Object.values(routeNames).includes(route.name.toString())
}
const isSideBarToggleVisible = computed(() => {
return authStore.userContextReady || authStore.publicLinkContextReady
})
const isSideBarToggleDisabled = computed(() => {
return isRuntimeRoute(unref(router.currentRoute))
})

const contentOnLeftPortal = ref(false)
const updateLeftPortal = (newContent: { hasContent: boolean; sources: string[] }) => {
contentOnLeftPortal.value = newContent.hasContent
}

return {
configOptions,
contentOnLeftPortal,
currentTheme,
updateLeftPortal,
isNotificationBellEnabled,
hideLogo,
logoWidth,
isEmbedModeEnabled,
isSideBarToggleVisible,
isSideBarToggleDisabled,
homeLink,
topBarCenterExtensionPoint,
appMenuExtensions
}
},
computed: {
sidebarLogoAlt() {
return this.$gettext('Navigate to personal files page')
},
const sidebarLogoAlt = computed(() => {
return $gettext('Navigate to personal files page')
})

isFeedbackLinkEnabled() {
return !this.configOptions.disableFeedbackLink
},
const isFeedbackLinkEnabled = computed(() => {
return !unref(configOptions).disableFeedbackLink
})

feedbackLinkOptions() {
const feedback = this.configOptions.feedbackLink
if (!this.isFeedbackLinkEnabled || !feedback) {
return {}
}
const feedbackLinkOptions = computed(() => {
const feedback = unref(configOptions).feedbackLink
if (!unref(isFeedbackLinkEnabled) || !feedback) {
return {}
}

return {
...(feedback.href && { href: feedback.href }),
...(feedback.ariaLabel && { ariaLabel: feedback.ariaLabel }),
...(feedback.description && { description: feedback.description })
}
}
},
async created() {
const image = new Image()
const imageDimensions = (await new Promise((resolve) => {
image.onload = () => {
resolve({
height: image.height,
width: image.width
})
}
image.src = this.currentTheme.logo
})) as { height: number; width: number }
// max-height of logo is 38px, so we calculate the width based on the ratio of the image
// and add 70px to account for the width of the left side of the topbar
this.logoWidth = `${imageDimensions.width / (imageDimensions.height / 38) + 70}px`
return {
...(feedback.href && { href: feedback.href }),
...(feedback.ariaLabel && { ariaLabel: feedback.ariaLabel }),
...(feedback.description && { description: feedback.description })
}
}
})
</script>
<style scoped>
@reference '@opencloud-eu/design-system/tailwind';
Expand Down
8 changes: 6 additions & 2 deletions packages/web-runtime/src/container/bootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,12 +138,16 @@ const getEmbedConfigFromQuery = (
*/
export const announceConfiguration = async ({
path,
configStore
configStore,
token
}: {
path: string
configStore: ConfigStore
token?: string
}) => {
const request = await fetch(path, { headers: { 'X-Request-ID': uuidV4() } })
const request = await fetch(path, {
headers: { 'X-Request-ID': uuidV4(), ...(token && { Authorization: `Bearer ${token}` }) }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just thinking, what about public links? Should we authenticate with public link auth as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm do you think it's necessary? Isn't public basically always anonymous and therefore non-user-specific?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True. I guess we can start without and if there'll be good reasons from someone, then we think about it again.

})
if (request.status !== 200) {
throw new Error(`config could not be loaded. HTTP status-code ${request.status}`)
}
Expand Down
6 changes: 6 additions & 0 deletions packages/web-runtime/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,12 @@ export const bootstrapApp = async (configurationPath: string, appsReadyCallback:
return
}

await announceConfiguration({
path: configurationPath,
configStore,
token: authStore.accessToken
})

const clientService = app.config.globalProperties.$clientService
const previewService = app.config.globalProperties.$previewService
const passwordPolicyService = app.config.globalProperties.passwordPolicyService
Expand Down
7 changes: 1 addition & 6 deletions packages/web-runtime/src/layouts/Application.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<div v-if="isIE11" class="bg-role-surface-container text-center py-4">
<p class="m-0" v-text="ieDeprecationWarning" />
</div>
<top-bar :applications-list="Object.values(apps)" />
<top-bar />
</div>
<div
id="web-content-main"
Expand Down Expand Up @@ -53,7 +53,6 @@ import orderBy from 'lodash-es/orderBy'
import {
AppLoadingSpinner,
CustomComponentTarget,
useAppsStore,
useAuthStore,
useExtensionRegistry,
useLocalStorage,
Expand All @@ -69,7 +68,6 @@ import { useActiveApp, useRoute, useRouteMeta, useSpacesLoading } from '@openclo
import { computed, nextTick, onBeforeUnmount, onMounted, provide, ref, unref, watch } from 'vue'
import { RouteLocationAsRelativeTyped, useRouter } from 'vue-router'
import { useGettext } from 'vue3-gettext'
import { storeToRefs } from 'pinia'
import { progressBarExtensionPoint } from '../extensionPoints'

const MOBILE_BREAKPOINT = 640
Expand All @@ -82,9 +80,6 @@ const activeApp = useActiveApp()
const extensionRegistry = useExtensionRegistry()
const { isSideBarOpen } = useSideBar()

const appsStore = useAppsStore()
const { apps } = storeToRefs(appsStore)

const extensionNavItems = computed(() =>
getExtensionNavItems({ extensionRegistry, appId: unref(activeApp) })
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,7 @@ const getWrapper = ({
props: {
applicationsList: [
mock<ApplicationInformation>({
type: 'extension',
icon: '',
applicationMenu: undefined
icon: ''
})
]
},
Expand Down