// // Copyright © 2020, 2021 Anticrm Platform Contributors. // Copyright © 2021, 2023 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. // import { Account, AggregateValue, AnyAttribute, Attribute, CategoryType, Class, Client, Doc, DocumentQuery, FindOptions, Hierarchy, Lookup, Mixin, Obj, ObjQueryType, PrimitiveType, Ref, SortingOrder, Space, Tx, TxOperations, Type, UXObject, WithLookup } from '@hcengineering/core' import { Asset, IntlString, Plugin, Resource, Status, plugin } from '@hcengineering/platform' import { Preference } from '@hcengineering/preference' import { AnyComponent, AnySvelteComponent, Location, Location as PlatformLocation, PopupAlignment, PopupPosAlignment } from '@hcengineering/ui' /** * @public */ export interface KeyFilterPreset { _class: Ref> key: string attribute?: AnyAttribute component: AnyComponent label?: IntlString group?: 'top' | 'bottom' showNested?: boolean } /** * @public */ export interface KeyFilter extends KeyFilterPreset { attribute: AnyAttribute component: AnyComponent label: IntlString group?: 'top' | 'bottom' } /** * @public */ export interface FilterMode extends Doc { label: IntlString selectedLabel?: IntlString disableValueSelector?: boolean result: FilterFunction } /** * @public */ export type FilterFunction = Resource<(filter: Filter, onUpdate: () => void) => Promise>> /** * @public */ export interface Filter { key: KeyFilter nested?: Filter mode: Ref modes: Ref[] value: any[] props?: Record index: number onRemove?: () => void } /** * @public */ export interface FilteredView extends Doc { name: string location: PlatformLocation filters: string viewOptions?: ViewOptions filterClass?: Ref> viewletId?: Ref | null sharable?: boolean users: Ref[] createdBy: Ref attachedTo: string } /** * @public */ export interface ClassFilters extends Class { filters: (KeyFilterPreset | string)[] ignoreKeys?: string[] // Ignore attributes not specified in the "filters" array strict?: boolean // Allows to filter out the provided keys, leaving only the necessary ones getVisibleFilters?: Resource<(filters: KeyFilter[], space?: Ref) => Promise> } /** * @public */ export interface AttributeFilter extends Class> { component: AnyComponent group?: 'top' | 'bottom' } /** * @public */ export interface AttributeEditor extends Class { inlineEditor: AnyComponent // If defined could be used for ShowEditor declarative actions. popup?: AnyComponent } /** * @public */ export interface CollectionEditor extends Class { editor: AnyComponent inlineEditor?: AnyComponent } /** * @public */ export interface InlineAttributEditor extends Class { editor: AnyComponent } /** * @public */ export interface ArrayEditor extends Class { editor?: AnyComponent inlineEditor?: AnyComponent } /** * @public */ export interface CollectionPresenter extends Class { presenter: AnyComponent } /** * @public */ export interface AttributePresenter extends Class { presenter: AnyComponent } /** * @public */ export interface AttributeFilterPresenter extends Class { presenter: AnyComponent } /** * @public */ export interface ActivityAttributePresenter extends Class { presenter: AnyComponent } /** * @public */ export interface SpacePresenter extends Class { presenter: AnyComponent } /** * @public */ export interface ObjectPresenter extends Class { presenter: AnyComponent } /** * @public */ export interface ListItemPresenter extends Class { presenter: AnyComponent } /** * @public */ export interface ObjectEditor extends Class { editor: AnyComponent pinned?: boolean } /** * @public */ export interface ObjectEditorFooter extends Class { editor: AnyComponent props?: Record } /** * @public */ export interface SpaceHeader extends Class { header: AnyComponent } /** * @public */ export interface SpaceName extends Class { getName: Resource<(client: Client, space: Space) => Promise> } /** * @public */ export interface ObjectEditorHeader extends Class { editor: AnyComponent } /** * @public */ export interface ObjectValidator extends Class { validator: Resource<(doc: T, client: Client) => Promise> } /** * @public */ export interface ObjectTitle extends Class { titleProvider: Resource<(client: Client, ref: Ref, doc?: T) => Promise> } /** * @public */ export interface ObjectIcon extends Class { component: AnyComponent } /** * @public */ export interface ObjectIdentifier extends Class { provider: Resource<(client: Client, ref: Ref, doc?: T) => Promise> } /** * @public */ export interface ViewletDescriptor extends Doc, UXObject { component: AnyComponent } /** * @public */ export interface ListHeaderExtra extends Class { presenters: AnyComponent[] } /** * @public */ export type SortFunc = Resource< ( client: TxOperations, values: PrimitiveType[], space: Ref | undefined, viewletDescriptorId?: Ref ) => Promise > /** * @public */ export interface ClassSortFuncs extends Class { func: SortFunc } /** * @public */ export type GetAllValuesFunc = Resource< ( query: DocumentQuery | undefined, onUpdate: () => void, queryId: Ref, attr: AnyAttribute ) => Promise > /** * @public */ export interface AllValuesFunc extends Class { func: GetAllValuesFunc } /** * @public */ export interface GrouppingManager { groupByCategories: (categories: any[]) => AggregateValue[] groupValues: (val: Doc[], targets: Set) => Doc[] groupValuesWithEmpty: ( hierarchy: Hierarchy, _class: Ref>, key: string, query: DocumentQuery | undefined ) => Array> hasValue: (value: Doc | undefined | null, values: any[]) => boolean } /** * @public */ export type GrouppingManagerResource = Resource /** * @public */ export interface Groupping extends Class { grouppingManager: GrouppingManagerResource } /** * @public */ export interface AggregationManager { close: () => void notifyTx: (tx: Tx) => Promise updateLookup: (resultDoc: WithLookup, attr: Attribute) => Promise categorize: (target: Array>, attr: AnyAttribute) => Promise>> getAttrClass: () => Ref> updateSorting?: (finalOptions: FindOptions, attr: AnyAttribute) => Promise } /** * @public */ export type AggregationManagerResource = Resource /** * @public */ export type CreateAggregationManagerFunc = Resource<(client: Client, lqCallback: () => void) => AggregationManager> /** * @public */ export interface Aggregation extends Class { createAggregationManager: CreateAggregationManagerFunc } /** * @public */ export interface Viewlet extends Doc { attachTo: Ref> baseQuery?: DocumentQuery descriptor: Ref options?: FindOptions config: (BuildModelKey | string)[] configOptions?: ViewletConfigOptions viewOptions?: ViewOptionsModel variant?: string } /** * @public */ export interface ViewletConfigOptions { hiddenKeys?: string[] strict?: boolean extraProps?: Omit sortable?: boolean } /** * @public */ export interface LinkPresenter extends Doc { pattern: string component: AnyComponent } /** * @public * * "Alt + K" =\> Alt and K should be pressed together * "J T" - J and then T shold be pressed. */ export type KeyBinding = string /** * @public */ export type ViewActionInput = 'focus' | 'selection' | 'any' | 'none' /** * @public */ export type ViewActionFunction> = ( doc: T | T[] | undefined, evt?: Event, params?: P ) => Promise /** * @public */ export type ViewActionAvailabilityFunction = (doc: T | T[] | undefined) => Promise /** * @public */ export type ViewAction

