Skip to content

Commit

Permalink
Move themes to separate pinia store
Browse files Browse the repository at this point in the history
  • Loading branch information
pascalwengerter committed Nov 7, 2023
1 parent 96a7370 commit 93a9bd0
Show file tree
Hide file tree
Showing 17 changed files with 179 additions and 126 deletions.
18 changes: 18 additions & 0 deletions changelog/unreleased/change-theme-handling
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
Change: Theme handling

The handling of themes has been throughoutly reworked.
Themes now feature a human-readable title, can specify whether they are suitable as light or dark mode themes.
If only one theme is provided, this theme will be set.
If two themes are provided and one is a dark and one is a light mode one, there is a light/dark mode switch button in the AppBar.
If more than two themes are provided or the two provided themes are not a dark and a light mode one respectively,
the UI features a theme-switch button featuring a dropdown of the human-readable names of the available themes

https://github.com/owncloud/web/issues/2404
https://github.com/owncloud/web/issues/8424
https://github.com/owncloud/web/issues/9403
https://github.com/owncloud/web/issues/9885

https://github.com/owncloud/web/pull/8855
https://github.com/owncloud/web/pull/9396
https://github.com/owncloud/web/pull/9401
https://github.com/owncloud/web/pull/9698
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
</div>
<div>
<div class="logo-wrapper">
<img alt="" :src="logo" class="oc-p-xs" />
<img alt="" :src="currentTheme.logo.topbar" class="oc-p-xs" />
</div>
<input
id="logo-upload-input"
Expand Down Expand Up @@ -59,6 +59,7 @@ export default defineComponent({
},
setup() {
const store = useStore()
const { currentTheme } = useThemeStore()
const logoInput: VNodeRef = ref(null)
Expand All @@ -75,7 +76,6 @@ export default defineComponent({
resources: unref(menuItems)
}))
const logo = computed(() => store.getters.configuration.currentTheme.logo.topbar)
const menuSections = computed(() => [
{
name: 'primaryActions',
Expand All @@ -87,7 +87,7 @@ export default defineComponent({
return {
actionOptions,
logo,
currentTheme,
menuItems,
menuSections,
supportedLogoMimeTypesAcceptValue,
Expand Down
4 changes: 3 additions & 1 deletion packages/web-app-files/src/views/FilesDrop.vue
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
</div>

<div class="oc-text-center oc-mt-xxl">
<p v-text="configuration.currentTheme.general.slogan" />
<p v-text="currentTheme.general.slogan" />
</div>
</div>
</div>
Expand Down Expand Up @@ -78,6 +78,7 @@ export default defineComponent({
setup() {
const uppyService = useService<UppyService>('$uppyService')
const store = useStore()
const { currentTheme } = useThemeStore()
const router = useRouter()
const route = useRoute()
const language = useGettext()
Expand Down Expand Up @@ -204,6 +205,7 @@ export default defineComponent({
})
return {
currentTheme,
dragareaEnabled,
loading,
errorMessage,
Expand Down
8 changes: 5 additions & 3 deletions packages/web-pkg/src/components/AppBanner.vue
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@

<script lang="ts">
import { computed, defineComponent, ref, unref } from 'vue'
import { useRouter, useStore } from '../composables'
import { useRouter } from '../composables'
import { buildUrl } from '../helpers/router'
import { useSessionStorage } from '@vueuse/core'
Expand All @@ -52,9 +52,11 @@ export default defineComponent({
setup(props) {
const appBannerWasClosed = useSessionStorage('app_banner_closed', null)
const isVisible = ref<boolean>(unref(appBannerWasClosed) === null)
const store = useStore()
const router = useRouter()
const appBannerSettings = unref(store.getters.configuration.currentTheme.appBanner)
const themeStore = useThemeStore()
const appBannerSettings = themeStore.currentTheme.appBanner
const appUrl = computed(() => {
return buildUrl(router, `/f/${props.fileId}`)
.toString()
Expand Down
9 changes: 7 additions & 2 deletions packages/web-runtime/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,15 @@ import { eventBus, useRouter } from '@ownclouders/web-pkg'
import { useHead } from './composables/head'
import { useStore } from '@ownclouders/web-pkg'
import { RouteLocation } from 'vue-router'
import { useThemeStore } from './store/theme'
export default defineComponent({
components: {
SkipTo
},
setup() {
const store = useStore()
const { currentTheme } = useThemeStore()
const router = useRouter()
useHead({ store })
Expand Down Expand Up @@ -97,6 +99,10 @@ export default defineComponent({
store.commit('Files/SET_CURRENT_FOLDER', null)
}
)
return {
currentTheme
}
},
data() {
return {
Expand Down Expand Up @@ -171,10 +177,9 @@ export default defineComponent({
}
const glue = ' - '
const titleSegments = [routeTitle]
const generalName = this.configuration.currentTheme.general.name
return {
shortDocumentTitle: titleSegments.join(glue),
fullDocumentTitle: [...titleSegments, generalName].join(glue)
fullDocumentTitle: [...titleSegments, this.currentTheme.general.name].join(glue)
}
}
}
Expand Down
51 changes: 29 additions & 22 deletions packages/web-runtime/src/components/Topbar/ThemeSwitcher.vue
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<template>
<!-- TODO: Only show if themes.length === 2 && 1 theme isDark === true && 1 theme isDark === false -->
<oc-button
v-oc-tooltip="buttonLabel"
class="themeswitcher-btn"
Expand All @@ -10,17 +11,25 @@
<span class="oc-visible@s" :aria-label="switchLabel" />
<oc-icon :name="switchIcon" fill-type="line" variation="inherit" />
</oc-button>
<!-- TODO: Add button + ocdrop for >2 themes or (themes.length === 2 && both isDark || !isDark) -->
</template>
<script lang="ts">
import { computed, unref, watch, defineComponent } from 'vue'
import { mapGetters } from 'vuex'
import { computed, unref, watch, defineComponent, ref } from 'vue'
import { useGettext } from 'vue3-gettext'
import { useStore, useLocalStorage } from '@ownclouders/web-pkg'
import { themeNameDark, themeNameLight, useDefaultThemeName } from '../../composables'
// TODO: Consider using https://vueuse.org/core/useColorMode/#usecolormode
export default defineComponent({
setup() {
const { $gettext } = useGettext()
const store = useStore()
const currentThemeName = useLocalStorage('oc_currentThemeName', useDefaultThemeName())
const currentThemeName = ref('')
// TODO: Move to theme store?
currentThemeName.value = useLocalStorage('oc_currentThemeName', useDefaultThemeName())
const currentTheme = computed(() => store.getters.configuration.themes[unref(currentThemeName)])
const applyTheme = (theme) => {
for (const param in theme.designTokens.colorPalette) {
Expand All @@ -31,31 +40,29 @@ export default defineComponent({
}
}
const buttonLabel = computed(() => $gettext('Click to switch theme'))
const switchLabel = computed(() => $gettext('Currently used theme'))
watch(currentThemeName, async () => {
await store.dispatch('loadTheme', { theme: unref(currentTheme) })
applyTheme(unref(currentTheme))
})
return { currentThemeName, currentTheme }
},
computed: {
...mapGetters(['configuration']),
isLightTheme() {
return [null, themeNameLight].includes(this.currentThemeName)
},
buttonLabel() {
return this.$gettext('Click to switch theme')
},
switchIcon() {
return this.isLightTheme ? 'sun' : 'moon-clear'
},
switchLabel() {
return this.$gettext('Currently used theme')
const isLightTheme = computed(() => [null, themeNameLight].includes(currentThemeName.value))
const switchIcon = computed(() => (isLightTheme.value ? 'sun' : 'moon-clear'))
const toggleTheme = () => {
currentThemeName.value = isLightTheme.value ? themeNameDark : themeNameLight
}
},
methods: {
toggleTheme() {
this.currentThemeName = this.isLightTheme ? themeNameDark : themeNameLight
return {
buttonLabel,
currentThemeName,
currentTheme,
switchIcon,
switchLabel,
toggleTheme
}
}
})
Expand Down
18 changes: 7 additions & 11 deletions packages/web-runtime/src/components/Topbar/TopBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
to="/"
class="oc-width-1-1"
>
<oc-img :src="logoImage" :alt="sidebarLogoAlt" class="oc-logo-image" />
<oc-img :src="currentTheme.logo.topbar" :alt="sidebarLogoAlt" class="oc-logo-image" />
</router-link>
</div>
<div v-if="!contentOnLeftPortal" class="oc-topbar-center">
Expand All @@ -26,7 +26,7 @@
</div>
<template v-if="!isEmbedModeEnabled">
<portal to="app.runtime.header.right" :order="50">
<theme-switcher v-if="darkThemeAvailable" />
<theme-switcher v-if="!hasOnlyOneTheme" />
<feedback-link v-if="isFeedbackLinkEnabled" v-bind="feedbackLinkOptions" />
</portal>
<portal to="app.runtime.header.right" :order="100">
Expand Down Expand Up @@ -56,6 +56,7 @@ import {
} from '@ownclouders/web-pkg'
import { computed, unref, PropType, ref } from 'vue'
import { useGettext } from 'vue3-gettext'
import { useThemeStore } from '../../store/theme'
export default {
components: {
Expand All @@ -74,6 +75,7 @@ export default {
},
setup(props) {
const store = useStore()
const { currentTheme, hasOnlyOneTheme } = useThemeStore()
const notificationsSupport = useCapabilityNotifications()
const isUserContext = useUserContext({ store })
const language = useGettext()
Expand Down Expand Up @@ -180,7 +182,9 @@ export default {
return {
contentOnLeftPortal,
currentTheme,
updateLeftPortal,
hasOnlyOneTheme,
isNotificationBellEnabled,
userMenuItems,
appMenuItems,
Expand All @@ -191,18 +195,10 @@ export default {
computed: {
...mapGetters(['configuration', 'user']),
darkThemeAvailable() {
return this.configuration.themes.default && this.configuration.themes['default-dark']
},
sidebarLogoAlt() {
return this.$gettext('Navigate to personal files page')
},
logoImage() {
return this.configuration.currentTheme.logo.topbar
},
isFeedbackLinkEnabled() {
return !this.configuration?.options?.disableFeedbackLink
},
Expand All @@ -229,7 +225,7 @@ export default {
width: image.width
})
}
image.src = this.configuration.currentTheme.logo.topbar
image.src = this.currentTheme.logo.topbar
})) 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
Expand Down
10 changes: 2 additions & 8 deletions packages/web-runtime/src/components/Topbar/UserMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -144,16 +144,10 @@ export default defineComponent({
})
const store = useStore()
const imprintUrl = computed(() => {
return (
store.getters.configuration.currentTheme.general?.imprintUrl ||
store.getters.configuration.options.imprintUrl
)
return store.getters.configuration.options.imprintUrl
})
const privacyUrl = computed(() => {
return (
store.getters.configuration.currentTheme.general?.privacyUrl ||
store.getters.configuration.options.privacyUrl
)
return store.getters.configuration.options.privacyUrl
})
return {
hasSpaces: useCapabilitySpacesEnabled(),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
export const themeNameLight = 'default'
export const themeNameDark = 'default-dark'

export type ThemeName = typeof themeNameDark | typeof themeNameLight

export const useDefaultThemeName = (): string => {
return window.matchMedia('(prefers-color-scheme: dark)').matches ? themeNameDark : themeNameLight
}
12 changes: 7 additions & 5 deletions packages/web-runtime/src/container/bootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
UppyService
} from '@ownclouders/web-pkg'
import { default as storeOptions } from '../store'
import { useThemeStore } from '../store/theme'
import { init as sentryInit } from '@sentry/vue'
import { configurationManager, RawConfig, ConfigurationManager } from '@ownclouders/web-pkg'
import { webdav } from '@ownclouders/web-client/src/webdav'
Expand Down Expand Up @@ -276,26 +277,27 @@ const rewriteDeprecatedAppNames = (
* @param designSystem
*/
export const announceTheme = async ({
store,
app,
designSystem,
runtimeConfiguration
}: {
store: Store<unknown>
app: App
designSystem: any
runtimeConfiguration?: RuntimeConfiguration
}): Promise<void> => {
// TODO: StoreToRefs, or nah?
const { currentTheme, initializeThemes } = useThemeStore()

const { web, common } = await loadTheme(runtimeConfiguration?.theme)
await store.dispatch('loadThemes', { theme: web, common })
const currentThemeName = useLocalStorage('oc_currentThemeName', null) // note: use null as default so that we can fall back to system preferences
if (unref(currentThemeName) === null) {
currentThemeName.value = useDefaultThemeName()
}
await store.dispatch('loadTheme', { theme: web[unref(currentThemeName)] || web.default })

await initializeThemes([web], common, unref(currentThemeName))

app.use(designSystem, {
tokens: store.getters.theme.designTokens
tokens: currentTheme.designTokens
})
}

Expand Down
4 changes: 2 additions & 2 deletions packages/web-runtime/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ export const bootstrapApp = async (configurationPath: string): Promise<void> =>
})

const customTranslationsPromise = loadCustomTranslations({ configurationManager })
const themePromise = announceTheme({ store, app, designSystem, runtimeConfiguration })
const themePromise = announceTheme({ app, designSystem, runtimeConfiguration })
const [customTranslations] = await Promise.all([
customTranslationsPromise,
applicationsPromise,
Expand Down Expand Up @@ -264,7 +264,7 @@ export const bootstrapErrorApp = async (err: Error): Promise<void> => {
const store = await announceStore({ runtimeConfiguration: {} })
announceVersions({ store })
const app = createApp(pages.failure)
await announceTheme({ store, app, designSystem })
await announceTheme({ app, designSystem })
console.error(err)
app.use(store)
await announceTranslations({
Expand Down
Loading

0 comments on commit 93a9bd0

Please sign in to comment.