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
3 changes: 2 additions & 1 deletion apps/theming/lib/Listener/BeforePreferenceListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class BeforePreferenceListener implements IEventListener {
/**
* @var string[]
*/
private const ALLOWED_KEYS = ['force_enable_blur_filter', 'shortcuts_disabled', 'primary_color'];
private const ALLOWED_KEYS = ['force_enable_blur_filter', 'show_header_datetime', 'shortcuts_disabled', 'primary_color'];

public function __construct(
private IAppManager $appManager,
Expand Down Expand Up @@ -52,6 +52,7 @@ private function handleThemingValues(BeforePreferenceSetEvent|BeforePreferenceDe
if ($event instanceof BeforePreferenceSetEvent) {
switch ($event->getConfigKey()) {
case 'force_enable_blur_filter':
case 'show_header_datetime':
$event->setValid($event->getConfigValue() === 'yes' || $event->getConfigValue() === 'no');
break;
case 'shortcuts_disabled':
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,15 @@ public function handle(Event $event): void {
}
return false;
});

$this->initialState->provideLazyInitialState('showHeaderDateTime', function () {
if ($this->userSession->getUser()) {
$uid = $this->userSession->getUser()->getUID();
return $this->config->getUserValue($uid, Application::APP_ID, 'show_header_datetime', 'no');
}

return 'no';
});
}

$this->themeInjectionService->injectHeaders();
Expand Down
1 change: 1 addition & 0 deletions apps/theming/lib/Settings/Personal.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ public function getForm(): TemplateResponse {
$this->initialStateService->provideInitialState('enforceTheme', $enforcedTheme);
$this->initialStateService->provideInitialState('isUserThemingDisabled', $this->themingDefaults->isUserThemingDisabled());
$this->initialStateService->provideInitialState('enableBlurFilter', $this->config->getUserValue($this->userId, 'theming', 'force_enable_blur_filter', ''));
$this->initialStateService->provideInitialState('showHeaderDateTime', $this->config->getUserValue($this->userId, 'theming', 'show_header_datetime', 'no'));
$this->initialStateService->provideInitialState('navigationBar', [
'userAppOrder' => json_decode($this->config->getUserValue($this->userId, 'core', 'apporder', '[]'), true, flags:JSON_THROW_ON_ERROR),
'enforcedDefaultApp' => $forcedDefaultEntry
Expand Down
39 changes: 39 additions & 0 deletions apps/theming/src/views/UserTheming.vue
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,14 @@
@update:modelValue="changeEnableBlurFilter">
{{ t('theming', 'Enable blur background filter (may increase GPU load)') }}
</NcCheckboxRadioSwitch>

<h3>{{ t('theming', 'Visibility options') }}</h3>
<NcCheckboxRadioSwitch
type="checkbox"
:modelValue="showHeaderDateTime === 'yes'"
@update:modelValue="changeShowHeaderDateTime">
{{ t('theming', 'Show date and time in top bar') }}
</NcCheckboxRadioSwitch>
</NcSettingsSection>

<NcNoteCard v-if="isUserThemingDisabled" type="info">
Expand All @@ -63,6 +71,7 @@ import type { ITheme } from '../components/ThemePreviewItem.vue'

import axios, { isAxiosError } from '@nextcloud/axios'
import { showError } from '@nextcloud/dialogs'
import { emit } from '@nextcloud/event-bus'
import { loadState } from '@nextcloud/initial-state'
import { t } from '@nextcloud/l10n'
import { generateOcsUrl } from '@nextcloud/router'
Expand All @@ -82,6 +91,7 @@ const enforceTheme = loadState('theming', 'enforceTheme', '')
const isUserThemingDisabled = loadState('theming', 'isUserThemingDisabled')

const enableBlurFilter = ref(loadState('theming', 'enableBlurFilter', ''))
const showHeaderDateTime = ref(loadState('theming', 'showHeaderDateTime', 'no'))

const availableThemes = loadState<ITheme[]>('theming', 'themes', [])
const themes = ref(availableThemes.filter((theme) => theme.type === 1))
Expand Down Expand Up @@ -179,6 +189,35 @@ async function changeEnableBlurFilter() {
refreshStyles()
}

/**
* Handle top bar date/time visibility change
*
* @param visible - Whether the date/time should be visible
*/
async function changeShowHeaderDateTime(visible: boolean) {
const previousValue = showHeaderDateTime.value
showHeaderDateTime.value = visible ? 'yes' : 'no'

try {
await axios({
url: generateOcsUrl('apps/provisioning_api/api/v1/config/users/{appId}/{configKey}', {
appId: 'theming',
configKey: 'show_header_datetime',
}),
data: {
configValue: showHeaderDateTime.value,
},
method: 'POST',
})

emit('theming:show-header-datetime:changed', { enabled: visible })
} catch (error) {
showHeaderDateTime.value = previousValue
logger.error('theming: Unable to update header date and time visibility.', { error })
showError(t('theming', 'Unable to apply the setting.'))
}
}

/**
*
*/
Expand Down
87 changes: 87 additions & 0 deletions core/src/components/HeaderDateTime.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<!--
- SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
- SPDX-License-Identifier: AGPL-3.0-or-later
-->
<template>
<span v-if="enabled" class="header-date-time" :title="localizedDateTime">
{{ localizedDateTime }}
</span>
Comment on lines +6 to +8
Copy link
Contributor

Choose a reason for hiding this comment

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

Should rather just use NcDateTime from nextcloud-vue

</template>

<script lang="ts">
import { defineComponent } from 'vue'

export default defineComponent({
Comment on lines +11 to +14
Copy link
Contributor

Choose a reason for hiding this comment

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

new files should - if possible - use script setup syntax to be future proof

name: 'HeaderDateTime',

props: {
enabled: {
type: Boolean,
default: false,
},
},

data() {
return {
localizedDateTime: '',
clockInterval: null as number | null,
}
},

watch: {
enabled: {
immediate: true,
handler(enabled: boolean) {
if (!enabled) {
this.stopClock()
return
}

this.updateDateTime()
this.startClock()
},
},
},

beforeDestroy() {
this.stopClock()
},

methods: {
startClock() {
if (this.clockInterval !== null) {
return
}

this.clockInterval = window.setInterval(this.updateDateTime, 30000)
},

stopClock() {
if (this.clockInterval === null) {
return
}

window.clearInterval(this.clockInterval)
this.clockInterval = null
},

updateDateTime() {
this.localizedDateTime = new Intl.DateTimeFormat(undefined, {
dateStyle: 'short',
timeStyle: 'short',
}).format(new Date())
},
},
})
</script>

<style lang="scss" scoped>
.header-date-time {
margin-inline-end: calc(2 * var(--default-grid-baseline));
white-space: nowrap;
font-size: var(--font-size-small);
line-height: var(--header-height);
color: var(--color-background-plain-text);
user-select: none;
}
</style>
89 changes: 55 additions & 34 deletions core/src/views/AccountMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,46 +3,49 @@
- SPDX-License-Identifier: AGPL-3.0-or-later
-->
<template>
<NcHeaderMenu
id="user-menu"
class="account-menu"
is-nav
:aria-label="t('core', 'Settings menu')"
:description="avatarDescription">
<template #trigger>
<!-- The `key` is a hack as NcAvatar does not handle updating the preloaded status on show status change -->
<NcAvatar
:key="String(showUserStatus)"
class="account-menu__avatar"
disable-menu
disable-tooltip
:hide-user-status="!showUserStatus"
:user="currentUserId"
:preloaded-user-status="userStatus" />
</template>
<ul class="account-menu__list">
<AccountMenuProfileEntry
:id="profileEntry.id"
:name="profileEntry.name"
:href="profileEntry.href"
:active="profileEntry.active" />
<AccountMenuEntry
v-for="entry in otherEntries"
:id="entry.id"
:key="entry.id"
:name="entry.name"
:href="entry.href"
:active="entry.active"
:icon="entry.icon" />
</ul>
</NcHeaderMenu>
<div class="account-menu-wrapper">
<HeaderDateTime :enabled="showHeaderDateTime" />
Copy link
Contributor

Choose a reason for hiding this comment

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

I do not think this belongs to the account menu

<NcHeaderMenu
id="user-menu"
class="account-menu"
is-nav
:aria-label="t('core', 'Settings menu')"
:description="avatarDescription">
<template #trigger>
<!-- The `key` is a hack as NcAvatar does not handle updating the preloaded status on show status change -->
<NcAvatar
:key="String(showUserStatus)"
class="account-menu__avatar"
disable-menu
disable-tooltip
:hide-user-status="!showUserStatus"
:user="currentUserId"
:preloaded-user-status="userStatus" />
</template>
<ul class="account-menu__list">
<AccountMenuProfileEntry
:id="profileEntry.id"
:name="profileEntry.name"
:href="profileEntry.href"
:active="profileEntry.active" />
<AccountMenuEntry
v-for="entry in otherEntries"
:id="entry.id"
:key="entry.id"
:name="entry.name"
:href="entry.href"
:active="entry.active"
:icon="entry.icon" />
</ul>
</NcHeaderMenu>
</div>
</template>

<script lang="ts">
import { getCurrentUser } from '@nextcloud/auth'
import axios from '@nextcloud/axios'
import { getCapabilities } from '@nextcloud/capabilities'
import { emit, subscribe } from '@nextcloud/event-bus'
import { emit, subscribe, unsubscribe } from '@nextcloud/event-bus'
import { loadState } from '@nextcloud/initial-state'
import { t } from '@nextcloud/l10n'
import { generateOcsUrl } from '@nextcloud/router'
Expand All @@ -51,6 +54,7 @@ import NcAvatar from '@nextcloud/vue/components/NcAvatar'
import NcHeaderMenu from '@nextcloud/vue/components/NcHeaderMenu'
import AccountMenuEntry from '../components/AccountMenu/AccountMenuEntry.vue'
import AccountMenuProfileEntry from '../components/AccountMenu/AccountMenuProfileEntry.vue'
import HeaderDateTime from '../components/HeaderDateTime.vue'
import logger from '../logger.js'

interface ISettingsNavigationEntry {
Expand Down Expand Up @@ -120,6 +124,7 @@ export default defineComponent({
components: {
AccountMenuEntry,
AccountMenuProfileEntry,
HeaderDateTime,
NcAvatar,
NcHeaderMenu,
},
Expand All @@ -142,6 +147,7 @@ export default defineComponent({
data() {
return {
showUserStatus: false,
showHeaderDateTime: loadState('theming', 'showHeaderDateTime', 'no') === 'yes',
userStatus: {
status: null,
icon: null,
Expand Down Expand Up @@ -185,9 +191,15 @@ export default defineComponent({

mounted() {
subscribe('user_status:status.updated', this.handleUserStatusUpdated)
subscribe('theming:show-header-datetime:changed', this.handleShowHeaderDateTimeChanged)
emit('core:user-menu:mounted')
},

beforeDestroy() {
unsubscribe('user_status:status.updated', this.handleUserStatusUpdated)
unsubscribe('theming:show-header-datetime:changed', this.handleShowHeaderDateTimeChanged)
},

methods: {
handleUserStatusUpdated(state) {
if (this.currentUserId === state.userId) {
Expand All @@ -199,6 +211,10 @@ export default defineComponent({
}
},

handleShowHeaderDateTimeChanged(state: { enabled?: boolean }) {
this.showHeaderDateTime = state.enabled === true
},

translateStatus(status) {
const statusMap = Object.fromEntries(USER_DEFINABLE_STATUSES.map(({ type, label }) => [type, label]))
if (statusMap[status]) {
Expand All @@ -215,6 +231,11 @@ export default defineComponent({
padding: 0 !important;
}

.account-menu-wrapper {
display: flex;
align-items: center;
}

.account-menu {
:deep(*) {
// do not apply the alpha mask on the avatar div
Expand Down
Loading