mirror of
https://github.com/hcengineering/platform.git
synced 2025-05-12 10:25:51 +00:00
Uberf 10285 refactor header button (#8655)
Signed-off-by: Anton Alexeyev <alexeyev.anton@gmail.com>
This commit is contained in:
parent
74d1dcc83d
commit
b159c0610a
@ -1018,24 +1018,28 @@ export function createModel (builder: Builder): void {
|
|||||||
},
|
},
|
||||||
override: [recruit.action.CreateGlobalApplication]
|
override: [recruit.action.CreateGlobalApplication]
|
||||||
})
|
})
|
||||||
createAction(builder, {
|
createAction(
|
||||||
action: view.actionImpl.ShowPopup,
|
builder,
|
||||||
actionProps: {
|
{
|
||||||
component: recruit.component.CreateCandidate,
|
action: view.actionImpl.ShowPopup,
|
||||||
element: 'top'
|
actionProps: {
|
||||||
|
component: recruit.component.CreateCandidate,
|
||||||
|
element: 'top'
|
||||||
|
},
|
||||||
|
label: recruit.string.CreateTalent,
|
||||||
|
icon: recruit.icon.Create,
|
||||||
|
keyBinding: ['keyC'],
|
||||||
|
input: 'none',
|
||||||
|
category: recruit.category.Recruit,
|
||||||
|
target: core.class.Doc,
|
||||||
|
context: {
|
||||||
|
mode: ['workbench', 'browser'],
|
||||||
|
application: recruit.app.Recruit,
|
||||||
|
group: 'create'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
label: recruit.string.CreateTalent,
|
recruit.action.CreateTalent
|
||||||
icon: recruit.icon.Create,
|
)
|
||||||
keyBinding: ['keyC'],
|
|
||||||
input: 'none',
|
|
||||||
category: recruit.category.Recruit,
|
|
||||||
target: core.class.Doc,
|
|
||||||
context: {
|
|
||||||
mode: ['workbench', 'browser'],
|
|
||||||
application: recruit.app.Recruit,
|
|
||||||
group: 'create'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
createAction(builder, {
|
createAction(builder, {
|
||||||
action: view.actionImpl.ShowPopup,
|
action: view.actionImpl.ShowPopup,
|
||||||
|
130
packages/ui/src/components/HeaderButton.svelte
Normal file
130
packages/ui/src/components/HeaderButton.svelte
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
<!--
|
||||||
|
// Copyright © 2025 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 { HeaderButtonAction, SelectPopupValueType } from '../types'
|
||||||
|
import { checkPermission, Client, getCurrentAccount, hasAccountRole, TxOperations } from '@hcengineering/core'
|
||||||
|
import { ButtonWithDropdown, Button, Loading, IconAdd, IconDropdown } from '../index'
|
||||||
|
|
||||||
|
export let mainActionId: number | string | null = null
|
||||||
|
export let loading = false
|
||||||
|
export let client: TxOperations & Client
|
||||||
|
export let actions: HeaderButtonAction[] = []
|
||||||
|
export let visibleActions: string[] = []
|
||||||
|
|
||||||
|
let allowedActions: HeaderButtonAction[] = []
|
||||||
|
let items: HeaderButtonAction[] = []
|
||||||
|
let mainAction: HeaderButtonAction | undefined = undefined
|
||||||
|
$: filterVisibleActions(allowedActions, visibleActions)
|
||||||
|
|
||||||
|
function filterVisibleActions (allowed: HeaderButtonAction[], visible: (string | number | null)[]): void {
|
||||||
|
items = allowed.filter((action) => visible.includes(action.id))
|
||||||
|
mainAction = items.find((a) => a.id === mainActionId)
|
||||||
|
if (mainAction === undefined && items.length > 0) {
|
||||||
|
mainAction = items[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function filterAllowedActions (): Promise<SelectPopupValueType[]> {
|
||||||
|
const result: HeaderButtonAction[] = []
|
||||||
|
for (const action of actions) {
|
||||||
|
if (await isActionAllowed(action)) {
|
||||||
|
result.push(action)
|
||||||
|
}
|
||||||
|
action.keyBinding = await action.keyBindingPromise
|
||||||
|
}
|
||||||
|
allowedActions = result
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
async function isActionAllowed (action: HeaderButtonAction): Promise<boolean> {
|
||||||
|
if (action.accountRole === undefined && action.permission === undefined) return true
|
||||||
|
if (action.accountRole !== undefined && hasAccountRole(getCurrentAccount(), action.accountRole)) return true
|
||||||
|
return (
|
||||||
|
action.permission !== undefined && (await checkPermission(client, action.permission.id, action.permission.space))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#await filterAllowedActions()}
|
||||||
|
<Loading shrink />
|
||||||
|
{:then filtered}
|
||||||
|
{#if mainAction !== undefined}
|
||||||
|
{#if loading}
|
||||||
|
<Loading shrink />
|
||||||
|
{:else}
|
||||||
|
<div class="antiNav-subheader">
|
||||||
|
{#if items.length === 1}
|
||||||
|
<Button
|
||||||
|
icon={IconAdd}
|
||||||
|
justify="left"
|
||||||
|
kind="primary"
|
||||||
|
label={mainAction.label}
|
||||||
|
width="100%"
|
||||||
|
on:click={mainAction.callback}
|
||||||
|
showTooltip={{
|
||||||
|
direction: 'bottom',
|
||||||
|
label: mainAction.label,
|
||||||
|
keys: mainAction.keyBinding
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div slot="content" class="draft-circle-container">
|
||||||
|
{#if mainAction.draft === true}
|
||||||
|
<div class="draft-circle" />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
{:else}
|
||||||
|
<ButtonWithDropdown
|
||||||
|
icon={IconAdd}
|
||||||
|
justify={'left'}
|
||||||
|
kind={'primary'}
|
||||||
|
label={mainAction.label}
|
||||||
|
dropdownItems={items}
|
||||||
|
dropdownIcon={IconDropdown}
|
||||||
|
on:dropdown-selected={(ev) => {
|
||||||
|
items.find((a) => a.id === ev.detail)?.callback()
|
||||||
|
}}
|
||||||
|
on:click={mainAction.callback}
|
||||||
|
mainButtonId={mainAction.id !== null ? String(mainAction.id).replaceAll(':', '-') : undefined}
|
||||||
|
showTooltipMain={{
|
||||||
|
direction: 'bottom',
|
||||||
|
label: mainAction.label,
|
||||||
|
keys: mainAction.keyBinding
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div slot="content" class="draft-circle-container">
|
||||||
|
{#if mainAction.draft === true}
|
||||||
|
<div class="draft-circle" />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</ButtonWithDropdown>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
{/await}
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.draft-circle-container {
|
||||||
|
margin-left: auto;
|
||||||
|
padding-right: 12px;
|
||||||
|
}
|
||||||
|
.draft-circle {
|
||||||
|
height: 6px;
|
||||||
|
width: 6px;
|
||||||
|
background-color: var(--primary-bg-color);
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
</style>
|
@ -54,6 +54,7 @@ export { getCurrentLocation, locationToUrl, navigate, location, setLocationStora
|
|||||||
export { default as EditBox } from './components/EditBox.svelte'
|
export { default as EditBox } from './components/EditBox.svelte'
|
||||||
export { default as Label } from './components/Label.svelte'
|
export { default as Label } from './components/Label.svelte'
|
||||||
export { default as Button } from './components/Button.svelte'
|
export { default as Button } from './components/Button.svelte'
|
||||||
|
export { default as HeaderButton } from './components/HeaderButton.svelte'
|
||||||
export { default as ButtonWithDropdown } from './components/ButtonWithDropdown.svelte'
|
export { default as ButtonWithDropdown } from './components/ButtonWithDropdown.svelte'
|
||||||
export { default as ButtonGroup } from './components/ButtonGroup.svelte'
|
export { default as ButtonGroup } from './components/ButtonGroup.svelte'
|
||||||
export { default as Status } from './components/Status.svelte'
|
export { default as Status } from './components/Status.svelte'
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
// 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.
|
||||||
//
|
//
|
||||||
import { type Timestamp } from '@hcengineering/core'
|
import { type AccountRole, type Permission, type Timestamp, type Ref, type TypedSpace } from '@hcengineering/core'
|
||||||
import type {
|
import type {
|
||||||
Asset,
|
Asset,
|
||||||
IntlString,
|
IntlString,
|
||||||
@ -510,6 +510,21 @@ export interface SelectPopupValueType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export interface HeaderButtonAction extends SelectPopupValueType {
|
||||||
|
callback: () => void
|
||||||
|
keyBindingPromise?: Promise<string[] | undefined>
|
||||||
|
keyBinding?: string[] | undefined
|
||||||
|
draft?: boolean
|
||||||
|
accountRole?: AccountRole
|
||||||
|
permission?: {
|
||||||
|
id: Ref<Permission>
|
||||||
|
space: Ref<TypedSpace>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
|
@ -13,17 +13,9 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { AccountRole, Ref, Space, getCurrentAccount, hasAccountRole } from '@hcengineering/core'
|
import { AccountRole, Ref, Space, getCurrentAccount } from '@hcengineering/core'
|
||||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||||
import {
|
import { HeaderButton, showPopup } from '@hcengineering/ui'
|
||||||
Button,
|
|
||||||
ButtonWithDropdown,
|
|
||||||
IconAdd,
|
|
||||||
IconDropdown,
|
|
||||||
Loading,
|
|
||||||
SelectPopupValueType,
|
|
||||||
showPopup
|
|
||||||
} from '@hcengineering/ui'
|
|
||||||
import { openDoc } from '@hcengineering/view-resources'
|
import { openDoc } from '@hcengineering/view-resources'
|
||||||
import { Analytics } from '@hcengineering/analytics'
|
import { Analytics } from '@hcengineering/analytics'
|
||||||
import { DocumentEvents } from '@hcengineering/document'
|
import { DocumentEvents } from '@hcengineering/document'
|
||||||
@ -71,64 +63,37 @@
|
|||||||
showPopup(CreateTeamspace, {}, 'top')
|
showPopup(CreateTeamspace, {}, 'top')
|
||||||
}
|
}
|
||||||
|
|
||||||
async function dropdownItemSelected (res?: SelectPopupValueType['id']): Promise<void> {
|
let mainActionId: string | undefined = undefined
|
||||||
if (res === document.string.CreateDocument) {
|
let visibleActions: string[] = []
|
||||||
await newDocument()
|
function updateActions (teamspace: boolean): void {
|
||||||
} else if (res === document.string.CreateTeamspace) {
|
mainActionId = document.string.CreateDocument
|
||||||
await newTeamspace()
|
if (teamspace) {
|
||||||
|
visibleActions = [document.string.CreateTeamspace, document.string.CreateDocument]
|
||||||
|
} else {
|
||||||
|
visibleActions = [document.string.CreateTeamspace]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const dropdownItems = hasAccountRole(me, AccountRole.User)
|
$: updateActions(hasTeamspace)
|
||||||
? [
|
|
||||||
{ id: document.string.CreateDocument, label: document.string.CreateDocument },
|
|
||||||
{ id: document.string.CreateTeamspace, label: document.string.CreateTeamspace }
|
|
||||||
]
|
|
||||||
: [{ id: document.string.CreateDocument, label: document.string.CreateDocument }]
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if loading}
|
<HeaderButton
|
||||||
<Loading shrink />
|
{loading}
|
||||||
{:else if hasAccountRole(getCurrentAccount(), AccountRole.User) || hasTeamspace}
|
{client}
|
||||||
<div class="antiNav-subheader">
|
{mainActionId}
|
||||||
{#if hasAccountRole(getCurrentAccount(), AccountRole.User)}
|
{visibleActions}
|
||||||
{#if hasTeamspace}
|
actions={[
|
||||||
<ButtonWithDropdown
|
{
|
||||||
icon={IconAdd}
|
id: document.string.CreateTeamspace,
|
||||||
justify={'left'}
|
label: document.string.CreateTeamspace,
|
||||||
kind={'primary'}
|
accountRole: AccountRole.Maintainer,
|
||||||
label={document.string.CreateDocument}
|
callback: newTeamspace
|
||||||
on:click={newDocument}
|
},
|
||||||
mainButtonId={'new-document'}
|
{
|
||||||
dropdownIcon={IconDropdown}
|
id: document.string.CreateDocument,
|
||||||
{dropdownItems}
|
label: document.string.CreateDocument,
|
||||||
on:dropdown-selected={(ev) => {
|
accountRole: AccountRole.User,
|
||||||
void dropdownItemSelected(ev.detail)
|
callback: newDocument
|
||||||
}}
|
}
|
||||||
/>
|
]}
|
||||||
{:else}
|
/>
|
||||||
<Button
|
|
||||||
id={'new-teamspace'}
|
|
||||||
icon={IconAdd}
|
|
||||||
label={document.string.CreateTeamspace}
|
|
||||||
justify={'left'}
|
|
||||||
width={'100%'}
|
|
||||||
kind={'primary'}
|
|
||||||
gap={'large'}
|
|
||||||
on:click={newTeamspace}
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
{:else if hasTeamspace}
|
|
||||||
<Button
|
|
||||||
id={'new-document'}
|
|
||||||
icon={IconAdd}
|
|
||||||
label={document.string.CreateDocument}
|
|
||||||
justify={'left'}
|
|
||||||
width={'100%'}
|
|
||||||
kind={'primary'}
|
|
||||||
gap={'large'}
|
|
||||||
on:click={newDocument}
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
|
@ -13,15 +13,14 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { AccountRole, Ref, getCurrentAccount, hasAccountRole } from '@hcengineering/core'
|
import { AccountRole, Ref, getCurrentAccount } from '@hcengineering/core'
|
||||||
import { type Drive, DriveEvents } from '@hcengineering/drive'
|
import { type Drive } from '@hcengineering/drive'
|
||||||
import { createQuery } from '@hcengineering/presentation'
|
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||||
import { Button, ButtonWithDropdown, IconAdd, IconDropdown, Loading, SelectPopupValueType } from '@hcengineering/ui'
|
import { HeaderButton } from '@hcengineering/ui'
|
||||||
|
|
||||||
import drive from '../plugin'
|
import drive from '../plugin'
|
||||||
import { getFolderIdFromFragment } from '../navigation'
|
import { getFolderIdFromFragment } from '../navigation'
|
||||||
import { showCreateDrivePopup, showCreateFolderPopup, uploadFilesToDrivePopup } from '../utils'
|
import { showCreateDrivePopup, showCreateFolderPopup, uploadFilesToDrivePopup } from '../utils'
|
||||||
import { Analytics } from '@hcengineering/analytics'
|
|
||||||
|
|
||||||
export let currentSpace: Ref<Drive> | undefined
|
export let currentSpace: Ref<Drive> | undefined
|
||||||
export let currentFragment: string | undefined
|
export let currentFragment: string | undefined
|
||||||
@ -29,6 +28,7 @@
|
|||||||
const me = getCurrentAccount()
|
const me = getCurrentAccount()
|
||||||
|
|
||||||
const query = createQuery()
|
const query = createQuery()
|
||||||
|
const client = getClient()
|
||||||
|
|
||||||
let loading = true
|
let loading = true
|
||||||
let hasDrive = false
|
let hasDrive = false
|
||||||
@ -44,16 +44,6 @@
|
|||||||
|
|
||||||
$: parent = getFolderIdFromFragment(currentFragment ?? '') ?? drive.ids.Root
|
$: parent = getFolderIdFromFragment(currentFragment ?? '') ?? drive.ids.Root
|
||||||
|
|
||||||
async function handleDropdownItemSelected (res?: SelectPopupValueType['id']): Promise<void> {
|
|
||||||
if (res === drive.string.CreateDrive) {
|
|
||||||
await handleCreateDrive()
|
|
||||||
} else if (res === drive.string.CreateFolder) {
|
|
||||||
await handleCreateFolder()
|
|
||||||
} else if (res === drive.string.UploadFile) {
|
|
||||||
await handleUploadFile()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleCreateDrive (): Promise<void> {
|
async function handleCreateDrive (): Promise<void> {
|
||||||
await showCreateDrivePopup()
|
await showCreateDrivePopup()
|
||||||
}
|
}
|
||||||
@ -68,47 +58,42 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const dropdownItems = hasAccountRole(me, AccountRole.User)
|
let visibleActions: string[] = []
|
||||||
? [
|
function updateActions (hasSpace: boolean): void {
|
||||||
{ id: drive.string.CreateDrive, label: drive.string.CreateDrive, icon: drive.icon.Drive },
|
if (hasSpace) {
|
||||||
{ id: drive.string.CreateFolder, label: drive.string.CreateFolder, icon: drive.icon.Folder },
|
visibleActions = [drive.string.CreateDrive, drive.string.CreateFolder, drive.string.UploadFile]
|
||||||
{ id: drive.string.UploadFile, label: drive.string.UploadFile, icon: drive.icon.File }
|
} else {
|
||||||
]
|
visibleActions = [drive.string.CreateDrive]
|
||||||
: [
|
}
|
||||||
{ id: drive.string.CreateFolder, label: drive.string.CreateFolder, icon: drive.icon.Folder },
|
}
|
||||||
{ id: drive.string.UploadFile, label: drive.string.UploadFile, icon: drive.icon.File }
|
|
||||||
]
|
$: updateActions(hasDrive)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if loading}
|
<HeaderButton
|
||||||
<Loading shrink />
|
{loading}
|
||||||
{:else}
|
{client}
|
||||||
<div class="antiNav-subheader">
|
mainActionId={drive.string.UploadFile}
|
||||||
{#if hasDrive}
|
{visibleActions}
|
||||||
<ButtonWithDropdown
|
actions={[
|
||||||
icon={IconAdd}
|
{
|
||||||
justify={'left'}
|
id: drive.string.CreateDrive,
|
||||||
kind={'primary'}
|
label: drive.string.CreateDrive,
|
||||||
label={drive.string.UploadFile}
|
icon: drive.icon.Drive,
|
||||||
mainButtonId={'new-document'}
|
accountRole: AccountRole.Maintainer,
|
||||||
dropdownIcon={IconDropdown}
|
callback: handleCreateDrive
|
||||||
{dropdownItems}
|
},
|
||||||
disabled={currentSpace === undefined}
|
{
|
||||||
on:click={handleUploadFile}
|
id: drive.string.CreateFolder,
|
||||||
on:dropdown-selected={(ev) => {
|
label: drive.string.CreateFolder,
|
||||||
void handleDropdownItemSelected(ev.detail)
|
icon: drive.icon.Folder,
|
||||||
}}
|
callback: handleCreateFolder
|
||||||
/>
|
},
|
||||||
{:else}
|
{
|
||||||
<Button
|
id: drive.string.UploadFile,
|
||||||
icon={IconAdd}
|
label: drive.string.UploadFile,
|
||||||
label={drive.string.CreateDrive}
|
icon: drive.icon.File,
|
||||||
justify={'left'}
|
callback: handleUploadFile
|
||||||
width={'100%'}
|
}
|
||||||
kind={'primary'}
|
]}
|
||||||
gap={'large'}
|
/>
|
||||||
on:click={handleCreateDrive}
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
|
@ -13,26 +13,29 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Button, showPopup, IconAdd } from '@hcengineering/ui'
|
import { showPopup, HeaderButton } from '@hcengineering/ui'
|
||||||
import lead from '../plugin'
|
import lead from '../plugin'
|
||||||
import CreateCustomer from './CreateCustomer.svelte'
|
import CreateCustomer from './CreateCustomer.svelte'
|
||||||
import { AccountRole, getCurrentAccount, hasAccountRole } from '@hcengineering/core'
|
import { AccountRole } from '@hcengineering/core'
|
||||||
|
import { getClient } from '@hcengineering/presentation'
|
||||||
|
|
||||||
|
const client = getClient()
|
||||||
|
|
||||||
async function newIssue (): Promise<void> {
|
async function newIssue (): Promise<void> {
|
||||||
showPopup(CreateCustomer, {}, 'top')
|
showPopup(CreateCustomer, {}, 'top')
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if hasAccountRole(getCurrentAccount(), AccountRole.User)}
|
<HeaderButton
|
||||||
<div class="antiNav-subheader">
|
{client}
|
||||||
<Button
|
mainActionId={lead.string.CreateCustomerLabel}
|
||||||
icon={IconAdd}
|
visibleActions={[lead.string.CreateCustomerLabel]}
|
||||||
label={lead.string.CreateCustomerLabel}
|
actions={[
|
||||||
justify={'left'}
|
{
|
||||||
width={'100%'}
|
id: lead.string.CreateCustomerLabel,
|
||||||
kind={'primary'}
|
label: lead.string.CreateCustomerLabel,
|
||||||
gap={'large'}
|
accountRole: AccountRole.User,
|
||||||
on:click={newIssue}
|
callback: newIssue
|
||||||
/>
|
}
|
||||||
</div>
|
]}
|
||||||
{/if}
|
/>
|
||||||
|
@ -14,17 +14,23 @@
|
|||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Analytics } from '@hcengineering/analytics'
|
import { Analytics } from '@hcengineering/analytics'
|
||||||
import { AccountRole, getCurrentAccount, hasAccountRole } from '@hcengineering/core'
|
import { AccountRole } from '@hcengineering/core'
|
||||||
import { MultipleDraftController } from '@hcengineering/presentation'
|
import { getClient, MultipleDraftController } from '@hcengineering/presentation'
|
||||||
import { RecruitEvents } from '@hcengineering/recruit'
|
import { RecruitEvents } from '@hcengineering/recruit'
|
||||||
import { Button, IconAdd, showPopup } from '@hcengineering/ui'
|
import { HeaderButton, showPopup } from '@hcengineering/ui'
|
||||||
import { onDestroy } from 'svelte'
|
import { onDestroy } from 'svelte'
|
||||||
import recruit from '../plugin'
|
import recruit from '../plugin'
|
||||||
import CreateCandidate from './CreateCandidate.svelte'
|
import CreateCandidate from './CreateCandidate.svelte'
|
||||||
|
import view from '@hcengineering/view'
|
||||||
|
|
||||||
let draftExists = false
|
let draftExists = false
|
||||||
|
|
||||||
|
const client = getClient()
|
||||||
const draftController = new MultipleDraftController(recruit.mixin.Candidate)
|
const draftController = new MultipleDraftController(recruit.mixin.Candidate)
|
||||||
|
const newRecruitKeyBindingPromise = client
|
||||||
|
.findOne(view.class.Action, { _id: recruit.action.CreateTalent })
|
||||||
|
.then((p) => p?.keyBinding)
|
||||||
|
|
||||||
onDestroy(
|
onDestroy(
|
||||||
draftController.hasNext((res) => {
|
draftController.hasNext((res) => {
|
||||||
draftExists = res
|
draftExists = res
|
||||||
@ -35,37 +41,36 @@
|
|||||||
showPopup(CreateCandidate, { shouldSaveDraft: true }, 'top')
|
showPopup(CreateCandidate, { shouldSaveDraft: true }, 'top')
|
||||||
Analytics.handleEvent(RecruitEvents.NewTalentButtonClicked)
|
Analytics.handleEvent(RecruitEvents.NewTalentButtonClicked)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mainActionId: string | undefined = undefined
|
||||||
|
let visibleActions: string[] = []
|
||||||
|
function updateActions (draft: boolean): void {
|
||||||
|
mainActionId = draft ? recruit.string.ResumeDraft : recruit.string.CreateTalent
|
||||||
|
visibleActions = [mainActionId]
|
||||||
|
}
|
||||||
|
|
||||||
|
$: updateActions(draftExists)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if hasAccountRole(getCurrentAccount(), AccountRole.User)}
|
<HeaderButton
|
||||||
<div class="antiNav-subheader">
|
{client}
|
||||||
<Button
|
{mainActionId}
|
||||||
icon={IconAdd}
|
{visibleActions}
|
||||||
label={draftExists ? recruit.string.ResumeDraft : recruit.string.CreateTalent}
|
actions={[
|
||||||
justify={'left'}
|
{
|
||||||
kind={'primary'}
|
id: recruit.string.CreateTalent,
|
||||||
width={'100%'}
|
label: recruit.string.CreateTalent,
|
||||||
gap={'large'}
|
accountRole: AccountRole.User,
|
||||||
on:click={newCandidate}
|
keyBindingPromise: newRecruitKeyBindingPromise,
|
||||||
>
|
callback: newCandidate
|
||||||
<div slot="content" class="draft-circle-container">
|
},
|
||||||
{#if draftExists}
|
{
|
||||||
<div class="draft-circle" />
|
id: recruit.string.ResumeDraft,
|
||||||
{/if}
|
label: recruit.string.ResumeDraft,
|
||||||
</div>
|
draft: true,
|
||||||
</Button>
|
accountRole: AccountRole.User,
|
||||||
</div>
|
keyBindingPromise: newRecruitKeyBindingPromise,
|
||||||
{/if}
|
callback: newCandidate
|
||||||
|
}
|
||||||
<style lang="scss">
|
]}
|
||||||
.draft-circle-container {
|
/>
|
||||||
margin-left: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.draft-circle {
|
|
||||||
height: 6px;
|
|
||||||
width: 6px;
|
|
||||||
background-color: var(--primary-bg-color);
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
@ -45,7 +45,8 @@
|
|||||||
"@hcengineering/task": "^0.6.20",
|
"@hcengineering/task": "^0.6.20",
|
||||||
"@hcengineering/calendar": "^0.6.24",
|
"@hcengineering/calendar": "^0.6.24",
|
||||||
"@hcengineering/ui": "^0.6.15",
|
"@hcengineering/ui": "^0.6.15",
|
||||||
"@hcengineering/tags": "^0.6.16"
|
"@hcengineering/tags": "^0.6.16",
|
||||||
|
"@hcengineering/view": "^0.6.13"
|
||||||
},
|
},
|
||||||
"repository": "https://github.com/hcengineering/platform",
|
"repository": "https://github.com/hcengineering/platform",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
|
@ -19,6 +19,7 @@ import { plugin } from '@hcengineering/platform'
|
|||||||
import type { ProjectTypeDescriptor, TaskType } from '@hcengineering/task'
|
import type { ProjectTypeDescriptor, TaskType } from '@hcengineering/task'
|
||||||
import { AnyComponent, Location, ResolvedLocation } from '@hcengineering/ui'
|
import { AnyComponent, Location, ResolvedLocation } from '@hcengineering/ui'
|
||||||
import type { Applicant, ApplicantMatch, Candidate, Opinion, Review, Vacancy, VacancyList } from './types'
|
import type { Applicant, ApplicantMatch, Candidate, Opinion, Review, Vacancy, VacancyList } from './types'
|
||||||
|
import { Action } from '@hcengineering/view'
|
||||||
|
|
||||||
export * from './types'
|
export * from './types'
|
||||||
export * from './analytics'
|
export * from './analytics'
|
||||||
@ -45,6 +46,9 @@ const recruit = plugin(recruitId, {
|
|||||||
descriptors: {
|
descriptors: {
|
||||||
VacancyType: '' as Ref<ProjectTypeDescriptor>
|
VacancyType: '' as Ref<ProjectTypeDescriptor>
|
||||||
},
|
},
|
||||||
|
action: {
|
||||||
|
CreateTalent: '' as Ref<Action<Doc, any>>
|
||||||
|
},
|
||||||
mixin: {
|
mixin: {
|
||||||
Candidate: '' as Ref<Mixin<Candidate>>,
|
Candidate: '' as Ref<Mixin<Candidate>>,
|
||||||
VacancyList: '' as Ref<Mixin<VacancyList>>,
|
VacancyList: '' as Ref<Mixin<VacancyList>>,
|
||||||
|
@ -14,17 +14,10 @@
|
|||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Analytics } from '@hcengineering/analytics'
|
import { Analytics } from '@hcengineering/analytics'
|
||||||
import core, {
|
import core, { AccountRole, Ref, Space } from '@hcengineering/core'
|
||||||
AccountRole,
|
|
||||||
Ref,
|
|
||||||
Space,
|
|
||||||
getCurrentAccount,
|
|
||||||
hasAccountRole,
|
|
||||||
checkPermission
|
|
||||||
} from '@hcengineering/core'
|
|
||||||
import { MultipleDraftController, createQuery, getClient } from '@hcengineering/presentation'
|
import { MultipleDraftController, createQuery, getClient } from '@hcengineering/presentation'
|
||||||
import { TrackerEvents } from '@hcengineering/tracker'
|
import { TrackerEvents } from '@hcengineering/tracker'
|
||||||
import { Button, ButtonWithDropdown, IconAdd, IconDropdown, SelectPopupValueType, showPopup } from '@hcengineering/ui'
|
import { HeaderButton, showPopup } from '@hcengineering/ui'
|
||||||
import view from '@hcengineering/view'
|
import view from '@hcengineering/view'
|
||||||
|
|
||||||
import { onDestroy } from 'svelte'
|
import { onDestroy } from 'svelte'
|
||||||
@ -34,15 +27,35 @@
|
|||||||
export let currentSpace: Ref<Space> | undefined
|
export let currentSpace: Ref<Space> | undefined
|
||||||
|
|
||||||
let closed = true
|
let closed = true
|
||||||
|
|
||||||
let draftExists = false
|
let draftExists = false
|
||||||
|
let projectExists = false
|
||||||
|
let loading = true
|
||||||
|
|
||||||
|
const query = createQuery()
|
||||||
|
const client = getClient()
|
||||||
const draftController = new MultipleDraftController(tracker.ids.IssueDraft)
|
const draftController = new MultipleDraftController(tracker.ids.IssueDraft)
|
||||||
|
const newIssueKeyBindingPromise = client
|
||||||
|
.findOne(view.class.Action, { _id: tracker.action.NewIssue })
|
||||||
|
.then((p) => p?.keyBinding)
|
||||||
|
|
||||||
onDestroy(
|
onDestroy(
|
||||||
draftController.hasNext((res) => {
|
draftController.hasNext((res) => {
|
||||||
draftExists = res
|
draftExists = res
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
query.query(tracker.class.Project, {}, (res) => {
|
||||||
|
projectExists = res.length > 0
|
||||||
|
loading = false
|
||||||
|
})
|
||||||
|
|
||||||
|
function newProject (): void {
|
||||||
|
closed = false
|
||||||
|
showPopup(tracker.component.CreateProject, {}, 'top', () => {
|
||||||
|
closed = true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
function newIssue (): void {
|
function newIssue (): void {
|
||||||
closed = false
|
closed = false
|
||||||
Analytics.handleEvent(TrackerEvents.NewIssueButtonClicked)
|
Analytics.handleEvent(TrackerEvents.NewIssueButtonClicked)
|
||||||
@ -51,115 +64,54 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const query = createQuery()
|
let mainActionId: string | undefined = undefined
|
||||||
|
let visibleActions: string[] = []
|
||||||
let projectExists = false
|
function updateActions (draft: boolean, project: boolean, closed: boolean): void {
|
||||||
|
mainActionId = draft || !closed ? tracker.string.ResumeDraft : tracker.string.NewIssue
|
||||||
query.query(tracker.class.Project, {}, (res) => {
|
if (project) {
|
||||||
projectExists = res.length > 0
|
visibleActions = [tracker.string.CreateProject, mainActionId, tracker.string.Import]
|
||||||
})
|
|
||||||
|
|
||||||
const client = getClient()
|
|
||||||
$: label = draftExists || !closed ? tracker.string.ResumeDraft : tracker.string.NewIssue
|
|
||||||
|
|
||||||
let keys: string[] | undefined = undefined
|
|
||||||
let canCreateProject = hasAccountRole(getCurrentAccount(), AccountRole.Maintainer)
|
|
||||||
|
|
||||||
function dropdownItemSelected (res?: SelectPopupValueType['id']): void {
|
|
||||||
if (res == null) return
|
|
||||||
|
|
||||||
if (res === tracker.string.CreateProject) {
|
|
||||||
closed = false
|
|
||||||
showPopup(tracker.component.CreateProject, {}, 'top', () => {
|
|
||||||
closed = true
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
newIssue()
|
visibleActions = [tracker.string.CreateProject]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void client.findOne(view.class.Action, { _id: tracker.action.NewIssue }).then((p) => (keys = p?.keyBinding))
|
$: updateActions(draftExists, projectExists, closed)
|
||||||
if (!canCreateProject) {
|
|
||||||
void checkPermission(client, core.permission.CreateProject, core.space.Space).then((hasPermission) => {
|
|
||||||
canCreateProject = hasPermission
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function getAvailableActions (): SelectPopupValueType[] {
|
|
||||||
const result = []
|
|
||||||
if (canCreateProject) {
|
|
||||||
result.push({
|
|
||||||
id: tracker.string.CreateProject,
|
|
||||||
label: tracker.string.CreateProject
|
|
||||||
})
|
|
||||||
}
|
|
||||||
result.push({
|
|
||||||
id: tracker.string.NewIssue,
|
|
||||||
label: tracker.string.NewIssue
|
|
||||||
})
|
|
||||||
if (hasAccountRole(getCurrentAccount(), AccountRole.User)) {
|
|
||||||
result.push({
|
|
||||||
id: tracker.string.Import,
|
|
||||||
label: tracker.string.Import
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="antiNav-subheader">
|
<HeaderButton
|
||||||
{#if projectExists}
|
{loading}
|
||||||
<ButtonWithDropdown
|
{client}
|
||||||
icon={IconAdd}
|
{mainActionId}
|
||||||
justify={'left'}
|
{visibleActions}
|
||||||
kind={'primary'}
|
actions={[
|
||||||
{label}
|
{
|
||||||
on:click={newIssue}
|
id: tracker.string.CreateProject,
|
||||||
dropdownItems={getAvailableActions()}
|
label: tracker.string.CreateProject,
|
||||||
dropdownIcon={IconDropdown}
|
accountRole: AccountRole.Maintainer,
|
||||||
on:dropdown-selected={(ev) => {
|
permission: {
|
||||||
dropdownItemSelected(ev.detail)
|
id: core.permission.CreateProject,
|
||||||
}}
|
space: core.space.Space
|
||||||
mainButtonId={'new-issue'}
|
},
|
||||||
showTooltipMain={{
|
callback: newProject
|
||||||
direction: 'bottom',
|
},
|
||||||
label,
|
{
|
||||||
keys
|
id: tracker.string.ResumeDraft,
|
||||||
}}
|
label: tracker.string.ResumeDraft,
|
||||||
>
|
draft: true,
|
||||||
<div slot="content" class="draft-circle-container">
|
keyBindingPromise: newIssueKeyBindingPromise,
|
||||||
{#if draftExists}
|
callback: newIssue
|
||||||
<div class="draft-circle" />
|
},
|
||||||
{/if}
|
{
|
||||||
</div>
|
id: tracker.string.NewIssue,
|
||||||
</ButtonWithDropdown>
|
label: tracker.string.NewIssue,
|
||||||
{:else}
|
keyBindingPromise: newIssueKeyBindingPromise,
|
||||||
<Button
|
callback: newIssue
|
||||||
disabled={!canCreateProject}
|
},
|
||||||
icon={IconAdd}
|
{
|
||||||
justify="left"
|
id: tracker.string.Import,
|
||||||
kind="primary"
|
label: tracker.string.Import,
|
||||||
label={tracker.string.CreateProject}
|
accountRole: AccountRole.User,
|
||||||
width="100%"
|
callback: newIssue
|
||||||
on:click={() => {
|
}
|
||||||
showPopup(tracker.component.CreateProject, {}, 'top', () => {
|
]}
|
||||||
closed = true
|
/>
|
||||||
})
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
.draft-circle-container {
|
|
||||||
margin-left: auto;
|
|
||||||
padding-right: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.draft-circle {
|
|
||||||
height: 6px;
|
|
||||||
width: 6px;
|
|
||||||
background-color: var(--primary-bg-color);
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
@ -18,7 +18,7 @@ export class DocumentsPage extends CommonPage {
|
|||||||
readonly popupMoveDocument: DocumentMovePopup
|
readonly popupMoveDocument: DocumentMovePopup
|
||||||
|
|
||||||
readonly buttonCreateDocument = (): Locator =>
|
readonly buttonCreateDocument = (): Locator =>
|
||||||
this.page.locator('div[data-float="navigator"] button[id="new-document"]')
|
this.page.locator('div[data-float="navigator"] button[id="document-string-CreateDocument"]')
|
||||||
|
|
||||||
readonly buttonDocumentWrapper = (name: string): Locator =>
|
readonly buttonDocumentWrapper = (name: string): Locator =>
|
||||||
this.page.locator(`button.hulyNavItem-container:has-text("${name}")`)
|
this.page.locator(`button.hulyNavItem-container:has-text("${name}")`)
|
||||||
|
@ -10,7 +10,8 @@ export class IssuesPage extends CommonTrackerPage {
|
|||||||
modelSelectorAll = (): Locator => this.page.locator('label[data-id="tab-all"]')
|
modelSelectorAll = (): Locator => this.page.locator('label[data-id="tab-all"]')
|
||||||
issues = (): Locator => this.page.locator('.antiPanel-navigator').locator('text="Issues"')
|
issues = (): Locator => this.page.locator('.antiPanel-navigator').locator('text="Issues"')
|
||||||
subIssues = (): Locator => this.page.locator('button:has-text("Add sub-issue")')
|
subIssues = (): Locator => this.page.locator('button:has-text("Add sub-issue")')
|
||||||
newIssue = (): Locator => this.page.locator('#new-issue')
|
newIssue = (): Locator => this.page.locator('#tracker-string-NewIssue')
|
||||||
|
resumeDraft = (): Locator => this.page.locator('#tracker-string-ResumeDraft')
|
||||||
modelSelectorActive = (): Locator => this.page.locator('label[data-id="tab-active"]')
|
modelSelectorActive = (): Locator => this.page.locator('label[data-id="tab-active"]')
|
||||||
modelSelectorBacklog = (): Locator => this.page.locator('label[data-id="tab-backlog"]')
|
modelSelectorBacklog = (): Locator => this.page.locator('label[data-id="tab-backlog"]')
|
||||||
buttonCreateNewIssue = (): Locator => this.page.locator('button > div', { hasText: 'New issue' })
|
buttonCreateNewIssue = (): Locator => this.page.locator('button > div', { hasText: 'New issue' })
|
||||||
@ -156,7 +157,7 @@ export class IssuesPage extends CommonTrackerPage {
|
|||||||
|
|
||||||
estimationSpan = (): Locator => this.page.locator('.estimation-container >> span').first()
|
estimationSpan = (): Locator => this.page.locator('.estimation-container >> span').first()
|
||||||
okButton = (): Locator => this.page.getByRole('button', { name: 'Ok', exact: true })
|
okButton = (): Locator => this.page.getByRole('button', { name: 'Ok', exact: true })
|
||||||
newIssueButton = (): Locator => this.page.locator('#new-issue')
|
newIssueButton = (): Locator => this.page.locator('#tracker-string-NewIssue')
|
||||||
issueNameInput = (): Locator => this.page.locator('#issue-name >> input')
|
issueNameInput = (): Locator => this.page.locator('#issue-name >> input')
|
||||||
issueDescriptionInput = (): Locator => this.page.locator('#issue-description >> [contenteditable]')
|
issueDescriptionInput = (): Locator => this.page.locator('#issue-description >> [contenteditable]')
|
||||||
statusEditor = (): Locator => this.page.locator('#status-editor')
|
statusEditor = (): Locator => this.page.locator('#status-editor')
|
||||||
@ -232,6 +233,10 @@ export class IssuesPage extends CommonTrackerPage {
|
|||||||
await this.newIssue().click()
|
await this.newIssue().click()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async clickOnResumeDraft (): Promise<void> {
|
||||||
|
await this.resumeDraft().click()
|
||||||
|
}
|
||||||
|
|
||||||
async navigateToMyIssues (): Promise<void> {
|
async navigateToMyIssues (): Promise<void> {
|
||||||
await this.myIssuesButton().click()
|
await this.myIssuesButton().click()
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,7 @@ test.describe('Tracker sub-issues tests', () => {
|
|||||||
await fillIssueForm(page, props)
|
await fillIssueForm(page, props)
|
||||||
await page.keyboard.press('Escape')
|
await page.keyboard.press('Escape')
|
||||||
await page.keyboard.press('Escape')
|
await page.keyboard.press('Escape')
|
||||||
await issuesPage.clickOnNewIssue()
|
await issuesPage.clickOnResumeDraft()
|
||||||
await checkIssueDraft(page, props)
|
await checkIssueDraft(page, props)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -161,7 +161,7 @@ test.describe('Tracker tests', () => {
|
|||||||
await issuesPage.inputTextPlaceholderFill('1')
|
await issuesPage.inputTextPlaceholderFill('1')
|
||||||
await issuesPage.setDueDate('24')
|
await issuesPage.setDueDate('24')
|
||||||
await issuesPage.pressEscapeTwice()
|
await issuesPage.pressEscapeTwice()
|
||||||
await issuesPage.clickOnNewIssue()
|
await issuesPage.clickOnResumeDraft()
|
||||||
await checkIssueDraft(page, {
|
await checkIssueDraft(page, {
|
||||||
name: issueName,
|
name: issueName,
|
||||||
description: issueName,
|
description: issueName,
|
||||||
|
Loading…
Reference in New Issue
Block a user