mirror of
https://github.com/hcengineering/platform.git
synced 2025-05-11 18:01:59 +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,7 +1018,9 @@ export function createModel (builder: Builder): void {
|
||||
},
|
||||
override: [recruit.action.CreateGlobalApplication]
|
||||
})
|
||||
createAction(builder, {
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
action: view.actionImpl.ShowPopup,
|
||||
actionProps: {
|
||||
component: recruit.component.CreateCandidate,
|
||||
@ -1035,7 +1037,9 @@ export function createModel (builder: Builder): void {
|
||||
application: recruit.app.Recruit,
|
||||
group: 'create'
|
||||
}
|
||||
})
|
||||
},
|
||||
recruit.action.CreateTalent
|
||||
)
|
||||
|
||||
createAction(builder, {
|
||||
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 Label } from './components/Label.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 ButtonGroup } from './components/ButtonGroup.svelte'
|
||||
export { default as Status } from './components/Status.svelte'
|
||||
|
@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// 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 {
|
||||
Asset,
|
||||
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
|
||||
*/
|
||||
|
@ -13,17 +13,9 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<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 {
|
||||
Button,
|
||||
ButtonWithDropdown,
|
||||
IconAdd,
|
||||
IconDropdown,
|
||||
Loading,
|
||||
SelectPopupValueType,
|
||||
showPopup
|
||||
} from '@hcengineering/ui'
|
||||
import { HeaderButton, showPopup } from '@hcengineering/ui'
|
||||
import { openDoc } from '@hcengineering/view-resources'
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
import { DocumentEvents } from '@hcengineering/document'
|
||||
@ -71,64 +63,37 @@
|
||||
showPopup(CreateTeamspace, {}, 'top')
|
||||
}
|
||||
|
||||
async function dropdownItemSelected (res?: SelectPopupValueType['id']): Promise<void> {
|
||||
if (res === document.string.CreateDocument) {
|
||||
await newDocument()
|
||||
} else if (res === document.string.CreateTeamspace) {
|
||||
await newTeamspace()
|
||||
let mainActionId: string | undefined = undefined
|
||||
let visibleActions: string[] = []
|
||||
function updateActions (teamspace: boolean): void {
|
||||
mainActionId = document.string.CreateDocument
|
||||
if (teamspace) {
|
||||
visibleActions = [document.string.CreateTeamspace, document.string.CreateDocument]
|
||||
} else {
|
||||
visibleActions = [document.string.CreateTeamspace]
|
||||
}
|
||||
}
|
||||
|
||||
const dropdownItems = hasAccountRole(me, AccountRole.User)
|
||||
? [
|
||||
{ id: document.string.CreateDocument, label: document.string.CreateDocument },
|
||||
{ id: document.string.CreateTeamspace, label: document.string.CreateTeamspace }
|
||||
]
|
||||
: [{ id: document.string.CreateDocument, label: document.string.CreateDocument }]
|
||||
$: updateActions(hasTeamspace)
|
||||
</script>
|
||||
|
||||
{#if loading}
|
||||
<Loading shrink />
|
||||
{:else if hasAccountRole(getCurrentAccount(), AccountRole.User) || hasTeamspace}
|
||||
<div class="antiNav-subheader">
|
||||
{#if hasAccountRole(getCurrentAccount(), AccountRole.User)}
|
||||
{#if hasTeamspace}
|
||||
<ButtonWithDropdown
|
||||
icon={IconAdd}
|
||||
justify={'left'}
|
||||
kind={'primary'}
|
||||
label={document.string.CreateDocument}
|
||||
on:click={newDocument}
|
||||
mainButtonId={'new-document'}
|
||||
dropdownIcon={IconDropdown}
|
||||
{dropdownItems}
|
||||
on:dropdown-selected={(ev) => {
|
||||
void dropdownItemSelected(ev.detail)
|
||||
}}
|
||||
<HeaderButton
|
||||
{loading}
|
||||
{client}
|
||||
{mainActionId}
|
||||
{visibleActions}
|
||||
actions={[
|
||||
{
|
||||
id: document.string.CreateTeamspace,
|
||||
label: document.string.CreateTeamspace,
|
||||
accountRole: AccountRole.Maintainer,
|
||||
callback: newTeamspace
|
||||
},
|
||||
{
|
||||
id: document.string.CreateDocument,
|
||||
label: document.string.CreateDocument,
|
||||
accountRole: AccountRole.User,
|
||||
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.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { AccountRole, Ref, getCurrentAccount, hasAccountRole } from '@hcengineering/core'
|
||||
import { type Drive, DriveEvents } from '@hcengineering/drive'
|
||||
import { createQuery } from '@hcengineering/presentation'
|
||||
import { Button, ButtonWithDropdown, IconAdd, IconDropdown, Loading, SelectPopupValueType } from '@hcengineering/ui'
|
||||
import { AccountRole, Ref, getCurrentAccount } from '@hcengineering/core'
|
||||
import { type Drive } from '@hcengineering/drive'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { HeaderButton } from '@hcengineering/ui'
|
||||
|
||||
import drive from '../plugin'
|
||||
import { getFolderIdFromFragment } from '../navigation'
|
||||
import { showCreateDrivePopup, showCreateFolderPopup, uploadFilesToDrivePopup } from '../utils'
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
|
||||
export let currentSpace: Ref<Drive> | undefined
|
||||
export let currentFragment: string | undefined
|
||||
@ -29,6 +28,7 @@
|
||||
const me = getCurrentAccount()
|
||||
|
||||
const query = createQuery()
|
||||
const client = getClient()
|
||||
|
||||
let loading = true
|
||||
let hasDrive = false
|
||||
@ -44,16 +44,6 @@
|
||||
|
||||
$: 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> {
|
||||
await showCreateDrivePopup()
|
||||
}
|
||||
@ -68,47 +58,42 @@
|
||||
}
|
||||
}
|
||||
|
||||
const dropdownItems = hasAccountRole(me, AccountRole.User)
|
||||
? [
|
||||
{ id: drive.string.CreateDrive, label: drive.string.CreateDrive, icon: drive.icon.Drive },
|
||||
{ id: drive.string.CreateFolder, label: drive.string.CreateFolder, icon: drive.icon.Folder },
|
||||
{ id: drive.string.UploadFile, label: drive.string.UploadFile, icon: drive.icon.File }
|
||||
]
|
||||
: [
|
||||
{ id: drive.string.CreateFolder, label: drive.string.CreateFolder, icon: drive.icon.Folder },
|
||||
{ id: drive.string.UploadFile, label: drive.string.UploadFile, icon: drive.icon.File }
|
||||
]
|
||||
let visibleActions: string[] = []
|
||||
function updateActions (hasSpace: boolean): void {
|
||||
if (hasSpace) {
|
||||
visibleActions = [drive.string.CreateDrive, drive.string.CreateFolder, drive.string.UploadFile]
|
||||
} else {
|
||||
visibleActions = [drive.string.CreateDrive]
|
||||
}
|
||||
}
|
||||
|
||||
$: updateActions(hasDrive)
|
||||
</script>
|
||||
|
||||
{#if loading}
|
||||
<Loading shrink />
|
||||
{:else}
|
||||
<div class="antiNav-subheader">
|
||||
{#if hasDrive}
|
||||
<ButtonWithDropdown
|
||||
icon={IconAdd}
|
||||
justify={'left'}
|
||||
kind={'primary'}
|
||||
label={drive.string.UploadFile}
|
||||
mainButtonId={'new-document'}
|
||||
dropdownIcon={IconDropdown}
|
||||
{dropdownItems}
|
||||
disabled={currentSpace === undefined}
|
||||
on:click={handleUploadFile}
|
||||
on:dropdown-selected={(ev) => {
|
||||
void handleDropdownItemSelected(ev.detail)
|
||||
}}
|
||||
<HeaderButton
|
||||
{loading}
|
||||
{client}
|
||||
mainActionId={drive.string.UploadFile}
|
||||
{visibleActions}
|
||||
actions={[
|
||||
{
|
||||
id: drive.string.CreateDrive,
|
||||
label: drive.string.CreateDrive,
|
||||
icon: drive.icon.Drive,
|
||||
accountRole: AccountRole.Maintainer,
|
||||
callback: handleCreateDrive
|
||||
},
|
||||
{
|
||||
id: drive.string.CreateFolder,
|
||||
label: drive.string.CreateFolder,
|
||||
icon: drive.icon.Folder,
|
||||
callback: handleCreateFolder
|
||||
},
|
||||
{
|
||||
id: drive.string.UploadFile,
|
||||
label: drive.string.UploadFile,
|
||||
icon: drive.icon.File,
|
||||
callback: handleUploadFile
|
||||
}
|
||||
]}
|
||||
/>
|
||||
{:else}
|
||||
<Button
|
||||
icon={IconAdd}
|
||||
label={drive.string.CreateDrive}
|
||||
justify={'left'}
|
||||
width={'100%'}
|
||||
kind={'primary'}
|
||||
gap={'large'}
|
||||
on:click={handleCreateDrive}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
@ -13,26 +13,29 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Button, showPopup, IconAdd } from '@hcengineering/ui'
|
||||
import { showPopup, HeaderButton } from '@hcengineering/ui'
|
||||
import lead from '../plugin'
|
||||
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> {
|
||||
showPopup(CreateCustomer, {}, 'top')
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if hasAccountRole(getCurrentAccount(), AccountRole.User)}
|
||||
<div class="antiNav-subheader">
|
||||
<Button
|
||||
icon={IconAdd}
|
||||
label={lead.string.CreateCustomerLabel}
|
||||
justify={'left'}
|
||||
width={'100%'}
|
||||
kind={'primary'}
|
||||
gap={'large'}
|
||||
on:click={newIssue}
|
||||
<HeaderButton
|
||||
{client}
|
||||
mainActionId={lead.string.CreateCustomerLabel}
|
||||
visibleActions={[lead.string.CreateCustomerLabel]}
|
||||
actions={[
|
||||
{
|
||||
id: lead.string.CreateCustomerLabel,
|
||||
label: lead.string.CreateCustomerLabel,
|
||||
accountRole: AccountRole.User,
|
||||
callback: newIssue
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
@ -14,17 +14,23 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
import { AccountRole, getCurrentAccount, hasAccountRole } from '@hcengineering/core'
|
||||
import { MultipleDraftController } from '@hcengineering/presentation'
|
||||
import { AccountRole } from '@hcengineering/core'
|
||||
import { getClient, MultipleDraftController } from '@hcengineering/presentation'
|
||||
import { RecruitEvents } from '@hcengineering/recruit'
|
||||
import { Button, IconAdd, showPopup } from '@hcengineering/ui'
|
||||
import { HeaderButton, showPopup } from '@hcengineering/ui'
|
||||
import { onDestroy } from 'svelte'
|
||||
import recruit from '../plugin'
|
||||
import CreateCandidate from './CreateCandidate.svelte'
|
||||
import view from '@hcengineering/view'
|
||||
|
||||
let draftExists = false
|
||||
|
||||
const client = getClient()
|
||||
const draftController = new MultipleDraftController(recruit.mixin.Candidate)
|
||||
const newRecruitKeyBindingPromise = client
|
||||
.findOne(view.class.Action, { _id: recruit.action.CreateTalent })
|
||||
.then((p) => p?.keyBinding)
|
||||
|
||||
onDestroy(
|
||||
draftController.hasNext((res) => {
|
||||
draftExists = res
|
||||
@ -35,37 +41,36 @@
|
||||
showPopup(CreateCandidate, { shouldSaveDraft: true }, 'top')
|
||||
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>
|
||||
|
||||
{#if hasAccountRole(getCurrentAccount(), AccountRole.User)}
|
||||
<div class="antiNav-subheader">
|
||||
<Button
|
||||
icon={IconAdd}
|
||||
label={draftExists ? recruit.string.ResumeDraft : recruit.string.CreateTalent}
|
||||
justify={'left'}
|
||||
kind={'primary'}
|
||||
width={'100%'}
|
||||
gap={'large'}
|
||||
on:click={newCandidate}
|
||||
>
|
||||
<div slot="content" class="draft-circle-container">
|
||||
{#if draftExists}
|
||||
<div class="draft-circle" />
|
||||
{/if}
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.draft-circle-container {
|
||||
margin-left: auto;
|
||||
<HeaderButton
|
||||
{client}
|
||||
{mainActionId}
|
||||
{visibleActions}
|
||||
actions={[
|
||||
{
|
||||
id: recruit.string.CreateTalent,
|
||||
label: recruit.string.CreateTalent,
|
||||
accountRole: AccountRole.User,
|
||||
keyBindingPromise: newRecruitKeyBindingPromise,
|
||||
callback: newCandidate
|
||||
},
|
||||
{
|
||||
id: recruit.string.ResumeDraft,
|
||||
label: recruit.string.ResumeDraft,
|
||||
draft: true,
|
||||
accountRole: AccountRole.User,
|
||||
keyBindingPromise: newRecruitKeyBindingPromise,
|
||||
callback: newCandidate
|
||||
}
|
||||
|
||||
.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/calendar": "^0.6.24",
|
||||
"@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",
|
||||
"publishConfig": {
|
||||
|
@ -19,6 +19,7 @@ import { plugin } from '@hcengineering/platform'
|
||||
import type { ProjectTypeDescriptor, TaskType } from '@hcengineering/task'
|
||||
import { AnyComponent, Location, ResolvedLocation } from '@hcengineering/ui'
|
||||
import type { Applicant, ApplicantMatch, Candidate, Opinion, Review, Vacancy, VacancyList } from './types'
|
||||
import { Action } from '@hcengineering/view'
|
||||
|
||||
export * from './types'
|
||||
export * from './analytics'
|
||||
@ -45,6 +46,9 @@ const recruit = plugin(recruitId, {
|
||||
descriptors: {
|
||||
VacancyType: '' as Ref<ProjectTypeDescriptor>
|
||||
},
|
||||
action: {
|
||||
CreateTalent: '' as Ref<Action<Doc, any>>
|
||||
},
|
||||
mixin: {
|
||||
Candidate: '' as Ref<Mixin<Candidate>>,
|
||||
VacancyList: '' as Ref<Mixin<VacancyList>>,
|
||||
|
@ -14,17 +14,10 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
import core, {
|
||||
AccountRole,
|
||||
Ref,
|
||||
Space,
|
||||
getCurrentAccount,
|
||||
hasAccountRole,
|
||||
checkPermission
|
||||
} from '@hcengineering/core'
|
||||
import core, { AccountRole, Ref, Space } from '@hcengineering/core'
|
||||
import { MultipleDraftController, createQuery, getClient } from '@hcengineering/presentation'
|
||||
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 { onDestroy } from 'svelte'
|
||||
@ -34,15 +27,35 @@
|
||||
export let currentSpace: Ref<Space> | undefined
|
||||
|
||||
let closed = true
|
||||
|
||||
let draftExists = false
|
||||
let projectExists = false
|
||||
let loading = true
|
||||
|
||||
const query = createQuery()
|
||||
const client = getClient()
|
||||
const draftController = new MultipleDraftController(tracker.ids.IssueDraft)
|
||||
const newIssueKeyBindingPromise = client
|
||||
.findOne(view.class.Action, { _id: tracker.action.NewIssue })
|
||||
.then((p) => p?.keyBinding)
|
||||
|
||||
onDestroy(
|
||||
draftController.hasNext((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 {
|
||||
closed = false
|
||||
Analytics.handleEvent(TrackerEvents.NewIssueButtonClicked)
|
||||
@ -51,115 +64,54 @@
|
||||
})
|
||||
}
|
||||
|
||||
const query = createQuery()
|
||||
|
||||
let projectExists = false
|
||||
|
||||
query.query(tracker.class.Project, {}, (res) => {
|
||||
projectExists = res.length > 0
|
||||
})
|
||||
|
||||
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
|
||||
})
|
||||
let mainActionId: string | undefined = undefined
|
||||
let visibleActions: string[] = []
|
||||
function updateActions (draft: boolean, project: boolean, closed: boolean): void {
|
||||
mainActionId = draft || !closed ? tracker.string.ResumeDraft : tracker.string.NewIssue
|
||||
if (project) {
|
||||
visibleActions = [tracker.string.CreateProject, mainActionId, tracker.string.Import]
|
||||
} else {
|
||||
newIssue()
|
||||
visibleActions = [tracker.string.CreateProject]
|
||||
}
|
||||
}
|
||||
|
||||
void client.findOne(view.class.Action, { _id: tracker.action.NewIssue }).then((p) => (keys = p?.keyBinding))
|
||||
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
|
||||
}
|
||||
$: updateActions(draftExists, projectExists, closed)
|
||||
</script>
|
||||
|
||||
<div class="antiNav-subheader">
|
||||
{#if projectExists}
|
||||
<ButtonWithDropdown
|
||||
icon={IconAdd}
|
||||
justify={'left'}
|
||||
kind={'primary'}
|
||||
{label}
|
||||
on:click={newIssue}
|
||||
dropdownItems={getAvailableActions()}
|
||||
dropdownIcon={IconDropdown}
|
||||
on:dropdown-selected={(ev) => {
|
||||
dropdownItemSelected(ev.detail)
|
||||
}}
|
||||
mainButtonId={'new-issue'}
|
||||
showTooltipMain={{
|
||||
direction: 'bottom',
|
||||
label,
|
||||
keys
|
||||
}}
|
||||
>
|
||||
<div slot="content" class="draft-circle-container">
|
||||
{#if draftExists}
|
||||
<div class="draft-circle" />
|
||||
{/if}
|
||||
</div>
|
||||
</ButtonWithDropdown>
|
||||
{:else}
|
||||
<Button
|
||||
disabled={!canCreateProject}
|
||||
icon={IconAdd}
|
||||
justify="left"
|
||||
kind="primary"
|
||||
label={tracker.string.CreateProject}
|
||||
width="100%"
|
||||
on:click={() => {
|
||||
showPopup(tracker.component.CreateProject, {}, 'top', () => {
|
||||
closed = true
|
||||
})
|
||||
}}
|
||||
<HeaderButton
|
||||
{loading}
|
||||
{client}
|
||||
{mainActionId}
|
||||
{visibleActions}
|
||||
actions={[
|
||||
{
|
||||
id: tracker.string.CreateProject,
|
||||
label: tracker.string.CreateProject,
|
||||
accountRole: AccountRole.Maintainer,
|
||||
permission: {
|
||||
id: core.permission.CreateProject,
|
||||
space: core.space.Space
|
||||
},
|
||||
callback: newProject
|
||||
},
|
||||
{
|
||||
id: tracker.string.ResumeDraft,
|
||||
label: tracker.string.ResumeDraft,
|
||||
draft: true,
|
||||
keyBindingPromise: newIssueKeyBindingPromise,
|
||||
callback: newIssue
|
||||
},
|
||||
{
|
||||
id: tracker.string.NewIssue,
|
||||
label: tracker.string.NewIssue,
|
||||
keyBindingPromise: newIssueKeyBindingPromise,
|
||||
callback: newIssue
|
||||
},
|
||||
{
|
||||
id: tracker.string.Import,
|
||||
label: tracker.string.Import,
|
||||
accountRole: AccountRole.User,
|
||||
callback: newIssue
|
||||
}
|
||||
]}
|
||||
/>
|
||||
{/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 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 =>
|
||||
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"]')
|
||||
issues = (): Locator => this.page.locator('.antiPanel-navigator').locator('text="Issues"')
|
||||
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"]')
|
||||
modelSelectorBacklog = (): Locator => this.page.locator('label[data-id="tab-backlog"]')
|
||||
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()
|
||||
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')
|
||||
issueDescriptionInput = (): Locator => this.page.locator('#issue-description >> [contenteditable]')
|
||||
statusEditor = (): Locator => this.page.locator('#status-editor')
|
||||
@ -232,6 +233,10 @@ export class IssuesPage extends CommonTrackerPage {
|
||||
await this.newIssue().click()
|
||||
}
|
||||
|
||||
async clickOnResumeDraft (): Promise<void> {
|
||||
await this.resumeDraft().click()
|
||||
}
|
||||
|
||||
async navigateToMyIssues (): Promise<void> {
|
||||
await this.myIssuesButton().click()
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ test.describe('Tracker sub-issues tests', () => {
|
||||
await fillIssueForm(page, props)
|
||||
await page.keyboard.press('Escape')
|
||||
await page.keyboard.press('Escape')
|
||||
await issuesPage.clickOnNewIssue()
|
||||
await issuesPage.clickOnResumeDraft()
|
||||
await checkIssueDraft(page, props)
|
||||
})
|
||||
|
||||
|
@ -161,7 +161,7 @@ test.describe('Tracker tests', () => {
|
||||
await issuesPage.inputTextPlaceholderFill('1')
|
||||
await issuesPage.setDueDate('24')
|
||||
await issuesPage.pressEscapeTwice()
|
||||
await issuesPage.clickOnNewIssue()
|
||||
await issuesPage.clickOnResumeDraft()
|
||||
await checkIssueDraft(page, {
|
||||
name: issueName,
|
||||
description: issueName,
|
||||
|
Loading…
Reference in New Issue
Block a user