platform/plugins/setting-resources/src/components/EditEnum.svelte
2024-06-27 20:27:00 +07:00

346 lines
9.1 KiB
Svelte

<!--
// Copyright © 2022 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import core, { Enum } from '@hcengineering/core'
import presentation, { getClient, MessageBox } from '@hcengineering/presentation'
import {
IconAdd,
IconAttachment,
IconDelete,
showPopup,
Modal,
ModernEditbox,
Label,
ButtonIcon,
IconMoreV,
IconMoreV2,
eventToHTMLElement,
ModernPopup
} from '@hcengineering/ui'
import type { DropdownIntlItem } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
import setting from '../plugin'
import EnumValuesList from './EnumValuesList.svelte'
import IconBulletList from './icons/BulletList.svelte'
import Report from './icons/Report.svelte'
import { IntlString } from '@hcengineering/platform'
export let value: Enum | undefined
export let name: string = value?.name ?? ''
export let values: string[] = value?.enumValues ?? []
export let title: IntlString = setting.string.EditEnum
const client = getClient()
const dispatch = createEventDispatcher()
let matched = false
$: matched = values.includes(newValue.trim())
async function save (): Promise<void> {
if (value === undefined) {
await client.createDoc(core.class.Enum, core.space.Model, {
name,
enumValues: values
})
} else {
await client.update(value, {
name,
enumValues: values
})
}
dispatch('close')
}
function add () {
newValue = newValue.trim()
if (!newValue.length) return
if (matched) return
values.push(newValue)
values = values
newValue = ''
}
function remove (value: string) {
values = values.filter((p) => p !== value)
}
const handleKeydown = (evt: KeyboardEvent) => {
if (evt.key === 'Enter') {
add()
}
if (evt.key === 'Escape') {
newItem = false
newValue = ''
evt.stopPropagation()
}
}
let newValue = ''
let newItem: boolean = false
let opened: boolean = false
let inputFile: HTMLInputElement
function processText (text: string): void {
const newValues = text.split('\n').map((it) => it.trim())
for (const v of newValues) {
if (!values.includes(v)) {
values.push(v)
}
}
values = values
newValue = ''
}
async function processFile (file: File): Promise<void> {
const text = await file.text()
processText(text)
}
function fileSelected () {
const list = inputFile.files
if (list === null || list.length === 0) return
for (let index = 0; index < list.length; index++) {
const file = list.item(index)
if (file !== null) {
processFile(file)
}
}
inputFile.value = ''
}
function fileDrop (e: DragEvent) {
dragover = false
const list = e.dataTransfer?.files
if (list === undefined || list.length === 0) return
for (let index = 0; index < list.length; index++) {
const file = list.item(index)
if (file !== null) {
processFile(file)
}
}
}
function pasteAction (evt: ClipboardEvent): void {
const items = evt.clipboardData?.items ?? []
for (const index in items) {
const item = items[index]
if (item.kind === 'file') {
const blob = item.getAsFile()
if (blob !== null) {
processFile(blob)
}
}
}
}
async function handleClipboard (): Promise<void> {
const text = await navigator.clipboard.readText()
processText(text)
}
let dragover = false
const selection: number = 0
// $: filtered = newValue.length > 0 ? values.filter((it) => it.includes(newValue)) : values
// function onDelete () {
// showPopup(
// MessageBox,
// {
// label: view.string.DeleteObject,
// message: view.string.DeleteObjectConfirm,
// params: { count: filtered.length }
// },
// 'top',
// (result?: boolean) => {
// if (result === true) {
// values = values.filter((it) => !filtered.includes(it))
// newValue = ''
// }
// }
// )
// }
const items: (DropdownIntlItem & { action: () => void })[] = [
{
id: 'import',
icon: IconAttachment,
label: setting.string.ImportEnum,
action: () => {
inputFile.click()
}
},
{
id: 'paste',
icon: Report,
label: setting.string.ImportEnumCopy,
action: () => {
handleClipboard()
}
}
]
const openPopup = (ev: MouseEvent): void => {
if (!opened) {
opened = true
showPopup(ModernPopup, { items }, eventToHTMLElement(ev), (result) => {
if (result) items.find((it) => it.id === result)?.action()
opened = false
})
}
}
async function showConfirmationDialog (): Promise<void> {
const isEnumEmpty = values.length === 0
if (isEnumEmpty) {
dispatch('close')
} else {
showPopup(
MessageBox,
{
label: setting.string.NewEnumDialogClose,
message: setting.string.NewEnumDialogCloseNote
},
'top',
(result?: boolean) => {
if (result === true) dispatch('close')
}
)
}
}
</script>
<svelte:window on:paste={pasteAction} />
<input
bind:this={inputFile}
disabled={inputFile == null}
multiple
type="file"
name="file"
id="file"
style="display: none"
on:change={fileSelected}
/>
<Modal
label={title}
type={'type-popup'}
okLabel={presentation.string.Save}
okAction={save}
canSave={name.trim().length > 0 && values.length > 0}
onCancel={() => {
showConfirmationDialog()
}}
>
<div class="flex-col">
<ModernEditbox bind:value={name} label={setting.string.EnumTitle} kind={'ghost'} size={'large'} width={'100%'} />
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
class="hulyTableAttr-container mt-6"
class:dragDropZone={dragover}
on:dragover|preventDefault={() => {
dragover = true
}}
on:dragleave={() => {
dragover = false
}}
on:drop|preventDefault|stopPropagation={fileDrop}
>
<div class="hulyTableAttr-header font-medium-12">
<IconBulletList size={'small'} />
<span><Label label={setting.string.Options} /></span>
<div class="buttons-group tertiary-textColor">
<ButtonIcon
kind={'tertiary'}
icon={IconMoreV}
size={'small'}
pressed={opened}
inheritColor
hasMenu
on:click={(ev) => {
openPopup(ev)
}}
/>
<ButtonIcon
kind={'primary'}
icon={IconAdd}
size={'small'}
tooltip={{ label: setting.string.Add }}
on:click={() => {
if (newItem) {
add()
} else {
newItem = true
}
}}
/>
</div>
</div>
{#if values.length > 0 || newItem}
<div class="hulyTableAttr-content options">
<EnumValuesList
bind:values
disableMouseOver={newItem}
on:remove={(e) => {
remove(e.detail)
}}
/>
{#if newItem}
<div class="hulyTableAttr-content__row hovered">
<div class="hulyTableAttr-content__row-dragMenu">
<IconMoreV2 size={'small'} />
</div>
<div class="hulyTableAttr-content__row-label font-regular-14 accent grow">
<ModernEditbox
kind={'ghost'}
size={'small'}
label={setting.string.EnterOptionTitle}
on:keydown={handleKeydown}
on:blur={() => {
newValue = newValue.trim()
if (!newValue.length) return
add()
newItem = false
}}
bind:value={newValue}
width={'100%'}
autoFocus
/>
</div>
{#if matched}
<div class="hulyChip-item error font-medium-12">
<Label label={presentation.string.Match} />
</div>
{/if}
<ButtonIcon
kind={'tertiary'}
icon={IconDelete}
size={'small'}
on:click={() => {
newValue = ''
newItem = false
}}
/>
</div>
{/if}
</div>
{/if}
</div>
</div>
</Modal>
<style lang="scss">
.dragDropZone {
border: 2px dashed var(--theme-popup-hover);
}
</style>