UBERF-4127 (#4137)

Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
Denis Bykhov 2023-12-05 11:38:12 +06:00 committed by GitHub
parent e50bdd461b
commit d2b1e64b65
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 395 additions and 116 deletions

View File

@ -983,6 +983,9 @@ dependencies:
minio: minio:
specifier: ^7.0.26 specifier: ^7.0.26
version: 7.1.3 version: 7.1.3
moment-timezone:
specifier: ^0.5.43
version: 0.5.43
mongodb: mongodb:
specifier: ^4.11.0 specifier: ^4.11.0
version: 4.17.1 version: 4.17.1
@ -13221,6 +13224,16 @@ packages:
resolution: {integrity: sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A==} resolution: {integrity: sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A==}
dev: false dev: false
/moment-timezone@0.5.43:
resolution: {integrity: sha512-72j3aNyuIsDxdF1i7CEgV2FfxM1r6aaqJyLB2vwb33mXYyoyLly+F1zbWqhA3/bVIoJ4szlUoMbUnVdid32NUQ==}
dependencies:
moment: 2.29.4
dev: false
/moment@2.29.4:
resolution: {integrity: sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==}
dev: false
/mongodb-connection-string-url@2.6.0: /mongodb-connection-string-url@2.6.0:
resolution: {integrity: sha512-WvTZlI9ab0QYtTYnuMLgobULWhokRjtC7db9LtcVfJ+Hsnyr5eo6ZtNAt3Ly24XZScGMelOcGtm7lSn0332tPQ==} resolution: {integrity: sha512-WvTZlI9ab0QYtTYnuMLgobULWhokRjtC7db9LtcVfJ+Hsnyr5eo6ZtNAt3Ly24XZScGMelOcGtm7lSn0332tPQ==}
dependencies: dependencies:
@ -17768,7 +17781,7 @@ packages:
dev: false dev: false
file:projects/calendar-resources.tgz(@types/node@16.11.68)(esbuild@0.16.17)(postcss-load-config@4.0.1)(postcss@8.4.31)(ts-node@10.9.1): file:projects/calendar-resources.tgz(@types/node@16.11.68)(esbuild@0.16.17)(postcss-load-config@4.0.1)(postcss@8.4.31)(ts-node@10.9.1):
resolution: {integrity: sha512-uhWVaA9LfyRGkkRYOipr4pcykZVqdl5EHUGCF146vAHuv01YV+njwZCeSRzbDYEB9VzQdPMdhh6/Wol8Zm3ENg==, tarball: file:projects/calendar-resources.tgz} resolution: {integrity: sha512-GKA5RNUEh4K+pp2mAf479tKTKebnRqldRDNln/RgNekUVHs/vqOuzHbu/4uHlIpC1QJXxyi7H+m6lidp5bqv7A==, tarball: file:projects/calendar-resources.tgz}
id: file:projects/calendar-resources.tgz id: file:projects/calendar-resources.tgz
name: '@rush-temp/calendar-resources' name: '@rush-temp/calendar-resources'
version: 0.0.0 version: 0.0.0
@ -17785,6 +17798,7 @@ packages:
eslint-plugin-svelte3: 4.0.0(eslint@8.51.0)(svelte@3.55.1) eslint-plugin-svelte3: 4.0.0(eslint@8.51.0)(svelte@3.55.1)
fast-equals: 2.0.4 fast-equals: 2.0.4
jest: 29.7.0(@types/node@16.11.68)(ts-node@10.9.1) jest: 29.7.0(@types/node@16.11.68)(ts-node@10.9.1)
moment-timezone: 0.5.43
prettier: 3.1.0 prettier: 3.1.0
prettier-plugin-svelte: 3.1.0(prettier@3.1.0)(svelte@4.2.5) prettier-plugin-svelte: 3.1.0(prettier@3.1.0)(svelte@4.2.5)
sass: 1.69.0 sass: 1.69.0
@ -23711,7 +23725,7 @@ packages:
dev: false dev: false
file:projects/ui.tgz(@types/node@16.11.68)(esbuild@0.16.17)(postcss-load-config@4.0.1)(postcss@8.4.31)(ts-node@10.9.1): file:projects/ui.tgz(@types/node@16.11.68)(esbuild@0.16.17)(postcss-load-config@4.0.1)(postcss@8.4.31)(ts-node@10.9.1):
resolution: {integrity: sha512-g5wJSxU0oHaWmOknjGHp3d+ML2V2bnveZV51xeHaq5GfbEwCdHat3DOsEt6zC3BMVRZsyJz8EmwNnd6A+KZYcA==, tarball: file:projects/ui.tgz} resolution: {integrity: sha512-N7xpf37pbt76Fi7r6f4ioMRsUhBC6xMQmAliUP5wX0OLlzS/sU7SHQFqY5BYcXBV9HhyTcRqQ3cIG2yGgQivDQ==, tarball: file:projects/ui.tgz}
id: file:projects/ui.tgz id: file:projects/ui.tgz
name: '@rush-temp/ui' name: '@rush-temp/ui'
version: 0.0.0 version: 0.0.0
@ -23731,6 +23745,7 @@ packages:
fast-equals: 2.0.4 fast-equals: 2.0.4
jest: 29.7.0(@types/node@16.11.68)(ts-node@10.9.1) jest: 29.7.0(@types/node@16.11.68)(ts-node@10.9.1)
just-clone: 6.2.0 just-clone: 6.2.0
moment-timezone: 0.5.43
prettier: 3.1.0 prettier: 3.1.0
prettier-plugin-svelte: 3.1.0(prettier@3.1.0)(svelte@4.2.5) prettier-plugin-svelte: 3.1.0(prettier@3.1.0)(svelte@4.2.5)
sass: 1.69.0 sass: 1.69.0

View File

@ -117,11 +117,14 @@ export class TEvent extends TAttachedDoc implements Event {
access!: 'freeBusyReader' | 'reader' | 'writer' | 'owner' access!: 'freeBusyReader' | 'reader' | 'writer' | 'owner'
visibility?: Visibility visibility?: Visibility
timeZone?: string
} }
@Model(calendar.class.ReccuringEvent, calendar.class.Event) @Model(calendar.class.ReccuringEvent, calendar.class.Event)
@UX(calendar.string.ReccuringEvent, calendar.icon.Calendar) @UX(calendar.string.ReccuringEvent, calendar.icon.Calendar)
export class TReccuringEvent extends TEvent implements ReccuringEvent { export class TReccuringEvent extends TEvent implements ReccuringEvent {
declare timeZone: string
rules!: RecurringRule[] rules!: RecurringRule[]
exdate!: Timestamp[] exdate!: Timestamp[]
rdate!: Timestamp[] rdate!: Timestamp[]
@ -132,7 +135,6 @@ export class TReccuringEvent extends TEvent implements ReccuringEvent {
@UX(calendar.string.Event, calendar.icon.Calendar) @UX(calendar.string.Event, calendar.icon.Calendar)
export class TReccuringInstance extends TReccuringEvent implements ReccuringInstance { export class TReccuringInstance extends TReccuringEvent implements ReccuringInstance {
recurringEventId!: Ref<ReccuringEvent> recurringEventId!: Ref<ReccuringEvent>
declare originalStartTime: number
isCancelled?: boolean isCancelled?: boolean
virtual?: boolean virtual?: boolean
} }

View File

@ -13,10 +13,15 @@
// limitations under the License. // limitations under the License.
// //
import { type Calendar, type Event, type ReccuringEvent } from '@hcengineering/calendar' import { calendarId, type Calendar, type Event, type ReccuringEvent } from '@hcengineering/calendar'
import contact from '@hcengineering/contact' import contact from '@hcengineering/contact'
import core, { type Ref, TxOperations } from '@hcengineering/core' import core, { TxOperations, type Ref } from '@hcengineering/core'
import { type MigrateOperation, type MigrationClient, type MigrationUpgradeClient } from '@hcengineering/model' import {
tryMigrate,
type MigrateOperation,
type MigrationClient,
type MigrationUpgradeClient
} from '@hcengineering/model'
import { DOMAIN_SPACE } from '@hcengineering/model-core' import { DOMAIN_SPACE } from '@hcengineering/model-core'
import { DOMAIN_SETTING } from '@hcengineering/model-setting' import { DOMAIN_SETTING } from '@hcengineering/model-setting'
import { type Integration } from '@hcengineering/setting' import { type Integration } from '@hcengineering/setting'
@ -121,6 +126,17 @@ async function migrateSync (client: MigrationClient): Promise<void> {
) )
} }
async function migrateTimezone (client: MigrationClient): Promise<void> {
await client.update(
DOMAIN_CALENDAR,
{
_class: { $in: [calendar.class.ReccuringEvent, calendar.class.ReccuringInstance] },
timeZone: { $exists: false }
},
{ timeZone: 'Etc/GMT' }
)
}
export const calendarOperation: MigrateOperation = { export const calendarOperation: MigrateOperation = {
async migrate (client: MigrationClient): Promise<void> { async migrate (client: MigrationClient): Promise<void> {
await fixEventDueDate(client) await fixEventDueDate(client)
@ -128,6 +144,12 @@ export const calendarOperation: MigrateOperation = {
await fillOriginalStartTime(client) await fillOriginalStartTime(client)
await migrateSync(client) await migrateSync(client)
await migrateExternalCalendars(client) await migrateExternalCalendars(client)
await tryMigrate(client, calendarId, [
{
state: 'timezone',
func: migrateTimezone
}
])
}, },
async upgrade (client: MigrationUpgradeClient): Promise<void> { async upgrade (client: MigrationUpgradeClient): Promise<void> {
const tx = new TxOperations(client, core.account.System) const tx = new TxOperations(client, core.account.System)

View File

@ -43,7 +43,8 @@
"svelte": "^4.2.5", "svelte": "^4.2.5",
"fast-equals": "^2.0.3", "fast-equals": "^2.0.3",
"autolinker": "4.0.0", "autolinker": "4.0.0",
"emoji-regex": "^10.1.0" "emoji-regex": "^10.1.0",
"moment-timezone": "^0.5.43"
}, },
"repository": "https://github.com/hcenginneing/anticrm", "repository": "https://github.com/hcenginneing/anticrm",
"publishConfig": { "publishConfig": {

View File

@ -27,7 +27,7 @@
IconChevronDown, IconChevronDown,
ActionIcon, ActionIcon,
IconUndo IconUndo
} from '../..' } from '..'
interface TimeZoneGroup { interface TimeZoneGroup {
continent: string continent: string
@ -38,6 +38,7 @@
export let timeZones: TimeZone[] = [] export let timeZones: TimeZone[] = []
export let count: number export let count: number
export let reset: string | null export let reset: string | null
export let withAdd: boolean = true
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
@ -142,18 +143,20 @@
{#each items as item} {#each items as item}
<button class="menu-item no-focus items-center" on:click={() => dispatch('close', item.id)}> <button class="menu-item no-focus items-center" on:click={() => dispatch('close', item.id)}>
<span class="overflow-label label flex-grow">{item.city}</span> <span class="overflow-label label flex-grow">{item.city}</span>
<div class="tool ml-2"> {#if withAdd}
<Button <div class="tool ml-2">
icon={IconAdd} <Button
size={'small'} icon={IconAdd}
kind={'ghost'} size={'small'}
disabled={count > 4} kind={'ghost'}
on:click={() => { disabled={count > 4}
count++ on:click={() => {
dispatch('update', item.id) count++
}} dispatch('update', item.id)
/> }}
</div> />
</div>
{/if}
</button> </button>
{/each} {/each}
</div> </div>

View File

@ -18,11 +18,13 @@
import Icon from '../Icon.svelte' import Icon from '../Icon.svelte'
import Label from '../Label.svelte' import Label from '../Label.svelte'
import IconClose from '../icons/Close.svelte' import IconClose from '../icons/Close.svelte'
import { daysInMonth } from './internal/DateUtils' import { daysInMonth, getUserTimezone } from './internal/DateUtils'
import moment from 'moment-timezone'
export let currentDate: Date | null export let currentDate: Date | null
export let withTime: boolean = false export let withTime: boolean = false
export let kind: 'default' | 'plain' = 'default' export let kind: 'default' | 'plain' = 'default'
export let timeZone: string = getUserTimezone()
type TEdits = 'day' | 'month' | 'year' | 'hour' | 'min' type TEdits = 'day' | 'month' | 'year' | 'hour' | 'min'
interface IEdits { interface IEdits {
@ -44,19 +46,31 @@
if (date == null) date = new Date() if (date == null) date = new Date()
switch (id) { switch (id) {
case 'day': case 'day':
date = new Date(timeZone ? moment(date).tz(timeZone).date(val).valueOf() : moment(date).date(val).valueOf())
date.setDate(val) date.setDate(val)
break break
case 'month': case 'month':
date.setMonth(val - 1) date = new Date(
timeZone
? moment(date)
.tz(timeZone)
.month(val + 1)
.valueOf()
: moment(date)
.month(val + 1)
.valueOf()
)
break break
case 'year': case 'year':
date.setFullYear(val) date = new Date(timeZone ? moment(date).tz(timeZone).year(val).valueOf() : moment(date).year(val).valueOf())
break break
case 'hour': case 'hour':
date.setHours(val) date = new Date(timeZone ? moment(date).tz(timeZone).hours(val).valueOf() : moment(date).hours(val).valueOf())
break break
case 'min': case 'min':
date.setMinutes(val) date = new Date(
timeZone ? moment(date).tz(timeZone).minutes(val).valueOf() : moment(date).minutes(val).valueOf()
)
break break
} }
return date return date
@ -81,15 +95,15 @@
const getValue = (date: Date, id: TEdits): number => { const getValue = (date: Date, id: TEdits): number => {
switch (id) { switch (id) {
case 'day': case 'day':
return date.getDate() return timeZone ? moment(date).tz(timeZone).date() : moment(date).date()
case 'month': case 'month':
return date.getMonth() + 1 return timeZone ? moment(date).tz(timeZone).month() + 1 : moment(date).month() + 1
case 'year': case 'year':
return date.getFullYear() return timeZone ? moment(date).tz(timeZone).year() : moment(date).year()
case 'hour': case 'hour':
return date.getHours() return timeZone ? moment(date).tz(timeZone).hours() : moment(date).hours()
case 'min': case 'min':
return date.getMinutes() return timeZone ? moment(date).tz(timeZone).minutes() : moment(date).minutes()
} }
} }

View File

@ -19,11 +19,12 @@
import { import {
ActionIcon, ActionIcon,
Button, Button,
Scroller, IconClose,
deviceOptionsStore as deviceInfo,
checkAdaptiveMatching,
Label, Label,
IconClose Scroller,
checkAdaptiveMatching,
deviceOptionsStore as deviceInfo,
getUserTimezone
} from '../..' } from '../..'
import ui from '../../plugin' import ui from '../../plugin'
import DateInputBox from './DateInputBox.svelte' import DateInputBox from './DateInputBox.svelte'
@ -36,6 +37,7 @@
export let label = currentDate != null ? ui.string.EditDueDate : ui.string.AddDueDate export let label = currentDate != null ? ui.string.EditDueDate : ui.string.AddDueDate
export let detail: IntlString | undefined = undefined export let detail: IntlString | undefined = undefined
export let noShift: boolean = false export let noShift: boolean = false
export let timeZone: string = getUserTimezone()
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
@ -110,6 +112,7 @@
bind:this={dateInput} bind:this={dateInput}
bind:currentDate bind:currentDate
{withTime} {withTime}
{timeZone}
kind={'plain'} kind={'plain'}
on:close={() => { on:close={() => {
closeDP(withTime) closeDP(withTime)
@ -126,6 +129,7 @@
<MonthSquare <MonthSquare
bind:currentDate bind:currentDate
{viewDate} {viewDate}
{timeZone}
{mondayStart} {mondayStart}
viewUpdate={false} viewUpdate={false}
hideNavigator={'all'} hideNavigator={'all'}
@ -140,6 +144,7 @@
bind:currentDate bind:currentDate
viewDate={viewDateSec} viewDate={viewDateSec}
{mondayStart} {mondayStart}
{timeZone}
viewUpdate={false} viewUpdate={false}
noPadding noPadding
on:update={(result) => { on:update={(result) => {
@ -158,6 +163,7 @@
bind:currentDate bind:currentDate
{viewDate} {viewDate}
{mondayStart} {mondayStart}
{timeZone}
viewUpdate={false} viewUpdate={false}
hideNavigator={'all'} hideNavigator={'all'}
noPadding noPadding
@ -169,6 +175,7 @@
<div class="space" /> <div class="space" />
<MonthSquare <MonthSquare
bind:currentDate bind:currentDate
{timeZone}
viewDate={viewDateSec} viewDate={viewDateSec}
{mondayStart} {mondayStart}
viewUpdate={false} viewUpdate={false}

View File

@ -14,23 +14,26 @@
--> -->
<script lang="ts"> <script lang="ts">
import { afterUpdate, createEventDispatcher } from 'svelte' import { afterUpdate, createEventDispatcher } from 'svelte'
import IconNavPrev from '../icons/NavPrev.svelte'
import IconNavNext from '../icons/NavNext.svelte'
import Icon from '../Icon.svelte'
import {
firstDay,
day,
getWeekDayName,
areDatesEqual,
getMonthName,
daysInMonth,
TCellStyle,
ICell
} from './internal/DateUtils'
import { capitalizeFirstLetter } from '../../utils' import { capitalizeFirstLetter } from '../../utils'
import Icon from '../Icon.svelte'
import IconNavNext from '../icons/NavNext.svelte'
import IconNavPrev from '../icons/NavPrev.svelte'
import {
ICell,
TCellStyle,
areDatesEqual,
day,
daysInMonth,
firstDay,
getMonthName,
getUserTimezone,
getWeekDayName
} from './internal/DateUtils'
import moment from 'moment-timezone'
export let currentDate: Date | null export let currentDate: Date | null
export let mondayStart: boolean = true export let mondayStart: boolean = true
export let timeZone: string = getUserTimezone()
export let hideNavigator: boolean = false export let hideNavigator: boolean = false
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
@ -46,7 +49,16 @@
let days: ICell[] = [] let days: ICell[] = []
const getDateStyle = (date: Date): TCellStyle => { const getDateStyle = (date: Date): TCellStyle => {
if (currentDate != null && areDatesEqual(currentDate, date)) return 'selected' if (currentDate != null) {
const zonedTime = moment(currentDate).tz(timeZone)
if (
zonedTime.date() === date.getDate() &&
zonedTime.year() === date.getFullYear() &&
zonedTime.month() === date.getMonth()
) {
return 'selected'
}
}
return 'not-selected' return 'not-selected'
} }
const renderCellStyles = (): void => { const renderCellStyles = (): void => {

View File

@ -17,8 +17,18 @@
import IconArrowLeft from '../icons/ArrowLeft.svelte' import IconArrowLeft from '../icons/ArrowLeft.svelte'
import IconArrowRight from '../icons/ArrowRight.svelte' import IconArrowRight from '../icons/ArrowRight.svelte'
import Button from '../Button.svelte' import Button from '../Button.svelte'
import { firstDay, day, getWeekDayName, areDatesEqual, getMonthName, weekday, isWeekend } from './internal/DateUtils' import {
firstDay,
day,
getWeekDayName,
areDatesEqual,
getMonthName,
weekday,
isWeekend,
getUserTimezone
} from './internal/DateUtils'
import { capitalizeFirstLetter } from '../../utils' import { capitalizeFirstLetter } from '../../utils'
import moment from 'moment-timezone'
export let currentDate: Date | null export let currentDate: Date | null
export let viewDate: Date export let viewDate: Date
@ -27,6 +37,7 @@
export let viewUpdate: boolean = true export let viewUpdate: boolean = true
export let noPadding: boolean = false export let noPadding: boolean = false
export let displayedWeeksCount = 6 export let displayedWeeksCount = 6
export let timeZone: string = getUserTimezone()
export let selectedTo: Date | null | undefined = undefined export let selectedTo: Date | null | undefined = undefined
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
@ -49,8 +60,26 @@
} }
function isSelected (currentDate: Date | null, selectedTo: Date | null | undefined, target: Date): boolean { function isSelected (currentDate: Date | null, selectedTo: Date | null | undefined, target: Date): boolean {
if (currentDate != null && areDatesEqual(currentDate, target)) return true if (currentDate != null) {
if (selectedTo != null && areDatesEqual(selectedTo, target)) return true const zonedTime = moment(currentDate).tz(timeZone)
if (
zonedTime.date() === target.getDate() &&
zonedTime.year() === target.getFullYear() &&
zonedTime.month() === target.getMonth()
) {
return true
}
}
if (selectedTo != null) {
const zonedTime = moment(selectedTo).tz(timeZone)
if (
zonedTime.date() === target.getDate() &&
zonedTime.year() === target.getFullYear() &&
zonedTime.month() === target.getMonth()
) {
return true
}
}
return false return false
} }

View File

@ -15,8 +15,10 @@
<script lang="ts"> <script lang="ts">
import { createEventDispatcher } from 'svelte' import { createEventDispatcher } from 'svelte'
import Month from './Month.svelte' import Month from './Month.svelte'
import { getUserTimezone } from './internal/DateUtils'
export let currentDate: Date | null export let currentDate: Date | null
export let timeZone: string = getUserTimezone()
export let mondayStart: boolean = true export let mondayStart: boolean = true
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
@ -25,6 +27,7 @@
<div class="antiPopup popup"> <div class="antiPopup popup">
<Month <Month
bind:currentDate bind:currentDate
{timeZone}
{mondayStart} {mondayStart}
on:update={(result) => { on:update={(result) => {
if (result.detail !== undefined) { if (result.detail !== undefined) {

View File

@ -16,11 +16,13 @@
import { afterUpdate, createEventDispatcher } from 'svelte' import { afterUpdate, createEventDispatcher } from 'svelte'
import ui from '../../plugin' import ui from '../../plugin'
import Label from '../Label.svelte' import Label from '../Label.svelte'
import moment from 'moment-timezone'
export let currentDate: Date export let currentDate: Date
export let size: 'small' | 'medium' = 'medium' export let size: 'small' | 'medium' = 'medium'
export let noBorder: boolean = false export let noBorder: boolean = false
export let disabled: boolean = false export let disabled: boolean = false
export let timeZone: string | undefined = undefined
type TEdits = 'hour' | 'min' type TEdits = 'hour' | 'min'
interface IEdits { interface IEdits {
@ -38,14 +40,16 @@
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
const setValue = (val: number, date: Date | null, id: TEdits): Date => { const setValue = (val: number, date: Date | null, id: TEdits, timeZone: string | undefined): Date => {
if (date == null) date = new Date() if (date == null) date = new Date()
switch (id) { switch (id) {
case 'hour': case 'hour':
date.setHours(val) date = new Date(timeZone ? moment(date).tz(timeZone).hours(val).valueOf() : moment(date).hours(val).valueOf())
break break
case 'min': case 'min':
date.setMinutes(val) date = new Date(
timeZone ? moment(date).tz(timeZone).minutes(val).valueOf() : moment(date).minutes(val).valueOf()
)
break break
} }
return date return date
@ -61,23 +65,23 @@
} }
} }
const getValue = (date: Date, id: TEdits): number => { const getValue = (date: Date, id: TEdits, timeZone: string | undefined): number => {
switch (id) { switch (id) {
case 'hour': case 'hour':
return date.getHours() return timeZone ? moment(date).tz(timeZone).hours() : moment(date).hours()
case 'min': case 'min':
return date.getMinutes() return timeZone ? moment(date).tz(timeZone).minutes() : moment(date).minutes()
} }
} }
const dateToEdits = (currentDate: Date | null): void => { const dateToEdits = (currentDate: Date | null, timeZone: string | undefined): void => {
if (currentDate == null) { if (currentDate == null) {
edits.forEach((edit) => { edits.forEach((edit) => {
edit.value = -1 edit.value = -1
}) })
} else { } else {
for (const edit of edits) { for (const edit of edits) {
edit.value = getValue(currentDate, edit.id) edit.value = getValue(currentDate, edit.id, timeZone)
} }
} }
edits = edits edits = edits
@ -85,7 +89,7 @@
export const isNull = (currentDate?: Date): boolean => { export const isNull = (currentDate?: Date): boolean => {
if (currentDate !== undefined) { if (currentDate !== undefined) {
dateToEdits(currentDate) dateToEdits(currentDate, timeZone)
} }
let result: boolean = false let result: boolean = false
edits.forEach((edit, i) => { edits.forEach((edit, i) => {
@ -113,8 +117,8 @@
} }
if (!isNull() && !startTyping) { if (!isNull() && !startTyping) {
fixEdits() fixEdits()
currentDate = setValue(edits[index].value, currentDate, ed) currentDate = setValue(edits[index].value, currentDate, ed, timeZone)
dateToEdits(currentDate) dateToEdits(currentDate, timeZone)
} }
edits = edits edits = edits
dispatch('update', currentDate) dispatch('update', currentDate)
@ -132,8 +136,8 @@
if (edits[index].value !== -1) { if (edits[index].value !== -1) {
const val = ev.code === 'ArrowUp' ? edits[index].value + 1 : edits[index].value - 1 const val = ev.code === 'ArrowUp' ? edits[index].value + 1 : edits[index].value - 1
if (currentDate) { if (currentDate) {
currentDate = setValue(val, currentDate, ed) currentDate = setValue(val, currentDate, ed, timeZone)
dateToEdits(currentDate) dateToEdits(currentDate, timeZone)
dispatch('update', currentDate) dispatch('update', currentDate)
} }
} }
@ -160,7 +164,7 @@
dispatch('save') dispatch('save')
} }
$: dateToEdits(currentDate) $: dateToEdits(currentDate, timeZone)
$: if (selected && edits[getIndex(selected)].el) edits[getIndex(selected)].el?.focus() $: if (selected && edits[getIndex(selected)].el) edits[getIndex(selected)].el?.focus()
afterUpdate(() => { afterUpdate(() => {

View File

@ -11,6 +11,8 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import { type TimeZone } from '../../../types'
export const DAYS_IN_WEEK = 7 export const DAYS_IN_WEEK = 7
export const MILLISECONDS_IN_MINUTE = 60000 export const MILLISECONDS_IN_MINUTE = 60000
@ -127,6 +129,26 @@ export function getFormattedDate (value: number | null): string {
return value === null ? '' : new Date(value).toLocaleString('default', { month: 'short', day: 'numeric' }) return value === null ? '' : new Date(value).toLocaleString('default', { month: 'short', day: 'numeric' })
} }
export const getTimeZoneName = (): string => { export const getTimeZoneName = (val: string = Intl.DateTimeFormat().resolvedOptions().timeZone): string => {
return Intl.DateTimeFormat().resolvedOptions().timeZone?.split('/')[1] ?? '' return val.split('/')[1] ?? ''
}
export const convertTimeZone = (tz: string): TimeZone => {
const tzSpace = tz.replace(/_/gi, ' ')
const parts = tzSpace.split('/')
if (tz === '' || parts.length === 1) return { id: tz, continent: tzSpace, city: tzSpace, short: tzSpace }
return {
id: tz,
continent: parts[0],
city: parts.length > 2 ? `${parts[1]} - ${parts[2]}` : parts[1],
short: parts.length > 2 ? parts[2] : parts[1]
}
}
export function getUserTimezone (): string {
if (window.Intl !== undefined) {
return Intl.DateTimeFormat().resolvedOptions().timeZone
} else {
return 'Etc/GMT'
}
} }

View File

@ -13,9 +13,9 @@
// limitations under the License. // limitations under the License.
--> -->
<script lang="ts"> <script lang="ts">
import { showPopup, TimeZone } from '../..' import { convertTimeZone, showPopup, TimeZone } from '../..'
import ClockFace from './ClockFace.svelte' import ClockFace from './ClockFace.svelte'
import TimeZonesPopup from './TimeZonesPopup.svelte' import TimeZonesPopup from '../TimeZonesPopup.svelte'
const clockSize: string = '80px' const clockSize: string = '80px'
const tzs: string[] = [] const tzs: string[] = []
@ -30,17 +30,6 @@
if (!Intl.supportedValuesOf) console.log('Your browser does not support Intl.supportedValuesOf().') if (!Intl.supportedValuesOf) console.log('Your browser does not support Intl.supportedValuesOf().')
else for (const timeZone of Intl.supportedValuesOf('timeZone')) tzs.push(timeZone) else for (const timeZone of Intl.supportedValuesOf('timeZone')) tzs.push(timeZone)
const convertTimeZone = (tz: string): TimeZone => {
const tzSpace = tz.replaceAll('_', ' ')
const parts = tzSpace.split('/')
if (tz === '' || parts.length === 1) return { id: tz, continent: tzSpace, city: tzSpace, short: tzSpace }
return {
id: tz,
continent: parts[0],
city: parts.length > 2 ? `${parts[1]} - ${parts[2]}` : parts[1],
short: parts.length > 2 ? parts[2] : parts[1]
}
}
if (tzs.length > 0) tzs.forEach((tz) => timeZones.push(convertTimeZone(tz))) if (tzs.length > 0) tzs.forEach((tz) => timeZones.push(convertTimeZone(tz)))
const saveTZ = (): void => { const saveTZ = (): void => {

View File

@ -209,6 +209,7 @@ export { default as ModeSelector } from './components/ModeSelector.svelte'
export { default as SimpleTimePopup } from './components/calendar/SimpleTimePopup.svelte' export { default as SimpleTimePopup } from './components/calendar/SimpleTimePopup.svelte'
export { default as NumberInput } from './components/NumberInput.svelte' export { default as NumberInput } from './components/NumberInput.svelte'
export { default as Lazy } from './components/Lazy.svelte' export { default as Lazy } from './components/Lazy.svelte'
export { default as TimeZonesPopup } from './components/TimeZonesPopup.svelte'
export * from './types' export * from './types'
export * from './location' export * from './location'

View File

@ -51,6 +51,7 @@
"@hcengineering/view-resources": "^0.6.0", "@hcengineering/view-resources": "^0.6.0",
"@hcengineering/view": "^0.6.9", "@hcengineering/view": "^0.6.9",
"@hcengineering/workbench": "^0.6.9", "@hcengineering/workbench": "^0.6.9",
"fast-equals": "^2.0.3" "fast-equals": "^2.0.3",
"moment-timezone": "^0.5.43"
} }
} }

View File

@ -18,19 +18,28 @@
import { Class, Doc, Ref, getCurrentAccount } from '@hcengineering/core' import { Class, Doc, Ref, getCurrentAccount } from '@hcengineering/core'
import presentation, { createQuery, getClient } from '@hcengineering/presentation' import presentation, { createQuery, getClient } from '@hcengineering/presentation'
import { StyledTextBox } from '@hcengineering/text-editor' import { StyledTextBox } from '@hcengineering/text-editor'
import { Button, EditBox, Icon, IconClose, IconMoreH, createFocusManager, showPopup } from '@hcengineering/ui' import {
Button,
EditBox,
FocusHandler,
Icon,
IconClose,
IconMoreH,
createFocusManager,
getUserTimezone,
showPopup
} from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte' import { createEventDispatcher } from 'svelte'
import calendar from '../plugin' import calendar from '../plugin'
import { saveUTC } from '../utils' import { saveUTC } from '../utils'
import CalendarSelector from './CalendarSelector.svelte'
import EventParticipants from './EventParticipants.svelte' import EventParticipants from './EventParticipants.svelte'
import EventReminders from './EventReminders.svelte' import EventReminders from './EventReminders.svelte'
import EventTimeEditor from './EventTimeEditor.svelte' import EventTimeEditor from './EventTimeEditor.svelte'
import EventTimeExtraButton from './EventTimeExtraButton.svelte' import EventTimeExtraButton from './EventTimeExtraButton.svelte'
import LocationEditor from './LocationEditor.svelte'
import ReccurancePopup from './ReccurancePopup.svelte' import ReccurancePopup from './ReccurancePopup.svelte'
import VisibilityEditor from './VisibilityEditor.svelte' import VisibilityEditor from './VisibilityEditor.svelte'
import CalendarSelector from './CalendarSelector.svelte'
import LocationEditor from './LocationEditor.svelte'
import FocusHandler from '@hcengineering/ui/src/components/FocusHandler.svelte'
export let attachedTo: Ref<Doc> = calendar.ids.NoAttached export let attachedTo: Ref<Doc> = calendar.ids.NoAttached
export let attachedToClass: Ref<Class<Doc>> = calendar.class.Event export let attachedToClass: Ref<Class<Doc>> = calendar.class.Event
@ -41,7 +50,6 @@
const now = new Date() const now = new Date()
const defaultDuration = 60 * 60 * 1000 const defaultDuration = 60 * 60 * 1000
const allDayDuration = 24 * 60 * 60 * 1000 - 1 const allDayDuration = 24 * 60 * 60 * 1000 - 1
// const offsetTZ = new Date().getTimezoneOffset() * 60 * 1000
let startDate = let startDate =
date === undefined ? now.getTime() : withTime ? date.getTime() : date.setHours(now.getHours(), now.getMinutes()) date === undefined ? now.getTime() : withTime ? date.getTime() : date.setHours(now.getHours(), now.getMinutes())
@ -49,6 +57,7 @@
let dueDate = startDate + duration let dueDate = startDate + duration
let allDay = false let allDay = false
let location = '' let location = ''
let timeZone: string = getUserTimezone()
let reminders = [30 * 60 * 1000] let reminders = [30 * 60 * 1000]
@ -99,7 +108,8 @@
location, location,
allDay, allDay,
access: 'owner', access: 'owner',
originalStartTime: allDay ? saveUTC(date) : date originalStartTime: allDay ? saveUTC(date) : date,
timeZone
}) })
} else { } else {
await client.addCollection(calendar.class.Event, space, attachedTo, attachedToClass, 'events', { await client.addCollection(calendar.class.Event, space, attachedTo, attachedToClass, 'events', {
@ -114,6 +124,7 @@
title, title,
location, location,
allDay, allDay,
timeZone,
access: 'owner' access: 'owner'
}) })
} }
@ -168,8 +179,14 @@
</div> </div>
</div> </div>
<div class="block first flex-no-shrink"> <div class="block first flex-no-shrink">
<EventTimeEditor {allDay} bind:startDate bind:dueDate focusIndex={10004} /> <EventTimeEditor {allDay} bind:startDate bind:dueDate {timeZone} focusIndex={10004} />
<EventTimeExtraButton bind:allDay bind:rules on:repeat={setRecurrance} on:allday={allDayChangeHandler} /> <EventTimeExtraButton
bind:allDay
bind:timeZone
bind:rules
on:repeat={setRecurrance}
on:allday={allDayChangeHandler}
/>
</div> </div>
<div class="block rightCropPadding"> <div class="block rightCropPadding">
<LocationEditor focusIndex={10010} bind:value={location} /> <LocationEditor focusIndex={10010} bind:value={location} />

View File

@ -19,10 +19,11 @@
ButtonSize, ButtonSize,
DatePopup, DatePopup,
SimpleDatePopup, SimpleDatePopup,
eventToHTMLElement,
showPopup,
TimeInputBox, TimeInputBox,
TimeShiftPresenter TimeShiftPresenter,
eventToHTMLElement,
getUserTimezone,
showPopup
} from '@hcengineering/ui' } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte' import { createEventDispatcher } from 'svelte'
import DateLocalePresenter from './DateLocalePresenter.svelte' import DateLocalePresenter from './DateLocalePresenter.svelte'
@ -36,6 +37,7 @@
export let size: ButtonSize = 'medium' export let size: ButtonSize = 'medium'
export let disabled: boolean = false export let disabled: boolean = false
export let focusIndex = -1 export let focusIndex = -1
export let timeZone: string = getUserTimezone()
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
@ -45,7 +47,7 @@
if (!showDate) { if (!showDate) {
showPopup( showPopup(
DatePopup, DatePopup,
{ currentDate, withTime: !withoutTime, label: ui.string.SelectDate, noShift: true }, { currentDate, withTime: !withoutTime, timeZone, label: ui.string.SelectDate, noShift: true },
undefined, undefined,
(res) => { (res) => {
if (res) { if (res) {
@ -58,7 +60,7 @@
} }
function dateClick (e: MouseEvent) { function dateClick (e: MouseEvent) {
showPopup(SimpleDatePopup, { currentDate }, eventToHTMLElement(e), (res) => { showPopup(SimpleDatePopup, { currentDate, timeZone }, eventToHTMLElement(e), (res) => {
if (res) { if (res) {
date = res.getTime() date = res.getTime()
dispatch('update', date) dispatch('update', date)
@ -80,10 +82,11 @@
{#if showDate || withoutTime} {#if showDate || withoutTime}
<Button {kind} {size} padding={'0 .5rem'} {focusIndex} on:click={dateClick} {disabled}> <Button {kind} {size} padding={'0 .5rem'} {focusIndex} on:click={dateClick} {disabled}>
<svelte:fragment slot="content"> <svelte:fragment slot="content">
<DateLocalePresenter date={currentDate.getTime()} /> <DateLocalePresenter date={currentDate.getTime()} {timeZone} />
</svelte:fragment> </svelte:fragment>
</Button> </Button>
{/if} {/if}
{#if !withoutTime} {#if !withoutTime}
<Button <Button
{kind} {kind}
@ -96,6 +99,7 @@
<svelte:fragment slot="content"> <svelte:fragment slot="content">
<TimeInputBox <TimeInputBox
bind:currentDate bind:currentDate
{timeZone}
noBorder noBorder
size={'small'} size={'small'}
on:update={(date) => { on:update={(date) => {

View File

@ -13,9 +13,14 @@
// limitations under the License. // limitations under the License.
--> -->
<script lang="ts"> <script lang="ts">
import { getUserTimezone } from '@hcengineering/ui'
export let date: number export let date: number
export let timeZone: string = getUserTimezone()
const current = new Date() const current = new Date()
let options: Intl.DateTimeFormatOptions = { let options: Intl.DateTimeFormatOptions = {
timeZone,
day: 'numeric', day: 'numeric',
weekday: 'short', weekday: 'short',
month: 'short' month: 'short'
@ -26,6 +31,8 @@
year: '2-digit' year: '2-digit'
} }
} }
$: options.timeZone = timeZone
</script> </script>
{new Date(date).toLocaleDateString('default', options)} {new Date(date).toLocaleDateString('default', options)}

View File

@ -18,20 +18,28 @@
import { DocumentUpdate, Ref } from '@hcengineering/core' import { DocumentUpdate, Ref } from '@hcengineering/core'
import presentation, { getClient } from '@hcengineering/presentation' import presentation, { getClient } from '@hcengineering/presentation'
import { StyledTextBox } from '@hcengineering/text-editor' import { StyledTextBox } from '@hcengineering/text-editor'
import { Button, EditBox, Icon, IconClose, createFocusManager, showPopup } from '@hcengineering/ui' import {
Button,
EditBox,
FocusHandler,
Icon,
IconClose,
createFocusManager,
getUserTimezone,
showPopup
} from '@hcengineering/ui'
import { deepEqual } from 'fast-equals' import { deepEqual } from 'fast-equals'
import { createEventDispatcher } from 'svelte' import { createEventDispatcher } from 'svelte'
import calendar from '../plugin' import calendar from '../plugin'
import { isReadOnly, saveUTC, updateReccuringInstance } from '../utils' import { isReadOnly, saveUTC, updateReccuringInstance } from '../utils'
import CalendarSelector from './CalendarSelector.svelte'
import EventParticipants from './EventParticipants.svelte' import EventParticipants from './EventParticipants.svelte'
import EventReminders from './EventReminders.svelte' import EventReminders from './EventReminders.svelte'
import EventTimeEditor from './EventTimeEditor.svelte' import EventTimeEditor from './EventTimeEditor.svelte'
import EventTimeExtraButton from './EventTimeExtraButton.svelte' import EventTimeExtraButton from './EventTimeExtraButton.svelte'
import LocationEditor from './LocationEditor.svelte'
import ReccurancePopup from './ReccurancePopup.svelte' import ReccurancePopup from './ReccurancePopup.svelte'
import VisibilityEditor from './VisibilityEditor.svelte' import VisibilityEditor from './VisibilityEditor.svelte'
import CalendarSelector from './CalendarSelector.svelte'
import LocationEditor from './LocationEditor.svelte'
import FocusHandler from '@hcengineering/ui/src/components/FocusHandler.svelte'
export let object: Event export let object: Event
$: readOnly = isReadOnly(object) $: readOnly = isReadOnly(object)
@ -48,6 +56,7 @@
let visibility = object.visibility ?? 'public' let visibility = object.visibility ?? 'public'
let reminders = [...(object.reminders ?? [])] let reminders = [...(object.reminders ?? [])]
let space = object.space let space = object.space
let timeZone: string = object.timeZone ?? getUserTimezone()
let description = object.description let description = object.description
let location = object.location let location = object.location
@ -84,6 +93,9 @@
if (object.location !== location) { if (object.location !== location) {
update.location = location update.location = location
} }
if (object.timeZone !== timeZone) {
update.timeZone = timeZone
}
if (allDay !== object.allDay) { if (allDay !== object.allDay) {
update.date = allDay ? saveUTC(startDate) : startDate update.date = allDay ? saveUTC(startDate) : startDate
update.dueDate = allDay ? saveUTC(dueDate) : dueDate update.dueDate = allDay ? saveUTC(dueDate) : dueDate
@ -170,8 +182,16 @@
</div> </div>
</div> </div>
<div class="block first flex-no-shrink"> <div class="block first flex-no-shrink">
<EventTimeEditor {allDay} bind:startDate bind:dueDate disabled={readOnly} focusIndex={10004} /> <EventTimeEditor {allDay} bind:startDate {timeZone} bind:dueDate disabled={readOnly} focusIndex={10004} />
<EventTimeExtraButton bind:allDay bind:rules on:repeat={setRecurrance} on:allday={allDayChangeHandler} noRepeat /> <EventTimeExtraButton
bind:allDay
bind:timeZone
bind:rules
on:repeat={setRecurrance}
{readOnly}
on:allday={allDayChangeHandler}
noRepeat
/>
</div> </div>
<div class="block rightCropPadding"> <div class="block rightCropPadding">
<LocationEditor bind:value={location} focusIndex={10005} /> <LocationEditor bind:value={location} focusIndex={10005} />

View File

@ -13,18 +13,20 @@
// limitations under the License. // limitations under the License.
--> -->
<script lang="ts"> <script lang="ts">
import { Icon, areDatesEqual, IconArrowRight } from '@hcengineering/ui' import { Icon, IconArrowRight, getUserTimezone } from '@hcengineering/ui'
import moment from 'moment-timezone'
import { createEventDispatcher } from 'svelte'
import calendar from '../plugin' import calendar from '../plugin'
import DateEditor from './DateEditor.svelte' import DateEditor from './DateEditor.svelte'
import { createEventDispatcher } from 'svelte'
export let startDate: number export let startDate: number
export let dueDate: number export let dueDate: number
export let allDay: boolean export let allDay: boolean
export let disabled: boolean = false export let disabled: boolean = false
export let timeZone: string = getUserTimezone()
export let focusIndex = -1 export let focusIndex = -1
$: sameDate = areDatesEqual(new Date(startDate), new Date(dueDate)) $: sameDate = moment(startDate).tz(timeZone).isSame(moment(dueDate).tz(timeZone), 'date')
let diff = dueDate - startDate let diff = dueDate - startDate
const allDayDuration = 24 * 60 * 60 * 1000 - 1 const allDayDuration = 24 * 60 * 60 * 1000 - 1
@ -56,6 +58,7 @@
bind:date={startDate} bind:date={startDate}
direction={sameDate ? 'horizontal' : 'vertical'} direction={sameDate ? 'horizontal' : 'vertical'}
withoutTime={allDay} withoutTime={allDay}
{timeZone}
on:update={dateChange} on:update={dateChange}
{disabled} {disabled}
{focusIndex} {focusIndex}
@ -70,6 +73,7 @@
showDate={!sameDate} showDate={!sameDate}
difference={startDate} difference={startDate}
{disabled} {disabled}
{timeZone}
focusIndex={focusIndex !== -1 ? focusIndex + 1 : focusIndex} focusIndex={focusIndex !== -1 ? focusIndex + 1 : focusIndex}
on:update={dueChange} on:update={dueChange}
/> />

View File

@ -14,28 +14,67 @@
--> -->
<script lang="ts"> <script lang="ts">
import { RecurringRule } from '@hcengineering/calendar' import { RecurringRule } from '@hcengineering/calendar'
import { Button, CheckBox, Icon, Label, MILLISECONDS_IN_MINUTE } from '@hcengineering/ui' import {
Button,
CheckBox,
Icon,
Label,
TimeZone,
TimeZonesPopup,
convertTimeZone,
eventToHTMLElement,
getUserTimezone,
showPopup
} from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte' import { createEventDispatcher } from 'svelte'
import calendar from '../plugin' import calendar from '../plugin'
import RRulePresenter from './RRulePresenter.svelte' import RRulePresenter from './RRulePresenter.svelte'
import TimeZoneSelector from './TimeZoneSelector.svelte'
export let allDay: boolean export let allDay: boolean
export let rules: RecurringRule[] = [] export let rules: RecurringRule[] = []
export let noRepeat: boolean = false export let noRepeat: boolean = false
export let timeZone: string
export let readOnly: boolean = false
const myTimezone = getUserTimezone()
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
const offsetTZ = new Date().getTimezoneOffset() * 60 * 1000 function showTimezoneSelector (e: MouseEvent) {
const timeZones: TimeZone[] = []
const tzs: string[] = []
if (!Intl.supportedValuesOf) console.log('Your browser does not support Intl.supportedValuesOf().')
else for (const timeZone of Intl.supportedValuesOf('timeZone')) tzs.push(timeZone)
if (tzs.length > 0) tzs.forEach((tz) => timeZones.push(convertTimeZone(tz)))
showPopup(
TimeZonesPopup,
{
timeZones,
withAdd: false,
selected: timeZone,
count: 1,
reset: null
},
eventToHTMLElement(e),
(result) => {
if (result !== undefined) {
timeZone = result
}
}
)
}
</script> </script>
{#if !allDay && rules.length === 0} {#if !allDay && rules.length === 0 && myTimezone === timeZone}
<div class="antiButton ghost x-small sh-no-shape text-11px pl-2 pr-2 pt-1 pb-1 mt-1 ml-5-5 gap-3 w-min"> <div class="antiButton ghost x-small sh-no-shape text-11px pl-2 pr-2 pt-1 pb-1 mt-1 ml-5-5 gap-3 w-min">
<!-- svelte-ignore a11y-click-events-have-key-events --> <!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions --> <!-- svelte-ignore a11y-no-static-element-interactions -->
<div class="overflow-label cursor-pointer" on:click={() => (allDay = true)}> <div class="overflow-label cursor-pointer" on:click={() => (allDay = true)}>
<Label label={calendar.string.AllDay} /> <Label label={calendar.string.AllDay} />
</div> </div>
<div class="overflow-label cursor-default"> <div class="overflow-label cursor-default" on:click={showTimezoneSelector}>
<Label label={calendar.string.TimeZone} /> <Label label={calendar.string.TimeZone} />
</div> </div>
{#if !noRepeat} {#if !noRepeat}
@ -54,6 +93,7 @@
kind={'ghost'} kind={'ghost'}
padding={'0 .5rem'} padding={'0 .5rem'}
justify={'left'} justify={'left'}
disabled={readOnly}
on:click={() => { on:click={() => {
allDay = !allDay allDay = !allDay
dispatch('allday') dispatch('allday')
@ -62,13 +102,7 @@
</div> </div>
<div class="flex-row-center gap-1-5 mt-1"> <div class="flex-row-center gap-1-5 mt-1">
<Icon icon={calendar.icon.Globe} size={'small'} fill={'var(--theme-dark-color)'} /> <Icon icon={calendar.icon.Globe} size={'small'} fill={'var(--theme-dark-color)'} />
<Button label={calendar.string.TimeZone} kind={'ghost'} padding={'0 .5rem'} justify={'left'}> <TimeZoneSelector bind:timeZone disabled={readOnly} />
<svelte:fragment slot="content">
<span class="ml-2 content-darker-color">
GMT{(offsetTZ > 0 ? '-' : '+') + Math.abs(offsetTZ / MILLISECONDS_IN_MINUTE / 60)}
</span>
</svelte:fragment>
</Button>
</div> </div>
{#if !noRepeat} {#if !noRepeat}
<div class="flex-row-center gap-1-5 mt-1"> <div class="flex-row-center gap-1-5 mt-1">
@ -78,6 +112,7 @@
kind={'ghost'} kind={'ghost'}
padding={'0 .5rem'} padding={'0 .5rem'}
justify={'left'} justify={'left'}
disabled={readOnly}
on:click={() => dispatch('repeat')} on:click={() => dispatch('repeat')}
> >
<svelte:fragment slot="content"> <svelte:fragment slot="content">

View File

@ -0,0 +1,62 @@
<!--
// Copyright © 2023 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import {
Button,
TimeZone,
TimeZonesPopup,
convertTimeZone,
eventToHTMLElement,
getTimeZoneName,
showPopup
} from '@hcengineering/ui'
import calendar from '../plugin'
export let timeZone: string
export let disabled: boolean = false
function open (e: MouseEvent) {
const timeZones: TimeZone[] = []
const tzs: string[] = []
if (!Intl.supportedValuesOf) console.log('Your browser does not support Intl.supportedValuesOf().')
else for (const timeZone of Intl.supportedValuesOf('timeZone')) tzs.push(timeZone)
if (tzs.length > 0) tzs.forEach((tz) => timeZones.push(convertTimeZone(tz)))
showPopup(
TimeZonesPopup,
{
timeZones,
withAdd: false,
selected: timeZone,
count: 1,
reset: null
},
eventToHTMLElement(e),
(result) => {
if (result !== undefined) {
timeZone = result
}
}
)
}
</script>
<Button {disabled} label={calendar.string.TimeZone} kind={'ghost'} padding={'0 .5rem'} justify={'left'} on:click={open}>
<svelte:fragment slot="content">
<span class="ml-2 content-darker-color">
{getTimeZoneName(timeZone)}
</span>
</svelte:fragment>
</Button>

View File

@ -102,7 +102,8 @@ async function deleteRecHandler (res: any, object: ReccuringInstance): Promise<v
rules: object.rules, rules: object.rules,
exdate: object.exdate, exdate: object.exdate,
visibility: object.visibility, visibility: object.visibility,
access: object.access access: object.access,
timeZone: object.timeZone
}, },
object._id object._id
) )

View File

@ -162,6 +162,7 @@ export async function updateReccuringInstance (
rules: object.rules, rules: object.rules,
exdate: object.exdate, exdate: object.exdate,
rdate: object.rdate, rdate: object.rdate,
timeZone: object.timeZone,
...ops ...ops
}, },
object._id object._id

View File

@ -69,6 +69,7 @@ export interface ReccuringEvent extends Event {
exdate: Timestamp[] exdate: Timestamp[]
rdate: Timestamp[] rdate: Timestamp[]
originalStartTime: Timestamp originalStartTime: Timestamp
timeZone: string
} }
/** /**
@ -101,6 +102,8 @@ export interface Event extends AttachedDoc {
visibility?: Visibility visibility?: Visibility
access: 'freeBusyReader' | 'reader' | 'writer' | 'owner' access: 'freeBusyReader' | 'reader' | 'writer' | 'owner'
timeZone?: string
} }
/** /**