Calendar Week view and some fixes (#1220)

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Andrey Sobolev 2022-03-29 15:51:34 +07:00 committed by GitHub
parent f003dcd5a0
commit 24dfc7f274
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 321 additions and 19 deletions

View File

@ -33,6 +33,6 @@
"@anticrm/tags-resources": "~0.6.0",
"@anticrm/ui": "~0.6.0",
"@anticrm/model-core": "~0.6.0",
"@anticrm/model-view": "~0.6.0"
"@anticrm/model-view": "~0.6.0"
}
}

View File

@ -407,6 +407,10 @@
}
}
.antiTable-body__border {
border: 1px solid var(--theme-button-border-hovered);
}
&.highlightRows .antiTable-body__row {
&:hover, &.checking { background-color: var(--theme-table-bg-hover); }
}

View File

@ -13,6 +13,7 @@
// limitations under the License.
-->
<script type="ts">
import { createEventDispatcher } from 'svelte'
import { areDatesEqual, day, firstDay, getWeekDayName, isWeekend, weekday } from './internal/DateUtils'
export let mondayStart = true
@ -22,10 +23,13 @@
export let currentDate: Date = new Date()
export let displayedWeeksCount = 6
const dispatch = createEventDispatcher()
$: firstDayOfCurrentMonth = firstDay(currentDate, mondayStart)
function onSelect (date: Date) {
value = date
dispatch('change', value)
}
const todayDate = new Date()

View File

