mirror of
https://github.com/hcengineering/platform.git
synced 2025-04-30 20:25:38 +00:00
Added hyperlink type, ScrollerBar (#2353)
Signed-off-by: Alexander Platov <sas_lord@mail.ru>
This commit is contained in:
parent
47f5237fc9
commit
35e4fcd8aa
@ -143,6 +143,10 @@ export class TType extends TObj implements Type<any> {
|
|||||||
@Model(core.class.TypeString, core.class.Type)
|
@Model(core.class.TypeString, core.class.Type)
|
||||||
export class TTypeString extends TType {}
|
export class TTypeString extends TType {}
|
||||||
|
|
||||||
|
@UX(core.string.Hyperlink)
|
||||||
|
@Model(core.class.TypeHyperlink, core.class.Type)
|
||||||
|
export class TTypeHyperlink extends TType {}
|
||||||
|
|
||||||
@UX(core.string.IntlString)
|
@UX(core.string.IntlString)
|
||||||
@Model(core.class.TypeIntlString, core.class.Type)
|
@Model(core.class.TypeIntlString, core.class.Type)
|
||||||
export class TTypeIntlString extends TType {}
|
export class TTypeIntlString extends TType {}
|
||||||
|
@ -38,6 +38,7 @@ import {
|
|||||||
TTypeMarkup,
|
TTypeMarkup,
|
||||||
TTypeNumber,
|
TTypeNumber,
|
||||||
TTypeString,
|
TTypeString,
|
||||||
|
TTypeHyperlink,
|
||||||
TTypeTimestamp,
|
TTypeTimestamp,
|
||||||
TTypeRelatedDocument,
|
TTypeRelatedDocument,
|
||||||
TVersion
|
TVersion
|
||||||
@ -81,6 +82,7 @@ export function createModel (builder: Builder): void {
|
|||||||
TTypeNumber,
|
TTypeNumber,
|
||||||
TTypeBoolean,
|
TTypeBoolean,
|
||||||
TTypeString,
|
TTypeString,
|
||||||
|
TTypeHyperlink,
|
||||||
TCollection,
|
TCollection,
|
||||||
TVersion,
|
TVersion,
|
||||||
TTypeIntlString,
|
TTypeIntlString,
|
||||||
|
@ -278,6 +278,10 @@ export function createModel (builder: Builder): void {
|
|||||||
editor: setting.component.StringTypeEditor
|
editor: setting.component.StringTypeEditor
|
||||||
})
|
})
|
||||||
|
|
||||||
|
builder.mixin(core.class.TypeHyperlink, core.class.Class, view.mixin.ObjectEditor, {
|
||||||
|
editor: setting.component.HyperlinkTypeEditor
|
||||||
|
})
|
||||||
|
|
||||||
builder.mixin(core.class.TypeBoolean, core.class.Class, view.mixin.ObjectEditor, {
|
builder.mixin(core.class.TypeBoolean, core.class.Class, view.mixin.ObjectEditor, {
|
||||||
editor: setting.component.BooleanTypeEditor
|
editor: setting.component.BooleanTypeEditor
|
||||||
})
|
})
|
||||||
|
@ -33,6 +33,7 @@ export default mergeIds(settingId, setting, {
|
|||||||
component: {
|
component: {
|
||||||
EnumSetting: '' as AnyComponent,
|
EnumSetting: '' as AnyComponent,
|
||||||
StringTypeEditor: '' as AnyComponent,
|
StringTypeEditor: '' as AnyComponent,
|
||||||
|
HyperlinkTypeEditor: '' as AnyComponent,
|
||||||
BooleanTypeEditor: '' as AnyComponent,
|
BooleanTypeEditor: '' as AnyComponent,
|
||||||
NumberTypeEditor: '' as AnyComponent,
|
NumberTypeEditor: '' as AnyComponent,
|
||||||
DateTypeEditor: '' as AnyComponent,
|
DateTypeEditor: '' as AnyComponent,
|
||||||
|
@ -295,6 +295,13 @@ export function createModel (builder: Builder): void {
|
|||||||
view.component.StringEditor,
|
view.component.StringEditor,
|
||||||
view.component.StringEditorPopup
|
view.component.StringEditorPopup
|
||||||
)
|
)
|
||||||
|
classPresenter(
|
||||||
|
builder,
|
||||||
|
core.class.TypeHyperlink,
|
||||||
|
view.component.HyperlinkPresenter,
|
||||||
|
view.component.StringEditor,
|
||||||
|
view.component.StringEditorPopup
|
||||||
|
)
|
||||||
classPresenter(builder, core.class.TypeIntlString, view.component.IntlStringPresenter)
|
classPresenter(builder, core.class.TypeIntlString, view.component.IntlStringPresenter)
|
||||||
classPresenter(builder, core.class.TypeNumber, view.component.NumberPresenter, view.component.NumberEditor)
|
classPresenter(builder, core.class.TypeNumber, view.component.NumberPresenter, view.component.NumberEditor)
|
||||||
classPresenter(
|
classPresenter(
|
||||||
@ -520,6 +527,10 @@ export function createModel (builder: Builder): void {
|
|||||||
component: view.component.ValueFilter
|
component: view.component.ValueFilter
|
||||||
})
|
})
|
||||||
|
|
||||||
|
builder.mixin(core.class.TypeHyperlink, core.class.Class, view.mixin.AttributeFilter, {
|
||||||
|
component: view.component.ValueFilter
|
||||||
|
})
|
||||||
|
|
||||||
builder.mixin(core.class.TypeBoolean, core.class.Class, view.mixin.AttributeFilter, {
|
builder.mixin(core.class.TypeBoolean, core.class.Class, view.mixin.AttributeFilter, {
|
||||||
component: view.component.ValueFilter
|
component: view.component.ValueFilter
|
||||||
})
|
})
|
||||||
|
@ -43,6 +43,7 @@ export default mergeIds(viewId, view, {
|
|||||||
StringEditor: '' as AnyComponent,
|
StringEditor: '' as AnyComponent,
|
||||||
StringEditorPopup: '' as AnyComponent,
|
StringEditorPopup: '' as AnyComponent,
|
||||||
StringPresenter: '' as AnyComponent,
|
StringPresenter: '' as AnyComponent,
|
||||||
|
HyperlinkPresenter: '' as AnyComponent,
|
||||||
IntlStringPresenter: '' as AnyComponent,
|
IntlStringPresenter: '' as AnyComponent,
|
||||||
NumberEditor: '' as AnyComponent,
|
NumberEditor: '' as AnyComponent,
|
||||||
NumberPresenter: '' as AnyComponent,
|
NumberPresenter: '' as AnyComponent,
|
||||||
|
@ -36,6 +36,11 @@ export type Timestamp = number
|
|||||||
*/
|
*/
|
||||||
export type Markup = string
|
export type Markup = string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export type Hyperlink = string
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
@ -228,6 +233,11 @@ export interface EnumOf extends Type<string> {
|
|||||||
of: Ref<Enum>
|
of: Ref<Enum>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export interface TypeHyperlink extends Type<Hyperlink> {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
|
@ -27,6 +27,7 @@ import type {
|
|||||||
Enum,
|
Enum,
|
||||||
EnumOf,
|
EnumOf,
|
||||||
FullTextData,
|
FullTextData,
|
||||||
|
Hyperlink,
|
||||||
Interface,
|
Interface,
|
||||||
Obj,
|
Obj,
|
||||||
PluginConfiguration,
|
PluginConfiguration,
|
||||||
@ -78,6 +79,7 @@ export default plugin(coreId, {
|
|||||||
Type: '' as Ref<Class<Type<any>>>,
|
Type: '' as Ref<Class<Type<any>>>,
|
||||||
TypeString: '' as Ref<Class<Type<string>>>,
|
TypeString: '' as Ref<Class<Type<string>>>,
|
||||||
TypeIntlString: '' as Ref<Class<Type<IntlString>>>,
|
TypeIntlString: '' as Ref<Class<Type<IntlString>>>,
|
||||||
|
TypeHyperlink: '' as Ref<Class<Type<Hyperlink>>>,
|
||||||
TypeNumber: '' as Ref<Class<Type<number>>>,
|
TypeNumber: '' as Ref<Class<Type<number>>>,
|
||||||
TypeMarkup: '' as Ref<Class<Type<string>>>,
|
TypeMarkup: '' as Ref<Class<Type<string>>>,
|
||||||
TypeBoolean: '' as Ref<Class<Type<boolean>>>,
|
TypeBoolean: '' as Ref<Class<Type<boolean>>>,
|
||||||
@ -131,6 +133,7 @@ export default plugin(coreId, {
|
|||||||
Array: '' as IntlString,
|
Array: '' as IntlString,
|
||||||
Name: '' as IntlString,
|
Name: '' as IntlString,
|
||||||
Enum: '' as IntlString,
|
Enum: '' as IntlString,
|
||||||
Description: '' as IntlString
|
Description: '' as IntlString,
|
||||||
|
Hyperlink: '' as IntlString
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
"Collection": "Collection",
|
"Collection": "Collection",
|
||||||
"Array": "Array",
|
"Array": "Array",
|
||||||
"Enum": "Enum",
|
"Enum": "Enum",
|
||||||
"Members": "Members"
|
"Members": "Members",
|
||||||
|
"Hyperlink": "URL"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
"Collection": "Коллекция",
|
"Collection": "Коллекция",
|
||||||
"Array": "Массив",
|
"Array": "Массив",
|
||||||
"Enum": "Справочник",
|
"Enum": "Справочник",
|
||||||
"Members": "Участники"
|
"Members": "Участники",
|
||||||
|
"Hyperlink": "URL"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,7 @@ import core, {
|
|||||||
Enum,
|
Enum,
|
||||||
EnumOf,
|
EnumOf,
|
||||||
generateId,
|
generateId,
|
||||||
|
Hyperlink,
|
||||||
IndexKind,
|
IndexKind,
|
||||||
Interface,
|
Interface,
|
||||||
Markup,
|
Markup,
|
||||||
@ -354,6 +355,13 @@ export function TypeString (): Type<string> {
|
|||||||
return { _class: core.class.TypeString, label: core.string.String }
|
return { _class: core.class.TypeString, label: core.string.String }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export function TypeHyperlink (): Type<Hyperlink> {
|
||||||
|
return { _class: core.class.TypeHyperlink, label: core.string.Hyperlink }
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
|
@ -518,6 +518,7 @@ input.search {
|
|||||||
.max-h-60 { max-height: 15rem; }
|
.max-h-60 { max-height: 15rem; }
|
||||||
.max-w-30 { max-width: 7.5rem; }
|
.max-w-30 { max-width: 7.5rem; }
|
||||||
.max-w-60 { max-width: 15rem; }
|
.max-w-60 { max-width: 15rem; }
|
||||||
|
.max-w-80 { max-width: 20rem; }
|
||||||
.max-w-240 { max-width: 60rem; }
|
.max-w-240 { max-width: 60rem; }
|
||||||
.clear-mins {
|
.clear-mins {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
189
packages/ui/src/components/ScrollerBar.svelte
Normal file
189
packages/ui/src/components/ScrollerBar.svelte
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
<!--
|
||||||
|
// Copyright © 2020, 2021 Anticrm Platform Contributors.
|
||||||
|
// Copyright © 2021 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 { onDestroy, onMount } from 'svelte'
|
||||||
|
|
||||||
|
export let scroller: HTMLElement
|
||||||
|
export let gap: 'none' | 'small' | 'big' = 'small'
|
||||||
|
|
||||||
|
let divBar: HTMLElement
|
||||||
|
let isScrolling: boolean = false
|
||||||
|
let dX: number
|
||||||
|
let timer: number
|
||||||
|
|
||||||
|
let maskLeft: boolean = false
|
||||||
|
let maskRight: boolean = false
|
||||||
|
let mask: 'left' | 'right' | 'both' | 'none' = 'none'
|
||||||
|
let stepStyle: string
|
||||||
|
$: stepStyle = gap === 'small' ? 'gap-1' : gap === 'big' ? 'gap-2' : ''
|
||||||
|
|
||||||
|
const checkBar = (): void => {
|
||||||
|
if (divBar && scroller) {
|
||||||
|
const trackW = scroller.clientWidth
|
||||||
|
const scrollW = scroller.scrollWidth
|
||||||
|
const proc = scrollW / trackW
|
||||||
|
divBar.style.width = scroller.clientWidth / proc + 'px'
|
||||||
|
divBar.style.left = scroller.scrollLeft / proc + 'px'
|
||||||
|
if (mask === 'none') divBar.style.visibility = 'hidden'
|
||||||
|
else {
|
||||||
|
divBar.style.visibility = 'visible'
|
||||||
|
if (divBar) {
|
||||||
|
if (timer) {
|
||||||
|
clearTimeout(timer)
|
||||||
|
divBar.style.opacity = '1'
|
||||||
|
}
|
||||||
|
timer = setTimeout(() => {
|
||||||
|
if (divBar) divBar.style.opacity = '0'
|
||||||
|
}, 2000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (scroller.clientWidth >= scroller.scrollWidth) divBar.style.visibility = 'hidden'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onScroll = (event: MouseEvent): void => {
|
||||||
|
if (isScrolling && divBar && scroller) {
|
||||||
|
const rectScroll = scroller.getBoundingClientRect()
|
||||||
|
let X = event.clientX - dX
|
||||||
|
if (X < rectScroll.left) X = rectScroll.left
|
||||||
|
if (X > rectScroll.right - divBar.clientWidth) X = rectScroll.right - divBar.clientWidth
|
||||||
|
divBar.style.left = X - rectScroll.x + 'px'
|
||||||
|
const leftBar = X - rectScroll.x
|
||||||
|
const widthScroll = rectScroll.width - divBar.clientWidth
|
||||||
|
const procBar = leftBar / widthScroll
|
||||||
|
scroller.scrollLeft = (scroller.scrollWidth - scroller.clientWidth) * procBar
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const onScrollEnd = (event: MouseEvent): void => {
|
||||||
|
const el: HTMLElement = event.currentTarget as HTMLElement
|
||||||
|
if (el && isScrolling) {
|
||||||
|
document.removeEventListener('mousemove', onScroll)
|
||||||
|
document.body.style.userSelect = 'auto'
|
||||||
|
document.body.style.webkitUserSelect = 'auto'
|
||||||
|
}
|
||||||
|
document.removeEventListener('mouseup', onScrollEnd)
|
||||||
|
isScrolling = false
|
||||||
|
}
|
||||||
|
const onScrollStart = (event: MouseEvent): void => {
|
||||||
|
const el: HTMLElement = event.currentTarget as HTMLElement
|
||||||
|
if (el && scroller) {
|
||||||
|
dX = event.clientX - el.getBoundingClientRect().x
|
||||||
|
document.addEventListener('mouseup', onScrollEnd)
|
||||||
|
document.addEventListener('mousemove', onScroll)
|
||||||
|
document.body.style.userSelect = 'none'
|
||||||
|
document.body.style.webkitUserSelect = 'none'
|
||||||
|
isScrolling = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkMask = (): void => {
|
||||||
|
maskLeft = !!(scroller && scroller.scrollLeft > 1)
|
||||||
|
maskRight = !!(scroller && scroller.scrollWidth - scroller.scrollLeft - scroller.clientWidth > 1)
|
||||||
|
if (maskLeft || maskRight) {
|
||||||
|
if (maskLeft && maskRight) mask = 'both'
|
||||||
|
else if (maskLeft) mask = 'left'
|
||||||
|
else if (maskRight) mask = 'right'
|
||||||
|
} else mask = 'none'
|
||||||
|
|
||||||
|
if (!isScrolling) checkBar()
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
if (scroller) {
|
||||||
|
const observer = new IntersectionObserver(() => checkMask(), { root: null, threshold: 0.1 })
|
||||||
|
const tempEl = scroller.querySelector('*') as HTMLElement
|
||||||
|
if (tempEl) observer.observe(tempEl)
|
||||||
|
checkMask()
|
||||||
|
scroller.addEventListener('scroll', checkMask)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
onDestroy(() => {
|
||||||
|
if (scroller) scroller.removeEventListener('scroll', checkMask)
|
||||||
|
})
|
||||||
|
const _resize = (): void => checkMask()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:window on:resize={_resize} />
|
||||||
|
<div class="scrollerbar-container">
|
||||||
|
<div bind:this={scroller} class="antiStatesBar mask-{mask} {stepStyle}">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="bar"
|
||||||
|
class:hovered={isScrolling}
|
||||||
|
bind:this={divBar}
|
||||||
|
on:mousedown={onScrollStart}
|
||||||
|
on:mouseleave={checkMask}
|
||||||
|
/>
|
||||||
|
<div class="track" class:hovered={isScrolling} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.scrollerbar-container {
|
||||||
|
position: relative;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.track {
|
||||||
|
visibility: hidden;
|
||||||
|
position: absolute;
|
||||||
|
bottom: -8px;
|
||||||
|
left: 0;
|
||||||
|
height: 5px;
|
||||||
|
transform-origin: center;
|
||||||
|
transform: scaleX(0);
|
||||||
|
transition: all 0.1s ease-in-out;
|
||||||
|
background-color: var(--scrollbar-track-color);
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar {
|
||||||
|
visibility: hidden;
|
||||||
|
position: absolute;
|
||||||
|
bottom: -8px;
|
||||||
|
left: 0;
|
||||||
|
height: 5px;
|
||||||
|
min-width: 2rem;
|
||||||
|
max-width: calc(100% - 12px);
|
||||||
|
transform-origin: center;
|
||||||
|
transform: scaleY(0.5);
|
||||||
|
background-color: var(--scrollbar-bar-color);
|
||||||
|
border-radius: 0.125rem;
|
||||||
|
opacity: 0;
|
||||||
|
box-shadow: 0 0 1px 1px var(--board-bg-color);
|
||||||
|
cursor: pointer;
|
||||||
|
z-index: 1;
|
||||||
|
transition: all 0.15s;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&.hovered {
|
||||||
|
background-color: var(--scrollbar-bar-hover);
|
||||||
|
transform: scaleY(1);
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
opacity: 1 !important;
|
||||||
|
box-shadow: 0 0 1px black;
|
||||||
|
|
||||||
|
& + .track {
|
||||||
|
visibility: visible;
|
||||||
|
left: 0;
|
||||||
|
transform: scaleY(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.hovered {
|
||||||
|
transition: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -97,6 +97,7 @@ export { default as Submenu } from './components/Submenu.svelte'
|
|||||||
export { default as TimeShiftPicker } from './components/TimeShiftPicker.svelte'
|
export { default as TimeShiftPicker } from './components/TimeShiftPicker.svelte'
|
||||||
export { default as ErrorPresenter } from './components/ErrorPresenter.svelte'
|
export { default as ErrorPresenter } from './components/ErrorPresenter.svelte'
|
||||||
export { default as Scroller } from './components/Scroller.svelte'
|
export { default as Scroller } from './components/Scroller.svelte'
|
||||||
|
export { default as ScrollerBar } from './components/ScrollerBar.svelte'
|
||||||
export { default as TabList } from './components/TabList.svelte'
|
export { default as TabList } from './components/TabList.svelte'
|
||||||
|
|
||||||
export { default as IconAdd } from './components/icons/Add.svelte'
|
export { default as IconAdd } from './components/icons/Add.svelte'
|
||||||
|
@ -0,0 +1,25 @@
|
|||||||
|
<!--
|
||||||
|
// 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 { IndexKind } from '@hcengineering/core'
|
||||||
|
import { TypeHyperlink } from '@hcengineering/model'
|
||||||
|
import { createEventDispatcher, onMount } from 'svelte'
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
dispatch('change', { type: TypeHyperlink(), index: IndexKind.FullText })
|
||||||
|
})
|
||||||
|
</script>
|
@ -39,6 +39,7 @@ import EnumTypeEditor from './components/typeEditors/EnumTypeEditor.svelte'
|
|||||||
import NumberTypeEditor from './components/typeEditors/NumberTypeEditor.svelte'
|
import NumberTypeEditor from './components/typeEditors/NumberTypeEditor.svelte'
|
||||||
import RefEditor from './components/typeEditors/RefEditor.svelte'
|
import RefEditor from './components/typeEditors/RefEditor.svelte'
|
||||||
import StringTypeEditor from './components/typeEditors/StringTypeEditor.svelte'
|
import StringTypeEditor from './components/typeEditors/StringTypeEditor.svelte'
|
||||||
|
import HyperlinkTypeEditor from './components/typeEditors/HyperlinkTypeEditor.svelte'
|
||||||
import WorkspaceSettings from './components/WorkspaceSettings.svelte'
|
import WorkspaceSettings from './components/WorkspaceSettings.svelte'
|
||||||
import setting from './plugin'
|
import setting from './plugin'
|
||||||
|
|
||||||
@ -81,6 +82,7 @@ export default async (): Promise<Resources> => ({
|
|||||||
ManageStatuses,
|
ManageStatuses,
|
||||||
ClassSetting,
|
ClassSetting,
|
||||||
StringTypeEditor,
|
StringTypeEditor,
|
||||||
|
HyperlinkTypeEditor,
|
||||||
BooleanTypeEditor,
|
BooleanTypeEditor,
|
||||||
NumberTypeEditor,
|
NumberTypeEditor,
|
||||||
RefEditor,
|
RefEditor,
|
||||||
|
@ -17,8 +17,8 @@
|
|||||||
import { Ref, SortingOrder } from '@hcengineering/core'
|
import { Ref, SortingOrder } from '@hcengineering/core'
|
||||||
import { createQuery } from '@hcengineering/presentation'
|
import { createQuery } from '@hcengineering/presentation'
|
||||||
import task, { SpaceWithStates, State } from '@hcengineering/task'
|
import task, { SpaceWithStates, State } from '@hcengineering/task'
|
||||||
import { getPlatformColor } from '@hcengineering/ui'
|
import { getPlatformColor, ScrollerBar } from '@hcengineering/ui'
|
||||||
import { createEventDispatcher, onDestroy, onMount } from 'svelte'
|
import { createEventDispatcher } from 'svelte'
|
||||||
import StatesBarElement from './StatesBarElement.svelte'
|
import StatesBarElement from './StatesBarElement.svelte'
|
||||||
import type { StatesBarPosition } from '../..'
|
import type { StatesBarPosition } from '../..'
|
||||||
|
|
||||||
@ -27,18 +27,7 @@
|
|||||||
export let gap: 'none' | 'small' | 'big' = 'small'
|
export let gap: 'none' | 'small' | 'big' = 'small'
|
||||||
|
|
||||||
let states: State[] = []
|
let states: State[] = []
|
||||||
|
|
||||||
let divScroll: HTMLElement
|
let divScroll: HTMLElement
|
||||||
let divBar: HTMLElement
|
|
||||||
let isScrolling: boolean = false
|
|
||||||
let dX: number
|
|
||||||
let timer: number
|
|
||||||
|
|
||||||
let maskLeft: boolean = false
|
|
||||||
let maskRight: boolean = false
|
|
||||||
let mask: 'left' | 'right' | 'both' | 'none' = 'none'
|
|
||||||
let stepStyle: string
|
|
||||||
$: stepStyle = gap === 'small' ? 'gap-1' : gap === 'big' ? 'gap-2' : ''
|
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
@ -56,77 +45,6 @@
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const checkBar = (): void => {
|
|
||||||
if (divBar && divScroll) {
|
|
||||||
const trackW = divScroll.clientWidth
|
|
||||||
const scrollW = divScroll.scrollWidth
|
|
||||||
const proc = scrollW / trackW
|
|
||||||
divBar.style.width = divScroll.clientWidth / proc + 'px'
|
|
||||||
divBar.style.left = divScroll.scrollLeft / proc + 'px'
|
|
||||||
if (mask === 'none') divBar.style.visibility = 'hidden'
|
|
||||||
else {
|
|
||||||
divBar.style.visibility = 'visible'
|
|
||||||
if (divBar) {
|
|
||||||
if (timer) {
|
|
||||||
clearTimeout(timer)
|
|
||||||
divBar.style.opacity = '1'
|
|
||||||
}
|
|
||||||
timer = setTimeout(() => {
|
|
||||||
if (divBar) divBar.style.opacity = '0'
|
|
||||||
}, 2000)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (divScroll.clientWidth >= divScroll.scrollWidth) divBar.style.visibility = 'hidden'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const onScroll = (event: MouseEvent): void => {
|
|
||||||
if (isScrolling && divBar && divScroll) {
|
|
||||||
const rectScroll = divScroll.getBoundingClientRect()
|
|
||||||
let X = event.clientX - dX
|
|
||||||
if (X < rectScroll.left) X = rectScroll.left
|
|
||||||
if (X > rectScroll.right - divBar.clientWidth) X = rectScroll.right - divBar.clientWidth
|
|
||||||
divBar.style.left = X - rectScroll.x + 'px'
|
|
||||||
const leftBar = X - rectScroll.x
|
|
||||||
const widthScroll = rectScroll.width - divBar.clientWidth
|
|
||||||
const procBar = leftBar / widthScroll
|
|
||||||
divScroll.scrollLeft = (divScroll.scrollWidth - divScroll.clientWidth) * procBar
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const onScrollEnd = (event: MouseEvent): void => {
|
|
||||||
const el: HTMLElement = event.currentTarget as HTMLElement
|
|
||||||
if (el && isScrolling) {
|
|
||||||
document.removeEventListener('mousemove', onScroll)
|
|
||||||
document.body.style.userSelect = 'auto'
|
|
||||||
document.body.style.webkitUserSelect = 'auto'
|
|
||||||
}
|
|
||||||
document.removeEventListener('mouseup', onScrollEnd)
|
|
||||||
isScrolling = false
|
|
||||||
}
|
|
||||||
const onScrollStart = (event: MouseEvent): void => {
|
|
||||||
const el: HTMLElement = event.currentTarget as HTMLElement
|
|
||||||
if (el && divScroll) {
|
|
||||||
dX = event.clientX - el.getBoundingClientRect().x
|
|
||||||
document.addEventListener('mouseup', onScrollEnd)
|
|
||||||
document.addEventListener('mousemove', onScroll)
|
|
||||||
document.body.style.userSelect = 'none'
|
|
||||||
document.body.style.webkitUserSelect = 'none'
|
|
||||||
isScrolling = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const checkMask = (): void => {
|
|
||||||
maskLeft = !!(divScroll && divScroll.scrollLeft > 1)
|
|
||||||
maskRight = !!(divScroll && divScroll.scrollWidth - divScroll.scrollLeft - divScroll.clientWidth > 1)
|
|
||||||
if (maskLeft || maskRight) {
|
|
||||||
if (maskLeft && maskRight) mask = 'both'
|
|
||||||
else if (maskLeft) mask = 'left'
|
|
||||||
else if (maskRight) mask = 'right'
|
|
||||||
} else mask = 'none'
|
|
||||||
|
|
||||||
if (!isScrolling) checkBar()
|
|
||||||
}
|
|
||||||
|
|
||||||
const selectItem = (ev: Event, item: State): void => {
|
const selectItem = (ev: Event, item: State): void => {
|
||||||
const el: HTMLElement = ev.currentTarget as HTMLElement
|
const el: HTMLElement = ev.currentTarget as HTMLElement
|
||||||
const rect = el.getBoundingClientRect()
|
const rect = el.getBoundingClientRect()
|
||||||
@ -149,100 +67,18 @@
|
|||||||
else if (n === states.length - 1) return 'end'
|
else if (n === states.length - 1) return 'end'
|
||||||
else return 'middle'
|
else return 'middle'
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
if (divScroll) {
|
|
||||||
const observer = new IntersectionObserver(() => checkMask(), { root: null, threshold: 0.1 })
|
|
||||||
const tempEl = divScroll.querySelector('*') as HTMLElement
|
|
||||||
if (tempEl) observer.observe(tempEl)
|
|
||||||
checkMask()
|
|
||||||
divScroll.addEventListener('scroll', checkMask)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
onDestroy(() => {
|
|
||||||
if (divScroll) divScroll.removeEventListener('scroll', checkMask)
|
|
||||||
})
|
|
||||||
const _resize = (): void => checkMask()
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:window on:resize={_resize} />
|
<ScrollerBar {gap} bind:scroller={divScroll}>
|
||||||
<div class="statesbar-container">
|
{#each states as item, i (item._id)}
|
||||||
<div bind:this={divScroll} class="antiStatesBar mask-{mask} {stepStyle}">
|
<StatesBarElement
|
||||||
{#each states as item, i (item._id)}
|
label={item.title}
|
||||||
<StatesBarElement
|
position={getPosition(i)}
|
||||||
label={item.title}
|
selected={item._id === state}
|
||||||
position={getPosition(i)}
|
color={getPlatformColor(item.color)}
|
||||||
selected={item._id === state}
|
on:click={(ev) => {
|
||||||
color={getPlatformColor(item.color)}
|
if (item._id !== state) selectItem(ev, item)
|
||||||
on:click={(ev) => {
|
}}
|
||||||
if (item._id !== state) selectItem(ev, item)
|
/>
|
||||||
}}
|
{/each}
|
||||||
/>
|
</ScrollerBar>
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="bar"
|
|
||||||
class:hovered={isScrolling}
|
|
||||||
bind:this={divBar}
|
|
||||||
on:mousedown={onScrollStart}
|
|
||||||
on:mouseleave={checkMask}
|
|
||||||
/>
|
|
||||||
<div class="track" class:hovered={isScrolling} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
.statesbar-container {
|
|
||||||
position: relative;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.track {
|
|
||||||
visibility: hidden;
|
|
||||||
position: absolute;
|
|
||||||
bottom: -8px;
|
|
||||||
left: 0;
|
|
||||||
height: 5px;
|
|
||||||
transform-origin: center;
|
|
||||||
transform: scaleX(0);
|
|
||||||
transition: all 0.1s ease-in-out;
|
|
||||||
background-color: var(--scrollbar-track-color);
|
|
||||||
border-radius: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bar {
|
|
||||||
visibility: hidden;
|
|
||||||
position: absolute;
|
|
||||||
bottom: -8px;
|
|
||||||
left: 0;
|
|
||||||
height: 5px;
|
|
||||||
min-width: 2rem;
|
|
||||||
max-width: calc(100% - 12px);
|
|
||||||
transform-origin: center;
|
|
||||||
transform: scaleY(0.5);
|
|
||||||
background-color: var(--scrollbar-bar-color);
|
|
||||||
border-radius: 0.125rem;
|
|
||||||
opacity: 0;
|
|
||||||
box-shadow: 0 0 1px 1px var(--board-bg-color);
|
|
||||||
cursor: pointer;
|
|
||||||
z-index: 1;
|
|
||||||
transition: all 0.15s;
|
|
||||||
|
|
||||||
&:hover,
|
|
||||||
&.hovered {
|
|
||||||
background-color: var(--scrollbar-bar-hover);
|
|
||||||
transform: scaleY(1);
|
|
||||||
border-radius: 0.25rem;
|
|
||||||
opacity: 1 !important;
|
|
||||||
box-shadow: 0 0 1px black;
|
|
||||||
|
|
||||||
& + .track {
|
|
||||||
visibility: visible;
|
|
||||||
left: 0;
|
|
||||||
transform: scaleY(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&.hovered {
|
|
||||||
transition: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
@ -0,0 +1,27 @@
|
|||||||
|
<!--
|
||||||
|
// Copyright © 2020, 2021 Anticrm Platform Contributors.
|
||||||
|
// Copyright © 2021 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 { ScrollerBar } from '@hcengineering/ui'
|
||||||
|
|
||||||
|
export let value: string
|
||||||
|
let divScroll: HTMLElement
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="clear-mins max-w-80">
|
||||||
|
<ScrollerBar bind:scroller={divScroll}>
|
||||||
|
<a href={value} target="_blank" class="select-text">{value}</a>
|
||||||
|
</ScrollerBar>
|
||||||
|
</div>
|
@ -46,6 +46,7 @@ import RolePresenter from './components/RolePresenter.svelte'
|
|||||||
import SpacePresenter from './components/SpacePresenter.svelte'
|
import SpacePresenter from './components/SpacePresenter.svelte'
|
||||||
import StringEditor from './components/StringEditor.svelte'
|
import StringEditor from './components/StringEditor.svelte'
|
||||||
import StringPresenter from './components/StringPresenter.svelte'
|
import StringPresenter from './components/StringPresenter.svelte'
|
||||||
|
import HyperlinkPresenter from './components/HyperlinkPresenter.svelte'
|
||||||
import Table from './components/Table.svelte'
|
import Table from './components/Table.svelte'
|
||||||
import TableBrowser from './components/TableBrowser.svelte'
|
import TableBrowser from './components/TableBrowser.svelte'
|
||||||
import TimestampPresenter from './components/TimestampPresenter.svelte'
|
import TimestampPresenter from './components/TimestampPresenter.svelte'
|
||||||
@ -123,6 +124,7 @@ export default async (): Promise<Resources> => ({
|
|||||||
SpacePresenter,
|
SpacePresenter,
|
||||||
StringEditor,
|
StringEditor,
|
||||||
StringPresenter,
|
StringPresenter,
|
||||||
|
HyperlinkPresenter,
|
||||||
NumberEditor,
|
NumberEditor,
|
||||||
NumberPresenter,
|
NumberPresenter,
|
||||||
BooleanPresenter,
|
BooleanPresenter,
|
||||||
|
Loading…
Reference in New Issue
Block a user