mirror of
https://github.com/hcengineering/platform.git
synced 2024-12-23 11:31:57 +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()
|
const dispatch = createEventDispatcher()
|
||||||
</script>
|
</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">
|
||||||
<div class="antiCard-header__title"><Label {label} params={labelProps ?? {}} /></div>
|
<div class="antiCard-header__title"><Label {label} params={labelProps ?? {}} /></div>
|
||||||
{#if $$slots.error}
|
{#if $$slots.error}
|
||||||
|
@ -131,15 +131,12 @@
|
|||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
.emphasized .emphasized-focus + .label {
|
|
||||||
top: 0.5rem;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.emphasized {
|
.emphasized {
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
background-color: var(--theme-bg-accent-color);
|
background-color: var(--theme-bg-accent-color);
|
||||||
border: 1px solid var(--theme-bg-accent-hover);
|
border: 1px solid var(--theme-bg-accent-hover);
|
||||||
border-radius: 0.75rem;
|
border-radius: .5rem;
|
||||||
&.emphasized-focus {
|
&.emphasized-focus {
|
||||||
background-color: var(--theme-bg-focused-color);
|
background-color: var(--theme-bg-focused-color);
|
||||||
border-color: var(--theme-bg-focused-border);
|
border-color: var(--theme-bg-focused-border);
|
||||||
@ -149,9 +146,5 @@
|
|||||||
overflow: auto;
|
overflow: auto;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
line-height: 150%;
|
line-height: 150%;
|
||||||
|
|
||||||
.nolabel {
|
|
||||||
padding-top: 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -49,6 +49,7 @@ input {
|
|||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
outline: none;
|
outline: none;
|
||||||
color: var(--theme-caption-color);
|
color: var(--theme-caption-color);
|
||||||
|
&.wrong-input { background-color: var(--system-error-color) !important; }
|
||||||
}
|
}
|
||||||
audio, canvas, embed, iframe, img, object, svg, video {
|
audio, canvas, embed, iframe, img, object, svg, video {
|
||||||
display: block;
|
display: block;
|
||||||
@ -108,10 +109,8 @@ p:last-child { margin-block-end: 0; }
|
|||||||
.inline-flex { display: inline-flex; }
|
.inline-flex { display: inline-flex; }
|
||||||
.flex-grow { flex-grow: 1; }
|
.flex-grow { flex-grow: 1; }
|
||||||
.flex-no-shrink { flex-shrink: 0; }
|
.flex-no-shrink { flex-shrink: 0; }
|
||||||
.flex-nowrap {
|
.flex-wrap { flex-wrap: wrap; }
|
||||||
display: flex;
|
.flex-nowrap { flex-wrap: nowrap; }
|
||||||
flex-wrap: nowrap;
|
|
||||||
}
|
|
||||||
.flex-baseline {
|
.flex-baseline {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
@ -141,9 +140,6 @@ p:last-child { margin-block-end: 0; }
|
|||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
flex-wrap: nowrap;
|
flex-wrap: nowrap;
|
||||||
}
|
}
|
||||||
.flex-wrap {
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
.flex-row-top {
|
.flex-row-top {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
@ -297,6 +293,8 @@ p:last-child { margin-block-end: 0; }
|
|||||||
.mb-2 { margin-bottom: .5rem; }
|
.mb-2 { margin-bottom: .5rem; }
|
||||||
.mb-3 { margin-bottom: .75rem; }
|
.mb-3 { margin-bottom: .75rem; }
|
||||||
.mb-4 { margin-bottom: 1rem; }
|
.mb-4 { margin-bottom: 1rem; }
|
||||||
|
.mx-1 { margin: 0 .125rem; }
|
||||||
|
.mx-2 { margin: 0 .25rem; }
|
||||||
|
|
||||||
.pr-1 { padding-right: .25rem; }
|
.pr-1 { padding-right: .25rem; }
|
||||||
.pr-4 { padding-right: 1rem; }
|
.pr-4 { padding-right: 1rem; }
|
||||||
@ -305,6 +303,7 @@ p:last-child { margin-block-end: 0; }
|
|||||||
.p-10 { padding: 2.5rem; }
|
.p-10 { padding: 2.5rem; }
|
||||||
|
|
||||||
/* --------- */
|
/* --------- */
|
||||||
|
.no-word-wrap { word-wrap: none; }
|
||||||
.relative { position: relative; }
|
.relative { position: relative; }
|
||||||
.abs-lt-content {
|
.abs-lt-content {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@ -344,8 +343,8 @@ p:last-child { margin-block-end: 0; }
|
|||||||
.h-2 { height: .5rem; }
|
.h-2 { height: .5rem; }
|
||||||
.h-9 { height: 2.25rem; }
|
.h-9 { height: 2.25rem; }
|
||||||
.w-full { width: 100%; }
|
.w-full { width: 100%; }
|
||||||
.w-2125rem {width: 21.25rem; }
|
.w-85 {width: 21.25rem; }
|
||||||
.w-4125rem {width: 41.25rem; }
|
.w-165 {width: 41.25rem; }
|
||||||
.min-w-0 { min-width: 0; }
|
.min-w-0 { min-width: 0; }
|
||||||
.min-h-0 { min-height: 0; }
|
.min-h-0 { min-height: 0; }
|
||||||
.clear-mins {
|
.clear-mins {
|
||||||
@ -433,6 +432,8 @@ a.no-line {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
width: max-content;
|
width: max-content;
|
||||||
|
user-select: none;
|
||||||
|
min-width: auto;
|
||||||
}
|
}
|
||||||
.overflow-label {
|
.overflow-label {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
@ -466,26 +466,17 @@
|
|||||||
& > .icon { color: var(--theme-caption-color); }
|
& > .icon { color: var(--theme-caption-color); }
|
||||||
}
|
}
|
||||||
|
|
||||||
.label, .result {
|
.label {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
|
||||||
.label {
|
|
||||||
font-size: .75rem;
|
font-size: .75rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: var(--theme-content-accent-color);
|
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 {
|
.group {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -494,20 +485,6 @@
|
|||||||
margin-left: .75rem;
|
margin-left: .75rem;
|
||||||
min-height: 0;
|
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 */
|
/* Wraps */
|
||||||
@ -518,6 +495,7 @@
|
|||||||
flex-wrap: nowrap;
|
flex-wrap: nowrap;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
|
||||||
|
&.conners {
|
||||||
&::after, &::before {
|
&::after, &::before {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 6px;
|
width: 6px;
|
||||||
@ -534,7 +512,31 @@
|
|||||||
right: -4px;
|
right: -4px;
|
||||||
clip-path: path('M0,6h6v-6h-1v5h-5z');
|
clip-path: path('M0,6h6v-6h-1v5h-5z');
|
||||||
}
|
}
|
||||||
|
}
|
||||||
&.wraped::before, &.wraped::after { content: ''; }
|
&.wraped::before, &.wraped::after { content: ''; }
|
||||||
&.focusWI:focus-within::before, &.focusWI:focus-within::after { content: ''; }
|
&.focusWI:focus-within::before, &.focusWI:focus-within::after { content: ''; }
|
||||||
&.focus:focus::before, &.focus:focus::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'}/>
|
<Icon {icon} size={'small'}/>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="antiWrapper focusWI">
|
<div class="antiWrapper conners focusWI">
|
||||||
{#if format === 'password'}
|
{#if format === 'password'}
|
||||||
<input bind:this={input} type='passsword' bind:value placeholder={phTraslate} {style} on:input={(ev) => ev.target && computeSize(ev.target)} on:change/>
|
<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'}
|
{:else if format === 'number'}
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
export let component: AnySvelteComponent | AnyComponent | undefined = undefined
|
export let component: AnySvelteComponent | AnyComponent | undefined = undefined
|
||||||
export let props: any | undefined = undefined
|
export let props: any | undefined = undefined
|
||||||
export let anchor: HTMLElement | undefined = undefined
|
export let anchor: HTMLElement | undefined = undefined
|
||||||
|
export let onUpdate: ((result: any) => void) | undefined = undefined
|
||||||
export let fill = false
|
export let fill = false
|
||||||
|
|
||||||
let triggerHTML: HTMLElement
|
let triggerHTML: HTMLElement
|
||||||
@ -34,7 +35,7 @@
|
|||||||
name={`tooltip-${label}`}
|
name={`tooltip-${label}`}
|
||||||
bind:this={triggerHTML}
|
bind:this={triggerHTML}
|
||||||
on:mousemove={() => {
|
on:mousemove={() => {
|
||||||
if (!shown) showTooltip(label, triggerHTML, direction, component, props, anchor)
|
if (!shown) showTooltip(label, triggerHTML, direction, component, props, anchor, onUpdate)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
|
@ -28,6 +28,7 @@
|
|||||||
let clWidth: number
|
let clWidth: number
|
||||||
|
|
||||||
$: tooltipSW = !$tooltip.component
|
$: tooltipSW = !$tooltip.component
|
||||||
|
$: onUpdate = $tooltip.onUpdate
|
||||||
|
|
||||||
const fitTooltip = (): void => {
|
const fitTooltip = (): void => {
|
||||||
if (($tooltip.label || $tooltip.component) && tooltipHTML) {
|
if (($tooltip.label || $tooltip.component) && tooltipHTML) {
|
||||||
@ -146,27 +147,15 @@
|
|||||||
whileShow(ev)
|
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}
|
{#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">
|
{#if $tooltip.label}<div class="fs-title mb-4">
|
||||||
<Label label={$tooltip.label} params={$tooltip.props ?? {}} />
|
<Label label={$tooltip.label} params={$tooltip.props ?? {}} />
|
||||||
</div>{/if}
|
</div>{/if}
|
||||||
{#if typeof $tooltip.component === 'string'}
|
{#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}
|
{:else}
|
||||||
<svelte:component this={$tooltip.component} {...$tooltip.props} />
|
<svelte:component this={$tooltip.component} {...$tooltip.props} on:update={onUpdate !== undefined ? onUpdate : async () => {}} />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div bind:this={nubHTML} class="nub {nubDirection ?? ''}" />
|
<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.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { TSelectDate } from '../types'
|
|
||||||
import DatePresenter from './DatePresenter.svelte'
|
import DatePresenter from './DatePresenter.svelte'
|
||||||
|
|
||||||
export let value: TSelectDate
|
export let value: number | null | undefined
|
||||||
export let bigDay: boolean = false
|
|
||||||
export let wraped: boolean = false
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<DatePresenter {value} {bigDay} {wraped} withTime />
|
<DatePresenter {value} withTime />
|
@ -70,8 +70,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.day-name,
|
.month-calendar {
|
||||||
.selected-month-controller {
|
user-select: none;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
.day-name {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
@ -102,14 +105,9 @@
|
|||||||
border: 1px solid var(--primary-button-focused-border);
|
border: 1px solid var(--primary-button-focused-border);
|
||||||
background-color: var(--primary-button-enabled);
|
background-color: var(--primary-button-enabled);
|
||||||
color: var(--primary-button-color);
|
color: var(--primary-button-color);
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.wrongMonth {
|
.wrongMonth {
|
||||||
color: var(--grayscale-grey-03);
|
color: var(--grayscale-grey-03);
|
||||||
}
|
}
|
||||||
.month-name {
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: bold;
|
|
||||||
margin: 0 5px;
|
|
||||||
color: var(--theme-content-dark-color);
|
|
||||||
}
|
|
||||||
</style>
|
</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;
|
display: grid;
|
||||||
grid-template-columns: repeat(4, 1fr);
|
grid-template-columns: repeat(4, 1fr);
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
.row {
|
|
||||||
display: table-row;
|
|
||||||
}
|
|
||||||
.th {
|
|
||||||
display: table-cell;
|
|
||||||
}
|
|
||||||
.calendar {
|
|
||||||
display: table-cell;
|
|
||||||
padding: 0.3em;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</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
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
const DAYS_IN_WEEK = 7
|
export const DAYS_IN_WEEK = 7
|
||||||
const MILLISECONDS_IN_DAY = 86400000
|
export const MILLISECONDS_IN_DAY = 86400000
|
||||||
const MILLISECONDS_IN_MINUTE = 60000
|
export const MILLISECONDS_IN_MINUTE = 60000
|
||||||
|
|
||||||
export function firstDay (date: Date, mondayStart: boolean): Date {
|
export function firstDay (date: Date, mondayStart: boolean): Date {
|
||||||
const firstDayOfMonth = new Date(date)
|
const firstDayOfMonth = new Date(date)
|
||||||
@ -33,6 +33,15 @@ export function firstDay (date: Date, mondayStart: boolean): Date {
|
|||||||
return result
|
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 {
|
export function getWeekDayName (weekDay: Date, weekFormat: 'narrow' | 'short' | 'long' | undefined = 'short'): string {
|
||||||
const locale = new Intl.NumberFormat().resolvedOptions().locale
|
const locale = new Intl.NumberFormat().resolvedOptions().locale
|
||||||
return new Intl.DateTimeFormat(locale, {
|
return new Intl.DateTimeFormat(locale, {
|
||||||
@ -67,6 +76,15 @@ export function getMonthName (date: Date): string {
|
|||||||
const locale = new Intl.NumberFormat().resolvedOptions().locale
|
const locale = new Intl.NumberFormat().resolvedOptions().locale
|
||||||
return new Intl.DateTimeFormat(locale, { month: 'long' }).format(date)
|
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 {
|
export function getMonday (d: Date, mondayStart: boolean): Date {
|
||||||
d = new Date(d)
|
d = new Date(d)
|
||||||
const day = d.getDay()
|
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 PopupItem } from './components/PopupItem.svelte'
|
||||||
export { default as TextArea } from './components/TextArea.svelte'
|
export { default as TextArea } from './components/TextArea.svelte'
|
||||||
export { default as Section } from './components/Section.svelte'
|
export { default as Section } from './components/Section.svelte'
|
||||||
export { default as DatePicker } from './components/DatePicker.svelte'
|
export { default as DatePicker } from './components/calendar/DatePicker.svelte'
|
||||||
export { default as DatePopup } from './components/DatePopup.svelte'
|
export { default as DatePopup } from './components/calendar/DatePopup.svelte'
|
||||||
export { default as DatePresenter } from './components/DatePresenter.svelte'
|
export { default as TimePopup } from './components/calendar/TimePopup.svelte'
|
||||||
export { default as DateTimePresenter } from './components/DateTimePresenter.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 StylishEdit } from './components/StylishEdit.svelte'
|
||||||
export { default as Grid } from './components/Grid.svelte'
|
export { default as Grid } from './components/Grid.svelte'
|
||||||
export { default as Row } from './components/Row.svelte'
|
export { default as Row } from './components/Row.svelte'
|
||||||
|
@ -8,7 +8,8 @@ export const tooltipstore = writable<LabelAndProps>({
|
|||||||
direction: undefined,
|
direction: undefined,
|
||||||
component: undefined,
|
component: undefined,
|
||||||
props: undefined,
|
props: undefined,
|
||||||
anchor: undefined
|
anchor: undefined,
|
||||||
|
onUpdate: undefined
|
||||||
})
|
})
|
||||||
|
|
||||||
export function showTooltip (
|
export function showTooltip (
|
||||||
@ -17,7 +18,8 @@ export function showTooltip (
|
|||||||
direction?: TooltipAligment,
|
direction?: TooltipAligment,
|
||||||
component?: AnySvelteComponent | AnyComponent,
|
component?: AnySvelteComponent | AnyComponent,
|
||||||
props?: any,
|
props?: any,
|
||||||
anchor?: HTMLElement
|
anchor?: HTMLElement,
|
||||||
|
onUpdate?: (result: any) => void
|
||||||
): void {
|
): void {
|
||||||
tooltipstore.set({
|
tooltipstore.set({
|
||||||
label: label,
|
label: label,
|
||||||
@ -25,7 +27,8 @@ export function showTooltip (
|
|||||||
direction: direction,
|
direction: direction,
|
||||||
component: component,
|
component: component,
|
||||||
props: props,
|
props: props,
|
||||||
anchor: anchor
|
anchor: anchor,
|
||||||
|
onUpdate: onUpdate
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,6 +39,7 @@ export function closeTooltip (): void {
|
|||||||
direction: undefined,
|
direction: undefined,
|
||||||
component: undefined,
|
component: undefined,
|
||||||
props: undefined,
|
props: undefined,
|
||||||
anchor: undefined
|
anchor: undefined,
|
||||||
|
onUpdate: undefined
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -74,6 +74,7 @@ export interface LabelAndProps {
|
|||||||
component?: AnySvelteComponent | AnyComponent
|
component?: AnySvelteComponent | AnyComponent
|
||||||
props?: any
|
props?: any
|
||||||
anchor: HTMLElement | undefined
|
anchor: HTMLElement | undefined
|
||||||
|
onUpdate?: (result: any) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ListItem {
|
export interface ListItem {
|
||||||
@ -86,11 +87,3 @@ export interface DropdownTextItem {
|
|||||||
id: string
|
id: string
|
||||||
label: 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">
|
<div class="antiSelect">
|
||||||
{#if date}
|
{#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}
|
{#if interval > 0}
|
||||||
{#await formatDueDate(interval) then t}
|
{#await formatDueDate(interval) then t}
|
||||||
<span class='ml-2 mr-1 whitespace-nowrap'>({t})</span>
|
<span class='ml-2 mr-1 whitespace-nowrap'>({t})</span>
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
|
|
||||||
let title: string = value.title
|
let title: string = value.title
|
||||||
let description: string = value.description
|
let description: string = value.description
|
||||||
let startDate: Date = new Date(value.date)
|
let startDate: number = value.date
|
||||||
let participants: Ref<Employee>[] = value.participants ?? []
|
let participants: Ref<Employee>[] = value.participants ?? []
|
||||||
const space = calendar.space.PersonalEvents
|
const space = calendar.space.PersonalEvents
|
||||||
|
|
||||||
@ -39,7 +39,7 @@
|
|||||||
|
|
||||||
async function saveReminder () {
|
async function saveReminder () {
|
||||||
await client.updateDoc(value._class, value.space, value._id, {
|
await client.updateDoc(value._class, value.space, value._id, {
|
||||||
date: startDate.getTime(),
|
date: startDate,
|
||||||
description,
|
description,
|
||||||
participants,
|
participants,
|
||||||
title
|
title
|
||||||
@ -56,7 +56,7 @@
|
|||||||
label={calendar.string.EditReminder}
|
label={calendar.string.EditReminder}
|
||||||
okAction={saveReminder}
|
okAction={saveReminder}
|
||||||
okLabel={presentation.string.Save}
|
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}
|
{space}
|
||||||
on:close={() => {
|
on:close={() => {
|
||||||
dispatch('close')
|
dispatch('close')
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
import { Event } from '@anticrm/calendar'
|
import { Event } from '@anticrm/calendar'
|
||||||
import { Class, Doc, DocumentQuery, FindOptions, Ref } from '@anticrm/core'
|
import { Class, Doc, DocumentQuery, FindOptions, Ref } from '@anticrm/core'
|
||||||
import { Tooltip } from '@anticrm/ui'
|
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 calendar from '../plugin'
|
||||||
import EventsPopup from './EventsPopup.svelte'
|
import EventsPopup from './EventsPopup.svelte'
|
||||||
|
|
||||||
|
@ -37,6 +37,6 @@
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
{/await}
|
{/await}
|
||||||
</div>
|
</div>
|
||||||
<DateTimePresenter value={new Date(value.date + value.shift)} />
|
<DateTimePresenter value={value.date + value.shift} />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
@ -37,7 +37,7 @@
|
|||||||
{#await getEvent(tx.objectId) then event}
|
{#await getEvent(tx.objectId) then event}
|
||||||
{#if event}
|
{#if event}
|
||||||
<span class="over-underline caption-color" on:click={() => { click(event) }}>{event.title}</span> 
|
<span class="over-underline caption-color" on:click={() => { click(event) }}>{event.title}</span> 
|
||||||
<DateTimePresenter value={new Date(event.date)} />
|
<DateTimePresenter value={event.date} />
|
||||||
{/if}
|
{/if}
|
||||||
{/await}
|
{/await}
|
||||||
</div>
|
</div>
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
import type { Candidate, Review } from '@anticrm/recruit'
|
import type { Candidate, Review } from '@anticrm/recruit'
|
||||||
import task, { SpaceWithStates } from '@anticrm/task'
|
import task, { SpaceWithStates } from '@anticrm/task'
|
||||||
import { StyledTextBox } from '@anticrm/text-editor'
|
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 view from '@anticrm/view'
|
||||||
import { createEventDispatcher } from 'svelte'
|
import { createEventDispatcher } from 'svelte'
|
||||||
import recruit from '../../plugin'
|
import recruit from '../../plugin'
|
||||||
@ -38,8 +38,8 @@
|
|||||||
|
|
||||||
let title: string = ''
|
let title: string = ''
|
||||||
let description: string = ''
|
let description: string = ''
|
||||||
let startDate: Date = new Date()
|
let startDate: number = Date.now()
|
||||||
let dueDate: Date = new Date()
|
let dueDate: number = Date.now()
|
||||||
let location: string = ''
|
let location: string = ''
|
||||||
let company: Ref<Organization> | undefined = undefined
|
let company: Ref<Organization> | undefined = undefined
|
||||||
|
|
||||||
@ -102,8 +102,8 @@
|
|||||||
|
|
||||||
await client.addCollection(recruit.class.Review, doc.space, doc.attachedTo, doc.attachedToClass, 'reviews', {
|
await client.addCollection(recruit.class.Review, doc.space, doc.attachedTo, doc.attachedToClass, 'reviews', {
|
||||||
number: (incResult as any).object.sequence,
|
number: (incResult as any).object.sequence,
|
||||||
date: startDate?.getTime() ?? null,
|
date: startDate ?? null,
|
||||||
dueDate: dueDate?.getTime() ?? null,
|
dueDate: dueDate ?? null,
|
||||||
description,
|
description,
|
||||||
verdict: '',
|
verdict: '',
|
||||||
title,
|
title,
|
||||||
@ -133,6 +133,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
$: validate(doc, doc._class)
|
$: validate(doc, doc._class)
|
||||||
|
const updateStart = (result: any): void => {
|
||||||
|
if (result.detail !== undefined) {
|
||||||
|
dueDate = result.detail
|
||||||
|
dueDate = dueDate
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Card
|
<Card
|
||||||
@ -152,20 +158,23 @@
|
|||||||
>
|
>
|
||||||
<StatusControl slot="error" {status} />
|
<StatusControl slot="error" {status} />
|
||||||
|
|
||||||
<Grid column={1} rowGap={1.75}>
|
<Grid column={2} rowGap={1.75}>
|
||||||
<Grid column={!preserveCandidate ? 2 : 1}>
|
<EditBox label={recruit.string.Title} icon={recruit.icon.Review} bind:value={title} maxWidth={'13rem'} />
|
||||||
<StylishEdit bind:value={title} label={recruit.string.Title} />
|
|
||||||
{#if !preserveCandidate}
|
{#if !preserveCandidate}
|
||||||
<div class="antiComponentBox">
|
|
||||||
<UserBox
|
<UserBox
|
||||||
_class={contact.class.Person}
|
_class={contact.class.Person}
|
||||||
title={recruit.string.Candidate}
|
title={recruit.string.Candidate}
|
||||||
caption={recruit.string.Candidates}
|
caption={recruit.string.Candidates}
|
||||||
bind:value={doc.attachedTo}
|
bind:value={doc.attachedTo}
|
||||||
/>
|
/>
|
||||||
</div>
|
{:else}
|
||||||
|
<div></div>
|
||||||
{/if}
|
{/if}
|
||||||
</Grid>
|
<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
|
<StyledTextBox
|
||||||
emphasized
|
emphasized
|
||||||
showButtons={false}
|
showButtons={false}
|
||||||
@ -174,17 +183,6 @@
|
|||||||
alwaysEdit
|
alwaysEdit
|
||||||
placeholder={recruit.string.AddDescription}
|
placeholder={recruit.string.AddDescription}
|
||||||
/>
|
/>
|
||||||
<Grid column={2}>
|
</Row>
|
||||||
<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>
|
</Grid>
|
||||||
</Card>
|
</Card>
|
||||||
|
@ -106,18 +106,3 @@
|
|||||||
on:change={() => client.update(object, { verdict: object.verdict })}
|
on:change={() => client.update(object, { verdict: object.verdict })}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/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'
|
import setting from '../../plugin'
|
||||||
|
|
||||||
export let tx: TxUpdateDoc<Integration>
|
export let tx: TxUpdateDoc<Integration>
|
||||||
export let doc: Integration
|
// export let doc: Integration
|
||||||
|
|
||||||
const client = getClient()
|
const client = getClient()
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@
|
|||||||
|
|
||||||
let name: string
|
let name: string
|
||||||
const done = false
|
const done = false
|
||||||
let dueTo: Date
|
let dueTo: number
|
||||||
|
|
||||||
$: _space = space
|
$: _space = space
|
||||||
|
|
||||||
@ -48,7 +48,7 @@
|
|||||||
{
|
{
|
||||||
name,
|
name,
|
||||||
done,
|
done,
|
||||||
dueTo: dueTo?.getTime() ?? undefined
|
dueTo: dueTo ?? undefined
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
export let item: TodoItem
|
export let item: TodoItem
|
||||||
|
|
||||||
let name: string = ''
|
let name: string = ''
|
||||||
let dueTo: Date | undefined | null = null
|
let dueTo: number | null | undefined = null
|
||||||
|
|
||||||
let _itemId: Ref<TodoItem>
|
let _itemId: Ref<TodoItem>
|
||||||
|
|
||||||
@ -32,7 +32,7 @@
|
|||||||
_itemId = item._id
|
_itemId = item._id
|
||||||
name = item.name
|
name = item.name
|
||||||
if (item.dueTo != null) {
|
if (item.dueTo != null) {
|
||||||
dueTo = new Date(item.dueTo)
|
dueTo = item.dueTo
|
||||||
} else {
|
} else {
|
||||||
dueTo = null
|
dueTo = null
|
||||||
}
|
}
|
||||||
@ -51,7 +51,7 @@
|
|||||||
ops.name = name
|
ops.name = name
|
||||||
}
|
}
|
||||||
if (item.dueTo !== dueTo) {
|
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) {
|
if (Object.keys(ops).length === 0) {
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
$: todos = (value.$lookup?.todoItems as TodoItem[]) ?? []
|
$: todos = (value.$lookup?.todoItems as TodoItem[]) ?? []
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="applications-container">
|
<div class="flex-col">
|
||||||
{#if todos.length > 0}
|
{#if todos.length > 0}
|
||||||
<Table
|
<Table
|
||||||
_class={task.class.TodoItem}
|
_class={task.class.TodoItem}
|
||||||
@ -38,17 +38,3 @@
|
|||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</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">
|
<script lang="ts">
|
||||||
import { TypeDate } from '@anticrm/core'
|
// import { TypeDate } from '@anticrm/core'
|
||||||
import { IntlString } from '@anticrm/platform'
|
// import { IntlString } from '@anticrm/platform'
|
||||||
import { DatePopup, showPopup } from '@anticrm/ui'
|
import { DatePresenter } from '@anticrm/ui'
|
||||||
import DatePresenter from './DatePresenter.svelte'
|
|
||||||
|
|
||||||
export let value: number | Date | undefined
|
export let value: number | null | undefined
|
||||||
export let label: IntlString
|
// export let label: IntlString
|
||||||
export let onChange: (value: any) => void
|
export let onChange: (value: any) => void
|
||||||
|
// export let attributeType: TypeDate | undefined
|
||||||
export let attributeType: TypeDate | undefined
|
|
||||||
|
|
||||||
$: date = value ? new Date(value) : new Date()
|
|
||||||
let container: HTMLElement
|
|
||||||
let opened: boolean = false
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex-row-center" bind:this={container}
|
<DatePresenter {value} on:change={onChange} editable />
|
||||||
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>
|
|
||||||
|
@ -15,19 +15,11 @@
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { TypeDate } from '@anticrm/core'
|
// import { TypeDate } from '@anticrm/core'
|
||||||
import { DatePresenter } from '@anticrm/ui'
|
import { DatePresenter } from '@anticrm/ui'
|
||||||
|
|
||||||
export let value: number | Date | undefined
|
export let value: number | null | undefined
|
||||||
export let attributeType: TypeDate | undefined
|
// export let attributeType: TypeDate | undefined
|
||||||
|
|
||||||
$: date = value ? new Date(value) : undefined
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="antiSelect">
|
<DatePresenter {value} />
|
||||||
{#if date}
|
|
||||||
<DatePresenter value={date} withTime={attributeType?.withTime ?? false} />
|
|
||||||
{:else}
|
|
||||||
No date
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
|
@ -213,13 +213,17 @@
|
|||||||
{#if client}
|
{#if client}
|
||||||
<svg class="svg-mask">
|
<svg class="svg-mask">
|
||||||
<clipPath id="notify-normal">
|
<clipPath id="notify-normal">
|
||||||
<path
|
<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" />
|
||||||
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>
|
||||||
<clipPath id="notify-small">
|
<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" />
|
<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>
|
||||||
|
<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>
|
</svg>
|
||||||
<div class="workbench-container">
|
<div class="workbench-container">
|
||||||
<div class="antiPanel-application" on:click={toggleNav}>
|
<div class="antiPanel-application" on:click={toggleNav}>
|
||||||
|
@ -142,17 +142,17 @@ test.describe('recruit tests', () => {
|
|||||||
// Click button:has-text("Review")
|
// Click button:has-text("Review")
|
||||||
await page.click('button:has-text("Review")')
|
await page.click('button:has-text("Review")')
|
||||||
// Click [placeholder="\ "]
|
// Click [placeholder="\ "]
|
||||||
await page.click('[placeholder="\\ "]')
|
await page.click('[placeholder="placeholder"]')
|
||||||
// Fill [placeholder="\ "]
|
// Fill [placeholder="\ "]
|
||||||
await page.fill('[placeholder="\\ "]', 'Meet PEterson')
|
await page.fill('[placeholder="placeholder"]', 'Meet PEterson')
|
||||||
// Click text=Location Company Company >> [placeholder="\ "]
|
// 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="\ "]
|
// 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
|
// Click text=Company Company >> div
|
||||||
await page.click('text=Company Company >> div')
|
// await page.click('text=Company Company >> div')
|
||||||
// Click button:has-text("Apple")
|
// 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
|
// Click text=Candidate Not selected >> span
|
||||||
await page.click('text=Candidate Not selected >> span')
|
await page.click('text=Candidate Not selected >> span')
|
||||||
// Click button:has-text("Andrey P.")
|
// Click button:has-text("Andrey P.")
|
||||||
|
Loading…
Reference in New Issue
Block a user