mirror of
https://github.com/hcengineering/platform.git
synced 2024-11-21 16:09:12 +03:00
feat(planner): new slots, fixes and improvements (#4961)
Signed-off-by: Eduard Aksamitov <e@euaaaio.ru>
This commit is contained in:
parent
eff7b71f43
commit
def45ff217
@ -236,7 +236,7 @@ export function createModel (builder: Builder): void {
|
||||
}
|
||||
},
|
||||
label: time.string.CreateToDo,
|
||||
icon: time.icon.Target,
|
||||
icon: time.icon.Calendar,
|
||||
keyBinding: [],
|
||||
input: 'none',
|
||||
category: time.category.Time,
|
||||
@ -262,7 +262,7 @@ export function createModel (builder: Builder): void {
|
||||
}
|
||||
},
|
||||
label: time.string.CreateToDo,
|
||||
icon: time.icon.Target,
|
||||
icon: time.icon.Calendar,
|
||||
keyBinding: [],
|
||||
input: 'none',
|
||||
category: time.category.Time,
|
||||
|
@ -699,6 +699,7 @@ input.search {
|
||||
.min-w-8 { min-width: 2rem; }
|
||||
.min-w-9 { min-width: 2.25rem; }
|
||||
.min-w-12 { min-width: 3rem; }
|
||||
.min-w-28 { min-width: 7rem; }
|
||||
.min-w-50 { min-width: 12.5rem; }
|
||||
.min-w-60 { min-width: 15rem; }
|
||||
.min-w-80 { min-width: 20rem; }
|
||||
|
@ -26,6 +26,7 @@
|
||||
--button-negative-active-BackgroundColor: #c42a32;
|
||||
|
||||
--tag-on-accent-PorpoiseText: #FFFFFF;
|
||||
--tag-accent-SunshineBackground: #FFBD2E;
|
||||
}
|
||||
|
||||
/* Dark Theme */
|
||||
@ -70,6 +71,8 @@
|
||||
|
||||
--tag-on-subtle-PorpoiseText: #F2F4F6;
|
||||
--tag-subtle-PorpoiseBackground: #343F49;
|
||||
--tag-nuance-SunshineBackground: #262F40;
|
||||
--tag-accent-SunshineText: #FFBD2E;
|
||||
--tag-nuance-SkyBackground: #1F2737;
|
||||
|
||||
--icon-disabled-IconColor: #394358;
|
||||
@ -145,6 +148,8 @@
|
||||
|
||||
--tag-on-subtle-PorpoiseText: #293139;
|
||||
--tag-subtle-PorpoiseBackground: #C8D1D9;
|
||||
--tag-nuance-SunshineBackground: #FEF2E2;
|
||||
--tag-accent-SunshineText: #8E5E00;
|
||||
--tag-nuance-SkyBackground: #EEF4FD;
|
||||
|
||||
--icon-disabled-IconColor: #B3BCCC;
|
||||
|
@ -14,9 +14,11 @@
|
||||
//
|
||||
|
||||
/* Typography */
|
||||
.font-regular-11,
|
||||
.font-medium-11,
|
||||
.font-regular-12,
|
||||
.font-medium-12,
|
||||
.font-caps-medium-12,
|
||||
.font-bold-12,
|
||||
.font-regular-14,
|
||||
.font-medium-14,
|
||||
@ -30,11 +32,13 @@
|
||||
line-height: 1rem;
|
||||
color: var(--global-primary-TextColor);
|
||||
}
|
||||
.font-regular-11,
|
||||
.font-medium-11 {
|
||||
font-size: 0.6875rem;
|
||||
}
|
||||
.font-regular-12,
|
||||
.font-medium-12,
|
||||
.font-caps-medium-12,
|
||||
.font-bold-12 {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
@ -44,6 +48,7 @@
|
||||
.paragraph-regular-14 {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
.font-regular-11,
|
||||
.font-regular-12,
|
||||
.font-regular-14,
|
||||
.paragraph-regular-14 {
|
||||
@ -51,6 +56,7 @@
|
||||
}
|
||||
.font-medium-11,
|
||||
.font-medium-12,
|
||||
.font-caps-medium-12,
|
||||
.font-medium-14,
|
||||
.heading-medium-16,
|
||||
.heading-medium-20 {
|
||||
@ -74,6 +80,9 @@
|
||||
line-height: 1.25rem;
|
||||
color: var(--global-tertiary-TextColor);
|
||||
}
|
||||
.font-caps-medium-12 {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
/* Panels */
|
||||
* {
|
||||
|
@ -120,6 +120,9 @@
|
||||
}
|
||||
|
||||
.icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: var(--spacing-2_5);
|
||||
height: var(--spacing-2_5);
|
||||
|
||||
@ -159,10 +162,11 @@
|
||||
}
|
||||
&.small {
|
||||
height: var(--global-small-Size);
|
||||
gap: var(--spacing-0_25);
|
||||
border-radius: var(--small-BorderRadius);
|
||||
|
||||
&.type-button {
|
||||
padding: 0 var(--spacing-1_5);
|
||||
padding: 0 var(--spacing-1);
|
||||
}
|
||||
&.type-button-icon {
|
||||
width: var(--global-small-Size);
|
||||
@ -363,6 +367,7 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
& > * {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
50
packages/ui/src/components/Hotkey.svelte
Normal file
50
packages/ui/src/components/Hotkey.svelte
Normal file
@ -0,0 +1,50 @@
|
||||
<!--
|
||||
// Copyright © 2024 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import type { AnySvelteComponent } from '../types'
|
||||
import Icon from './Icon.svelte'
|
||||
import KeyShift from './icons/KeyShift.svelte'
|
||||
import KeyOption from './icons/KeyOption.svelte'
|
||||
import KeyCommand from './icons/KeyCommand.svelte'
|
||||
|
||||
export let key: string
|
||||
|
||||
const predefinedKeys: Record<string, AnySvelteComponent> = {
|
||||
shift: KeyShift,
|
||||
option: KeyOption,
|
||||
command: KeyCommand
|
||||
}
|
||||
|
||||
$: isPredefinedKey = key in predefinedKeys
|
||||
</script>
|
||||
|
||||
<span class="hotkey flex-center min-w-4 h-4 font-regular-11" class:text={!isPredefinedKey}>
|
||||
{#if isPredefinedKey}
|
||||
<Icon icon={predefinedKeys[key]} size="x-small" />
|
||||
{:else}
|
||||
{key}
|
||||
{/if}
|
||||
</span>
|
||||
|
||||
<style lang="scss">
|
||||
.hotkey {
|
||||
border-radius: var(--min-BorderRadius);
|
||||
background-color: var(--global-ui-hover-BackgroundColor);
|
||||
|
||||
&.text {
|
||||
padding: 0 var(--spacing-0_5);
|
||||
}
|
||||
}
|
||||
</style>
|
25
packages/ui/src/components/HotkeyGroup.svelte
Normal file
25
packages/ui/src/components/HotkeyGroup.svelte
Normal file
@ -0,0 +1,25 @@
|
||||
<!--
|
||||
// Copyright © 2024 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import Hotkey from './Hotkey.svelte'
|
||||
|
||||
export let keys: string[]
|
||||
</script>
|
||||
|
||||
<div class="flex flex-gap-0-5">
|
||||
{#each keys as key}
|
||||
<Hotkey {key} />
|
||||
{/each}
|
||||
</div>
|
12
packages/ui/src/components/icons/KeyCommand.svelte
Normal file
12
packages/ui/src/components/icons/KeyCommand.svelte
Normal file
@ -0,0 +1,12 @@
|
||||
<script lang="ts">
|
||||
export let size: 'x-small' | 'small' | 'medium' | 'large'
|
||||
export let fill: string = 'currentColor'
|
||||
</script>
|
||||
|
||||
<svg class="svg-{size}" {fill} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M13 11h6V8a5 5 0 1 1 5 5h-3v6h3a5 5 0 1 1-5 5v-3h-6v3a5 5 0 1 1-5-5h3v-6H8a5 5 0 1 1 5-5zm-2-3v3H8a3 3 0 1 1 3-3m2 11h6v-6h-6zm-5 2a3 3 0 1 0 3 3v-3zm13 0v3a3 3 0 1 0 3-3zm0-10V8a3 3 0 1 1 3 3z"
|
||||
/>
|
||||
</svg>
|
8
packages/ui/src/components/icons/KeyOption.svelte
Normal file
8
packages/ui/src/components/icons/KeyOption.svelte
Normal file
@ -0,0 +1,8 @@
|
||||
<script lang="ts">
|
||||
export let size: 'x-small' | 'small' | 'medium' | 'large'
|
||||
export let fill: string = 'currentColor'
|
||||
</script>
|
||||
|
||||
<svg class="svg-{size}" {fill} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||
<path d="M9.465 8H2V6h8.535l12 18H30v2h-8.535zM18 6h12v2H18z" />
|
||||
</svg>
|
8
packages/ui/src/components/icons/KeyShift.svelte
Normal file
8
packages/ui/src/components/icons/KeyShift.svelte
Normal file
@ -0,0 +1,8 @@
|
||||
<script lang="ts">
|
||||
export let size: 'x-small' | 'small' | 'medium' | 'large'
|
||||
export let fill: string = 'currentColor'
|
||||
</script>
|
||||
|
||||
<svg class="svg-{size}" {fill} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 16v10h8V16h5.593L16 5.037 6.408 16zM2 18h8v10h12V18h8L16 2z" />
|
||||
</svg>
|
@ -141,6 +141,8 @@ export { default as NavGroup } from './components/NavGroup.svelte'
|
||||
export { default as Modal } from './components/Modal.svelte'
|
||||
export { default as AccordionItem } from './components/AccordionItem.svelte'
|
||||
export { default as NotificationToast } from './components/NotificationToast.svelte'
|
||||
export { default as Hotkey } from './components/Hotkey.svelte'
|
||||
export { default as HotkeyGroup } from './components/HotkeyGroup.svelte'
|
||||
|
||||
export { default as IconAdd } from './components/icons/Add.svelte'
|
||||
export { default as IconCircleAdd } from './components/icons/CircleAdd.svelte'
|
||||
@ -213,6 +215,9 @@ export { default as IconTableOfContents } from './components/icons/TableOfConten
|
||||
export { default as IconRight } from './components/icons/Right.svelte'
|
||||
export { default as IconDropdownDown } from './components/icons/DropdownDown.svelte'
|
||||
export { default as IconDropdownRight } from './components/icons/DropdownRight.svelte'
|
||||
export { default as IconKeyCommand } from './components/icons/KeyCommand.svelte'
|
||||
export { default as IconKeyOption } from './components/icons/KeyOption.svelte'
|
||||
export { default as IconKeyShift } from './components/icons/KeyShift.svelte'
|
||||
|
||||
export { default as PanelInstance } from './components/PanelInstance.svelte'
|
||||
export { default as Panel } from './components/Panel.svelte'
|
||||
|
@ -14,9 +14,9 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import ui, {
|
||||
Button,
|
||||
ButtonKind,
|
||||
ButtonSize,
|
||||
ButtonBase,
|
||||
ButtonBaseKind,
|
||||
ButtonBaseSize,
|
||||
DatePopup,
|
||||
SimpleDatePopup,
|
||||
TimeInputBox,
|
||||
@ -33,8 +33,8 @@
|
||||
export let direction: 'vertical' | 'horizontal' = 'vertical'
|
||||
export let showDate: boolean = true
|
||||
export let withoutTime: boolean
|
||||
export let kind: ButtonKind = 'ghost'
|
||||
export let size: ButtonSize = 'medium'
|
||||
export let kind: ButtonBaseKind = 'tertiary'
|
||||
export let size: ButtonBaseSize = 'small'
|
||||
export let disabled: boolean = false
|
||||
export let focusIndex = -1
|
||||
export let timeZone: string = getUserTimezone()
|
||||
@ -77,45 +77,49 @@
|
||||
<div
|
||||
class="dateEditor-container {direction}"
|
||||
class:difference={difference > 0}
|
||||
class:gap-1-5={direction === 'horizontal'}
|
||||
class:flex-gap-2={direction === 'horizontal'}
|
||||
>
|
||||
{#if showDate || withoutTime}
|
||||
<Button {kind} {size} padding={'0 .5rem'} {focusIndex} on:click={dateClick} {disabled}>
|
||||
<svelte:fragment slot="content">
|
||||
<div class="min-w-28">
|
||||
<ButtonBase type="type-button" {kind} {size} {disabled} {focusIndex} on:click={dateClick}>
|
||||
<DateLocalePresenter date={currentDate.getTime()} {timeZone} />
|
||||
</svelte:fragment>
|
||||
</Button>
|
||||
</ButtonBase>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if showDate && !withoutTime && direction === 'horizontal'}
|
||||
<div class="divider" />
|
||||
{/if}
|
||||
|
||||
{#if !withoutTime}
|
||||
<Button
|
||||
<ButtonBase
|
||||
type="type-button"
|
||||
{kind}
|
||||
{size}
|
||||
padding={'0 .5rem'}
|
||||
{disabled}
|
||||
focusIndex={focusIndex !== -1 ? focusIndex + 1 : focusIndex}
|
||||
on:click={timeClick}
|
||||
{disabled}
|
||||
>
|
||||
<svelte:fragment slot="content">
|
||||
<TimeInputBox
|
||||
bind:currentDate
|
||||
{timeZone}
|
||||
noBorder
|
||||
size={'small'}
|
||||
on:update={(date) => {
|
||||
updateTime(date.detail)
|
||||
}}
|
||||
/>
|
||||
{#if difference > 0}
|
||||
<div class="ml-2 flex-no-shrink content-darker-color overflow-label">
|
||||
<TimeShiftPresenter value={date - difference} exact />
|
||||
</div>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</Button>
|
||||
<TimeInputBox
|
||||
bind:currentDate
|
||||
{timeZone}
|
||||
noBorder
|
||||
size={'small'}
|
||||
on:update={(date) => {
|
||||
updateTime(date.detail)
|
||||
}}
|
||||
/>
|
||||
</ButtonBase>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if !withoutTime && difference > 0}
|
||||
<div class="divider" />
|
||||
<div class="duration font-regular-14">
|
||||
<TimeShiftPresenter value={date - difference} exact />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.dateEditor-container {
|
||||
display: flex;
|
||||
@ -131,8 +135,19 @@
|
||||
align-items: start;
|
||||
}
|
||||
&:not(.difference) {
|
||||
align-items: stretch;
|
||||
align-items: start;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.duration {
|
||||
padding: var(--spacing-1);
|
||||
color: var(--tag-accent-SunshineText);
|
||||
}
|
||||
|
||||
.divider {
|
||||
width: 0;
|
||||
height: 1.25rem;
|
||||
border-left: 1px solid var(--global-ui-BorderColor);
|
||||
}
|
||||
</style>
|
||||
|
@ -241,7 +241,7 @@
|
||||
.eventPopup-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-width: 25rem;
|
||||
max-width: 40rem;
|
||||
min-width: 25rem;
|
||||
min-height: 0;
|
||||
background: var(--theme-popup-color);
|
||||
|
@ -13,10 +13,9 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Icon, IconArrowRight, areDatesEqual, getUserTimezone } from '@hcengineering/ui'
|
||||
import { utcToZonedTime } from 'date-fns-tz'
|
||||
import { areDatesEqual, getUserTimezone } from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import calendar from '../plugin'
|
||||
import { utcToZonedTime } from 'date-fns-tz'
|
||||
import DateEditor from './DateEditor.svelte'
|
||||
|
||||
export let startDate: number
|
||||
@ -50,10 +49,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex-row-center">
|
||||
<div class="self-start flex-no-shrink mt-2 mr-1-5 content-dark-color">
|
||||
<Icon icon={calendar.icon.Watch} size={'small'} />
|
||||
</div>
|
||||
<div class="flex-row-center flex-gap-2">
|
||||
<DateEditor
|
||||
bind:date={startDate}
|
||||
direction={sameDate ? 'horizontal' : 'vertical'}
|
||||
@ -63,9 +59,7 @@
|
||||
{disabled}
|
||||
{focusIndex}
|
||||
/>
|
||||
<div class="self-end flex-no-shrink mb-2 ml-1-5 mr-1-5 content-darker-color">
|
||||
<IconArrowRight size={'small'} />
|
||||
</div>
|
||||
<div class="flex-no-shrink content-darker-color">—</div>
|
||||
<DateEditor
|
||||
bind:date={dueDate}
|
||||
direction={sameDate ? 'horizontal' : 'vertical'}
|
||||
|
@ -164,7 +164,7 @@
|
||||
}}
|
||||
/>
|
||||
<div class="flex-row-center">
|
||||
<DateEditor bind:date={until} withoutTime kind="regular" disabled={selected !== 'on'} />
|
||||
<DateEditor bind:date={until} withoutTime kind="secondary" disabled={selected !== 'on'} />
|
||||
</div>
|
||||
<RadioButton
|
||||
labelIntl={calendar.string.After}
|
||||
|
@ -24,11 +24,6 @@
|
||||
<symbol id="hashtag" viewBox="0 0 32 32">
|
||||
<path d="M13.9839 5.18321C14.0828 4.63985 13.7224 4.11922 13.1791 4.02035C12.6357 3.92149 12.1151 4.28183 12.0162 4.82519L11.0744 10.0011L6.99205 10.0007C6.43976 10.0006 5.992 10.4483 5.99194 11.0006C5.99189 11.5529 6.43955 12.0006 6.99184 12.0007L10.7105 12.0011L9.25503 20.0005L4.99996 20.0007C4.44767 20.0007 3.99998 20.4484 4 21.0007C4.00002 21.553 4.44776 22.0007 5.00004 22.0007L8.89112 22.0005L8.01389 26.8218C7.91502 27.3651 8.27536 27.8858 8.81872 27.9846C9.36208 28.0835 9.88271 27.7232 9.98158 27.1798L10.924 22.0004L18.8885 22.0001L18.0106 26.8181C17.9116 27.3614 18.2718 27.8821 18.8152 27.9811C19.3585 28.0801 19.8792 27.7199 19.9782 27.1766L20.9214 22L25 21.9998C25.5523 21.9998 26 21.5521 26 20.9998C26 20.4475 25.5522 19.9998 25 19.9998L21.2858 20L22.743 12.0023L27.0001 12.0028C27.5524 12.0028 28.0001 11.5552 28.0002 11.0029C28.0003 10.4506 27.5526 10.0028 27.0003 10.0028L23.1074 10.0024L23.9852 5.1848C24.0842 4.64146 23.724 4.12074 23.1806 4.02174C22.6373 3.92274 22.1166 4.28295 22.0176 4.82629L21.0745 10.0021L13.1072 10.0013L13.9839 5.18321ZM12.7433 12.0013L20.7101 12.0021L19.2529 20.0001L11.2879 20.0004L12.7433 12.0013Z" />
|
||||
</symbol>
|
||||
<symbol id="target" viewBox="0 0 16 16" fill="none">
|
||||
<circle cx="8" cy="8" r="6.5" stroke="currentColor"/>
|
||||
<circle cx="8" cy="8" r="3.5" stroke="currentColor"/>
|
||||
<circle cx="8" cy="8" r="0.5" stroke="currentColor"/>
|
||||
</symbol>
|
||||
<symbol id="flag" viewBox="0 0 14 14" fill="none">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.75 1.3125C1.75 1.07088 1.94588 0.875 2.1875 0.875H11.8125C11.9715 0.875 12.1181 0.961309 12.1952 1.10041C12.2723 1.23952 12.2678 1.40951 12.1835 1.54437L10.1409 4.8125L12.1835 8.08063C12.2678 8.21549 12.2723 8.38548 12.1952 8.52459C12.1181 8.66369 11.9715 8.75 11.8125 8.75H2.625V12.6875C2.625 12.9291 2.42912 13.125 2.1875 13.125C1.94588 13.125 1.75 12.9291 1.75 12.6875V1.3125ZM2.625 7.875H11.0231L9.254 5.04437C9.16533 4.90251 9.16533 4.72249 9.254 4.58063L11.0231 1.75H2.625V7.875Z" fill="currentColor"/>
|
||||
</symbol>
|
||||
@ -44,4 +39,7 @@
|
||||
<path d="M9.99998 18.7501C9.89651 18.75 9.79468 18.7243 9.70367 18.6751L2.12886 14.5963C1.82502 14.4327 1.71137 14.0537 1.87505 13.7499C2.03867 13.4462 2.41749 13.3326 2.72124 13.4962L9.99998 17.4152L17.2785 13.4963C17.5823 13.3327 17.9613 13.4464 18.125 13.7502C18.2886 14.0541 18.1749 14.4332 17.871 14.5968L10.2963 18.6755C10.2052 18.7246 10.1034 18.7502 9.99998 18.7501Z" fill="currentColor"/>
|
||||
<path d="M9.99998 11.2501C9.89651 11.25 9.79468 11.2243 9.70367 11.1751L1.57867 6.80005C1.47934 6.74654 1.39635 6.66713 1.33851 6.57026C1.28066 6.47338 1.25012 6.36266 1.25012 6.24983C1.25012 6.13701 1.28066 6.02628 1.33851 5.92941C1.39635 5.83254 1.47934 5.75313 1.57867 5.69961L9.70367 1.32461C9.7947 1.27548 9.89653 1.24976 9.99998 1.24976C10.1034 1.24976 10.2053 1.27548 10.2963 1.32461L18.4213 5.69961C18.5206 5.75313 18.6036 5.83254 18.6615 5.92941C18.7193 6.02628 18.7498 6.13701 18.7498 6.24983C18.7498 6.36266 18.7193 6.47338 18.6615 6.57026C18.6036 6.66713 18.5206 6.74654 18.4213 6.80005L10.2963 11.1751C10.2053 11.2243 10.1034 11.25 9.99998 11.2501ZM3.19336 6.25005L9.99998 9.91524L16.8066 6.25005L9.99998 2.58493L3.19336 6.25005Z" fill="currentColor"/>
|
||||
</symbol>
|
||||
<symbol id="calendar" viewBox="0 0 32 32" fill="none">
|
||||
<path clip-rule="evenodd" fill-rule="evenodd" fill="currentColor" d="M28 24.005C28 26.214 26.21 28 24 28H8a4 4 0 0 1-4-4V8a4 4 0 0 1 4-4h2V3a1 1 0 1 1 2 0v1h8V3a1 1 0 1 1 2 0v1h2a4 4 0 0 1 4 4zM20 6v1a1 1 0 1 0 2 0V6h2a2 2 0 0 1 2 2v4H6V8a2 2 0 0 1 2-2h2v1a1 1 0 1 0 2 0V6zM6 14v10a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V14z"/>
|
||||
</symbol>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 8.9 KiB After Width: | Height: | Size: 9.0 KiB |
@ -55,6 +55,7 @@
|
||||
"CreatedToDo": "Created Todo",
|
||||
"NewToDoDetails": "New Todo: {details}",
|
||||
"MarkedAsDone": "Completed",
|
||||
"WorkSchedule": "Work schedule"
|
||||
"WorkSchedule": "Work schedule",
|
||||
"SummaryDuration": "Summary"
|
||||
}
|
||||
}
|
||||
|
@ -55,6 +55,7 @@
|
||||
"CreatedToDo": "Tarea creada",
|
||||
"NewToDoDetails": "Nueva tarea pendiente: {details}",
|
||||
"MarkedAsDone": "Marcado como hecho",
|
||||
"WorkSchedule": "Horario de trabajo"
|
||||
"WorkSchedule": "Horario de trabajo",
|
||||
"SummaryDuration": "Sumerio"
|
||||
}
|
||||
}
|
@ -55,6 +55,7 @@
|
||||
"CreatedToDo": "Tarefa criada",
|
||||
"NewToDoDetails": "Nova tarefa pendente: {details}",
|
||||
"MarkedAsDone": "Concluído",
|
||||
"WorkSchedule": "Horário de trabalho"
|
||||
"WorkSchedule": "Horário de trabalho",
|
||||
"SummaryDuration": "Sumário"
|
||||
}
|
||||
}
|
@ -55,6 +55,7 @@
|
||||
"CreatedToDo": "Создал(а) Todo",
|
||||
"NewToDoDetails": "Новое Todo: {details}",
|
||||
"MarkedAsDone": "Выполнено",
|
||||
"WorkSchedule": "Расписание работы"
|
||||
"WorkSchedule": "Расписание работы",
|
||||
"SummaryDuration": "Всего"
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ loadMetadata(time.icon, {
|
||||
Team: `${icons}#team`,
|
||||
Hashtag: `${icons}#hashtag`,
|
||||
Inbox: `${icons}#inbox`,
|
||||
Target: `${icons}#target`,
|
||||
Calendar: `${icons}#calendar`,
|
||||
Flag: `${icons}#flag`,
|
||||
FilledFlag: `${icons}#filledFlag`,
|
||||
Planned: `${icons}#planned`,
|
||||
|
@ -13,22 +13,22 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Calendar, generateEventId } from '@hcengineering/calendar'
|
||||
import calendar from '@hcengineering/calendar-resources/src/plugin'
|
||||
import { PersonAccount } from '@hcengineering/contact'
|
||||
import core, { AttachedData, Doc, Ref, generateId, getCurrentAccount } from '@hcengineering/core'
|
||||
import { Button, Component, EditBox, IconClose, Label, Scroller } from '@hcengineering/ui'
|
||||
import { SpaceSelector, createQuery, getClient } from '@hcengineering/presentation'
|
||||
import tagsPlugin, { TagReference } from '@hcengineering/tags'
|
||||
import task from '@hcengineering/task'
|
||||
import { StyledTextBox } from '@hcengineering/text-editor'
|
||||
import { Button, Component, EditBox, IconClose, Label } from '@hcengineering/ui'
|
||||
import { ToDo, ToDoPriority, WorkSlot } from '@hcengineering/time'
|
||||
import { Calendar, generateEventId } from '@hcengineering/calendar'
|
||||
import tagsPlugin, { TagReference } from '@hcengineering/tags'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import time from '../plugin'
|
||||
import DueDateEditor from './DueDateEditor.svelte'
|
||||
import PriorityEditor from './PriorityEditor.svelte'
|
||||
import Workslots from './Workslots.svelte'
|
||||
import { VisibilityEditor } from '@hcengineering/calendar-resources'
|
||||
import { StyledTextBox } from '@hcengineering/text-editor'
|
||||
import { PersonAccount } from '@hcengineering/contact'
|
||||
import calendar from '@hcengineering/calendar-resources/src/plugin'
|
||||
import task from '@hcengineering/task'
|
||||
import PriorityEditor from './PriorityEditor.svelte'
|
||||
import DueDateEditor from './DueDateEditor.svelte'
|
||||
import Workslots from './Workslots.svelte'
|
||||
import time from '../plugin'
|
||||
|
||||
export let object: Doc | undefined
|
||||
|
||||
@ -185,71 +185,72 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="block flex-no-shrink">
|
||||
<div class="pb-4">
|
||||
<StyledTextBox
|
||||
alwaysEdit={true}
|
||||
maxHeight="limited"
|
||||
showButtons={false}
|
||||
placeholder={calendar.string.Description}
|
||||
bind:content={todo.description}
|
||||
/>
|
||||
</div>
|
||||
<div class="flex-row-center gap-1-5 mb-1">
|
||||
<DueDateEditor bind:value={todo.dueDate} />
|
||||
<PriorityEditor bind:value={todo.priority} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="block flex-no-shrink">
|
||||
<div class="flex-row-center gap-1-5 mb-1">
|
||||
<div>
|
||||
<Label label={time.string.AddTo} />
|
||||
<Scroller>
|
||||
<div class="block flex-no-shrink">
|
||||
<div class="pb-4">
|
||||
<StyledTextBox
|
||||
alwaysEdit={true}
|
||||
maxHeight="limited"
|
||||
showButtons={false}
|
||||
placeholder={calendar.string.Description}
|
||||
bind:content={todo.description}
|
||||
/>
|
||||
</div>
|
||||
<div class="flex-row-center gap-1-5 mb-1">
|
||||
<PriorityEditor bind:value={todo.priority} />
|
||||
<VisibilityEditor size="small" bind:value={todo.visibility} />
|
||||
<DueDateEditor bind:value={todo.dueDate} />
|
||||
</div>
|
||||
<SpaceSelector
|
||||
_class={task.class.Project}
|
||||
query={{ archived: false, members: getCurrentAccount()._id }}
|
||||
label={core.string.Space}
|
||||
autoSelect={false}
|
||||
allowDeselect
|
||||
kind={'regular'}
|
||||
size={'medium'}
|
||||
focus={false}
|
||||
bind:space={todo.attachedSpace}
|
||||
/>
|
||||
<VisibilityEditor bind:value={todo.visibility} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="block flex-no-shrink">
|
||||
<div class="flex-row-center gap-1-5 mb-1">
|
||||
<Component
|
||||
is={tagsPlugin.component.DraftTagsEditor}
|
||||
props={{ tags, targetClass: time.class.ToDo }}
|
||||
on:change={(e) => {
|
||||
tags = e.detail
|
||||
}}
|
||||
/>
|
||||
<div class="block flex-no-shrink">
|
||||
<div class="flex-row-center gap-1-5 mb-1">
|
||||
<div>
|
||||
<Label label={time.string.AddTo} />
|
||||
</div>
|
||||
<SpaceSelector
|
||||
_class={task.class.Project}
|
||||
query={{ archived: false, members: getCurrentAccount()._id }}
|
||||
label={core.string.Space}
|
||||
autoSelect={false}
|
||||
allowDeselect
|
||||
kind={'regular'}
|
||||
size={'medium'}
|
||||
focus={false}
|
||||
bind:space={todo.attachedSpace}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="block flex-no-shrink end">
|
||||
<div class="flex-row-center gap-1-5">
|
||||
<div class="block flex-no-shrink">
|
||||
<div class="flex-row-center gap-1-5 mb-1">
|
||||
<Component
|
||||
is={tagsPlugin.component.DraftTagsEditor}
|
||||
props={{ tags, targetClass: time.class.ToDo }}
|
||||
on:change={(e) => {
|
||||
tags = e.detail
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="block flex-gap-4 flex-no-shrink end">
|
||||
<Workslots
|
||||
bind:slots
|
||||
shortcuts={false}
|
||||
on:remove={removeSlot}
|
||||
on:create={createSlot}
|
||||
on:change={changeSlot}
|
||||
on:dueChange={changeDueSlot}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-row-reverse btn flex-no-shrink">
|
||||
<Button
|
||||
kind="primary"
|
||||
{loading}
|
||||
label={time.string.AddToDo}
|
||||
on:click={saveToDo}
|
||||
disabled={todo?.title === undefined || todo?.title === ''}
|
||||
/>
|
||||
</div>
|
||||
<div class="flex-row-reverse btn flex-no-shrink">
|
||||
<Button
|
||||
kind="primary"
|
||||
{loading}
|
||||
label={time.string.AddToDo}
|
||||
on:click={saveToDo}
|
||||
disabled={todo?.title === undefined || todo?.title === ''}
|
||||
/>
|
||||
</div>
|
||||
</Scroller>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
|
@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import ui, { Button, DatePopup, Icon, Label, showPopup } from '@hcengineering/ui'
|
||||
import ui, { ButtonBase, DatePopup, showPopup } from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import time from '../plugin'
|
||||
|
||||
@ -22,16 +22,20 @@
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let opened: boolean = false
|
||||
</script>
|
||||
|
||||
<Button
|
||||
kind={'regular'}
|
||||
on:click={(e) => {
|
||||
$: buttonTitle = value ? new Date(value).toLocaleDateString() : undefined
|
||||
$: buttonLabel = buttonTitle === undefined ? ui.string.DueDate : undefined
|
||||
|
||||
function handleClick (e: MouseEvent) {
|
||||
if (!opened) {
|
||||
opened = true
|
||||
showPopup(
|
||||
DatePopup,
|
||||
{ noShift: true, currentDate: value ? new Date(value) : null, label: ui.string.SetDueDate },
|
||||
{
|
||||
noShift: true,
|
||||
currentDate: value ? new Date(value) : null,
|
||||
label: ui.string.SetDueDate
|
||||
},
|
||||
'top',
|
||||
(result) => {
|
||||
if (result != null && result.value !== undefined) {
|
||||
@ -42,14 +46,17 @@
|
||||
}
|
||||
)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div slot="content" class="flex-row-center flex-gap-1">
|
||||
<Icon icon={time.icon.Target} size="medium" />
|
||||
{#if value}
|
||||
{new Date(value).toLocaleDateString()}
|
||||
{:else}
|
||||
<Label label={ui.string.DueDate} />
|
||||
{/if}
|
||||
</div>
|
||||
</Button>
|
||||
}
|
||||
</script>
|
||||
|
||||
<ButtonBase
|
||||
kind="secondary"
|
||||
size="small"
|
||||
type="type-button"
|
||||
icon={time.icon.Calendar}
|
||||
iconSize="small"
|
||||
title={buttonTitle}
|
||||
label={buttonLabel}
|
||||
pressed={opened}
|
||||
on:click={handleClick}
|
||||
/>
|
||||
|
@ -190,8 +190,8 @@
|
||||
</div>
|
||||
{/if}
|
||||
<div class="slots-content">
|
||||
<div class="flex-row-top justify-between flex-gap-2">
|
||||
<span class="font-medium-14 secondary-textColor">
|
||||
<div class="flex-row-top justify-between items-center flex-gap-2">
|
||||
<span class="font-caps-medium-12 slots-content-title">
|
||||
<Label label={time.string.WorkSchedule} />
|
||||
</span>
|
||||
<div class="flex-row-center gap-2">
|
||||
@ -235,9 +235,8 @@
|
||||
}
|
||||
.slots-content {
|
||||
gap: var(--spacing-2);
|
||||
padding: var(--spacing-3) var(--spacing-4);
|
||||
padding: var(--spacing-2) var(--spacing-4);
|
||||
border-top: 1px solid var(--theme-divider-color);
|
||||
border-bottom: 1px solid var(--theme-divider-color);
|
||||
}
|
||||
.eventPopup-container {
|
||||
display: flex;
|
||||
|
@ -46,7 +46,7 @@
|
||||
}
|
||||
})
|
||||
$: selected = selectPopupPriorities.find((item) => item.id === value)
|
||||
$: selectedLabel = selected?.label ?? time.string.NoPriority
|
||||
$: selectedLabel = selected?.label === time.string.NoPriority ? time.string.SetPriority : selected?.label
|
||||
|
||||
$: icon = selected?.id === ToDoPriority.NoPriority ? time.icon.Flag : selected?.icon
|
||||
$: iconProps = selected?.iconProps
|
||||
|
@ -1,35 +1,14 @@
|
||||
<script lang="ts">
|
||||
import { translate } from '@hcengineering/platform'
|
||||
import { DAY, HOUR, MINUTE, themeStore } from '@hcengineering/ui'
|
||||
import { themeStore } from '@hcengineering/ui'
|
||||
import { WorkSlot } from '@hcengineering/time'
|
||||
import time from '../plugin'
|
||||
import { calculateEventsDuration, formatEventsDuration } from '../utils'
|
||||
|
||||
export let events: WorkSlot[]
|
||||
|
||||
$: duration = events.reduce((acc, curr) => acc + curr.dueDate - curr.date, 0)
|
||||
|
||||
let res: string = ''
|
||||
|
||||
async function formatTime (value: number) {
|
||||
res = ''
|
||||
const days = Math.floor(value / DAY)
|
||||
if (days > 0) {
|
||||
res += await translate(time.string.Days, { days }, $themeStore.language)
|
||||
}
|
||||
const hours = Math.floor((value % DAY) / HOUR)
|
||||
if (hours > 0) {
|
||||
res += ' '
|
||||
res += await translate(time.string.Hours, { hours }, $themeStore.language)
|
||||
}
|
||||
const minutes = Math.floor((value % HOUR) / MINUTE)
|
||||
if (minutes > 0) {
|
||||
res += ' '
|
||||
res += await translate(time.string.Minutes, { minutes }, $themeStore.language)
|
||||
}
|
||||
res = res.trim()
|
||||
}
|
||||
|
||||
$: formatTime(duration)
|
||||
let duration: string
|
||||
$: formatEventsDuration(calculateEventsDuration(events), $themeStore.language).then((res) => {
|
||||
duration = res
|
||||
})
|
||||
</script>
|
||||
|
||||
{res}
|
||||
{duration}
|
||||
|
@ -13,15 +13,31 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { ButtonBase, ButtonIcon, IconDelete, themeStore, Hotkey, HotkeyGroup } from '@hcengineering/ui'
|
||||
import { EventTimeEditor } from '@hcengineering/calendar-resources'
|
||||
import { ActionIcon, Button, Icon, IconCircleAdd, IconClose, Scroller } from '@hcengineering/ui'
|
||||
import { WorkSlot } from '@hcengineering/time'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import time from '../plugin'
|
||||
import { calculateEventsDuration, formatEventsDuration } from '../utils'
|
||||
import Label from '@hcengineering/ui/src/components/Label.svelte'
|
||||
|
||||
export let slots: WorkSlot[] = []
|
||||
export let shortcuts: boolean = true
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let duration: string
|
||||
$: formatEventsDuration(calculateEventsDuration(slots), $themeStore.language).then((res) => {
|
||||
duration = res
|
||||
})
|
||||
|
||||
function handleKeyDown (event: KeyboardEvent): void {
|
||||
if (!shortcuts) return
|
||||
if (event.shiftKey && event.key === 'Enter') {
|
||||
dispatch('create')
|
||||
}
|
||||
}
|
||||
|
||||
async function change (e: CustomEvent<{ startDate: number, dueDate: number }>, slot: WorkSlot): Promise<void> {
|
||||
const { startDate, dueDate } = e.detail
|
||||
dispatch('change', { startDate, dueDate, slot: slot._id })
|
||||
@ -33,50 +49,77 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex-col container w-full flex-gap-1">
|
||||
<Scroller>
|
||||
{#each slots as slot}
|
||||
<div class="flex-between w-full pr-4 slot">
|
||||
<EventTimeEditor
|
||||
allDay={false}
|
||||
startDate={slot.date}
|
||||
bind:dueDate={slot.dueDate}
|
||||
on:change={(e) => change(e, slot)}
|
||||
on:dueChange={(e) => dueChange(e, slot)}
|
||||
<svelte:window on:keydown={handleKeyDown} />
|
||||
|
||||
<div class="flex-col w-full flex-gap-1">
|
||||
{#each slots as slot, i}
|
||||
<div class="flex justify-start items-center flex-gap-2 w-full pr-4 slot">
|
||||
<Hotkey key={(i + 1).toString()} />
|
||||
<EventTimeEditor
|
||||
allDay={false}
|
||||
startDate={slot.date}
|
||||
dueDate={slot.dueDate}
|
||||
on:change={(e) => change(e, slot)}
|
||||
on:dueChange={(e) => dueChange(e, slot)}
|
||||
/>
|
||||
<div class="tool">
|
||||
<ButtonIcon
|
||||
kind="tertiary"
|
||||
size="small"
|
||||
icon={IconDelete}
|
||||
on:click={() => {
|
||||
dispatch('remove', { _id: slot._id })
|
||||
}}
|
||||
/>
|
||||
<div class="tool">
|
||||
<ActionIcon
|
||||
icon={IconClose}
|
||||
size={'small'}
|
||||
action={() => {
|
||||
dispatch('remove', { _id: slot._id })
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</Scroller>
|
||||
<div class="flex-row-center">
|
||||
<div class="mr-1-5">
|
||||
<Icon icon={IconCircleAdd} size="small" />
|
||||
</div>
|
||||
<Button padding={'0 .5rem'} kind="ghost" label={time.string.AddSlot} on:click={() => dispatch('create')} />
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
<div class="flex-row-center flex-gap-4">
|
||||
<ButtonBase
|
||||
kind="secondary"
|
||||
type="type-button"
|
||||
size="medium"
|
||||
label={time.string.AddSlot}
|
||||
on:click={() => dispatch('create')}
|
||||
>
|
||||
{#if shortcuts}
|
||||
<HotkeyGroup keys={['shift', 'Enter']} />
|
||||
{/if}
|
||||
</ButtonBase>
|
||||
{#if duration}
|
||||
<div class="font-regular-14">
|
||||
<Label label={time.string.SummaryDuration} />:
|
||||
<br />
|
||||
<span class="duration">{duration}</span>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.container {
|
||||
max-height: 10rem;
|
||||
.slot {
|
||||
position: relative;
|
||||
padding: var(--spacing-1) var(--spacing-1) var(--spacing-1) var(--spacing-2_5);
|
||||
border-radius: var(--small-BorderRadius);
|
||||
background-color: var(--tag-nuance-SunshineBackground);
|
||||
|
||||
&:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 0.25rem;
|
||||
height: 100%;
|
||||
background-color: var(--tag-accent-SunshineBackground);
|
||||
border-radius: var(--small-BorderRadius) 0 0 var(--small-BorderRadius);
|
||||
}
|
||||
|
||||
.tool {
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.slot {
|
||||
.tool {
|
||||
visibility: hidden;
|
||||
}
|
||||
&:hover {
|
||||
.tool {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
.duration {
|
||||
color: var(--tag-accent-SunshineText);
|
||||
}
|
||||
</style>
|
||||
|
@ -61,6 +61,7 @@ export default mergeIds(timeId, time, {
|
||||
AddTo: '' as IntlString,
|
||||
AddTitle: '' as IntlString,
|
||||
MyWork: '' as IntlString,
|
||||
WorkSchedule: '' as IntlString
|
||||
WorkSchedule: '' as IntlString,
|
||||
SummaryDuration: '' as IntlString
|
||||
}
|
||||
})
|
||||
|
@ -1,6 +1,9 @@
|
||||
import type { Client, Ref } from '@hcengineering/core'
|
||||
import type { DefSeparators } from '@hcengineering/ui'
|
||||
import type { WorkSlot, ToDo } from '@hcengineering/time'
|
||||
import type { DefSeparators } from '@hcengineering/ui'
|
||||
import type { Client, Ref } from '@hcengineering/core'
|
||||
import { DAY, HOUR, MINUTE } from '@hcengineering/ui'
|
||||
import { translate } from '@hcengineering/platform'
|
||||
import timePlugin from './plugin'
|
||||
import time from '@hcengineering/time'
|
||||
|
||||
export * from './types'
|
||||
@ -36,3 +39,46 @@ export async function ToDoTitleProvider (client: Client, ref: Ref<ToDo>, doc?: T
|
||||
|
||||
return object.title
|
||||
}
|
||||
|
||||
export function calculateEventsDuration (events: WorkSlot[]): number {
|
||||
const points = events.flatMap((event) => [
|
||||
{ time: event.date, type: 'start' },
|
||||
{ time: event.dueDate, type: 'end' }
|
||||
])
|
||||
|
||||
points.sort((a, b) => a.time - b.time)
|
||||
|
||||
let activeEvents = 0
|
||||
let duration = 0
|
||||
let lastTime = 0
|
||||
|
||||
points.forEach((point) => {
|
||||
if (activeEvents > 0) {
|
||||
duration += point.time - lastTime
|
||||
}
|
||||
activeEvents += point.type === 'start' ? 1 : -1
|
||||
lastTime = point.time
|
||||
})
|
||||
|
||||
return duration
|
||||
}
|
||||
|
||||
export async function formatEventsDuration (duration: number, language: string): Promise<string> {
|
||||
let text = ''
|
||||
const days = Math.floor(duration / DAY)
|
||||
if (days > 0) {
|
||||
text += await translate(timePlugin.string.Days, { days }, language)
|
||||
}
|
||||
const hours = Math.floor((duration % DAY) / HOUR)
|
||||
if (hours > 0) {
|
||||
text += ' '
|
||||
text += await translate(timePlugin.string.Hours, { hours }, language)
|
||||
}
|
||||
const minutes = Math.floor((duration % HOUR) / MINUTE)
|
||||
if (minutes > 0) {
|
||||
text += ' '
|
||||
text += await translate(timePlugin.string.Minutes, { minutes }, language)
|
||||
}
|
||||
text = text.trim()
|
||||
return text
|
||||
}
|
||||
|
@ -13,12 +13,12 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { Event, Visibility } from '@hcengineering/calendar'
|
||||
import { Person } from '@hcengineering/contact'
|
||||
import { AttachedDoc, Class, Doc, Hierarchy, Markup, Mixin, Ref, Space, Timestamp, Type } from '@hcengineering/core'
|
||||
import type { Asset, Plugin, Resource } from '@hcengineering/platform'
|
||||
import { AttachedDoc, Class, Doc, Hierarchy, Markup, Mixin, Ref, Space, Timestamp, Type } from '@hcengineering/core'
|
||||
import { IntlString, plugin } from '@hcengineering/platform'
|
||||
import { Event, Visibility } from '@hcengineering/calendar'
|
||||
import { AnyComponent } from '@hcengineering/ui'
|
||||
import { Person } from '@hcengineering/contact'
|
||||
|
||||
/**
|
||||
* @public
|
||||
@ -120,7 +120,7 @@ export default plugin(timeId, {
|
||||
Team: '' as Asset,
|
||||
Hashtag: '' as Asset,
|
||||
Inbox: '' as Asset,
|
||||
Target: '' as Asset,
|
||||
Calendar: '' as Asset,
|
||||
Flag: '' as Asset,
|
||||
FilledFlag: '' as Asset,
|
||||
Planned: '' as Asset,
|
||||
|
@ -1,5 +1,5 @@
|
||||
# Create workspace record in accounts
|
||||
./tool-local.sh create-workspace sanity-ws -o SanityTest
|
||||
./tool-local.sh create-workspace sanity-ws -w SanityTest
|
||||
# Create user record in accounts
|
||||
./tool-local.sh create-account user1 -f John -l Appleseed -p 1234
|
||||
./tool-local.sh confirm-email user1
|
||||
|
@ -40,8 +40,12 @@ export class PlanningPage extends CalendarPage {
|
||||
this.inputPopupCreateTitle = page.locator('div.popup input')
|
||||
this.inputPopupCreateDescription = page.locator('div.popup div.tiptap')
|
||||
this.inputPanelCreateDescription = page.locator('div.hulyModal-container div.tiptap')
|
||||
this.buttonPopupCreateDueDate = page.locator('div.popup button.antiButton', { hasText: 'Due date' })
|
||||
this.buttonPanelCreateDueDate = page.locator('div.hulyModal-container button.antiButton', { hasText: 'Due date' })
|
||||
this.buttonPopupCreateDueDate = page.locator(
|
||||
'div.popup div.block:first-child div.flex-row-center button:nth-child(3)'
|
||||
)
|
||||
this.buttonPanelCreateDueDate = page.locator(
|
||||
'div.hulyModal-container div.slots-content div.flex-row-top.justify-between div.flex-row-center button:first-child'
|
||||
)
|
||||
this.buttonPopupCreatePriority = page.locator('div.popup button#priorityButton')
|
||||
this.buttonPanelCreatePriority = page.locator('div.hulyModal-container button#priorityButton')
|
||||
this.buttonPopupCreateVisible = page.locator('div.popup button.type-button.menu', { hasText: 'visible' })
|
||||
@ -50,8 +54,8 @@ export class PlanningPage extends CalendarPage {
|
||||
})
|
||||
this.buttonPopupCreateAddLabel = page.locator('div.popup button.antiButton', { hasText: 'Add label' })
|
||||
this.buttonPanelCreateAddLabel = page.locator('.hulyHeader-titleGroup > button:nth-child(2)')
|
||||
this.buttonPopupCreateAddSlot = page.locator('div.popup button.antiButton', { hasText: 'Add Slot' })
|
||||
this.buttonPanelCreateAddSlot = page.locator('div.hulyModal-container button.antiButton', { hasText: 'Add Slot' })
|
||||
this.buttonPopupCreateAddSlot = page.locator('div.popup button', { hasText: 'Add Slot' })
|
||||
this.buttonPanelCreateAddSlot = page.locator('div.hulyModal-container button', { hasText: 'Add Slot' })
|
||||
this.buttonCalendarToday = page.locator('div.popup div.calendar button.day.today')
|
||||
this.buttonCreateToDo = page.locator('div.popup button.antiButton', { hasText: 'Add ToDo' })
|
||||
this.inputCreateToDoTitle = page.locator('div.toDos-container input[placeholder="Add todo, press Enter to save"]')
|
||||
@ -63,7 +67,7 @@ export class PlanningPage extends CalendarPage {
|
||||
)
|
||||
this.textPanelToDoDescription = page.locator('div.hulyModal-container div.top-content div.tiptap > p')
|
||||
this.textPanelDueDate = page.locator(
|
||||
'div.hulyModal-container div.slots-content div.flex-row-top.justify-between div.flex-row-center button.antiButton:first-child div[slot="content"]'
|
||||
'div.hulyModal-container div.slots-content div.flex-row-top.justify-between div.flex-row-center button:first-child span'
|
||||
)
|
||||
this.textPanelPriority = page.locator('div.hulyModal-container button#priorityButton svg')
|
||||
this.textPanelVisible = page.locator(
|
||||
@ -133,12 +137,12 @@ export class PlanningPage extends CalendarPage {
|
||||
|
||||
public async setTimeSlot (rowNumber: number, slot: Slot, popup: boolean = false): Promise<void> {
|
||||
const p = popup
|
||||
? 'div.popup div.horizontalBox div.flex-row-center'
|
||||
: 'div.hulyModal-container div.slots-content div.horizontalBox div.flex-row-center'
|
||||
? 'div.popup div.horizontalBox div.end div.flex-col div.flex'
|
||||
: 'div.hulyModal-container div.slots-content div.flex-col div.flex'
|
||||
const row = this.page.locator(p).nth(rowNumber)
|
||||
|
||||
// dateStart
|
||||
await row.locator('div.dateEditor-container:nth-child(2) button:first-child').click()
|
||||
await row.locator('div.dateEditor-container:nth-child(1) button:first-child').click()
|
||||
if (slot.dateStart === 'today') {
|
||||
await this.buttonCalendarToday.click()
|
||||
} else {
|
||||
@ -154,34 +158,34 @@ export class PlanningPage extends CalendarPage {
|
||||
const hours = slot.timeStart.substring(0, 2)
|
||||
const minutes = slot.timeStart.substring(2, slot.timeStart.length)
|
||||
await row
|
||||
.locator('div.dateEditor-container:nth-child(2) button:last-child span.digit:first-child')
|
||||
.locator('div.dateEditor-container:nth-child(1) button:last-child span.digit:first-child')
|
||||
.click({ delay: 200 })
|
||||
await row
|
||||
.locator('div.dateEditor-container:nth-child(2) button:last-child span.digit:first-child')
|
||||
.locator('div.dateEditor-container:nth-child(1) button:last-child span.digit:first-child')
|
||||
.pressSequentially(hours, { delay: 100 })
|
||||
await row
|
||||
.locator('div.dateEditor-container:nth-child(2) button:last-child span.digit:last-child')
|
||||
.locator('div.dateEditor-container:nth-child(1) button:last-child span.digit:last-child')
|
||||
.click({ delay: 200 })
|
||||
await row
|
||||
.locator('div.dateEditor-container:nth-child(2) button:last-child span.digit:last-child')
|
||||
.locator('div.dateEditor-container:nth-child(1) button:last-child span.digit:last-child')
|
||||
.pressSequentially(minutes, { delay: 100 })
|
||||
|
||||
// dateEnd + timeEnd
|
||||
await row.locator('div.dateEditor-container:nth-child(4) button').click()
|
||||
await row.locator('div.dateEditor-container.difference button').click()
|
||||
await this.fillSelectDatePopup(slot.dateEnd.day, slot.dateEnd.month, slot.dateEnd.year, slot.timeEnd)
|
||||
}
|
||||
|
||||
private async checkTimeSlot (rowNumber: number, slot: Slot, popup: boolean = false): Promise<void> {
|
||||
const p = popup
|
||||
? 'div.popup div.horizontalBox div.flex-row-center'
|
||||
: 'div.hulyModal-container div.slots-content div.horizontalBox div.flex-row-center'
|
||||
? 'div.popup div.horizontalBox div.end div.flex-col div.flex'
|
||||
: 'div.hulyModal-container div.slots-content div.flex-col div.flex'
|
||||
const row = this.page.locator(p).nth(rowNumber)
|
||||
// timeStart
|
||||
await expect(row.locator('div.dateEditor-container:nth-child(2) button:last-child div.datetime-input')).toHaveText(
|
||||
await expect(row.locator('div.dateEditor-container:nth-child(1) button:last-child div.datetime-input')).toHaveText(
|
||||
slot.timeStart
|
||||
)
|
||||
// timeEnd
|
||||
await expect(row.locator('div.dateEditor-container:nth-child(4) button > div:first-child')).toHaveText(slot.timeEnd)
|
||||
await expect(row.locator('div.dateEditor-container.difference button > div:first-child')).toHaveText(slot.timeEnd)
|
||||
}
|
||||
|
||||
async openToDoByName (toDoName: string): Promise<void> {
|
||||
@ -255,18 +259,17 @@ export class PlanningPage extends CalendarPage {
|
||||
}
|
||||
|
||||
public async deleteTimeSlot (rowNumber: number): Promise<void> {
|
||||
const row = this.page.locator('div.hulyModal-container div.slots-content div.horizontalBox div.tool').nth(rowNumber)
|
||||
const row = this.page
|
||||
.locator('div.hulyModal-container div.slots-content div.flex-col div.flex div.tool')
|
||||
.nth(rowNumber)
|
||||
await row.locator('xpath=..').hover()
|
||||
await row.locator('button').click()
|
||||
await expect(row.locator('button')).toBeHidden()
|
||||
await this.pressYesDeletePopup(this.page)
|
||||
}
|
||||
|
||||
public async checkTimeSlotEndDate (rowNumber: number, dateEnd: string): Promise<void> {
|
||||
const row = this.page
|
||||
.locator('div.hulyModal-container div.slots-content div.horizontalBox div.flex-row-center')
|
||||
.nth(rowNumber)
|
||||
const row = this.page.locator('div.hulyModal-container div.slots-content div.flex-col div.flex').nth(rowNumber)
|
||||
// dateEnd
|
||||
await expect(row.locator('div.dateEditor-container:nth-child(2) button:first-child')).toContainText(dateEnd)
|
||||
await expect(row.locator('div.dateEditor-container:nth-child(1) button:first-child')).toContainText(dateEnd)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user