@ -0,0 +1,103 @@
<!--
// Copyright © 2022 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 type="ts">
import { createEventDispatcher } from 'svelte'
import { addZero, day, getMonday, getWeekDayName } from './internal/DateUtils'
export let mondayStart = true
export let cellHeight: string | undefined = undefined
export let value: Date = new Date()
export let currentDate: Date = new Date()
export let displayedDaysCount = 7
export let displayedHours = 24
// export let startHour = 0
const dispatch = createEventDispatcher()
$: weekMonday = getMonday(currentDate, mondayStart)
function onSelect (date: Date) {
value = date
dispatch('change', value)
}
</script>
<table class="antiTable">
<thead class="scroller-thead">
<tr class="scroller-thead__tr">
<th>Hours</th>
{#each [...Array(displayedDaysCount).keys()] as dayOfWeek}
<th>
<div class="antiTable-cells">
{getWeekDayName(day(weekMonday, dayOfWeek), 'short')}
{day(weekMonday, dayOfWeek).getDate()}
</div>
</th>
{/each}
</tr>
</thead>
<tbody>
{#if $$slots.cell}
<slot name="header" date={day(weekMonday, 0)} days={displayedDaysCount} />
{/if}
{#each [...Array(displayedHours).keys()] as hourOfDay, row}
<tr class="antiTable-body__row">
<td style="width: 50px;" class="calendar-td">
<div class="antiTable-cells__firstCell">
{addZero(hourOfDay)}:00
</div>
</td>
{#each [...Array(displayedDaysCount).keys()] as dayIndex}
<td
class="antiTable-body__border calendar-td cell"
style={`height: ${cellHeight};`}
on:click={(evt) => {
onSelect(day(weekMonday, dayIndex))
}}
>
{#if $$slots.cell}
<slot name="cell" date={day(weekMonday, dayIndex, hourOfDay * 60)} />
{/if}
</td>
{/each}
</tr>
{/each}
</tbody>
</table>
<style lang="scss">
.calendar-td {
padding: 0;
margin: 0;
}
.selected {
// border-radius: 3px;
background-color: var(--primary-button-enabled);
// border-color: var(--primary-button-focused-border);
color: var(--primary-button-color);
border-radius: 0.5rem;
height: calc(100% - 5px);
width: calc(100% - 5px);
}
.cell {
width: 8rem;
overflow: hidden;
}
.cell:hover:not(.wrongMonth) {
// border: 1px solid var(--primary-button-focused-border);
background-color: var(--primary-button-enabled);
color: var(--primary-button-color);
}
</style>

View File

@ -39,7 +39,7 @@
{#each [...Array(12).keys()] as m}
<div class="antiComponentBox mt-2 mb-2 ml-2 mr-2 flex-grow" style={`min-width: ${minWidth};`}>
{getMonthName(month(value, m))}
<MonthCalendar {cellHeight} weekFormat="narrow" bind:value currentDate={month(currentDate, m)} {mondayStart}>
<MonthCalendar {cellHeight} weekFormat="narrow" bind:value currentDate={month(currentDate, m)} {mondayStart} on:change>
<!----> eslint-disable-next-line no-undef -->
<svelte:fragment slot="cell" let:date={date}>
<slot name="cell" date={date} />

View File

@ -11,9 +11,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.
const DAYS_IN_WEEK = 7
const MILLISECONDS_IN_DAY = 86400000
const MILLISECONDS_IN_MINUTE = 60000
export function firstDay (date: Date, mondayStart: boolean): Date {
const firstDayOfMonth = new Date(date)
@ -40,8 +40,8 @@ export function getWeekDayName (weekDay: Date, weekFormat: 'narrow' | 'short' |
}).format(weekDay)
}
export function day (firstDay: Date, offset: number): Date {
return new Date(firstDay.getTime() + offset * MILLISECONDS_IN_DAY)
export function day (firstDay: Date, offset: number, minutes?: number): Date {
return new Date(firstDay.getTime() + offset * MILLISECONDS_IN_DAY + (minutes ?? 0) * MILLISECONDS_IN_MINUTE)
}
export function weekday (firstDay: Date, w: number, d: number): Date {
@ -67,3 +67,16 @@ export function getMonthName (date: Date): string {
const locale = new Intl.NumberFormat().resolvedOptions().locale
return new Intl.DateTimeFormat(locale, { month: 'long' }).format(date)
}
export function getMonday (d: Date, mondayStart: boolean): Date {
d = new Date(d)
const day = d.getDay()
const diff = d.getDate() - day + (day === 0 ? -6 : 1) // adjust when day is sunday
return new Date(d.setDate(diff))
}
export function addZero (value: number): string {
if (value < 10) {
return `0${value}`
}
return `${value}`
}

View File

@ -120,3 +120,4 @@ export * from './colors'
export { default as MonthCalendar } from './components/calendar/MonthCalendar.svelte'
export { default as YearCalendar } from './components/calendar/YearCalendar.svelte'
export { default as WeekCalendar } from './components/calendar/WeekCalendar.svelte'

View File

@ -16,9 +16,11 @@
import { Event } from '@anticrm/calendar'
import { Class, Doc, DocumentQuery, FindOptions, Ref, SortingOrder, Space } from '@anticrm/core'
import { createQuery } from '@anticrm/presentation'
import { Button, IconBack, IconForward, MonthCalendar, ScrollBox, YearCalendar } from '@anticrm/ui'
import { Button, IconBack, IconForward, MonthCalendar, ScrollBox, WeekCalendar, YearCalendar } from '@anticrm/ui'
import Scroller from '@anticrm/ui/src/components/Scroller.svelte'
import calendar from '../plugin'
import Day from './Day.svelte'
import Hour from './Hour.svelte'
export let _class: Ref<Class<Doc>>
export let space: Ref<Space> | undefined = undefined
@ -38,7 +40,7 @@
let loading = false
let resultQuery: DocumentQuery<Event>
$: spaceOpt = (space ? { space } : {})
$: spaceOpt = space ? { space } : {}
$: resultQuery = search === '' ? { ...query, ...spaceOpt } : { ...query, $search: search, ...spaceOpt }
let objects: Event[] = []
@ -73,12 +75,48 @@
)
}
function findEvents (events: Event[], date: Date): Event[] {
return events.filter(
(it) => areDatesLess(new Date(it.date), date) && areDatesLess(date, new Date(it.dueDate ?? it.date))
function isSameDay (firstDate: Date, secondDate: Date): boolean {
return (
firstDate.getFullYear() === secondDate.getFullYear() &&
firstDate.getMonth() === secondDate.getMonth() &&
firstDate.getDate() === secondDate.getDate()
)
}
function findEvents (events: Event[], date: Date, minutes = false): Event[] {
return events.filter((it) => {
const d1 = new Date(it.date)
const d2 = new Date(it.dueDate ?? it.date)
const inDays = areDatesLess(d1, date) && areDatesLess(date, d2)
if (minutes) {
if (isSameDay(d1, date)) {
return (date.getTime() < d1.getTime() && d1.getTime() < date.getTime() + 3600 * 1000) ||
(d1.getTime() < date.getTime() && date.getTime() <= d2.getTime())
}
if (isSameDay(d2, date)) {
return (date.getTime() < d2.getTime() && d2.getTime() < date.getTime() + 3600000) ||
(date.getTime() < d2.getTime() && d1.getTime() < date.getTime())
}
// Somethere in middle
return inDays
}
return inDays
})
}
function findDayEvents (events: Event[], date: Date): Event[] {
return events.filter((it) => {
const d1 = new Date(it.date)
const d2 = new Date(it.dueDate ?? it.date)
const inDays = areDatesLess(d1, date) && areDatesLess(date, d2)
return inDays && !isSameDay(d1, date) && !isSameDay(d2, date)
})
}
interface ShiftType {
yearShift: number
monthShift: number
@ -128,6 +166,12 @@
month: 'long'
}).format(date)
}
function getWeekName (date: Date): string {
const onejan = new Date(date.getFullYear(), 0, 1)
const week = Math.ceil(((date.getTime() - onejan.getTime()) / 86400000 + onejan.getDay() + 1) / 7)
return `W${week}`
}
function currentDate (date: Date, shifts: ShiftType): Date {
const res = new Date(date)
@ -152,7 +196,7 @@
return `${date.getDate()} ${getMonthName(date)} ${date.getFullYear()}`
}
case CalendarMode.Week: {
return `${getMonthName(date)} ${date.getFullYear()}`
return `${getWeekName(date)} ${getMonthName(date)} ${date.getFullYear()}`
}
case CalendarMode.Month: {
return `${getMonthName(date)} ${date.getFullYear()}`
@ -175,14 +219,14 @@
on:click={() => {
mode = CalendarMode.Day
}}
/>
/> -->
<Button
size={'small'}
label={calendar.string.ModeWeek}
on:click={() => {
mode = CalendarMode.Week
}}
/> -->
/>
<Button
size={'small'}
label={calendar.string.ModeMonth}
@ -238,7 +282,15 @@
{#if mode === CalendarMode.Year}
<ScrollBox bothScroll>
<YearCalendar {mondayStart} cellHeight={'2.5rem'} bind:value currentDate={currentDate(date, shifts)}>
<YearCalendar
{mondayStart}
cellHeight={'2.5rem'}
bind:value
currentDate={currentDate(date, shifts)}
on:change={(evt) => {
date = evt.detail
}}
>
<svelte:fragment slot="cell" let:date>
<Day
events={findEvents(objects, date)}
@ -254,7 +306,15 @@
</ScrollBox>
{:else if mode === CalendarMode.Month}
<div class="flex flex-grow">
<MonthCalendar {mondayStart} cellHeight={'8.5rem'} bind:value currentDate={currentDate(date, shifts)}>
<MonthCalendar
{mondayStart}
cellHeight={'8.5rem'}
bind:value
currentDate={currentDate(date, shifts)}
on:change={(evt) => {
date = evt.detail
}}
>
<svelte:fragment slot="cell" let:date>
<Day
events={findEvents(objects, date)}
@ -269,4 +329,37 @@
</svelte:fragment>
</MonthCalendar>
</div>
{:else if mode === CalendarMode.Week}
<Scroller>
<WeekCalendar
{mondayStart}
cellHeight={'4.5rem'}
bind:value
currentDate={currentDate(date, shifts)}
on:change={(evt) => {
date = evt.detail
}}
>
<svelte:fragment slot="cell" let:date>
<Hour
events={findEvents(objects, date, true)}
{date}
{_class}
{baseMenuClass}
{options}
{config}
query={resultQuery}
/>
</svelte:fragment>
<svelte:fragment slot="header" let:date let:days>
<tr class="antiTable-body__row">
<td class="whitespace-nowrap">All day</td>
{#each [...Array(days).keys()] as day}
<td class="antiTable-body__border" />
{/each}
</tr>
</svelte:fragment>
</WeekCalendar>
</Scroller>
{/if}

View File

@ -80,7 +80,6 @@
<style lang="scss">
.cell {
display: flex;
flex-grow: 1;
justify-content: center;
.marker {

View File

@ -0,0 +1,83 @@
<!--
// Copyright © 2022 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 { Event } from '@anticrm/calendar'
import { Class, Doc, DocumentQuery, FindOptions, Ref } from '@anticrm/core'
import { Tooltip } from '@anticrm/ui'
import { addZero } from '@anticrm/ui/src/components/calendar/internal/DateUtils'
import calendar from '../plugin'
import EventsPopup from './EventsPopup.svelte'
export let events: Event[]
export let date: Date
export let _class: Ref<Class<Doc>>
export let query: DocumentQuery<Event> = {}
export let options: FindOptions<Event> | undefined = undefined
export let baseMenuClass: Ref<Class<Event>> | undefined = undefined
export let config: string[]
$: sorted = Array.from(events).sort((a, b) => a.date - b.date)
function from (eDate: number, date: Date): string {
const dd = new Date(Math.max(eDate, date.getTime()))
return `${addZero(date.getHours())}:${addZero(dd.getMinutes())}`
}
function to (dueDate: number, date:Date): string {
return `${addZero(date.getHours())}:${addZero(Math.min(59, Math.floor((dueDate - date.getTime()) / 60000)))}`
}
</script>
{#if events.length > 0}
<Tooltip
fill={true}
label={calendar.string.Events}
component={EventsPopup}
props={{ value: events, _class, query, options, baseMenuClass, config }}
>
<div class="cell">
<div class="flex flex-col flex-grow">
{#each sorted.slice(0, 4) as e, ei}
<div class="overflow-label flex flex-between">
{e.title}
<div>
{from(e.date, date)}
-
{to(e.dueDate ?? e.date, date)}
</div>
</div>
{/each}
{#if events.length > 4}
And {events.length - 4} more
{/if}
</div>
</div>
</Tooltip>
{/if}
<style lang="scss">
.cell {
padding: 0.5rem;
display: flex;
width: 100%;
height: 100%;
justify-content: center;
background-color: var(--theme-dialog-accent);
}
.title {
align-self: flex-start;
}
</style>

View File

@ -3,7 +3,6 @@
"version": "0.6.0",
"main": "src/index.ts",
"author": "Anticrm Platform Contributors",
"template": "@anticrm/platform-package",
"license": "EPL-2.0",
"scripts": {
"build": "echo 'no build for ui'",

View File

@ -4,6 +4,7 @@
"main": "lib/index.js",
"author": "Anticrm Platform Contributors",
"license": "EPL-2.0",
"template": "@anticrm/platform-package",
"scripts": {
"build": "heft build",
"build:watch": "tsc",

View File

@ -11,7 +11,7 @@
"@anticrm/platform-rig": "~0.6.0",
"svelte-loader":"^3.1.2",
"sass":"^1.37.5",
"svelte-preprocess":"^4.7.4",
"svelte-preprocess":"^4.10.3",
"@typescript-eslint/eslint-plugin": "^5.4.0",
"@typescript-eslint/parser": "^5.4.0",
"eslint-config-standard-with-typescript": "^21.0.1",
@ -31,6 +31,8 @@
"svelte": "^3.37.0"
},
"#replaces": [
".eslintrc.js"
".eslintrc.js",
"postcss.config.js",
"svelte.config.js"
]
}