mirror of
https://github.com/hcengineering/platform.git
synced 2024-12-23 03:22:19 +03:00
Update DatePicker, DatePresenter. (#1221)
Signed-off-by: Alexander Platov <sas_lord@mail.ru>
This commit is contained in:
parent
24dfc7f274
commit
1700006c3a
@ -41,7 +41,7 @@
|
||||
const dispatch = createEventDispatcher()
|
||||
</script>
|
||||
|
||||
<form class="antiCard" class:w-2125rem={size === 'small'} class:w-4125rem={size === 'medium'} on:submit|preventDefault={ () => {} }>
|
||||
<form class="antiCard" class:w-85={size === 'small'} class:w-165={size === 'medium'} on:submit|preventDefault={ () => {} }>
|
||||
<div class="antiCard-header">
|
||||
<div class="antiCard-header__title"><Label {label} params={labelProps ?? {}} /></div>
|
||||
{#if $$slots.error}
|
||||
|
@ -131,15 +131,12 @@
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
}
|
||||
.emphasized .emphasized-focus + .label {
|
||||
top: 0.5rem;
|
||||
}
|
||||
}
|
||||
.emphasized {
|
||||
padding: 1rem;
|
||||
background-color: var(--theme-bg-accent-color);
|
||||
border: 1px solid var(--theme-bg-accent-hover);
|
||||
border-radius: 0.75rem;
|
||||
border-radius: .5rem;
|
||||
&.emphasized-focus {
|
||||
background-color: var(--theme-bg-focused-color);
|
||||
border-color: var(--theme-bg-focused-border);
|
||||
@ -149,9 +146,5 @@
|
||||
overflow: auto;
|
||||
flex-grow: 1;
|
||||
line-height: 150%;
|
||||
|
||||
.nolabel {
|
||||
padding-top: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -49,6 +49,7 @@ input {
|
||||
background-color: transparent;
|
||||
outline: none;
|
||||
color: var(--theme-caption-color);
|
||||
&.wrong-input { background-color: var(--system-error-color) !important; }
|
||||
}
|
||||
audio, canvas, embed, iframe, img, object, svg, video {
|
||||
display: block;
|
||||
@ -108,10 +109,8 @@ p:last-child { margin-block-end: 0; }
|
||||
.inline-flex { display: inline-flex; }
|
||||
.flex-grow { flex-grow: 1; }
|
||||
.flex-no-shrink { flex-shrink: 0; }
|
||||
.flex-nowrap {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
.flex-wrap { flex-wrap: wrap; }
|
||||
.flex-nowrap { flex-wrap: nowrap; }
|
||||
.flex-baseline {
|
||||
display: inline-flex;
|
||||
align-items: baseline;
|
||||
@ -141,9 +140,6 @@ p:last-child { margin-block-end: 0; }
|
||||
align-items: stretch;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
.flex-wrap {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.flex-row-top {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
@ -297,6 +293,8 @@ p:last-child { margin-block-end: 0; }
|
||||
.mb-2 { margin-bottom: .5rem; }
|
||||
.mb-3 { margin-bottom: .75rem; }
|
||||
.mb-4 { margin-bottom: 1rem; }
|
||||
.mx-1 { margin: 0 .125rem; }
|
||||
.mx-2 { margin: 0 .25rem; }
|
||||
|
||||
.pr-1 { padding-right: .25rem; }
|
||||
.pr-4 { padding-right: 1rem; }
|
||||
@ -305,6 +303,7 @@ p:last-child { margin-block-end: 0; }
|
||||
.p-10 { padding: 2.5rem; }
|
||||
|
||||
/* --------- */
|
||||
.no-word-wrap { word-wrap: none; }
|
||||
.relative { position: relative; }
|
||||
.abs-lt-content {
|
||||
position: absolute;
|
||||
@ -344,8 +343,8 @@ p:last-child { margin-block-end: 0; }
|
||||
.h-2 { height: .5rem; }
|
||||
.h-9 { height: 2.25rem; }
|
||||
.w-full { width: 100%; }
|
||||
.w-2125rem {width: 21.25rem; }
|
||||
.w-4125rem {width: 41.25rem; }
|
||||
.w-85 {width: 21.25rem; }
|
||||
.w-165 {width: 41.25rem; }
|
||||
.min-w-0 { min-width: 0; }
|
||||
.min-h-0 { min-height: 0; }
|
||||
.clear-mins {
|
||||
@ -433,6 +432,8 @@ a.no-line {
|
||||
overflow: hidden;
|
||||
white-space: pre-wrap;
|
||||
width: max-content;
|
||||
user-select: none;
|
||||
min-width: auto;
|
||||
}
|
||||
.overflow-label {
|
||||
white-space: nowrap;
|
||||
|
@ -466,26 +466,17 @@
|
||||
& > .icon { color: var(--theme-caption-color); }
|
||||
}
|
||||
|
||||
.label, .result {
|
||||
.label {
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
user-select: none;
|
||||
|
||||
min-width: 0;
|
||||
}
|
||||
.label {
|
||||
font-size: .75rem;
|
||||
font-weight: 500;
|
||||
color: var(--theme-content-accent-color);
|
||||
}
|
||||
.result {
|
||||
&.selected { color: var(--theme-caption-color); }
|
||||
&.not-selected { color: var(--theme-content-dark-color); }
|
||||
&.highlight {
|
||||
font-weight: 500;
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.group {
|
||||
display: flex;
|
||||
@ -494,20 +485,6 @@
|
||||
margin-left: .75rem;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.divider {
|
||||
margin: 0 .125rem;
|
||||
font-weight: 500;
|
||||
font-size: .75em;
|
||||
color: var(--theme-content-dark-color);
|
||||
|
||||
&.inter { font-size: 1em; }
|
||||
&.max {
|
||||
margin: 0 .5rem;
|
||||
font-size: .75rem;
|
||||
color: var(--theme-content-trans-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Wraps */
|
||||
@ -518,23 +495,48 @@
|
||||
flex-wrap: nowrap;
|
||||
min-width: 0;
|
||||
|
||||
&::after, &::before {
|
||||
position: absolute;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
background-color: var(--primary-button-enabled);
|
||||
}
|
||||
&::before {
|
||||
top: -2px;
|
||||
left: -4px;
|
||||
clip-path: path('M0,6v-6h6v1h-5v5z');
|
||||
}
|
||||
&::after {
|
||||
bottom: -2px;
|
||||
right: -4px;
|
||||
clip-path: path('M0,6h6v-6h-1v5h-5z');
|
||||
&.conners {
|
||||
&::after, &::before {
|
||||
position: absolute;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
background-color: var(--primary-button-enabled);
|
||||
}
|
||||
&::before {
|
||||
top: -2px;
|
||||
left: -4px;
|
||||
clip-path: path('M0,6v-6h6v1h-5v5z');
|
||||
}
|
||||
&::after {
|
||||
bottom: -2px;
|
||||
right: -4px;
|
||||
clip-path: path('M0,6h6v-6h-1v5h-5z');
|
||||
}
|
||||
}
|
||||
&.wraped::before, &.wraped::after { content: ''; }
|
||||
&.focusWI:focus-within::before, &.focusWI:focus-within::after { content: ''; }
|
||||
&.focus:focus::before, &.focus:focus::after { content: ''; }
|
||||
|
||||
.result {
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
user-select: none;
|
||||
min-width: 0;
|
||||
|
||||
&.selected { color: var(--theme-caption-color); }
|
||||
&.not-selected { color: var(--theme-content-dark-color); }
|
||||
&.highlight {
|
||||
font-weight: 500;
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.divider {
|
||||
font-weight: 500;
|
||||
font-size: .75em;
|
||||
color: var(--theme-content-dark-color);
|
||||
|
||||
&.inter { font-size: 1em; }
|
||||
}
|
||||
}
|
||||
|
@ -1,80 +0,0 @@
|
||||
<!--
|
||||
// Copyright © 2020 Anticrm Platform Contributors.
|
||||
//
|
||||
// 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 { createEventDispatcher } from 'svelte'
|
||||
import { onMount } from 'svelte'
|
||||
import type { IntlString } from '@anticrm/platform'
|
||||
import Calendar from './icons/Calendar.svelte'
|
||||
import Close from './icons/Close.svelte'
|
||||
import ui, { Label, DatePopup, DatePresenter, showPopup } from '..'
|
||||
|
||||
export let title: IntlString
|
||||
export let value: Date | null | undefined = null
|
||||
export let withTime: boolean = false
|
||||
export let bigDay: boolean = false
|
||||
export let show: boolean = false
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let opened: boolean = false
|
||||
let container: HTMLElement
|
||||
let btn: HTMLElement
|
||||
|
||||
const changeValue = (result: any): void => {
|
||||
if (result !== undefined) {
|
||||
value = result
|
||||
dispatch('change', result)
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
if (btn && show) {
|
||||
btn.click()
|
||||
show = false
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="antiSelect"
|
||||
bind:this={container}
|
||||
on:click|preventDefault={() => {
|
||||
btn.focus()
|
||||
if (!opened) {
|
||||
opened = true
|
||||
showPopup(DatePopup, { title, value, withTime }, container, (ev) => {
|
||||
changeValue(ev)
|
||||
opened = false
|
||||
}, changeValue)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<button
|
||||
bind:this={btn}
|
||||
class="button round-2"
|
||||
class:selected={value}
|
||||
>
|
||||
<div class="icon">
|
||||
{#if show}<Close size={'small'} />{:else}<Calendar size={'medium'} />{/if}
|
||||
</div>
|
||||
</button>
|
||||
<div class="group">
|
||||
<span class="label"><Label label={title} /></span>
|
||||
{#if value != null}
|
||||
<DatePresenter {value} withTime={withTime} {bigDay} wraped={opened} />
|
||||
{:else}
|
||||
<span class="result not-selected"><Label label={ui.string.NotSelected} /></span>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
@ -1,335 +0,0 @@
|
||||
<!--
|
||||
// Copyright © 2020 Anticrm Platform Contributors.
|
||||
//
|
||||
// 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 type { IntlString } from '@anticrm/platform'
|
||||
import { translate } from '@anticrm/platform'
|
||||
import Back from './icons/Back.svelte'
|
||||
import Forward from './icons/Forward.svelte'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import ui, { Label, Button } from '..'
|
||||
import type { TSelectDate, TCellStyle, ICell } from '../types'
|
||||
|
||||
export let title: IntlString
|
||||
export let value: TSelectDate
|
||||
export let withTime: boolean = false
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
const getNow = (): Date => {
|
||||
const tempDate = new Date(Date.now())
|
||||
return new Date(tempDate.getFullYear(), tempDate.getMonth(), tempDate.getDate())
|
||||
}
|
||||
const today: Date = getNow()
|
||||
let todayString: string
|
||||
async function todayStr () {
|
||||
todayString = await translate(ui.string.Today, {})
|
||||
}
|
||||
todayStr()
|
||||
|
||||
let view: Date = (value === null || value === undefined) ? today : new Date(value)
|
||||
const months: Array<string> = [
|
||||
'January',
|
||||
'February',
|
||||
'March',
|
||||
'April',
|
||||
'May',
|
||||
'June',
|
||||
'July',
|
||||
'August',
|
||||
'September',
|
||||
'October',
|
||||
'November',
|
||||
'December'
|
||||
]
|
||||
let monthYear: string
|
||||
let days: Array<ICell> = []
|
||||
let result: TSelectDate = value
|
||||
let prevDiv: HTMLElement
|
||||
let nextDiv: HTMLElement
|
||||
let hourDiv: HTMLElement
|
||||
let minDiv: HTMLElement
|
||||
let okDiv: HTMLButtonElement
|
||||
|
||||
const daysInMonth = (date: Date): number => {
|
||||
return 33 - new Date(date.getFullYear(), date.getMonth(), 33).getDate()
|
||||
}
|
||||
|
||||
const compareDates = (d1: Date, d2: Date): boolean => {
|
||||
if (d1.getFullYear() == d2.getFullYear() &&
|
||||
d1.getMonth() == d2.getMonth() &&
|
||||
d1.getDate() == d2.getDate()) return true
|
||||
return false
|
||||
}
|
||||
const getDateStyle = (date: Date): TCellStyle => {
|
||||
if (value !== undefined && value !== null && compareDates(value, date)) return 'selected'
|
||||
return 'not-selected'
|
||||
}
|
||||
|
||||
const renderCellStyles = (): void => {
|
||||
days = []
|
||||
for (let i = 1; i <= daysInMonth(view); i++) {
|
||||
const tempDate = new Date(view.getFullYear(), view.getMonth(), i)
|
||||
days.push({
|
||||
dayOfWeek: (tempDate.getDay() === 0) ? 7 : tempDate.getDay(),
|
||||
style: getDateStyle(tempDate),
|
||||
today: compareDates(tempDate, today)
|
||||
})
|
||||
}
|
||||
days = days
|
||||
}
|
||||
renderCellStyles()
|
||||
|
||||
$: monthYear = months[view.getMonth()] + ' ' + view.getFullYear()
|
||||
$: if (value) renderCellStyles()
|
||||
|
||||
const zeroLead = (n: number): string => {
|
||||
if (n < 10) return '0' + n.toString()
|
||||
return n.toString()
|
||||
}
|
||||
|
||||
const keyPress = (ev: KeyboardEvent, isHour: boolean): void => {
|
||||
if (ev.key >= '0' && ev.key <= '9') {
|
||||
const keyNumber: number = parseInt(ev.key, 10)
|
||||
let number: number = isHour ? view.getHours() : view.getMinutes()
|
||||
let newNumber: number = ((isHour && number > 2) || (!isHour && number > 5))
|
||||
? keyNumber
|
||||
: number * 10 + keyNumber
|
||||
|
||||
if (isHour) {
|
||||
if (newNumber > 23) newNumber = 23
|
||||
view.setHours(newNumber)
|
||||
} else {
|
||||
if (newNumber > 59) newNumber = 59
|
||||
view.setMinutes(newNumber)
|
||||
}
|
||||
view = view
|
||||
value = view
|
||||
if (isHour && newNumber > 9) minDiv.focus()
|
||||
if (!isHour && newNumber > 9) okDiv.focus()
|
||||
}
|
||||
}
|
||||
|
||||
const keyDown = (ev: KeyboardEvent, isHour: boolean): void => {
|
||||
if (ev.key === 'Backspace') {
|
||||
console.log('BACKSPACE')
|
||||
if (isHour) view.setHours(0)
|
||||
else view.setMinutes(0)
|
||||
view = view
|
||||
value = view
|
||||
}
|
||||
}
|
||||
const navKey = (ev: KeyboardEvent): void => {
|
||||
if (ev.code === 'ArrowLeft') prevDiv.click()
|
||||
if (ev.code === 'ArrowRight') nextDiv.click()
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:window on:keydown={navKey} />
|
||||
<div class="popup">
|
||||
<div class="flex-col caption-color">
|
||||
<div class="title"><Label label={title} /></div>
|
||||
<div class="flex-between nav">
|
||||
<button
|
||||
bind:this={prevDiv}
|
||||
class="focused-button arrow"
|
||||
on:click|preventDefault={() => {
|
||||
view.setMonth(view.getMonth() - 1)
|
||||
view = view
|
||||
renderCellStyles()
|
||||
}}><div class="icon"><Back size={'small'} /></div></button>
|
||||
<div class="monthYear">
|
||||
{monthYear}
|
||||
</div>
|
||||
<button
|
||||
bind:this={nextDiv}
|
||||
class="focused-button arrow"
|
||||
on:click|preventDefault={() => {
|
||||
view.setMonth(view.getMonth() + 1)
|
||||
view = view
|
||||
renderCellStyles()
|
||||
}}><div class="icon"><Forward size={'small'} /></div></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="calendar">
|
||||
<div class="caption">Mo</div>
|
||||
<div class="caption">Tu</div>
|
||||
<div class="caption">We</div>
|
||||
<div class="caption">Th</div>
|
||||
<div class="caption">Fr</div>
|
||||
<div class="caption">Sa</div>
|
||||
<div class="caption">Su</div>
|
||||
{#each days as day, i}
|
||||
<div
|
||||
class="day {day.style}"
|
||||
class:today={day.today}
|
||||
data-today={day.today ? todayString : ''}
|
||||
style="grid-column: {day.dayOfWeek}/{day.dayOfWeek + 1};"
|
||||
on:click={() => {
|
||||
result = new Date(view.getFullYear(), view.getMonth(), i + 1, view.getHours(), view.getMinutes())
|
||||
view = value = result
|
||||
if (withTime) {
|
||||
dispatch('update', result)
|
||||
hourDiv.focus()
|
||||
} else dispatch('close', result)
|
||||
}}
|
||||
>
|
||||
{i + 1}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{#if withTime}
|
||||
<div class="calendar-divider" />
|
||||
<div class="flex-row-center flex-reverse">
|
||||
<div class="ml-2">
|
||||
<Button bind:input={okDiv} label={ui.string.Ok} size={'small'} primary on:click={() => { dispatch('close', view) }} />
|
||||
</div>
|
||||
<div class="time-container">
|
||||
<button
|
||||
bind:this={hourDiv}
|
||||
class="time-digit antiWrapper focus hours"
|
||||
on:keypress={(ev) => { keyPress(ev, true) }}
|
||||
on:keydown={(ev) => { keyDown(ev, true) }}
|
||||
>
|
||||
{zeroLead(view.getHours())}
|
||||
</button>
|
||||
<div class="time-divider">:</div>
|
||||
<button
|
||||
bind:this={minDiv}
|
||||
class="time-digit antiWrapper focus minutes"
|
||||
on:keypress={(ev) => { keyPress(ev, false) }}
|
||||
on:keydown={(ev) => { keyDown(ev, false) }}
|
||||
>
|
||||
{zeroLead(view.getMinutes())}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.popup {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 1rem;
|
||||
min-height: 0;
|
||||
color: var(--theme-caption-color);
|
||||
background-color: var(--theme-button-bg-focused);
|
||||
border: 1px solid var(--theme-button-border-enabled);
|
||||
border-radius: .75rem;
|
||||
box-shadow: 0px 10px 20px rgba(0, 0, 0, .2);
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.arrow {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
border: 1px solid var(--theme-bg-accent-color);
|
||||
border-radius: .25rem;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin-bottom: .75rem;
|
||||
font-weight: 500;
|
||||
text-align: left;
|
||||
}
|
||||
.nav {
|
||||
min-width: 16.5rem;
|
||||
|
||||
.monthYear {
|
||||
margin: 0 1rem;
|
||||
line-height: 150%;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.calendar {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(7, 1fr);
|
||||
gap: .125rem;
|
||||
margin-top: .5rem;
|
||||
|
||||
.caption, .day {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 2.25rem;
|
||||
height: 2.25rem;
|
||||
color: var(--theme-content-dark-color);
|
||||
}
|
||||
.caption { font-size: .75rem; }
|
||||
.day {
|
||||
border-radius: .5rem;
|
||||
border: 1px solid transparent;
|
||||
cursor: pointer;
|
||||
|
||||
&.selected {
|
||||
background-color: var(--primary-button-enabled);
|
||||
border-color: var(--primary-button-focused-border);
|
||||
color: var(--primary-button-color);
|
||||
}
|
||||
&.today {
|
||||
position: relative;
|
||||
border-color: var(--theme-content-color);
|
||||
font-weight: 500;
|
||||
color: var(--theme-caption-color);
|
||||
|
||||
&::after {
|
||||
position: absolute;
|
||||
content: attr(data-today);
|
||||
top: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
font-weight: 600;
|
||||
font-size: .35rem;
|
||||
text-transform: uppercase;
|
||||
color: var(--theme-content-dark-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.calendar-divider {
|
||||
flex-shrink: 0;
|
||||
margin: .5rem 0;
|
||||
height: 1px;
|
||||
background-color: var(--theme-menu-divider);
|
||||
}
|
||||
.time-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-grow: 1;
|
||||
min-width: 0;
|
||||
height: 100%;
|
||||
border: 1px solid var(--theme-button-border-enabled);
|
||||
border-radius: .75rem;
|
||||
}
|
||||
.time-digit, .time-divider {
|
||||
font-weight: 500;
|
||||
font-size: 1.25rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
.time-divider {
|
||||
margin: 0 .5rem;
|
||||
font-size: 1rem;
|
||||
color: var(--theme-content-dark-color);
|
||||
}
|
||||
.time-digit {
|
||||
padding: 0;
|
||||
font-weight: 600;
|
||||
color: var(--theme-caption-color);
|
||||
}
|
||||
</style>
|
@ -1,57 +0,0 @@
|
||||
<!--
|
||||
// Copyright © 2020 Anticrm Platform Contributors.
|
||||
//
|
||||
// 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 { getContext } from 'svelte'
|
||||
import type { TSelectDate } from '../types'
|
||||
|
||||
export let value: TSelectDate
|
||||
export let bigDay: boolean = false
|
||||
export let wraped: boolean = false
|
||||
export let withTime: boolean = false
|
||||
|
||||
const { currentLanguage } = getContext('lang')
|
||||
const inter: boolean = (currentLanguage === 'ru') ?? false
|
||||
|
||||
const zeroLead = (n: number): string => {
|
||||
if (n < 10) return '0' + n.toString()
|
||||
return n.toString()
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if value !== undefined}
|
||||
<div class="antiWrapper" class:wraped>
|
||||
<span class="result" class:selected={value !== null} class:not-selected={value === null} class:highlight={inter && bigDay}>
|
||||
{#if value === null}--{:else}{inter ? value.getDate() : value.getMonth() + 1}{/if}
|
||||
</span>
|
||||
<span class="divider" class:inter>{inter ? '.' : '/'}</span>
|
||||
<span class="result" class:selected={value !== null} class:not-selected={value === null} class:highlight={!inter && bigDay}>
|
||||
{#if value === null}--{:else}{inter ? value.getMonth() + 1 : value.getDate()}{/if}
|
||||
</span>
|
||||
<span class="divider" class:inter>{inter ? '.' : '/'}</span>
|
||||
<span class="result" class:selected={value !== null} class:not-selected={value === null}>
|
||||
{#if value === null}--{:else}{value.getFullYear()}{/if}
|
||||
</span>
|
||||
{#if withTime}
|
||||
<span class="divider max"> — </span>
|
||||
<span class="result" class:selected={value !== null} class:not-selected={value === null} class:highlight={!inter && bigDay}>
|
||||
{#if value === null}--{:else}{zeroLead(value.getHours())}{/if}
|
||||
</span>
|
||||
<span class="divider">:</span>
|
||||
<span class="result" class:selected={value !== null} class:not-selected={value === null}>
|
||||
{#if value === null}--{:else}{zeroLead(value.getMinutes())}{/if}
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
@ -75,7 +75,7 @@
|
||||
<Icon {icon} size={'small'}/>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="antiWrapper focusWI">
|
||||
<div class="antiWrapper conners focusWI">
|
||||
{#if format === 'password'}
|
||||
<input bind:this={input} type='passsword' bind:value placeholder={phTraslate} {style} on:input={(ev) => ev.target && computeSize(ev.target)} on:change/>
|
||||
{:else if format === 'number'}
|
||||
|
@ -22,6 +22,7 @@
|
||||
export let component: AnySvelteComponent | AnyComponent | undefined = undefined
|
||||
export let props: any | undefined = undefined
|
||||
export let anchor: HTMLElement | undefined = undefined
|
||||
export let onUpdate: ((result: any) => void) | undefined = undefined
|
||||
export let fill = false
|
||||
|
||||
let triggerHTML: HTMLElement
|
||||
@ -34,7 +35,7 @@
|
||||
name={`tooltip-${label}`}
|
||||
bind:this={triggerHTML}
|
||||
on:mousemove={() => {
|
||||
if (!shown) showTooltip(label, triggerHTML, direction, component, props, anchor)
|
||||
if (!shown) showTooltip(label, triggerHTML, direction, component, props, anchor, onUpdate)
|
||||
}}
|
||||
>
|
||||
<slot />
|
||||
|
@ -28,6 +28,7 @@
|
||||
let clWidth: number
|
||||
|
||||
$: tooltipSW = !$tooltip.component
|
||||
$: onUpdate = $tooltip.onUpdate
|
||||
|
||||
const fitTooltip = (): void => {
|
||||
if (($tooltip.label || $tooltip.component) && tooltipHTML) {
|
||||
@ -146,27 +147,15 @@
|
||||
whileShow(ev)
|
||||
}}
|
||||
/>
|
||||
<svg class="svg-mask">
|
||||
<clipPath id="nub-bg"
|
||||
><path
|
||||
d="M7.3.6 4.2 4.3C2.9 5.4 1.5 6 0 6v1h18V6c-1.5 0-2.9-.6-4.2-1.7L10.7.6C9.9-.1 8.5-.2 7.5.4c0 .1-.1.1-.2.2z"
|
||||
/></clipPath
|
||||
>
|
||||
<clipPath id="nub-border"
|
||||
><path
|
||||
d="M4.8 5.1 8 1.3s.1 0 .1-.1c.5-.3 1.4-.3 1.9.1L13.1 5l.1.1 1.2.9H18c-1.5 0-2.9-.6-4.2-1.7L10.7.6C9.9-.1 8.5-.2 7.5.4c0 .1-.1.1-.2.2L4.2 4.3C2.9 5.4 1.5 6 0 6h3.6l1.2-.9z"
|
||||
/></clipPath
|
||||
>
|
||||
</svg>
|
||||
{#if $tooltip.component}
|
||||
<div class="popup-tooltip" bind:clientWidth={clWidth} bind:this={tooltipHTML}>
|
||||
<div class="popup-tooltip antiPopup" bind:clientWidth={clWidth} bind:this={tooltipHTML}>
|
||||
{#if $tooltip.label}<div class="fs-title mb-4">
|
||||
<Label label={$tooltip.label} params={$tooltip.props ?? {}} />
|
||||
</div>{/if}
|
||||
{#if typeof $tooltip.component === 'string'}
|
||||
<Component is={$tooltip.component} props={$tooltip.props}/>
|
||||
<Component is={$tooltip.component} props={$tooltip.props} on:update={onUpdate !== undefined ? onUpdate : async () => {}} />
|
||||
{:else}
|
||||
<svelte:component this={$tooltip.component} {...$tooltip.props} />
|
||||
<svelte:component this={$tooltip.component} {...$tooltip.props} on:update={onUpdate !== undefined ? onUpdate : async () => {}} />
|
||||
{/if}
|
||||
</div>
|
||||
<div bind:this={nubHTML} class="nub {nubDirection ?? ''}" />
|
||||
|
39
packages/ui/src/components/calendar/DatePicker.svelte
Normal file
39
packages/ui/src/components/calendar/DatePicker.svelte
Normal file
@ -0,0 +1,39 @@
|
||||
<!--
|
||||
// Copyright © 2020 Anticrm Platform Contributors.
|
||||
//
|
||||
// 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 { createEventDispatcher } from 'svelte'
|
||||
import type { IntlString } from '@anticrm/platform'
|
||||
import { Label, DatePresenter } from '../..'
|
||||
|
||||
export let title: IntlString
|
||||
export let value: number | null | undefined = null
|
||||
export let withTime: boolean = false
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
const changeValue = (result: any): void => {
|
||||
if (result.detail !== undefined) {
|
||||
value = result.detail
|
||||
dispatch('change', value)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="antiSelect antiWrapper cursor-default">
|
||||
<div class="flex-col">
|
||||
<span class="label mb-1"><Label label={title} /></span>
|
||||
<DatePresenter {value} {withTime} editable on:change={changeValue} />
|
||||
</div>
|
||||
</div>
|
206
packages/ui/src/components/calendar/DatePopup.svelte
Normal file
206
packages/ui/src/components/calendar/DatePopup.svelte
Normal file
@ -0,0 +1,206 @@
|
||||
<!--
|
||||
// Copyright © 2020 Anticrm Platform Contributors.
|
||||
//
|
||||
// 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 { translate } from '@anticrm/platform'
|
||||
import { afterUpdate, createEventDispatcher, onDestroy, onMount } from 'svelte'
|
||||
import ui from '../..'
|
||||
import type { TCellStyle, ICell } from './internal/DateUtils'
|
||||
import { firstDay, day, getWeekDayName, areDatesEqual, getMonthName, daysInMonth } from './internal/DateUtils'
|
||||
|
||||
export let value: number | null | undefined
|
||||
export let mondayStart: boolean = true
|
||||
export let editable: boolean = false
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
let currentDate: Date = new Date(value ?? Date.now())
|
||||
let days: Array<ICell> = []
|
||||
let scrollDiv: HTMLElement
|
||||
|
||||
$: if (value) currentDate = new Date(value)
|
||||
$: firstDayOfCurrentMonth = firstDay(currentDate, mondayStart)
|
||||
|
||||
const getNow = (): Date => {
|
||||
const tempDate = new Date(Date.now())
|
||||
return new Date(tempDate.getFullYear(), tempDate.getMonth(), tempDate.getDate())
|
||||
}
|
||||
const today: Date = getNow()
|
||||
let todayString: string
|
||||
translate(ui.string.Today, {}).then(res => todayString = res)
|
||||
|
||||
|
||||
const getDateStyle = (date: Date): TCellStyle => {
|
||||
if (value !== undefined && value !== null && areDatesEqual(currentDate, date)) return 'selected'
|
||||
return 'not-selected'
|
||||
}
|
||||
|
||||
const renderCellStyles = (): void => {
|
||||
days = []
|
||||
for (let i = 1; i <= daysInMonth(currentDate); i++) {
|
||||
const tempDate = new Date(currentDate.getFullYear(), currentDate.getMonth(), i)
|
||||
days.push({
|
||||
dayOfWeek: (tempDate.getDay() === 0) ? 7 : tempDate.getDay(),
|
||||
style: getDateStyle(tempDate),
|
||||
focused: false,
|
||||
today: areDatesEqual(tempDate, today)
|
||||
})
|
||||
}
|
||||
days = days
|
||||
}
|
||||
$: if (currentDate) renderCellStyles()
|
||||
|
||||
const scrolling = (ev: Event): void => {
|
||||
console.log('!!! Scrolling:', ev)
|
||||
}
|
||||
|
||||
afterUpdate(() => {
|
||||
if (value) currentDate = new Date(value)
|
||||
})
|
||||
onMount(() => {
|
||||
if (scrollDiv) scrollDiv.addEventListener('wheel', scrolling)
|
||||
})
|
||||
onDestroy(() => {
|
||||
if (scrollDiv) scrollDiv.removeEventListener('wheel', scrolling)
|
||||
})
|
||||
</script>
|
||||
|
||||
<div bind:this={scrollDiv} class="convert-scroller">
|
||||
<div class="popup">
|
||||
<div class="flex-center monthYear">
|
||||
{#if currentDate}
|
||||
{getMonthName(currentDate)}
|
||||
<span class="ml-1">{currentDate.getFullYear()}</span>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if currentDate}
|
||||
<div class="calendar" class:no-editable={!editable}>
|
||||
{#each [...Array(7).keys()] as dayOfWeek}
|
||||
<div class="caption">{getWeekDayName(day(firstDayOfCurrentMonth, dayOfWeek), 'short')}</div>
|
||||
{/each}
|
||||
{#each days as day, i}
|
||||
<div
|
||||
class="day {day.style}"
|
||||
class:today={day.today}
|
||||
class:focused={day.focused}
|
||||
data-today={day.today ? todayString : ''}
|
||||
style="grid-column: {day.dayOfWeek}/{day.dayOfWeek + 1};"
|
||||
on:click|stopPropagation={() => {
|
||||
if (currentDate) currentDate.setDate(i + 1)
|
||||
value = currentDate.getTime()
|
||||
dispatch('update', value)
|
||||
}}
|
||||
>
|
||||
{i + 1}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.convert-scroller {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: .5rem;
|
||||
user-select: none;
|
||||
|
||||
overflow-x: scroll;
|
||||
overflow-y: scroll;
|
||||
// width: calc(100% - 1px);
|
||||
// max-height: calc(100% - 1px);
|
||||
// mask-image: linear-gradient(0deg, rgba(0, 0, 0, 0) 0, rgba(0, 0, 0, 1) 2rem, rgba(0, 0, 0, 1) calc(100% - 2rem), rgba(0, 0, 0, 0) 100%);
|
||||
&::-webkit-scrollbar:vertical { width: 0; }
|
||||
&::-webkit-scrollbar:horizontal { height: 0; }
|
||||
}
|
||||
|
||||
.popup {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
// width: calc(100% + 1px);
|
||||
// height: calc(100% + 1px);
|
||||
color: var(--theme-caption-color);
|
||||
border-radius: .5rem;
|
||||
// pointer-events: none;
|
||||
}
|
||||
|
||||
.monthYear {
|
||||
margin: 0 1rem;
|
||||
// line-height: 150%;
|
||||
color: var(--theme-content-accent-color);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.calendar {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(7, 1fr);
|
||||
gap: .125rem;
|
||||
|
||||
.caption, .day {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 1.75rem;
|
||||
height: 1.75rem;
|
||||
color: var(--theme-content-dark-color);
|
||||
}
|
||||
.caption {
|
||||
font-size: .75rem;
|
||||
color: var(--theme-content-trans-color);
|
||||
}
|
||||
.day {
|
||||
background-color: rgba(var(--theme-caption-color), .05);
|
||||
border: 1px solid transparent;
|
||||
border-radius: .25rem;
|
||||
cursor: pointer;
|
||||
|
||||
&.selected {
|
||||
background-color: var(--primary-button-enabled);
|
||||
border-color: var(--primary-button-focused-border);
|
||||
color: var(--primary-button-color);
|
||||
}
|
||||
&.today {
|
||||
position: relative;
|
||||
border-color: var(--theme-content-color);
|
||||
font-weight: 500;
|
||||
color: var(--theme-caption-color);
|
||||
|
||||
&::after {
|
||||
position: absolute;
|
||||
content: attr(data-today);
|
||||
top: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
font-weight: 600;
|
||||
font-size: .35rem;
|
||||
text-transform: uppercase;
|
||||
color: var(--theme-content-dark-color);
|
||||
}
|
||||
}
|
||||
&.focused { box-shadow: 0 0 0 3px var(--primary-button-outline); }
|
||||
}
|
||||
|
||||
// &.no-editable { pointer-events: none; }
|
||||
}
|
||||
</style>
|
415
packages/ui/src/components/calendar/DatePresenter.svelte
Normal file
415
packages/ui/src/components/calendar/DatePresenter.svelte
Normal file
@ -0,0 +1,415 @@
|
||||
<!--
|
||||
// Copyright © 2020 Anticrm Platform Contributors.
|
||||
//
|
||||
// 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 { onMount, createEventDispatcher, afterUpdate, onDestroy } from 'svelte'
|
||||
import { daysInMonth } from './internal/DateUtils'
|
||||
import { TimePopup, tooltipstore as tooltip, showTooltip } from '../..'
|
||||
import DatePopup from './DatePopup.svelte'
|
||||
import DPClock from './icons/DPClock.svelte'
|
||||
import DPCalendar from './icons/DPCalendar.svelte'
|
||||
|
||||
export let value: number | null | undefined
|
||||
export let withDate: boolean = true
|
||||
export let withTime: boolean = false
|
||||
export let editable: boolean = false
|
||||
|
||||
const INPUT_WIDTH_INCREMENT = 2
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
type TEdits = 'day' | 'month' | 'year' | 'hour' | 'min'
|
||||
interface IEdits {
|
||||
id: TEdits
|
||||
numeric: number
|
||||
value: string
|
||||
el?: HTMLInputElement
|
||||
}
|
||||
const editsType: TEdits[] = ['day', 'month', 'year', 'hour', 'min']
|
||||
const getIndex = (id: TEdits): number => editsType.indexOf(id)
|
||||
const today: Date = new Date(Date.now())
|
||||
let currentDate: Date = new Date(value ?? Date.now())
|
||||
|
||||
let selected: TEdits = 'day'
|
||||
let dateDiv: HTMLElement
|
||||
let timeDiv: HTMLElement
|
||||
let dateBox: HTMLElement
|
||||
let timeBox: HTMLElement
|
||||
let dateContainer: HTMLElement
|
||||
let text: HTMLElement
|
||||
|
||||
let dateShow: boolean = false
|
||||
$: dateShow = !!($tooltip.label || $tooltip.component)
|
||||
|
||||
function computeSize (t: HTMLInputElement | EventTarget | null, ed: TEdits) {
|
||||
const target = t as HTMLInputElement
|
||||
const val = (target.value === '' || target.value.length < 2) ? '00' : target.value
|
||||
text.innerHTML = val.replaceAll(' ', ' ')
|
||||
target.style.width = text.clientWidth + INPUT_WIDTH_INCREMENT + 'px'
|
||||
}
|
||||
|
||||
const edits: IEdits[] = [{ id: 'day', numeric: 0, value: '0' }, { id: 'month', numeric: 0, value: '0' },
|
||||
{ id: 'year', numeric: 0, value: '0' }, { id: 'hour', numeric: 0, value: '0' },
|
||||
{ id: 'min', numeric: 0, value: '0' }]
|
||||
|
||||
const getValue = (date: Date | null | undefined = today, id: TEdits): number => {
|
||||
switch (id) {
|
||||
case 'day': return date ? date.getDate() : today.getDate()
|
||||
case 'month': return date ? date.getMonth() + 1 : today.getMonth() + 1
|
||||
case 'year': return date ? date.getFullYear() : today.getFullYear()
|
||||
case 'hour': return date ? date.getHours() : today.getHours()
|
||||
case 'min': return date ? date.getMinutes() : today.getMinutes()
|
||||
}
|
||||
}
|
||||
const setValue = (val: number, date: Date, id: TEdits): Date => {
|
||||
switch (id) {
|
||||
case 'day':
|
||||
date.setDate(val)
|
||||
break
|
||||
case 'month':
|
||||
date.setMonth(val - 1)
|
||||
break
|
||||
case 'year':
|
||||
date.setFullYear(val)
|
||||
break
|
||||
case 'hour':
|
||||
date.setHours(val)
|
||||
break
|
||||
case 'min':
|
||||
date.setMinutes(val)
|
||||
break
|
||||
}
|
||||
return date
|
||||
}
|
||||
const getMaxValue = (date: Date, id: TEdits): number => {
|
||||
switch (id) {
|
||||
case 'day': return daysInMonth(date)
|
||||
case 'month': return 12
|
||||
case 'year': return 3000
|
||||
case 'hour': return 23
|
||||
case 'min': return 59
|
||||
}
|
||||
}
|
||||
|
||||
const dateToEdits = (): void => {
|
||||
edits.forEach(edit => {
|
||||
edit.numeric = getValue(currentDate, edit.id)
|
||||
if (value !== undefined && value !== null) edit.value = edit.numeric.toString().padStart(edit.id === 'year' ? 4 : 2, '0')
|
||||
else edit.value = (edit.id === 'year') ? '----' : '--'
|
||||
if (edit.el) {
|
||||
edit.el.value = edit.value
|
||||
computeSize(edit.el, edit.id)
|
||||
}
|
||||
})
|
||||
}
|
||||
const saveDate = (): void => {
|
||||
value = currentDate.getTime()
|
||||
dateToEdits()
|
||||
$tooltip.props = { value }
|
||||
dispatch('change', value)
|
||||
}
|
||||
$: if (value) dateToEdits()
|
||||
|
||||
const onInput = (t: HTMLInputElement | EventTarget | null, ed: TEdits) => {
|
||||
const target = t as HTMLInputElement
|
||||
const val: number = Number(target.value)
|
||||
if (isNaN(val)) {
|
||||
target.classList.add('wrong-input')
|
||||
edits[getIndex(ed)].el?.select()
|
||||
} else {
|
||||
target.classList.remove('wrong-input')
|
||||
if (val > getMaxValue(currentDate ?? today, ed)) {
|
||||
setValue(getMaxValue(currentDate ?? today, ed), currentDate ?? today, ed)
|
||||
target.classList.add('wrong-input')
|
||||
edits[getIndex(ed)].el?.select()
|
||||
} else {
|
||||
currentDate = setValue(val, currentDate ?? today, ed)
|
||||
saveDate()
|
||||
if (ed === 'day' && val > daysInMonth(currentDate ?? today) / 10) edits[1].el?.select()
|
||||
else if (ed === 'month' && val > 1) edits[2].el?.select()
|
||||
else if (ed === 'year' && val > 1900 && withTime) edits[3].el?.select()
|
||||
else if (ed === 'hour' && val > 2) edits[4].el?.select()
|
||||
}
|
||||
dateToEdits()
|
||||
}
|
||||
computeSize(t, ed)
|
||||
}
|
||||
|
||||
const keyDown = (ev: KeyboardEvent, ed: TEdits): void => {
|
||||
const target = ev.target as HTMLInputElement
|
||||
const index = getIndex(ed)
|
||||
|
||||
if (ev.code === 'Backspace') {
|
||||
target.value = ''
|
||||
target.classList.remove('wrong-input')
|
||||
}
|
||||
if (ev.code === 'ArrowUp' || ev.code === 'ArrowDown' && target.value !== '') {
|
||||
let val = (ev.code === 'ArrowUp')
|
||||
? edits[index].numeric + 1
|
||||
: edits[index].numeric - 1
|
||||
if (currentDate) {
|
||||
target.classList.remove('wrong-input')
|
||||
currentDate = setValue(val, currentDate, ed)
|
||||
saveDate()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const updateFromTooltip = (result: any): void => {
|
||||
if (result.detail !== undefined) {
|
||||
currentDate = new Date(result.detail)
|
||||
saveDate()
|
||||
}
|
||||
}
|
||||
const hoverEdits = (ev: MouseEvent, t: 'date' | 'time'): void => {
|
||||
if (!dateShow) showTooltip(undefined,
|
||||
t === 'date' ? dateBox : timeBox,
|
||||
undefined, t === 'date' ? DatePopup : TimePopup,
|
||||
{ value: currentDate ?? today },
|
||||
undefined,
|
||||
updateFromTooltip)
|
||||
// $tooltip.props = { value }
|
||||
}
|
||||
|
||||
const editClick = (ev: MouseEvent, el: TEdits): void => {
|
||||
ev.stopPropagation()
|
||||
const target = ev.target as HTMLInputElement
|
||||
target.select()
|
||||
}
|
||||
|
||||
onMount(() => { dateToEdits() })
|
||||
// onDestroy(() => { closeTooltip() })
|
||||
</script>
|
||||
|
||||
{#if currentDate !== undefined}
|
||||
<div bind:this={dateContainer} class="datetime-presenter-container">
|
||||
{#if editable}
|
||||
<div bind:this={dateDiv} class="flex-row-center flex-no-shrink flex-nowrap">
|
||||
<div class="datetime-icon" class:selected={withDate} on:click|stopPropagation={() => { withDate = !withDate }}>
|
||||
<div class="icon"><DPCalendar size={'full'} /></div>
|
||||
</div>
|
||||
<div class="hidden-text" bind:this={text} />
|
||||
<div
|
||||
bind:this={dateBox}
|
||||
class="datetime-presenter antiWrapper conners focusWI dateBox"
|
||||
class:zero={!withDate}
|
||||
on:mousemove={(ev) => hoverEdits(ev, 'date')}
|
||||
>
|
||||
<input
|
||||
type="text" placeholder={'00'} class="zone" class:selected={selected === edits[0].id}
|
||||
bind:this={edits[0].el} bind:value={edits[0].value}
|
||||
on:click|stopPropagation={ev => editClick(ev, edits[0].id)}
|
||||
on:input={ev => onInput(ev.target, edits[0].id)}
|
||||
on:keydown={ev => keyDown(ev, edits[0].id)}
|
||||
/>
|
||||
<div class="symbol">.</div>
|
||||
<input
|
||||
type="text" placeholder={'00'} class="zone" class:selected={selected === edits[1].id}
|
||||
bind:this={edits[1].el} bind:value={edits[1].value}
|
||||
on:click|stopPropagation={ev => editClick(ev, edits[1].id)}
|
||||
on:input={ev => onInput(ev.target, edits[1].id)}
|
||||
on:keydown={ev => keyDown(ev, edits[1].id)}
|
||||
/>
|
||||
<div class="symbol">.</div>
|
||||
<input
|
||||
type="text" placeholder={'0000'} class="zone" class:selected={selected === edits[2].id}
|
||||
bind:this={edits[2].el} bind:value={edits[2].value}
|
||||
on:click|stopPropagation={ev => editClick(ev, edits[2].id)}
|
||||
on:input={ev => onInput(ev.target, edits[2].id)}
|
||||
on:keydown={ev => keyDown(ev, edits[2].id)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="datetime-icon" class:selected={withTime} on:click|stopPropagation={() => { withTime = !withTime }}>
|
||||
<div class="icon"><DPClock size={'full'} /></div>
|
||||
</div>
|
||||
<div
|
||||
bind:this={timeBox}
|
||||
class="datetime-presenter antiWrapper conners focusWI timeBox"
|
||||
class:zero={!withTime}
|
||||
on:mousemove={(ev) => hoverEdits(ev, 'time')}
|
||||
>
|
||||
<input
|
||||
type="text" placeholder={'00'} class="zone" class:selected={selected === edits[3].id}
|
||||
bind:this={edits[3].el} bind:value={edits[3].value}
|
||||
on:click|stopPropagation={ev => editClick(ev, edits[3].id)}
|
||||
on:input={ev => onInput(ev.target, edits[3].id)}
|
||||
on:keydown={ev => keyDown(ev, edits[3].id)}
|
||||
/>
|
||||
<div class="symbol">:</div>
|
||||
<input
|
||||
type="text" placeholder={'00'} class="zone" class:selected={selected === edits[4].id}
|
||||
bind:this={edits[4].el} bind:value={edits[4].value}
|
||||
on:click|stopPropagation={ev => editClick(ev, edits[4].id)}
|
||||
on:input={ev => onInput(ev.target, edits[4].id)}
|
||||
on:blur={ev => onInput(ev.target, edits[4].id)}
|
||||
on:keydown={ev => keyDown(ev, edits[4].id)}
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex-col">
|
||||
<div bind:this={dateDiv} class="datetime-presenter readable">
|
||||
<div class="preview-icon"><DPCalendar size={'full'} /></div>
|
||||
{currentDate.getDate().toString().padStart(2, '0')}
|
||||
<div class="symbol">.</div>
|
||||
{currentDate.getMonth().toString().padStart(2, '0')}
|
||||
<div class="symbol">.</div>
|
||||
{currentDate.getFullYear()}
|
||||
</div>
|
||||
{#if withTime}
|
||||
<div bind:this={timeDiv} class="datetime-presenter readable">
|
||||
<div class="preview-icon"><DPClock size={'full'} /></div>
|
||||
{currentDate.getHours().toString().padStart(2, '0')}
|
||||
<div class="symbol">:</div>
|
||||
{currentDate.getMinutes().toString().padStart(2, '0')}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.datetime-presenter-container {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: nowrap;
|
||||
flex-shrink: 0;
|
||||
min-width: 0;
|
||||
width: auto;
|
||||
border-radius: .75rem;
|
||||
}
|
||||
|
||||
.datetime-presenter {
|
||||
flex-shrink: 0;
|
||||
border-radius: .5rem;
|
||||
|
||||
&.readable {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: nowrap;
|
||||
flex-shrink: 0;
|
||||
min-width: 0;
|
||||
color: var(--theme-content-accent-color);
|
||||
|
||||
.symbol { margin: 0 .125rem; }
|
||||
}
|
||||
|
||||
.zone {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-weight: 500;
|
||||
color: var(--theme-content-accent-color);
|
||||
cursor: pointer;
|
||||
z-index: 1;
|
||||
|
||||
&::before, &::after {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -.125rem;
|
||||
width: calc(100% + .25rem);
|
||||
height: 100%;
|
||||
border-radius: .75rem;
|
||||
z-index: -1;
|
||||
}
|
||||
&::before { background-color: var(--primary-button-outline); }
|
||||
&::after { background-color: var(--theme-bg-accent-hover); }
|
||||
&:hover::after { content: ''; }
|
||||
}
|
||||
|
||||
.symbol {
|
||||
font-weight: 500;
|
||||
color: var(--theme-content-dark-color);
|
||||
}
|
||||
}
|
||||
|
||||
.datetime-icon {
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
color: var(--theme-content-color);
|
||||
background-color: var(--theme-bg-accent-color);
|
||||
border-radius: .75rem;
|
||||
|
||||
&:hover {
|
||||
color: var(--theme-caption-color);
|
||||
background-color: var(--theme-bg-accent-hover);
|
||||
}
|
||||
|
||||
&.selected {
|
||||
margin-right: .25rem;
|
||||
color: var(--theme-content-accent-color);
|
||||
background-color: var(--theme-bg-accent-hover);
|
||||
|
||||
&:hover {
|
||||
color: var(--theme-caption-color);
|
||||
background-color: var(--theme-bg-focused-color);
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
overflow: hidden;
|
||||
width: .75rem;
|
||||
height: .75rem;
|
||||
}
|
||||
}
|
||||
|
||||
.preview-icon {
|
||||
margin-right: .25rem;
|
||||
width: .75rem;
|
||||
height: .75rem;
|
||||
color: var(--theme-content-dark-color);
|
||||
}
|
||||
|
||||
input {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 1.5rem;
|
||||
height: 1.25rem;
|
||||
color: var(--theme-caption-color);
|
||||
border: transparent;
|
||||
border-radius: .25rem;
|
||||
text-align: center;
|
||||
transition: background-color .2s ease-out;
|
||||
|
||||
&:hover { background-color: var(--theme-bg-accent-hover); }
|
||||
&:focus {
|
||||
color: var(--theme-bg-color);
|
||||
background-color: var(--primary-button-enabled);
|
||||
}
|
||||
&::placeholder { color: var(--theme-content-dark-color); }
|
||||
}
|
||||
|
||||
.timeBox, .dateBox {
|
||||
visibility: visible;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
width: auto;
|
||||
opacity: 1;
|
||||
|
||||
&.zero {
|
||||
visibility: hidden;
|
||||
width: 0;
|
||||
max-width: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
.dateBox { margin-right: .25rem; }
|
||||
</style>
|
@ -13,12 +13,9 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import type { TSelectDate } from '../types'
|
||||
import DatePresenter from './DatePresenter.svelte'
|
||||
|
||||
export let value: TSelectDate
|
||||
export let bigDay: boolean = false
|
||||
export let wraped: boolean = false
|
||||
export let value: number | null | undefined
|
||||
</script>
|
||||
|
||||
<DatePresenter {value} {bigDay} {wraped} withTime />
|
||||
<DatePresenter {value} withTime />
|
@ -70,8 +70,11 @@
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.day-name,
|
||||
.selected-month-controller {
|
||||
.month-calendar {
|
||||
user-select: none;
|
||||
cursor: default;
|
||||
}
|
||||
.day-name {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
@ -102,14 +105,9 @@
|
||||
border: 1px solid var(--primary-button-focused-border);
|
||||
background-color: var(--primary-button-enabled);
|
||||
color: var(--primary-button-color);
|
||||
cursor: pointer;
|
||||
}
|
||||
.wrongMonth {
|
||||
color: var(--grayscale-grey-03);
|
||||
}
|
||||
.month-name {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
margin: 0 5px;
|
||||
color: var(--theme-content-dark-color);
|
||||
}
|
||||
</style>
|
||||
|
56
packages/ui/src/components/calendar/TimePopup.svelte
Normal file
56
packages/ui/src/components/calendar/TimePopup.svelte
Normal file
@ -0,0 +1,56 @@
|
||||
<!--
|
||||
// Copyright © 2020 Anticrm Platform Contributors.
|
||||
//
|
||||
// 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 { createEventDispatcher } from 'svelte'
|
||||
import { MILLISECONDS_IN_DAY, DAYS_IN_WEEK } from './internal/DateUtils'
|
||||
|
||||
export let value: number
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
interface ITimes {
|
||||
label: string
|
||||
value: number
|
||||
}
|
||||
|
||||
let actions: Array<ITimes> = [{ label: '- week', value: -DAYS_IN_WEEK * MILLISECONDS_IN_DAY },
|
||||
{ label: '- day', value: -MILLISECONDS_IN_DAY },
|
||||
{ label: '- hour', value: -60 * 60 * 1000 },
|
||||
{ label: '- 30 min', value: -30 * 60 * 1000 },
|
||||
{ label: '- 5 min', value: -5 * 60 * 1000 },
|
||||
{ label: '+ 5 min', value: 5 * 60 * 1000 },
|
||||
{ label: '+ 30 min', value: 30 * 60 * 1000 },
|
||||
{ label: '+ hour', value: 60 * 60 * 1000 },
|
||||
{ label: '+ day', value: MILLISECONDS_IN_DAY },
|
||||
{ label: '+ week', value: DAYS_IN_WEEK * MILLISECONDS_IN_DAY }]
|
||||
</script>
|
||||
|
||||
<div class="scrollbox">
|
||||
{#each actions as action}
|
||||
<button class="ap-menuItem no-word-wrap" on:click={() => { dispatch('update', new Date(value + action.value)) }}>
|
||||
{action.label}
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.scrollbox {
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
</style>
|
@ -54,15 +54,5 @@
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
border-collapse: collapse;
|
||||
.row {
|
||||
display: table-row;
|
||||
}
|
||||
.th {
|
||||
display: table-cell;
|
||||
}
|
||||
.calendar {
|
||||
display: table-cell;
|
||||
padding: 0.3em;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -0,0 +1,8 @@
|
||||
<script lang="ts">
|
||||
export let size: 'x-small' | 'small' | 'medium' | 'large' | 'full'
|
||||
export let fill: string = 'currentColor'
|
||||
</script>
|
||||
|
||||
<svg class="svg-{size}" {fill} viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M16.8,2.8V2c0-0.4-0.3-0.8-0.8-0.8S15.2,1.6,15.2,2v0.8H8.8V2c0-0.4-0.3-0.8-0.8-0.8S7.2,1.6,7.2,2v0.8 c-3.2,0.3-5,2.3-5,5.7V17c0,3.7,2.1,5.8,5.8,5.8h8c3.7,0,5.8-2.1,5.8-5.8V8.5C21.8,5.1,19.9,3.1,16.8,2.8z M7.2,4.3V5 c0,0.4,0.3,0.8,0.8,0.8S8.8,5.4,8.8,5V4.2h6.5V5c0,0.4,0.3,0.8,0.8,0.8s0.8-0.3,0.8-0.8V4.3c2.3,0.2,3.4,1.6,3.5,4H3.8 C3.8,5.9,5,4.5,7.2,4.3z M16,21.2H8c-2.9,0-4.2-1.4-4.2-4.2V9.8h16.5V17C20.2,19.9,18.9,21.2,16,21.2z"/>
|
||||
</svg>
|
9
packages/ui/src/components/calendar/icons/DPClock.svelte
Normal file
9
packages/ui/src/components/calendar/icons/DPClock.svelte
Normal file
@ -0,0 +1,9 @@
|
||||
<script lang="ts">
|
||||
export let size: 'x-small' | 'small' | 'medium' | 'large' | 'full'
|
||||
export let fill: string = 'currentColor'
|
||||
</script>
|
||||
|
||||
<svg class="svg-{size}" {fill} viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12,1.2C6.1,1.2,1.2,6.1,1.2,12S6.1,22.8,12,22.8S22.8,17.9,22.8,12S17.9,1.2,12,1.2z M12,21.2c-5.1,0-9.2-4.2-9.2-9.2 S6.9,2.8,12,2.8s9.2,4.2,9.2,9.2S17.1,21.2,12,21.2z"/>
|
||||
<path d="M16.1,14.5L13,12.7c-0.3-0.2-0.6-0.7-0.6-1.1V7.5c0-0.4-0.3-0.8-0.8-0.8c-0.4,0-0.8,0.3-0.8,0.8v4.1c0,0.9,0.6,1.9,1.3,2.4 l3.1,1.9c0.1,0.1,0.2,0.1,0.4,0.1c0.2,0,0.5-0.1,0.7-0.4C16.6,15.2,16.5,14.7,16.1,14.5z"/>
|
||||
</svg>
|
@ -0,0 +1,8 @@
|
||||
<script lang="ts">
|
||||
export let size: 'x-small' | 'small' | 'medium' | 'large' | 'full'
|
||||
export let fill: string = 'currentColor'
|
||||
</script>
|
||||
|
||||
<svg class="svg-{size}" {fill} viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12,22C6.5,22,2,17.5,2,12C2,6.5,6.5,2,12,2c5.5,0,10,4.5,10,10C22,17.5,17.5,22,12,22z M12,4c-4.4,0-8,3.6-8,8 c0,4.4,3.6,8,8,8c4.4,0,8-3.6,8-8C20,7.6,16.4,4,12,4z M17,13h-6V7h2v4h4V13z"/>
|
||||
</svg>
|
9
packages/ui/src/components/calendar/icons/DPEnd.svelte
Normal file
9
packages/ui/src/components/calendar/icons/DPEnd.svelte
Normal file
@ -0,0 +1,9 @@
|
||||
<script lang="ts">
|
||||
export let size: 'x-small' | 'small' | 'medium' | 'large' | 'full'
|
||||
export let fill: string = 'currentColor'
|
||||
</script>
|
||||
|
||||
<svg class="svg-{size}" {fill} viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12.2,15.1l2.6-2.6c0.3-0.3,0.3-0.8,0-1.1L12.2,9c-0.3-0.3-0.8-0.3-1.1,0c-0.3,0.3-0.3,0.8,0,1.1l1.3,1.3H4 c-0.4,0-0.8,0.3-0.8,0.8c0,0.4,0.3,0.8,0.8,0.8h8.4l-1.3,1.3c-0.3,0.3-0.3,0.8,0,1.1c0.1,0.2,0.3,0.2,0.5,0.2S12.1,15.3,12.2,15.1z"/>
|
||||
<path d="M12,3.2c-0.4,0-0.8,0.3-0.8,0.8s0.3,0.8,0.8,0.8c4.3,0,7.2,3,7.2,7.2s-3,7.2-7.2,7.2c-0.4,0-0.8,0.3-0.8,0.8 s0.3,0.8,0.8,0.8c5.2,0,8.8-3.6,8.8-8.8S17.2,3.2,12,3.2z"/>
|
||||
</svg>
|
9
packages/ui/src/components/calendar/icons/DPStart.svelte
Normal file
9
packages/ui/src/components/calendar/icons/DPStart.svelte
Normal file
@ -0,0 +1,9 @@
|
||||
<script lang="ts">
|
||||
export let size: 'x-small' | 'small' | 'medium' | 'large' | 'full'
|
||||
export let fill: string = 'currentColor'
|
||||
</script>
|
||||
|
||||
<svg class="svg-{size}" {fill} viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M20.5,11.5L18,9c-0.3-0.3-0.8-0.3-1.1,0c-0.3,0.3-0.3,0.8,0,1.1l1.3,1.3H9.8c-0.4,0-0.8,0.3-0.8,0.8 c0,0.4,0.3,0.8,0.8,0.8h8.4l-1.3,1.3c-0.3,0.3-0.3,0.8,0,1.1c0.2,0.2,0.3,0.2,0.5,0.2s0.4-0.1,0.5-0.2l2.6-2.6 C20.8,12.3,20.8,11.8,20.5,11.5z"/>
|
||||
<path d="M11.8,19.2c-4.3,0-7.3-3-7.3-7.2s3-7.2,7.3-7.2c0.4,0,0.8-0.3,0.8-0.8s-0.3-0.8-0.8-0.8C6.6,3.2,3,6.8,3,12 s3.6,8.8,8.8,8.8c0.4,0,0.8-0.3,0.8-0.8S12.2,19.2,11.8,19.2z"/>
|
||||
</svg>
|
@ -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 const DAYS_IN_WEEK = 7
|
||||
export const MILLISECONDS_IN_DAY = 86400000
|
||||
export const MILLISECONDS_IN_MINUTE = 60000
|
||||
|
||||
export function firstDay (date: Date, mondayStart: boolean): Date {
|
||||
const firstDayOfMonth = new Date(date)
|
||||
@ -33,6 +33,15 @@ export function firstDay (date: Date, mondayStart: boolean): Date {
|
||||
return result
|
||||
}
|
||||
|
||||
export function getWeek (date: Date): number {
|
||||
const onejan = new Date(date.getFullYear(), 0, 1)
|
||||
return Math.ceil((((date.getTime() - onejan.getTime()) / MILLISECONDS_IN_DAY) + onejan.getDay() + 1) / DAYS_IN_WEEK)
|
||||
}
|
||||
|
||||
export function daysInMonth (date: Date): number {
|
||||
return 33 - new Date(date.getFullYear(), date.getMonth(), 33).getDate()
|
||||
}
|
||||
|
||||
export function getWeekDayName (weekDay: Date, weekFormat: 'narrow' | 'short' | 'long' | undefined = 'short'): string {
|
||||
const locale = new Intl.NumberFormat().resolvedOptions().locale
|
||||
return new Intl.DateTimeFormat(locale, {
|
||||
@ -67,6 +76,15 @@ export function getMonthName (date: Date): string {
|
||||
const locale = new Intl.NumberFormat().resolvedOptions().locale
|
||||
return new Intl.DateTimeFormat(locale, { month: 'long' }).format(date)
|
||||
}
|
||||
|
||||
export type TCellStyle = 'not-selected' | 'selected'
|
||||
export interface ICell {
|
||||
dayOfWeek: number
|
||||
style: TCellStyle
|
||||
focused: boolean
|
||||
today?: boolean
|
||||
}
|
||||
|
||||
export function getMonday (d: Date, mondayStart: boolean): Date {
|
||||
d = new Date(d)
|
||||
const day = d.getDay()
|
||||
|
@ -44,10 +44,11 @@ export { default as PopupMenu } from './components/PopupMenu.svelte'
|
||||
// export { default as PopupItem } from './components/PopupItem.svelte'
|
||||
export { default as TextArea } from './components/TextArea.svelte'
|
||||
export { default as Section } from './components/Section.svelte'
|
||||
export { default as DatePicker } from './components/DatePicker.svelte'
|
||||
export { default as DatePopup } from './components/DatePopup.svelte'
|
||||
export { default as DatePresenter } from './components/DatePresenter.svelte'
|
||||
export { default as DateTimePresenter } from './components/DateTimePresenter.svelte'
|
||||
export { default as DatePicker } from './components/calendar/DatePicker.svelte'
|
||||
export { default as DatePopup } from './components/calendar/DatePopup.svelte'
|
||||
export { default as TimePopup } from './components/calendar/TimePopup.svelte'
|
||||
export { default as DatePresenter } from './components/calendar/DatePresenter.svelte'
|
||||
export { default as DateTimePresenter } from './components/calendar/DateTimePresenter.svelte'
|
||||
export { default as StylishEdit } from './components/StylishEdit.svelte'
|
||||
export { default as Grid } from './components/Grid.svelte'
|
||||
export { default as Row } from './components/Row.svelte'
|
||||
|
@ -8,7 +8,8 @@ export const tooltipstore = writable<LabelAndProps>({
|
||||
direction: undefined,
|
||||
component: undefined,
|
||||
props: undefined,
|
||||
anchor: undefined
|
||||
anchor: undefined,
|
||||
onUpdate: undefined
|
||||
})
|
||||
|
||||
export function showTooltip (
|
||||
@ -17,7 +18,8 @@ export function showTooltip (
|
||||
direction?: TooltipAligment,
|
||||
component?: AnySvelteComponent | AnyComponent,
|
||||
props?: any,
|
||||
anchor?: HTMLElement
|
||||
anchor?: HTMLElement,
|
||||
onUpdate?: (result: any) => void
|
||||
): void {
|
||||
tooltipstore.set({
|
||||
label: label,
|
||||
@ -25,7 +27,8 @@ export function showTooltip (
|
||||
direction: direction,
|
||||
component: component,
|
||||
props: props,
|
||||
anchor: anchor
|
||||
anchor: anchor,
|
||||
onUpdate: onUpdate
|
||||
})
|
||||
}
|
||||
|
||||
@ -36,6 +39,7 @@ export function closeTooltip (): void {
|
||||
direction: undefined,
|
||||
component: undefined,
|
||||
props: undefined,
|
||||
anchor: undefined
|
||||
anchor: undefined,
|
||||
onUpdate: undefined
|
||||
})
|
||||
}
|
||||
|
@ -74,6 +74,7 @@ export interface LabelAndProps {
|
||||
component?: AnySvelteComponent | AnyComponent
|
||||
props?: any
|
||||
anchor: HTMLElement | undefined
|
||||
onUpdate?: (result: any) => void
|
||||
}
|
||||
|
||||
export interface ListItem {
|
||||
@ -86,11 +87,3 @@ export interface DropdownTextItem {
|
||||
id: string
|
||||
label: string
|
||||
}
|
||||
|
||||
export type TSelectDate = Date | null | undefined
|
||||
export type TCellStyle = 'not-selected' | 'selected'
|
||||
export interface ICell {
|
||||
dayOfWeek: number
|
||||
style: TCellStyle
|
||||
today?: boolean
|
||||
}
|
||||
|
@ -47,7 +47,7 @@
|
||||
|
||||
<div class="antiSelect">
|
||||
{#if date}
|
||||
<DatePresenter value={date} withTime={date.getMinutes() !== 0 && date.getHours() !== 0 && interval < DAY} />
|
||||
<DatePresenter value={date.getTime()} withTime={date.getMinutes() !== 0 && date.getHours() !== 0 && interval < DAY} />
|
||||
{#if interval > 0}
|
||||
{#await formatDueDate(interval) then t}
|
||||
<span class='ml-2 mr-1 whitespace-nowrap'>({t})</span>
|
||||
|
@ -26,7 +26,7 @@
|
||||
|
||||
let title: string = value.title
|
||||
let description: string = value.description
|
||||
let startDate: Date = new Date(value.date)
|
||||
let startDate: number = value.date
|
||||
let participants: Ref<Employee>[] = value.participants ?? []
|
||||
const space = calendar.space.PersonalEvents
|
||||
|
||||
@ -39,7 +39,7 @@
|
||||
|
||||
async function saveReminder () {
|
||||
await client.updateDoc(value._class, value.space, value._id, {
|
||||
date: startDate.getTime(),
|
||||
date: startDate,
|
||||
description,
|
||||
participants,
|
||||
title
|
||||
@ -56,7 +56,7 @@
|
||||
label={calendar.string.EditReminder}
|
||||
okAction={saveReminder}
|
||||
okLabel={presentation.string.Save}
|
||||
canSave={title.trim().length > 0 && startDate.getTime() > 0 && participants.length > 0}
|
||||
canSave={title.trim().length > 0 && startDate > 0 && participants.length > 0}
|
||||
{space}
|
||||
on:close={() => {
|
||||
dispatch('close')
|
||||
|
@ -16,7 +16,7 @@
|
||||
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 { addZero } from '@anticrm/ui/src/components/calendar/internal/DateUtils'
|
||||
import calendar from '../plugin'
|
||||
import EventsPopup from './EventsPopup.svelte'
|
||||
|
||||
|
@ -37,6 +37,6 @@
|
||||
</Tooltip>
|
||||
{/await}
|
||||
</div>
|
||||
<DateTimePresenter value={new Date(value.date + value.shift)} />
|
||||
<DateTimePresenter value={value.date + value.shift} />
|
||||
{/if}
|
||||
</div>
|
@ -37,7 +37,7 @@
|
||||
{#await getEvent(tx.objectId) then event}
|
||||
{#if event}
|
||||
<span class="over-underline caption-color" on:click={() => { click(event) }}>{event.title}</span> 
|
||||
<DateTimePresenter value={new Date(event.date)} />
|
||||
<DateTimePresenter value={event.date} />
|
||||
{/if}
|
||||
{/await}
|
||||
</div>
|
||||
|
@ -22,7 +22,7 @@
|
||||
import type { Candidate, Review } from '@anticrm/recruit'
|
||||
import task, { SpaceWithStates } from '@anticrm/task'
|
||||
import { StyledTextBox } from '@anticrm/text-editor'
|
||||
import { DatePicker, Grid, Status as StatusControl, StylishEdit } from '@anticrm/ui'
|
||||
import { DatePicker, Grid, Status as StatusControl, StylishEdit, EditBox, Row } from '@anticrm/ui'
|
||||
import view from '@anticrm/view'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import recruit from '../../plugin'
|
||||
@ -38,8 +38,8 @@
|
||||
|
||||
let title: string = ''
|
||||
let description: string = ''
|
||||
let startDate: Date = new Date()
|
||||
let dueDate: Date = new Date()
|
||||
let startDate: number = Date.now()
|
||||
let dueDate: number = Date.now()
|
||||
let location: string = ''
|
||||
let company: Ref<Organization> | undefined = undefined
|
||||
|
||||
@ -102,8 +102,8 @@
|
||||
|
||||
await client.addCollection(recruit.class.Review, doc.space, doc.attachedTo, doc.attachedToClass, 'reviews', {
|
||||
number: (incResult as any).object.sequence,
|
||||
date: startDate?.getTime() ?? null,
|
||||
dueDate: dueDate?.getTime() ?? null,
|
||||
date: startDate ?? null,
|
||||
dueDate: dueDate ?? null,
|
||||
description,
|
||||
verdict: '',
|
||||
title,
|
||||
@ -133,6 +133,12 @@
|
||||
}
|
||||
|
||||
$: validate(doc, doc._class)
|
||||
const updateStart = (result: any): void => {
|
||||
if (result.detail !== undefined) {
|
||||
dueDate = result.detail
|
||||
dueDate = dueDate
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Card
|
||||
@ -152,39 +158,31 @@
|
||||
>
|
||||
<StatusControl slot="error" {status} />
|
||||
|
||||
<Grid column={1} rowGap={1.75}>
|
||||
<Grid column={!preserveCandidate ? 2 : 1}>
|
||||
<StylishEdit bind:value={title} label={recruit.string.Title} />
|
||||
{#if !preserveCandidate}
|
||||
<div class="antiComponentBox">
|
||||
<UserBox
|
||||
_class={contact.class.Person}
|
||||
title={recruit.string.Candidate}
|
||||
caption={recruit.string.Candidates}
|
||||
bind:value={doc.attachedTo}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</Grid>
|
||||
<StyledTextBox
|
||||
emphasized
|
||||
showButtons={false}
|
||||
bind:content={description}
|
||||
label={recruit.string.Description}
|
||||
alwaysEdit
|
||||
placeholder={recruit.string.AddDescription}
|
||||
/>
|
||||
<Grid column={2}>
|
||||
<StylishEdit bind:value={location} label={recruit.string.Location} />
|
||||
<div class="antiComponentBox">
|
||||
<OrganizationSelector bind:value={company} label={recruit.string.Company} />
|
||||
</div>
|
||||
</Grid>
|
||||
<div class="antiComponentBox">
|
||||
<DatePicker title={recruit.string.StartDate} bind:value={startDate} withTime />
|
||||
</div>
|
||||
<div class="antiComponentBox">
|
||||
<DatePicker title={recruit.string.DueDate} bind:value={dueDate} withTime />
|
||||
</div>
|
||||
<Grid column={2} rowGap={1.75}>
|
||||
<EditBox label={recruit.string.Title} icon={recruit.icon.Review} bind:value={title} maxWidth={'13rem'} />
|
||||
{#if !preserveCandidate}
|
||||
<UserBox
|
||||
_class={contact.class.Person}
|
||||
title={recruit.string.Candidate}
|
||||
caption={recruit.string.Candidates}
|
||||
bind:value={doc.attachedTo}
|
||||
/>
|
||||
{:else}
|
||||
<div></div>
|
||||
{/if}
|
||||
<EditBox label={recruit.string.Location} icon={recruit.icon.Location} bind:value={location} maxWidth={'13rem'} />
|
||||
<OrganizationSelector bind:value={company} label={recruit.string.Company} />
|
||||
<DatePicker title={recruit.string.StartDate} bind:value={startDate} withTime on:change={updateStart} />
|
||||
<DatePicker title={recruit.string.DueDate} bind:value={dueDate} withTime />
|
||||
<Row>
|
||||
<StyledTextBox
|
||||
emphasized
|
||||
showButtons={false}
|
||||
bind:content={description}
|
||||
label={recruit.string.Description}
|
||||
alwaysEdit
|
||||
placeholder={recruit.string.AddDescription}
|
||||
/>
|
||||
</Row>
|
||||
</Grid>
|
||||
</Card>
|
||||
|
@ -106,18 +106,3 @@
|
||||
on:change={() => client.update(object, { verdict: object.verdict })}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.card {
|
||||
align-self: stretch;
|
||||
width: calc(50% - 3.5rem);
|
||||
}
|
||||
.arrows {
|
||||
width: 4rem;
|
||||
}
|
||||
|
||||
.description {
|
||||
height: 10rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
</style>
|
||||
|
@ -21,7 +21,7 @@
|
||||
import setting from '../../plugin'
|
||||
|
||||
export let tx: TxUpdateDoc<Integration>
|
||||
export let doc: Integration
|
||||
// export let doc: Integration
|
||||
|
||||
const client = getClient()
|
||||
|
||||
|
@ -27,7 +27,7 @@
|
||||
|
||||
let name: string
|
||||
const done = false
|
||||
let dueTo: Date
|
||||
let dueTo: number
|
||||
|
||||
$: _space = space
|
||||
|
||||
@ -48,7 +48,7 @@
|
||||
{
|
||||
name,
|
||||
done,
|
||||
dueTo: dueTo?.getTime() ?? undefined
|
||||
dueTo: dueTo ?? undefined
|
||||
}
|
||||
)
|
||||
}
|
||||
|
@ -24,7 +24,7 @@
|
||||
export let item: TodoItem
|
||||
|
||||
let name: string = ''
|
||||
let dueTo: Date | undefined | null = null
|
||||
let dueTo: number | null | undefined = null
|
||||
|
||||
let _itemId: Ref<TodoItem>
|
||||
|
||||
@ -32,7 +32,7 @@
|
||||
_itemId = item._id
|
||||
name = item.name
|
||||
if (item.dueTo != null) {
|
||||
dueTo = new Date(item.dueTo)
|
||||
dueTo = item.dueTo
|
||||
} else {
|
||||
dueTo = null
|
||||
}
|
||||
@ -51,7 +51,7 @@
|
||||
ops.name = name
|
||||
}
|
||||
if (item.dueTo !== dueTo) {
|
||||
ops.dueTo = (dueTo?.getTime() ?? null) as unknown as Timestamp
|
||||
ops.dueTo = (dueTo ?? null) as unknown as Timestamp
|
||||
}
|
||||
|
||||
if (Object.keys(ops).length === 0) {
|
||||
|
@ -24,7 +24,7 @@
|
||||
$: todos = (value.$lookup?.todoItems as TodoItem[]) ?? []
|
||||
</script>
|
||||
|
||||
<div class="applications-container">
|
||||
<div class="flex-col">
|
||||
{#if todos.length > 0}
|
||||
<Table
|
||||
_class={task.class.TodoItem}
|
||||
@ -38,17 +38,3 @@
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.applications-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.title {
|
||||
margin-right: 0.75rem;
|
||||
font-weight: 500;
|
||||
font-size: 1.25rem;
|
||||
color: var(--theme-caption-color);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -15,34 +15,14 @@
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import { TypeDate } from '@anticrm/core'
|
||||
import { IntlString } from '@anticrm/platform'
|
||||
import { DatePopup, showPopup } from '@anticrm/ui'
|
||||
import DatePresenter from './DatePresenter.svelte'
|
||||
// import { TypeDate } from '@anticrm/core'
|
||||
// import { IntlString } from '@anticrm/platform'
|
||||
import { DatePresenter } from '@anticrm/ui'
|
||||
|
||||
export let value: number | Date | undefined
|
||||
export let label: IntlString
|
||||
export let value: number | null | undefined
|
||||
// export let label: IntlString
|
||||
export let onChange: (value: any) => void
|
||||
|
||||
export let attributeType: TypeDate | undefined
|
||||
|
||||
$: date = value ? new Date(value) : new Date()
|
||||
let container: HTMLElement
|
||||
let opened: boolean = false
|
||||
// export let attributeType: TypeDate | undefined
|
||||
</script>
|
||||
|
||||
<div class="flex-row-center" bind:this={container}
|
||||
on:click|preventDefault={() => {
|
||||
if (!opened) {
|
||||
opened = true
|
||||
showPopup(DatePopup, { value: date, title: label, withTime: attributeType?.withTime ?? false }, container, (result) => {
|
||||
if (result) {
|
||||
value = result.getTime()
|
||||
onChange(value)
|
||||
}
|
||||
opened = false
|
||||
})
|
||||
}
|
||||
}} >
|
||||
<DatePresenter {value} {attributeType} />
|
||||
</div>
|
||||
<DatePresenter {value} on:change={onChange} editable />
|
||||
|
@ -15,19 +15,11 @@
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import { TypeDate } from '@anticrm/core'
|
||||
// import { TypeDate } from '@anticrm/core'
|
||||
import { DatePresenter } from '@anticrm/ui'
|
||||
|
||||
export let value: number | Date | undefined
|
||||
export let attributeType: TypeDate | undefined
|
||||
|
||||
$: date = value ? new Date(value) : undefined
|
||||
export let value: number | null | undefined
|
||||
// export let attributeType: TypeDate | undefined
|
||||
</script>
|
||||
|
||||
<div class="antiSelect">
|
||||
{#if date}
|
||||
<DatePresenter value={date} withTime={attributeType?.withTime ?? false} />
|
||||
{:else}
|
||||
No date
|
||||
{/if}
|
||||
</div>
|
||||
<DatePresenter {value} />
|
||||
|
@ -213,13 +213,17 @@
|
||||
{#if client}
|
||||
<svg class="svg-mask">
|
||||
<clipPath id="notify-normal">
|
||||
<path
|
||||
d="M0,0v52.5h52.5V0H0z M34,23.2c-3.2,0-5.8-2.6-5.8-5.8c0-3.2,2.6-5.8,5.8-5.8c3.2,0,5.8,2.6,5.8,5.8 C39.8,20.7,37.2,23.2,34,23.2z"
|
||||
/>
|
||||
<path d="M0,0v52.5h52.5V0H0z M34,23.2c-3.2,0-5.8-2.6-5.8-5.8c0-3.2,2.6-5.8,5.8-5.8c3.2,0,5.8,2.6,5.8,5.8 C39.8,20.7,37.2,23.2,34,23.2z" />
|
||||
</clipPath>
|
||||
<clipPath id="notify-small">
|
||||
<path d="M0,0v45h45V0H0z M29.5,20c-2.8,0-5-2.2-5-5s2.2-5,5-5s5,2.2,5,5S32.3,20,29.5,20z" />
|
||||
</clipPath>
|
||||
<clipPath id="nub-bg">
|
||||
<path d="M7.3.6 4.2 4.3C2.9 5.4 1.5 6 0 6v1h18V6c-1.5 0-2.9-.6-4.2-1.7L10.7.6C9.9-.1 8.5-.2 7.5.4c0 .1-.1.1-.2.2z" />
|
||||
</clipPath>
|
||||
<clipPath id="nub-border">
|
||||
<path d="M4.8 5.1 8 1.3s.1 0 .1-.1c.5-.3 1.4-.3 1.9.1L13.1 5l.1.1 1.2.9H18c-1.5 0-2.9-.6-4.2-1.7L10.7.6C9.9-.1 8.5-.2 7.5.4c0 .1-.1.1-.2.2L4.2 4.3C2.9 5.4 1.5 6 0 6h3.6l1.2-.9z" />
|
||||
</clipPath>
|
||||
</svg>
|
||||
<div class="workbench-container">
|
||||
<div class="antiPanel-application" on:click={toggleNav}>
|
||||
|
@ -142,17 +142,17 @@ test.describe('recruit tests', () => {
|
||||
// Click button:has-text("Review")
|
||||
await page.click('button:has-text("Review")')
|
||||
// Click [placeholder="\ "]
|
||||
await page.click('[placeholder="\\ "]')
|
||||
await page.click('[placeholder="placeholder"]')
|
||||
// Fill [placeholder="\ "]
|
||||
await page.fill('[placeholder="\\ "]', 'Meet PEterson')
|
||||
await page.fill('[placeholder="placeholder"]', 'Meet PEterson')
|
||||
// Click text=Location Company Company >> [placeholder="\ "]
|
||||
await page.click('text=Location Company Company >> [placeholder="\\ "]')
|
||||
await page.click('text=placeholder Location >> [placeholder="placeholder"]')
|
||||
// Fill text=Location Company Company >> [placeholder="\ "]
|
||||
await page.fill('text=Location Company Company >> [placeholder="\\ "]', 'NSK')
|
||||
await page.fill('text=placeholder Location >> [placeholder="placeholder"]', 'NSK')
|
||||
// Click text=Company Company >> div
|
||||
await page.click('text=Company Company >> div')
|
||||
// await page.click('text=Company Company >> div')
|
||||
// Click button:has-text("Apple")
|
||||
await page.click('button:has-text("Apple")')
|
||||
// await page.click('button:has-text("Apple")')
|
||||
// Click text=Candidate Not selected >> span
|
||||
await page.click('text=Candidate Not selected >> span')
|
||||
// Click button:has-text("Andrey P.")
|
||||
|
Loading…
Reference in New Issue
Block a user