> = Resource> /** * @public */ export interface ActionCategory extends Doc, UXObject { // Does category is visible for use in popup. visible: boolean } /** * @public */ export type ActionGroup = 'create' | 'edit' | 'associate' | 'copy' | 'tools' | 'other' | 'remove' /** * @public */ export interface Action> extends Doc, UXObject { // Action implementation details action: Resource> // Action implementation parameters actionProps?: P // If specified, will show sub menu based on actionPopup/actionProps actionPopup?: AnyComponent // If specified, action could be used only with one item selected. // single - one object is required // any - one or multiple objects are required // any - any input is suitable. input: ViewActionInput inline?: boolean // Focus and/or all selection document should match target class. target: Ref> // Action is applicable only for objects matching criteria query?: DocumentQuery // Action is shown only if the check is passed visibilityTester?: Resource> // If defined, types should be matched to proposed list inputProps?: Record>> // Kayboard bindings keyBinding?: KeyBinding[] // short description for action. description?: IntlString // Action category, for UI. category: Ref // Context action is defined for context: ViewContext // A list of actions replaced by this one. // For example, it could be global action and action for focus class, second one fill override first one. override?: Ref[] // Avaible only for workspace owners secured?: boolean allowedForEditableContent?: boolean } /** * @public * context - only for context menu actions. * workbench - global actions per application or entire workbench. * browser - actions for list/table/kanban browsing. * editor - actions for selected editor context. * panel - for panel based actions. * popup - for popup based actions, like Close of Popup. * input - for input based actions, some actions should be available for input controls. */ export type ViewContextType = 'context' | 'workbench' | 'browser' | 'editor' | 'panel' | 'popup' | 'input' | 'none' /** * @public */ export interface ViewContext { mode: ViewContextType | ViewContextType[] // Active application application?: Ref // Optional groupping group?: ActionGroup } /** * @public */ export interface ActionIgnore { _class: Ref> // Action to be ignored action: Ref // Document match to ignore if matching at least one document. query: DocumentQuery } /** * @public */ export interface IgnoreActions extends Class { actions: (Ref | ActionIgnore)[] } /** * @public */ export interface PreviewPresenter extends Class { presenter: AnyComponent } /** * @public */ export const viewId = 'view' as Plugin /** * @public */ export interface DisplayProps { key?: string excludeByKey?: string fixed?: 'left' | 'right' // using for align items in row align?: 'left' | 'right' | 'center' suffix?: boolean optional?: boolean compression?: boolean grow?: boolean dividerBefore?: boolean // should show divider before } /** * @public */ export interface BuildModelKey { key: string presenter?: AnyComponent | AnySvelteComponent // A set of extra props passed to presenter. props?: Record // A set of extra props which using for display. displayProps?: DisplayProps label?: IntlString sortingKey?: string | string[] // On client sorting function sortingFunction?: (a: Doc, b: Doc) => number } /** * @public */ export interface AttributeModel { key: string label: IntlString _class: Ref> presenter: AnySvelteComponent // Extra properties for component props?: Record displayProps?: DisplayProps sortingKey: string | string[] // Extra icon if applicable icon?: Asset attribute?: AnyAttribute collectionAttr: boolean isLookup: boolean castRequest?: Ref> } /** * @public */ export interface BuildModelOptions { client: Client _class: Ref> keys: (BuildModelKey | string)[] lookup?: Lookup ignoreMissing?: boolean } /** * Define document create popup widget * * @public * */ export interface ObjectFactory extends Class { component?: AnyComponent create?: Resource<(props?: Record) => Promise> } /** * @public */ export interface ViewletPreference extends Preference { attachedTo: Ref config: (BuildModelKey | string)[] } /** * @public */ export type ViewOptions = { groupBy: string[] orderBy: OrderOption } & Record /** * @public */ export interface ViewOption { type: string key: string defaultValue: any label: IntlString hidden?: (viewOptions: ViewOptions) => boolean actionTarget?: 'query' | 'category' | 'display' action?: Resource<(value: any, ...params: any) => any> } /** * @public */ export type ViewCategoryActionFunc = ( _class: Ref>, query: DocumentQuery | undefined, space: Ref | undefined, key: string, onUpdate: () => void, queryId: Ref, viewletDescriptorId?: Ref ) => Promise /** * @public */ export type ViewCategoryAction = Resource /** * @public */ export interface CategoryOption extends ViewOption { actionTarget: 'category' action: ViewCategoryAction } /** * @public */ export type ViewQueryAction = Resource< (value: any, query: DocumentQuery) => DocumentQuery | Promise> > /** * @public */ export interface ViewQueryOption extends ViewOption { actionTarget: 'query' action: ViewQueryAction } /** * @public */ export interface ToggleViewOption extends ViewOption { type: 'toggle' defaultValue: boolean } /** * @public */ export interface DropdownViewOption extends ViewOption { type: 'dropdown' defaultValue: string values: Array<{ label: IntlString, id: string, hidden?: (viewOptions: ViewOptions) => boolean }> } /** * @public */ export type ViewOptionModel = ToggleViewOption | DropdownViewOption /** * @public */ export type OrderOption = [string, SortingOrder] /** * @public */ export interface LinkProvider extends Class { encode: Resource<(doc: Doc, props: Record) => Promise> } /** * @public */ export interface ObjectPanel extends Class { component: AnyComponent } /** * @public */ export interface ViewOptionsModel { groupBy: string[] orderBy: OrderOption[] other: ViewOptionModel[] groupDepth?: number } /** * @public */ export interface IconProps { icon?: Asset color?: number } /** * @public */ const view = plugin(viewId, { mixin: { ClassFilters: '' as Ref>, AttributeFilter: '' as Ref>, AttributeEditor: '' as Ref>, CollectionPresenter: '' as Ref>, CollectionEditor: '' as Ref>, InlineAttributEditor: '' as Ref>, ArrayEditor: '' as Ref>, AttributePresenter: '' as Ref>, ActivityAttributePresenter: '' as Ref>, ListItemPresenter: '' as Ref>, ObjectEditor: '' as Ref>, ObjectPresenter: '' as Ref>, ObjectEditorHeader: '' as Ref>, ObjectEditorFooter: '' as Ref>, ObjectValidator: '' as Ref>, ObjectFactory: '' as Ref>, ObjectTitle: '' as Ref>, ObjectIdentifier: '' as Ref>, SpaceHeader: '' as Ref>, SpaceName: '' as Ref>, IgnoreActions: '' as Ref>, PreviewPresenter: '' as Ref>, ListHeaderExtra: '' as Ref>, SortFuncs: '' as Ref>, AllValuesFunc: '' as Ref>, ObjectPanel: '' as Ref>, LinkProvider: '' as Ref>, SpacePresenter: '' as Ref>, AttributeFilterPresenter: '' as Ref>, Aggregation: '' as Ref>, Groupping: '' as Ref>, ObjectIcon: '' as Ref> }, class: { ViewletPreference: '' as Ref>, ViewletDescriptor: '' as Ref>, Viewlet: '' as Ref>, Action: '' as Ref>, ActionCategory: '' as Ref>, LinkPresenter: '' as Ref>, FilterMode: '' as Ref>, FilteredView: '' as Ref> }, action: { Delete: '' as Ref, Archive: '' as Ref, Move: '' as Ref, MoveLeft: '' as Ref, MoveRight: '' as Ref, MoveUp: '' as Ref, MoveDown: '' as Ref, SelectItem: '' as Ref, SelectItemAll: '' as Ref, SelectItemNone: '' as Ref, SelectUp: '' as Ref, SelectDown: '' as Ref, ShowPreview: '' as Ref, ShowActions: '' as Ref, Preview: '' as Ref, // Edit document Open: '' as Ref, OpenInNewTab: '' as Ref }, viewlet: { Table: '' as Ref, List: '' as Ref }, component: { ActionsPopup: '' as AnyComponent, ObjectPresenter: '' as AnyComponent, EditDoc: '' as AnyComponent, SpacePresenter: '' as AnyComponent, BooleanTruePresenter: '' as AnyComponent, ValueSelector: '' as AnyComponent, GrowPresenter: '' as AnyComponent, DividerPresenter: '' as AnyComponent, IconWithEmoji: '' as AnyComponent, AttachedDocPanel: '' as AnyComponent }, ids: { IconWithEmoji: '' as Asset }, string: { CustomizeView: '' as IntlString, LabelNA: '' as IntlString, View: '' as IntlString, FilteredViews: '' as IntlString, NewFilteredView: '' as IntlString, FilteredViewName: '' as IntlString, Move: '' as IntlString, MoveClass: '' as IntlString, SelectToMove: '' as IntlString, Cancel: '' as IntlString, List: '' as IntlString, AddSavedView: '' as IntlString, Timeline: '' as IntlString, Public: '' as IntlString, Hide: '' as IntlString, Rename: '' as IntlString, Assigned: '' as IntlString, Open: '' as IntlString, OpenInNewTab: '' as IntlString, Created: '' as IntlString, Delete: '' as IntlString, Then: '' as IntlString, Or: '' as IntlString, Subscribed: '' as IntlString, HyperlinkPlaceholder: '' as IntlString, CopyToClipboard: '' as IntlString, NoGrouping: '' as IntlString, Type: '' as IntlString, UnArchive: '' as IntlString, Save: '' as IntlString, PublicView: '' as IntlString }, icon: { Table: '' as Asset, List: '' as Asset, Card: '' as Asset, Timeline: '' as Asset, Delete: '' as Asset, MoreH: '' as Asset, Move: '' as Asset, Archive: '' as Asset, Statuses: '' as Asset, Setting: '' as Asset, Open: '' as Asset, Edit: '' as Asset, CopyId: '' as Asset, CopyLink: '' as Asset, ArrowRight: '' as Asset, Views: '' as Asset, Pin: '' as Asset, Model: '' as Asset, DevModel: '' as Asset, ViewButton: '' as Asset, Filter: '' as Asset, Configure: '' as Asset, Database: '' as Asset }, category: { General: '' as Ref, GeneralNavigation: '' as Ref, Navigation: '' as Ref, Editor: '' as Ref, MarkdownFormatting: '' as Ref }, filter: { FilterArrayAll: '' as Ref, FilterArrayAny: '' as Ref, FilterObjectIn: '' as Ref, FilterObjectNin: '' as Ref, FilterValueIn: '' as Ref, FilterValueNin: '' as Ref, FilterBefore: '' as Ref, FilterAfter: '' as Ref, FilterContains: '' as Ref, FilterNestedMatch: '' as Ref, FilterNestedDontMatch: '' as Ref, FilterDateOutdated: '' as Ref, FilterDateToday: '' as Ref, FilterDateYesterday: '' as Ref, FilterDateWeek: '' as Ref, FilterDateNextW: '' as Ref, FilterDateM: '' as Ref, FilterDateNextM: '' as Ref, FilterDateNotSpecified: '' as Ref, FilterDateCustom: '' as Ref, FilterDateBetween: '' as Ref }, popup: { PositionElementAlignment: '' as Resource<(e?: Event) => PopupAlignment | undefined> }, actionImpl: { CopyTextToClipboard: '' as ViewAction<{ textProvider: Resource<(doc: Doc, props: Record) => Promise> props?: Record }>, UpdateDocument: '' as ViewAction<{ key: string value: any ask?: boolean label?: IntlString message?: IntlString }>, ShowPanel: '' as ViewAction<{ component?: AnyComponent element?: PopupPosAlignment rightSection?: AnyComponent }>, ShowPopup: '' as ViewAction<{ component: AnyComponent element?: PopupPosAlignment | Resource<(e?: Event) => PopupAlignment | undefined> _id?: string _class?: string _space?: string value?: string values?: string props?: Record // Will copy values from selection document to props fillProps?: Record }>, ShowEditor: '' as ViewAction<{ element?: PopupPosAlignment | Resource<(e?: Event) => PopupAlignment | undefined> attribute: string props?: Record }>, ValueSelector: '' as ViewAction<{ attribute: string // Class object finder _class?: Ref> query?: DocumentQuery queryOptions?: FindOptions // Will copy values from selection document to query // If set of docs passed, will do $in for values. fillQuery?: Record // A list of fields with matched values to perform action. docMatches?: string[] searchField?: string // Cast doc to mixin castRequest?: Ref> // Or list of values to select from values?: { icon?: Asset, label: IntlString, id: number | string }[] placeholder?: IntlString }>, AttributeSelector: '' as ViewAction<{ attribute: string isAction?: boolean fillProps?: Record // Or list of values to select from values?: { icon?: Asset, label: IntlString, id: number | string }[] // If defined, documents will be set into value valueKey?: string }> } }) export default view