Uberf 10285 refactor header button (#8655)

Signed-off-by: Anton Alexeyev <alexeyev.anton@gmail.com>
This commit is contained in:
utkaka 2025-04-22 22:33:02 +07:00 committed by GitHub
parent 74d1dcc83d
commit b159c0610a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 383 additions and 313 deletions

View File

@ -1018,7 +1018,9 @@ export function createModel (builder: Builder): void {
}, },
override: [recruit.action.CreateGlobalApplication] override: [recruit.action.CreateGlobalApplication]
}) })
createAction(builder, { createAction(
builder,
{
action: view.actionImpl.ShowPopup, action: view.actionImpl.ShowPopup,
actionProps: { actionProps: {
component: recruit.component.CreateCandidate, component: recruit.component.CreateCandidate,
@ -1035,7 +1037,9 @@ export function createModel (builder: Builder): void {
application: recruit.app.Recruit, application: recruit.app.Recruit,
group: 'create' group: 'create'
} }
}) },
recruit.action.CreateTalent
)
createAction(builder, { createAction(builder, {
action: view.actionImpl.ShowPopup, action: view.actionImpl.ShowPopup,

View 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>

View File

@ -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'

View File

@ -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
*/ */

View File

@ -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}

View File

@ -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
},
{
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}

View File

@ -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}

View File

@ -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>

View File

@ -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": {

View File

@ -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>>,

View File

@ -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>

View File

@ -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}")`)

View File

@ -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()
} }

View File

@ -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)
}) })

View File

@ -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,