}
export interface ICredentialsState {
credentialTypes: ICredentialTypeMap;
credentials: ICredentialMap;
}
export interface ITagsState {

import { getCredentialTypes,
	getCredentialsNewName,
	getAllCredentials,
	deleteCredential,
	getCredentialData,
	createNewCredential,
	updateCredential,
	oAuth2CredentialAuthorize,
	oAuth1CredentialAuthorize,
	testCredential,
} from '@/api/credentials';
import Vue from 'vue';
import { ActionContext, Module } from 'vuex';
import {
	ICredentialMap,
	ICredentialsResponse,
	ICredentialsState,
	ICredentialTypeMap,
	IRootState,
} from '../Interface';
import {
	ICredentialType,
	ICredentialsDecrypted,
	INodeCredentialTestResult,
	INodeTypeDescription,
} from 'n8n-workflow';
import { getAppNameFromCredType } from '@/components/helpers';

const DEFAULT_CREDENTIAL_NAME = 'Unnamed credential';
const DEFAULT_CREDENTIAL_POSTFIX = 'account';
const TYPES_WITH_DEFAULT_NAME = ['httpBasicAuth', 'oAuth2Api', 'httpDigestAuth', 'oAuth1Api'];

const module: Module<ICredentialsState, IRootState> = {
	namespaced: true,
	state: {
		credentialTypes: {},
		credentials: {},
	},
	mutations: {
		setCredentialTypes: (state: ICredentialsState, credentialTypes: ICredentialType[]) => {
			state.credentialTypes = credentialTypes.reduce((accu: ICredentialTypeMap, cred: ICredentialType) => {
				accu[cred.name] = cred;

				return accu;
			}, {});
		},
		setCredentials: (state: ICredentialsState, credentials: ICredentialsResponse[]) => {
			state.credentials = credentials.reduce((accu: ICredentialMap, cred: ICredentialsResponse) => {
				if (cred.id) {
					accu[cred.id] = cred;
				}

				return accu;
			}, {});
		},
		upsertCredential(state: ICredentialsState, credential: ICredentialsResponse) {
			if (credential.id) {
				Vue.set(state.credentials, credential.id, credential);
			}
		},
		deleteCredential(state: ICredentialsState, id: string) {
			Vue.delete(state.credentials, id);
		},
		enableOAuthCredential(state: ICredentialsState, credential: ICredentialsResponse) {
			// enable oauth event to track change between modals
		},
	},
	getters: {
		allCredentialTypes(state: ICredentialsState): ICredentialType[] {
			return Object.values(state.credentialTypes)
				.sort((a, b) => a.displayName.localeCompare(b.displayName));
		},
		allCredentials(state: ICredentialsState): ICredentialsResponse[] {
			return Object.values(state.credentials)
				.sort((a, b) => a.name.localeCompare(b.name));
		},
		allCredentialsByType(state: ICredentialsState, getters: any): {[type: string]: ICredentialsResponse[]} { // tslint:disable-line:no-any
			const credentials = getters.allCredentials as ICredentialsResponse[];
			const types = getters.allCredentialTypes as ICredentialType[];

			return types.reduce((accu: {[type: string]: ICredentialsResponse[]}, type: ICredentialType) => {
				accu[type.name] = credentials.filter((cred: ICredentialsResponse) => cred.type === type.name);

				return accu;
			}, {});
		},
		getCredentialTypeByName: (state: ICredentialsState) => {
			return (type: string) => state.credentialTypes[type];
		},
		getCredentialById: (state: ICredentialsState) => {
			return (id: string) => state.credentials[id];
		},
		getCredentialByIdAndType: (state: ICredentialsState) => {
			return (id: string, type: string) => {
				const credential = state.credentials[id];
				return !credential || credential.type !== type ? undefined : credential;
			};
		},
		getCredentialsByType: (state: ICredentialsState, getters: any) => { // tslint:disable-line:no-any
			return (credentialType: string): ICredentialsResponse[] => {
				return getters.allCredentialsByType[credentialType] || [];
			};
		},
		getNodesWithAccess (state: ICredentialsState, getters: any, rootState: IRootState, rootGetters: any) { // tslint:disable-line:no-any
			return (credentialTypeName: string) => {
				const nodeTypes: INodeTypeDescription[] = rootGetters.allNodeTypes;

				return nodeTypes.filter((nodeType: INodeTypeDescription) => {
					if (!nodeType.credentials) {
						return false;
					}

					for (const credentialTypeDescription of nodeType.credentials) {
						if (credentialTypeDescription.name === credentialTypeName ) {
							return true;
						}
					}

					return false;
				});
			};
		},
	},
	actions: {
		fetchCredentialTypes: async (context: ActionContext<ICredentialsState, IRootState>) => {
			if (context.getters.allCredentialTypes.length > 0) {
				return;
			}
			const credentialTypes = await getCredentialTypes(context.rootGetters.getRestApiContext);
			context.commit('setCredentialTypes', credentialTypes);
		},
		fetchAllCredentials: async (context: ActionContext<ICredentialsState, IRootState>) => {
			if (context.getters.allCredentials.length > 0) {
				return;
			}
			const credentials = await getAllCredentials(context.rootGetters.getRestApiContext);
			context.commit('setCredentials', credentials);
		},
		getCredentialData: async (context: ActionContext<ICredentialsState, IRootState>, { id }: {id: string}) => {
			return await getCredentialData(context.rootGetters.getRestApiContext, id);
		},
		createNewCredential: async (context: ActionContext<ICredentialsState, IRootState>, data: ICredentialsDecrypted) => {
			const credential = await createNewCredential(context.rootGetters.getRestApiContext, data);
			context.commit('upsertCredential', credential);

			return credential;
		},
		updateCredential: async (context: ActionContext<ICredentialsState, IRootState>, params: {data: ICredentialsDecrypted, id: string}) => {
			const { id, data } = params;
			const credential = await updateCredential(context.rootGetters.getRestApiContext, id, data);
			context.commit('upsertCredential', credential);

			return credential;
		},
		deleteCredential: async (context: ActionContext<ICredentialsState, IRootState>, { id }: {id: string}) => {
			const deleted = await deleteCredential(context.rootGetters.getRestApiContext, id);
			if (deleted) {
				context.commit('deleteCredential', id);
			}
		},
		oAuth2Authorize: async (context: ActionContext<ICredentialsState, IRootState>, data: ICredentialsResponse) => {
			return oAuth2CredentialAuthorize(context.rootGetters.getRestApiContext, data);
		},
		oAuth1Authorize: async (context: ActionContext<ICredentialsState, IRootState>, data: ICredentialsResponse) => {
			return oAuth1CredentialAuthorize(context.rootGetters.getRestApiContext, data);
		},
		testCredential: async (context: ActionContext<ICredentialsState, IRootState>, data: ICredentialsDecrypted): Promise<INodeCredentialTestResult> => {
			return testCredential(context.rootGetters.getRestApiContext, { credentials: data });
		},
		getNewCredentialName: async (context: ActionContext<ICredentialsState, IRootState>, params: { credentialTypeName: string }) => {
			try {
				const { credentialTypeName } = params;
				let newName = DEFAULT_CREDENTIAL_NAME;
				if (!TYPES_WITH_DEFAULT_NAME.includes(credentialTypeName)) {
					const { displayName } = context.getters.getCredentialTypeByName(credentialTypeName);
					newName = getAppNameFromCredType(displayName);
					newName = newName.length > 0 ? `${newName} ${DEFAULT_CREDENTIAL_POSTFIX}` : DEFAULT_CREDENTIAL_NAME;
				}

				const res = await getCredentialsNewName(context.rootGetters.getRestApiContext, newName);
				return res.name;
			} catch (e) {
				return DEFAULT_CREDENTIAL_NAME;
			}
		},
	},
};

export default module;


import {
	INodeProperties,
} from 'n8n-workflow';

const resource = [
	'project',
];

export const projectOperations: INodeProperties[] = [
	{
		displayName: 'Operation',
		name: 'operation',
		type: 'options',
		displayOptions: {
			show: {
				resource,
			},
		},
		options: [
			{
				name: 'Create',
				value: 'create',
				description: `Create a project`,
			},
			{
				name: 'Delete',
				value: 'delete',
				description: `Delete a project`,
			},
			{
				name: 'Get',
				value: 'get',
				description: 'Get data of a project',
			},
			{
				name: 'Get All',
				value: 'getAll',
				description: 'Get data of all projects',
			},
			{
				name: 'Update',
				value: 'update',
				description: `Update a project`,
			},
		],
		default: 'getAll',
		description: 'The operation to perform.',
	},

];

export const projectFields: INodeProperties[] = [

	/* -------------------------------------------------------------------------- */
	/*                                projects:getAll                             */
	/* -------------------------------------------------------------------------- */

	{
		displayName: 'Return All',
		name: 'returnAll',
		type: 'boolean',
		displayOptions: {
			show: {
				resource,
				operation: [
					'getAll',
				],
			},
		},
		default: false,
		description: 'Returns a list of your projects.',
	},
	{
		displayName: 'Limit',
		name: 'limit',
		type: 'number',
		displayOptions: {
			show: {
				resource,
				operation: [
					'getAll',
				],
				returnAll: [
					false,
				],
			},
		},
		typeOptions: {
			minValue: 1,
			maxValue: 100,
		},
		default: 100,
		description: 'How many results to return.',
	},
	{
		displayName: 'Filters',
		name: 'filters',
		type: 'collection',
		placeholder: 'Add Filter',
		default: {},
		displayOptions: {
			show: {
				resource,
				operation: [
					'getAll',
				],
			},
		},
		options: [
			{
				displayName: 'Client Id',
				name: 'client_id',
				type: 'string',
				default: '',
				description: 'Only return projects belonging to the client with the given ID.',
			},
			{
				displayName: 'Is Active',
				name: 'is_active',
				type: 'boolean',
				default: true,
				description: 'Pass true to only return active projects and false to return inactive projects.',
			},
			{
				displayName: 'Page',
				name: 'page',
				type: 'number',
				typeOptions: {
					minValue: 1,
				},
				default: 1,
				description: 'The page number to use in pagination.',
			},
			{
				displayName: 'Updated Since',
				name: 'updated_since',
				type: 'dateTime',
				default: '',
				description: 'Only return projects by updated_since.',
			},
		],
	},

	/* -------------------------------------------------------------------------- */
	/*                                project:get                                 */
	/* -------------------------------------------------------------------------- */
	{
		displayName: 'Project Id',
		name: 'id',
		type: 'string',
		default: '',
		required: true,
		displayOptions: {
			show: {
				operation: [
					'get',
				],
				resource,
			},
		},
		description: 'The ID of the project you are retrieving.',
	},

	/* -------------------------------------------------------------------------- */
	/*                                project:delete                              */
	/* -------------------------------------------------------------------------- */
	{
		displayName: 'Project Id',
		name: 'id',
		type: 'string',
		default: '',
		required: true,
		displayOptions: {
			show: {
				operation: [
					'delete',
				],
				resource,
			},
		},
		description: 'The ID of the project want to delete.',
	},

	/* -------------------------------------------------------------------------- */
	/*                                project:create                              */
	/* -------------------------------------------------------------------------- */
	{
		displayName: 'Name',
		name: 'name',
		type: 'string',
		displayOptions: {
			show: {
				operation: [
					'create',
				],
				resource,
			},
		},
		default: '',
		required: true,
		description: 'The name of the project.',
	},
	{
		displayName: 'Client Id',
		name: 'clientId',
		type: 'string',
		displayOptions: {
			show: {
				operation: [
					'create',
				],
				resource,
			},
		},
		default: '',
		required: true,
		description: 'The ID of the client to associate this project with.',
	},
	{
		displayName: 'Is Billable',
		name: 'isBillable',
		type: 'boolean',
		displayOptions: {
			show: {
				operation: [
					'create',
				],
				resource,
			},
		},
		default: true,
		required: true,
		description: 'Whether the project is billable or not.',
	},
	{
		displayName: 'Bill By',
		name: 'billBy',
		type: 'options',
		displayOptions: {
			show: {
				operation: [
					'create',
				],
				resource,
			},
		},
		options: [
			{
				name: 'none',
				value: 'none',
			},
			{
				name: 'People',
				value: 'People',
			},
			{
				name: 'Project',
				value: 'Project',
			},
			{
				name: 'Tasks',
				value: 'Tasks',
			},
		],
		default: 'none',
		required: true,
		description: 'The method by which the project is invoiced.',
	},
	{
		displayName: 'Budget By',
		name: 'budgetBy',
		type: 'string',
		displayOptions: {
			show: {
				operation: [
					'create',
				],
				resource,
			},
		},
		default: 'none',
		placeholder: '',
		required: true,
		description: 'The email of the user or "none".',
	},
	{
		displayName: 'Additional Fields',
		name: 'additionalFields',
		type: 'collection',
		placeholder: 'Add Field',
		displayOptions: {
			show: {
				operation: [
					'create',
				],
				resource,
			},
		},
		default: {},
		options: [
			{
				displayName: 'Budget',
				name: 'budget',
				type: 'number',
				typeOptions: {
					minValue: 0,
				},
				default: 0,
				description: 'The budget in hours for the project when budgeting by time.',
			},
			{
				displayName: 'Budget Is Monthly',
				name: 'budget_is_monthly',
				type: 'boolean',
				default: false,
				description: 'Option to have the budget reset every month. Defaults to false.',
			},
			{
				displayName: 'Cost Budget',
				name: 'cost_budget',
				type: 'string',
				default: '',
				description: 'The monetary budget for the project when budgeting by money.',
			},
			{
				displayName: 'Cost Budget Include Expenses',
				name: 'cost_budget_include_expenses',
				type: 'boolean',
				default: false,
				description: 'Option for budget of Total Project Fees projects to include tracked expenses. Defaults to false.',
			},
			{
				displayName: 'Ends On',
				name: 'ends_on',
				type: 'dateTime',
				default: '',
				description: 'Date the project will end.',
			},
			{
				displayName: 'Fee',
				name: 'fee',
				type: 'string',
				default: '',
				description: 'The amount you plan to invoice for the project. Only used by fixed-fee projects.',
			},
			{
				displayName: 'Hourly Rate',
				name: 'hourly_rate',
				type: 'string',
				default: '',
				description: 'Rate for projects billed by Project Hourly Rate.',
			},
			{
				displayName: 'Is Active',
				name: 'is_active',
				type: 'boolean',
				default: true,
				description: 'Whether the project is active or archived. Defaults to true',
			},
			{
				displayName: 'Is Fixed Fee',
				name: 'is_fixed_fee',
				type: 'boolean',
				default: false,
				description: 'Whether the project is a fixed-fee project or not.',
			},
			{
				displayName: 'Notes',
				name: 'notes',
				type: 'string',
				default: '',
				description: 'Notes about the project.',
			},
			{
				displayName: 'Notify When Over Budget',
				name: 'notify_when_over_budget',
				type: 'boolean',
				default: false,
				description: 'Whether project managers should be notified when the project goes over budget. Defaults to false.',
			},
			{
				displayName: 'Over Budget Notification Percentage',
				name: 'over_budget_notification_percentage',
				type: 'string',
				default: '',
				description: 'Percentage value used to trigger over budget email alerts. Example: use 10.0 for 10.0%.',
			},
			{
				displayName: 'Show Budget To All',
				name: 'show_budget_to_all',
				type: 'boolean',
				default: false,
				description: 'Option to show project budget to all employees. Does not apply to Total Project Fee projects. Defaults to false.',
			},
			{
				displayName: 'Starts On',
				name: 'starts_on',
				type: 'dateTime',
				default: '',
				description: 'Date the project was started.',
			},
		],
	},

	/* -------------------------------------------------------------------------- */
	/*                                project:update                              */
	/* -------------------------------------------------------------------------- */
	{
		displayName: 'Project Id',
		name: 'id',
		type: 'string',
		default: '',
		required: true,
		displayOptions: {
			show: {
				operation: [
					'update',
				],
				resource,
			},
		},
		description: 'The ID of the project want to update.',
	},
	{
		displayName: 'Update Fields',
		name: 'updateFields',
		type: 'collection',
		placeholder: 'Add Field',
		displayOptions: {
			show: {
				operation: [
					'update',
				],
				resource,
			},
		},
		default: {},
		options: [
			{
				displayName: 'Bill By',
				name: 'bill_by',
				type: 'options',
				options: [
					{
						name: 'none',
						value: 'none',
					},
					{
						name: 'People',
						value: 'People',
					},
					{
						name: 'Project',
						value: 'Project',
					},
					{
						name: 'Tasks',
						value: 'Tasks',
					},
				],
				default: 'none',
				description: 'The method by which the project is invoiced.',
			},
			{
				displayName: 'Budget',
				name: 'budget',
				type: 'string',
				default: '',
				description: 'The budget in hours for the project when budgeting by time.',
			},
			{
				displayName: 'Budget By',
				name: 'budget_by',
				type: 'string',
				default: '',
				description: 'The email of the user or "none".',
			},
			{
				displayName: 'Budget Is Monthly',
				name: 'budget_is_monthly',
				type: 'boolean',
				default: false,
				description: 'Option to have the budget reset every month. Defaults to false.',
			},
			{
				displayName: 'Client Id',
				name: 'client_id',
				type: 'string',
				default: '',
				description: 'The ID of the client to associate this project with.',
			},
			{
				displayName: 'Cost Budget',
				name: 'cost_budget',
				type: 'string',
				default: '',
				description: 'The monetary budget for the project when budgeting by money.',
			},
			{
				displayName: 'Cost Budget Include Expenses',
				name: 'cost_budget_include_expenses',
				type: 'boolean',
				default: false,
				description: 'Option for budget of Total Project Fees projects to include tracked expenses. Defaults to false.',
			},
			{
				displayName: 'Ends On',
				name: 'ends_on',
				type: 'dateTime',
				default: '',
				description: 'Date the project will end.',
			},
			{
				displayName: 'Fee',
				name: 'fee',
				type: 'string',
				default: '',
				description: 'The amount you plan to invoice for the project. Only used by fixed-fee projects.',
			},
			{
				displayName: 'Hourly Rate',
				name: 'hourly_rate',
				type: 'string',
				default: '',
				description: 'Rate for projects billed by Project Hourly Rate.',
			},
			{
				displayName: 'Is Active',
				name: 'is_active',
				type: 'boolean',
				default: true,
				description: 'Whether the project is active or archived. Defaults to true',
			},
			{
				displayName: 'Is Billable',
				name: 'is_billable',
				type: 'boolean',
				default: true,
				description: 'Whether the project is billable or not.',
			},
			{
				displayName: 'Is Fixed Fee',
				name: 'is_fixed_fee',
				type: 'boolean',
				default: false,
				description: 'Whether the project is a fixed-fee project or not.',
			},
			{
				displayName: 'Name',
				name: 'name',
				type: 'string',
				default: '',
				description: 'The name of the project.',
			},
			{
				displayName: 'Notes',
				name: 'notes',
				type: 'string',
				default: '',
				description: 'Notes about the project.',
			},
			{
				displayName: 'Notify When Over Budget',
				name: 'notify_when_over_budget',
				type: 'boolean',
				default: false,
				description: 'Whether project managers should be notified when the project goes over budget. Defaults to false.',
			},
			{
				displayName: 'Over Budget Notification Percentage',
				name: 'over_budget_notification_percentage',
				type: 'string',
				default: '',
				description: 'Percentage value used to trigger over budget email alerts. Example: use 10.0 for 10.0%.',
			},
			{
				displayName: 'Show Budget To All',
				name: 'show_budget_to_all',
				type: 'boolean',
				default: false,
				description: 'Option to show project budget to all employees. Does not apply to Total Project Fee projects. Defaults to false.',
			},
			{
				displayName: 'Starts On',
				name: 'starts_on',
				type: 'dateTime',
				default: '',
				description: 'Date the project was started.',
			},
		],
	},

];


import Vue from 'vue';

import { IconDefinition, library } from '@fortawesome/fontawesome-svg-core';
import {
	faAngleDoubleLeft,
	faAngleDown,
	faAngleRight,
	faAngleUp,
	faArrowLeft,
	faArrowRight,
	faAt,
	faBook,
	faBoxOpen,
	faBug,
	faCalendar,
	faCheck,
	faCheckCircle,
	faChevronDown,
	faChevronUp,
	faChevronLeft,
	faChevronRight,
	faCode,
	faCodeBranch,
	faCog,
	faCogs,
	faClock,
	faClone,
	faCloud,
	faCloudDownloadAlt,
	faCopy,
	faCut,
	faDotCircle,
	faEdit,
	faEnvelope,
	faEye,
	faExclamationTriangle,
	faExpand,
	faExternalLinkAlt,
	faExchangeAlt,
	faFile,
	faFileArchive,
	faFileCode,
	faFileDownload,
	faFileExport,
	faFileImport,
	faFilePdf,
	faFolderOpen,
	faGift,
	faHdd,
	faHome,
	faHourglass,
	faImage,
	faInbox,
	faInfo,
	faInfoCircle,
	faKey,
	faMapSigns,
	faNetworkWired,
	faPause,
	faPauseCircle,
	faPen,
	faPlay,
	faPlayCircle,
	faPlus,
	faPlusCircle,
	faQuestion,
	faQuestionCircle,
	faRedo,
	faRss,
	faSave,
	faSearch,
	faSearchMinus,
	faSearchPlus,
	faServer,
	faSignInAlt,
	faSlidersH,
	faSpinner,
	faStop,
	faSun,
	faSync,
	faSyncAlt,
	faTable,
	faTasks,
	faTerminal,
	faThLarge,
	faTimes,
	faTrash,
	faUndo,
	faUsers,
} from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';

function addIcon(icon: any) { // tslint:disable-line:no-any
	library.add(icon as IconDefinition);
}

addIcon(faAngleDoubleLeft);
addIcon(faAngleDown);
addIcon(faAngleRight);
addIcon(faAngleUp);
addIcon(faArrowLeft);
addIcon(faArrowRight);
addIcon(faAt);
addIcon(faBook);
addIcon(faBoxOpen);
addIcon(faBug);
addIcon(faCalendar);
addIcon(faCheck);
addIcon(faCheckCircle);
addIcon(faChevronLeft);
addIcon(faChevronRight);
addIcon(faChevronDown);
addIcon(faChevronUp);
addIcon(faCode);
addIcon(faCodeBranch);
addIcon(faCog);
addIcon(faCogs);
addIcon(faClock);
addIcon(faClone);
addIcon(faCloud);
addIcon(faCloudDownloadAlt);
addIcon(faCopy);
addIcon(faCut);
addIcon(faDotCircle);
addIcon(faEdit);
addIcon(faEnvelope);
addIcon(faEye);
addIcon(faExclamationTriangle);
addIcon(faExpand);
addIcon(faExternalLinkAlt);
addIcon(faExchangeAlt);
addIcon(faFile);
addIcon(faFileArchive);
addIcon(faFileCode);
addIcon(faFileDownload);
addIcon(faFileExport);
addIcon(faFileImport);
addIcon(faFilePdf);
addIcon(faFolderOpen);
addIcon(faGift);
addIcon(faHdd);
addIcon(faHome);
addIcon(faHourglass);
addIcon(faImage);
addIcon(faInbox);
addIcon(faInfo);
addIcon(faInfoCircle);
addIcon(faKey);
addIcon(faMapSigns);
addIcon(faNetworkWired);
addIcon(faPause);
addIcon(faPauseCircle);
addIcon(faPen);
addIcon(faPlay);
addIcon(faPlayCircle);
addIcon(faPlus);
addIcon(faPlusCircle);
addIcon(faQuestion);
addIcon(faQuestionCircle);
addIcon(faRedo);
addIcon(faRss);
addIcon(faSave);
addIcon(faSearch);
addIcon(faSearchMinus);
addIcon(faSearchPlus);
addIcon(faServer);
addIcon(faSignInAlt);
addIcon(faSlidersH);
addIcon(faSpinner);
addIcon(faStop);
addIcon(faSun);
addIcon(faSync);
addIcon(faSyncAlt);
addIcon(faTable);
addIcon(faTasks);
addIcon(faTerminal);
addIcon(faThLarge);
addIcon(faTimes);
addIcon(faTrash);
addIcon(faUndo);
addIcon(faUsers);

Vue.component('font-awesome-icon', FontAwesomeIcon);


setCredentialTypes: (state: ICredentialsState, credentialTypes: ICredentialType[]) => {


import {
	INodeProperties,
} from 'n8n-workflow';

export const coinOperations: INodeProperties[] = [
	{
		displayName: 'Operation',
		name: 'operation',
		type: 'options',
		displayOptions: {
			show: {
				resource: [
					'coin',
				],
			},
		},
		options: [
			{
				name: 'Candlestick',
				value: 'candlestick',
				description: 'Get a candlestick open-high-low-close chart for the selected currency',
			},
			{
				name: 'Get',
				value: 'get',
				description: 'Get current data for a coin',
			},
			{
				name: 'Get All',
				value: 'getAll',
				description: 'Get all coins',
			},
			{
				name: 'History',
				value: 'history',
				description: 'Get historical data (name, price, market, stats) at a given date for a coin',
			},
			{
				name: 'Market',
				value: 'market',
				description: 'Get prices and market related data for all trading pairs that match the selected currency',
			},
			{
				name: 'Market Chart',
				value: 'marketChart',
				description: 'Get historical market data include price, market cap, and 24h volume (granularity auto)',
			},
			{
				name: 'Price',
				value: 'price',
				description: 'Get the current price of any cryptocurrencies in any other supported currencies that you need',
			},
			{
				name: 'Ticker',
				value: 'ticker',
				description: 'Get coin tickers',
			},
		],
		default: 'getAll',
	},
];

export const coinFields: INodeProperties[] = [
	{
		displayName: 'Search By',
		name: 'searchBy',
		required: true,
		type: 'options',
		options: [
			{
				name: 'Coin ID',
				value: 'coinId',
			},
			{
				name: 'Contract address',
				value: 'contractAddress',
			},
		],
		displayOptions: {
			show: {
				operation: [
					'get',
					'marketChart',
					'price',
				],
				resource: [
					'coin',
				],
			},
		},
		default: 'coinId',
		description: 'Search by coin ID or contract address.',
	},
	{
		displayName: 'Coin ID',
		name: 'coinId',
		required: true,
		type: 'options',
		typeOptions: {
			loadOptionsMethod: 'getCoins',
		},
		displayOptions: {
			show: {
				operation: [
					'get',
				],
				resource: [
					'coin',
				],
			},
		},
		default: '',
		placeholder: 'bitcoin',
		description: 'Coin ID',
	},
	{
		displayName: 'Base Currency',
		name: 'baseCurrency',
		required: true,
		type: 'options',
		typeOptions: {
			loadOptionsMethod: 'getCoins',
		},
		displayOptions: {
			show: {

				operation: [
					'candlestick',
				],
				resource: [
					'coin',
				],
			},
		},
		default: '',
		description: 'The first currency in the pair. For BTC:ETH this is BTC.',
	},
	{
		displayName: 'Base Currency',
		name: 'baseCurrency',
		required: true,
		type: 'options',
		typeOptions: {
			loadOptionsMethod: 'getCurrencies',
		},
		displayOptions: {
			show: {

				operation: [
					'market',
				],
				resource: [
					'coin',
				],
			},
		},
		default: '',
		description: 'The first currency in the pair. For BTC:ETH this is BTC.',
	},
	{
		displayName: 'Coin ID',
		name: 'coinId',
		required: true,
		type: 'options',
		typeOptions: {
			loadOptionsMethod: 'getCoins',
		},
		displayOptions: {
			show: {
				operation: [
					'ticker',
					'history',
				],
				resource: [
					'coin',
				],
			},
		},
		default: '',
		placeholder: 'bitcoin',
		description: 'Coin ID',
	},
	{
		displayName: 'Base Currencies',
		name: 'baseCurrencies',
		required: true,
		type: 'multiOptions',
		typeOptions: {
			loadOptionsMethod: 'getCoins',
		},
		displayOptions: {
			show: {
				operation: [
					'price',
				],
				resource: [
					'coin',
				],
				searchBy: [
					'coinId',
				],
			},
		},
		default: [],
		placeholder: 'bitcoin',
		description: 'The first currency in the pair. For BTC:ETH this is BTC.',
	},
	{
		displayName: 'Platform ID',
		name: 'platformId',
		required: true,
		displayOptions: {
			show: {
				operation: [
					'get',
					'marketChart',
					'price',
				],
				resource: [
					'coin',
				],
				searchBy: [
					'contractAddress',
				],
			},
		},
		type: 'options',
		options: [
			{
				name: 'Ethereum',
				value: 'ethereum',
			},
		],
		default: 'ethereum',
		description: 'The id of the platform issuing tokens.',
	},
	{
		displayName: 'Contract address',
		name: 'contractAddress',
		required: true,
		type: 'string',
		default: '',
		displayOptions: {
			show: {
				operation: [
					'get',
					'marketChart',
				],
				resource: [
					'coin',
				],
				searchBy: [
					'contractAddress',
				],
			},
		},
		description: 'Token\'s contract address.',
	},
	{
		displayName: 'Contract addresses',
		name: 'contractAddresses',
		required: true,
		type: 'string',
		default: '',
		displayOptions: {
			show: {
				operation: [
					'price',
				],
				resource: [
					'coin',
				],
				searchBy: [
					'contractAddress',
				],
			},
		},
		description: 'The contract address of tokens, comma separated.',
	},
	{
		displayName: 'Base Currency',
		name: 'baseCurrency',
		required: true,
		type: 'options',
		typeOptions: {
			loadOptionsMethod: 'getCoins',
		},
		displayOptions: {
			show: {
				operation: [
					'marketChart',
				],
				resource: [
					'coin',
				],
				searchBy: [
					'coinId',
				],
			},
			hide: {
				searchBy: [
					'contractAddress',
				],
			},
		},
		default: '',
		description: 'The first currency in the pair. For BTC:ETH this is BTC.',
	},
	{
		displayName: 'Quote Currency',
		name: 'quoteCurrency',
		required: true,
		type: 'options',
		typeOptions: {
			loadOptionsMethod: 'getCurrencies',
		},
		displayOptions: {
			show: {
				operation: [
					'candlestick',
					'marketChart',
				],
				resource: [
					'coin',
				],
			},
		},
		default: '',
		description: 'The second currency in the pair. For BTC:ETH this is ETH.',
	},
	{
		displayName: 'Quote Currencies',
		name: 'quoteCurrencies',
		type: 'multiOptions',
		typeOptions: {
			loadOptionsMethod: 'getCurrencies',
		},
		required: true,
		displayOptions: {
			show: {
				operation: [
					'price',
				],
				resource: [
					'coin',
				],
			},
		},
		default: [],
		description: 'The second currency in the pair. For BTC:ETH this is ETH.',
	},
	{
		displayName: 'Range (days)',
		name: 'days',
		required: true,
		type: 'options',
		options: [
			{
				name: '1',
				value: '1',
			},
			{
				name: '7',
				value: '7',
			},
			{
				name: '14',
				value: '14',
			},
			{
				name: '30',
				value: '30',
			},
			{
				name: '90',
				value: '90',
			},
			{
				name: '180',
				value: '180',
			},
			{
				name: '365',
				value: '365',
			},
			{
				name: 'Max',
				value: 'max',
			},
		],
		displayOptions: {
			show: {
				operation: [
					'marketChart',
					'candlestick',
				],
				resource: [
					'coin',
				],
			},
		},
		default: '',
		description: 'Return data for this many days in the past from now.',
	},
	{
		displayName: 'Date',
		name: 'date',
		required: true,
		type: 'dateTime',
		displayOptions: {
			show: {
				operation: [
					'history',
				],
				resource: [
					'coin',
				],
			},
		},
		default: '',
		description: 'The date of data snapshot.',
	},
	{
		displayName: 'Return All',
		name: 'returnAll',
		type: 'boolean',
		displayOptions: {
			show: {
				operation: [
					'getAll',
					'market',
					'ticker',
				],
				resource: [
					'coin',
				],
			},
		},
		default: false,
		description: 'If all results should be returned or only up to a given limit.',
	},
	{
		displayName: 'Limit',
		name: 'limit',
		type: 'number',
		displayOptions: {
			show: {
				operation: [
					'getAll',
					'market',
					'ticker',
				],
				resource: [
					'coin',
				],
				returnAll: [
					false,
				],
			},
		},
		typeOptions: {
			minValue: 1,
			maxValue: 500,
		},
		default: 100,
		description: 'How many results to return.',
	},
	{
		displayName: 'Options',
		name: 'options',
		type: 'collection',
		placeholder: 'Add Option',
		default: {},
		displayOptions: {
			show: {
				resource: [
					'coin',
				],
				operation: [
					'market',
				],
			},
		},
		options: [
			{
				displayName: 'Coin IDs',
				name: 'ids',
				type: 'string',
				placeholder: 'bitcoin',
				default: '',
				description: 'Filter results by comma separated list of coin ID.',
			},
			{
				displayName: 'Category',
				name: 'category',
				type: 'options',
				options: [
					{
						name: 'Decentralized Finance Defi',
						value: 'decentralized_finance_defi',
					},
				],
				default: 'decentralized_finance_defi',
				description: 'Filter by coin category.',
			},
			{
				displayName: 'Order',
				name: 'order',
				type: 'options',
				options: [
					{
						name: 'Market Cap Desc',
						value: 'market_cap_desc',
					},
					{
						name: 'Gecko Desc',
						value: 'gecko_desc',
					},
					{
						name: 'Gecko Asc',
						value: 'gecko_asc',
					},
					{
						name: 'Market Cap Asc',
						value: 'market_cap_asc',
					},
					{
						name: 'Market Cap Desc',
						value: 'market_cap_desc',
					},
					{
						name: 'Volume Asc',
						value: 'volume_asc',
					},
					{
						name: 'Volume Desc',
						value: 'volume_desc',
					},
					{
						name: 'Id Asc',
						value: 'id_asc',
					},
					{
						name: 'Id Desc',
						value: 'id_desc',
					},
				],
				default: '',
				description: 'Sort results by field.',
			},
			{
				displayName: 'Sparkline',
				name: 'sparkline',
				type: 'boolean',
				default: false,
				description: 'Include sparkline 7 days data.',
			},
			{
				displayName: 'Price Change Percentage',
				name: 'price_change_percentage',
				type: 'multiOptions',
				options: [
					{
						name: '1h',
						value: '1h',
					},
					{
						name: '24h',
						value: '24h',
					},
					{
						name: '7d',
						value: '7d',
					},
					{
						name: '14d',
						value: '14d',
					},
					{
						name: '30d',
						value: '30d',
					},
					{
						name: '200d',
						value: '200d',
					},
					{
						name: '1y',
						value: '1y',
					},
				],
				default: [],
				description: 'Include price change percentage for specified times.',
			},
		],
	},
	{
		displayName: 'Options',
		name: 'options',
		type: 'collection',
		placeholder: 'Add Field',
		default: {},
		displayOptions: {
			show: {
				resource: [
					'coin',
				],
				operation: [
					'price',
				],
			},
		},
		options: [
			{
				displayName: 'Include 24hr Change',
				name: 'include_24hr_change',
				type: 'boolean',
				default: false,
			},
			{
				displayName: 'Include 24hr Vol',
				name: 'include_24hr_vol',
				type: 'boolean',
				default: false,
			},
			{
				displayName: 'Include Last Updated At',
				name: 'include_last_updated_at',
				type: 'boolean',
				default: false,
			},
			{
				displayName: 'Include Market Cap',
				name: 'include_market_cap',
				type: 'boolean',
				default: false,
			},
		],
	},
	{
		displayName: 'Options',
		name: 'options',
		type: 'collection',
		placeholder: 'Add Option',
		default: {},
		displayOptions: {
			show: {
				resource: [
					'coin',
				],
				operation: [
					'ticker',
				],
			},
		},
		options: [
			{
				displayName: 'Exchange IDs',
				name: 'exchange_ids',
				type: 'multiOptions',
				typeOptions: {
					loadOptionsMethod: 'getExchanges',
				},
				default: [],
				description: 'Filter results by exchange IDs.',
			},
			{
				displayName: 'Include Exchange Logo',
				name: 'include_exchange_logo',
				type: 'boolean',
				default: false,
				description: 'Include exchange logo.',
			},
			{
				displayName: 'Order',
				name: 'order',
				type: 'options',
				options: [
					{
						name: 'Trust Score Desc',
						value: 'trust_score_desc',
					},
					{
						name: 'Trust Score Asc',
						value: 'trust_score_asc',
					},
					{
						name: 'Volume Desc',
						value: 'volume_desc',
					},
				],
				default: 'trust_score_desc',
				description: 'Sorts results by the selected rule.',
			},
		],
	},
	{
		displayName: 'Options',
		name: 'options',
		type: 'collection',
		placeholder: 'Add Option',
		default: {},
		displayOptions: {
			show: {
				resource: [
					'coin',
				],
				operation: [
					'history',
				],
			},
		},
		options: [
			{
				displayName: 'Localization',
				name: 'localization',
				type: 'boolean',
				default: true,
				description: 'Set to false to exclude localized languages in response.',
			},
		],
	},
	{
		displayName: 'Options',
		name: 'options',
		type: 'collection',
		placeholder: 'Add Field',
		default: {},
		displayOptions: {
			show: {
				operation: [
					'get',
				],
				resource: [
					'coin',
				],
			},
		},
		options: [
			{
				displayName: 'Community data',
				name: 'community_data',
				type: 'boolean',
				default: false,
				description: 'Include community data.',
			},
			{
				displayName: 'Developer data',
				name: 'developer_data',
				type: 'boolean',
				default: false,
				description: 'Include developer data.',
			},
			{
				displayName: 'Localization',
				name: 'localization',
				type: 'boolean',
				default: false,
				description: 'Include all localized languages in response.',
			},
			{
				displayName: 'Market data',
				name: 'market_data',
				type: 'boolean',
				default: false,
				description: 'Include market data.',
			},
			{
				displayName: 'Sparkline',
				name: 'sparkline',
				type: 'boolean',
				default: false,
				description: 'Include sparkline 7 days data (eg. true, false).',
			},
			{
				displayName: 'Tickers',
				name: 'tickers',
				type: 'boolean',
				default: false,
				description: 'Include tickers data.',
			},
		],
	},
];


upsertCredential(state: ICredentialsState, credential: ICredentialsResponse) {

keep keep keep keep add keep keep keep keep


import {
	INodeProperties,
} from 'n8n-workflow';

export const projectOperations: INodeProperties[] = [
	{
		displayName: 'Operation',
		name: 'operation',
		type: 'options',
		displayOptions: {
			show: {
				resource: [
					'project',
				],
			},
		},
		options: [
			{
				name: 'Create',
				value: 'create',
				description: 'Create a project',
			},
			{
				name: 'Delete',
				value: 'delete',
				description: 'Delete a project',
			},
			{
				name: 'Get',
				value: 'get',
				description: 'Get a project',
			},
			{
				name: 'Get All',
				value: 'getAll',
				description: 'Get all projects',
			},
			{
				name: 'Update',
				value: 'update',
				description: 'Update a project',
			},
		],
		default: 'create',
		description: 'The operation to perform.',
	},
];

export const projectFields: INodeProperties[] = [

	/* -------------------------------------------------------------------------- */
	/*                                 project:create                             */
	/* -------------------------------------------------------------------------- */
	{
		displayName: 'Project Name',
		name: 'name',
		type: 'string',
		required: true,
		default: '',
		description: 'Name of project being created.',
		displayOptions: {
			show: {
				resource: [
					'project',
				],
				operation: [
					'create',
				],
			},
		},
	},
	{
		displayName: 'Additional Fields',
		name: 'additionalFields',
		type: 'collection',
		placeholder: 'Add Field',
		displayOptions: {
			show: {
				operation: [
					'create',
				],
				resource: [
					'project',
				],
			},
		},
		default: {},
		options: [
			{
				displayName: 'Billable',
				name: 'billable',
				type: 'boolean',
				default: true,
			},
			{
				displayName: 'Color',
				name: 'color',
				type: 'color',
				default: '#0000FF',
			},
			{
				displayName: 'Client ID',
				name: 'clientId',
				type: 'options',
				typeOptions: {
					loadOptionsDependsOn: [
						'workspaceId',
					],
					loadOptionsMethod: 'loadClientsForWorkspace',
				},
				default: '',
			},
			{
				displayName: 'Estimate',
				name: 'estimateUi',
				placeholder: 'Add Estimate',
				type: 'fixedCollection',
				default: {},
				typeOptions: {
					multipleValues: false,
				},
				options: [
					{
						displayName: 'Estimate',
						name: 'estimateValues',
						values: [
							{
								displayName: 'Estimate',
								name: 'estimate',
								type: 'number',
								default: 0,
							},
							{
								displayName: 'Type',
								name: 'type',
								type: 'options',
								options: [
									{
										name: 'Auto',
										value: 'AUTO',
									},
									{
										name: 'Manual',
										value: 'MANUAL',
									},
								],
								default: 'AUTO',
							},
						],
					},
				],
			},
			{
				displayName: 'Is Public',
				name: 'isPublic',
				type: 'boolean',
				default: true,
			},
			{
				displayName: 'Note',
				name: 'note',
				type: 'string',
				default: '',
				description: 'Note about the project',
			},
		],
	},
	/* -------------------------------------------------------------------------- */
	/*                                 project:delete                             */
	/* -------------------------------------------------------------------------- */
	{
		displayName: 'Project ID',
		name: 'projectId',
		type: 'string',
		required: true,
		default: '',
		displayOptions: {
			show: {
				resource: [
					'project',
				],
				operation: [
					'delete',
				],
			},
		},
	},
	/* -------------------------------------------------------------------------- */
	/*                                 project:get                                */
	/* -------------------------------------------------------------------------- */
	{
		displayName: 'Project ID',
		name: 'projectId',
		type: 'string',
		required: true,
		default: '',
		displayOptions: {
			show: {
				resource: [
					'project',
				],
				operation: [
					'get',
				],
			},
		},
	},
	/* -------------------------------------------------------------------------- */
	/*                                 project:getAll                             */
	/* -------------------------------------------------------------------------- */
	{
		displayName: 'Return All',
		name: 'returnAll',
		type: 'boolean',
		displayOptions: {
			show: {
				operation: [
					'getAll',
				],
				resource: [
					'project',
				],
			},
		},
		default: false,
		description: 'If all results should be returned or only up to a given limit.',
	},
	{
		displayName: 'Limit',
		name: 'limit',
		type: 'number',
		displayOptions: {
			show: {
				operation: [
					'getAll',
				],
				resource: [
					'project',
				],
				returnAll: [
					false,
				],
			},
		},
		typeOptions: {
			minValue: 1,
			maxValue: 500,
		},
		default: 100,
		description: 'How many results to return.',
	},
	{
		displayName: 'Additional Fields',
		name: 'additionalFields',
		type: 'collection',
		placeholder: 'Add Field',
		displayOptions: {
			show: {
				operation: [
					'getAll',
				],
				resource: [
					'project',
				],
			},
		},
		default: {},
		options: [
			{
				displayName: 'Archived',
				name: 'archived',
				type: 'boolean',
				default: true,
			},
			{
				displayName: 'Billable',
				name: 'billable',
				type: 'boolean',
				default: true,
			},
			{
				displayName: 'Client IDs',
				name: 'clients',
				type: 'multiOptions',
				typeOptions: {
					loadOptionsDependsOn: [
						'workspaceId',
					],
					loadOptionsMethod: 'loadClientsForWorkspace',
				},
				default: [],
			},
			{
				displayName: 'Contains Client',
				name: 'contains-client',
				type: 'boolean',
				default: false,
				description: 'If provided, projects will be filtered by whether they have a client.;			',
			},
			{
				displayName: 'Client Status',
				name: 'client-status',
				type: 'options',
				options: [
					{
						name: 'Active',
						value: 'ACTIVE',
					},
					{
						name: 'Archived',
						value: 'ARCHIVED',
					},
				],
				default: '',
				description: 'If provided, projects will be filtered by whether they have a client.',
			},
			{
				displayName: 'Contains User',
				name: 'contains-user',
				type: 'boolean',
				default: false,
				description: 'If provided, projects will be filtered by whether they have users.',
			},
			{
				displayName: 'Is Template',
				name: 'is-template',
				type: 'boolean',
				default: false,
				description: 'If provided, projects will be filtered by whether they are used as a template.',
			},
			{
				displayName: 'Name',
				name: 'name',
				type: 'string',
				default: '',
			},
			{
				displayName: 'Sort Column',
				name: 'sort-column',
				type: 'options',
				options: [
					{
						name: 'Name',
						value: 'NAME',
					},
					{
						name: 'Client Name',
						value: 'CLIENT_NAME',
					},
					{
						name: 'Duration',
						value: 'DURATION',
					},
				],
				default: '',
			},
			{
				displayName: 'Sort Order',
				name: 'sort-order',
				type: 'options',
				options: [
					{
						name: 'Ascending',
						value: 'ASCENDING',
					},
					{
						name: 'Descending',
						value: 'DESCENDING',
					},
				],
				default: '',
			},
			{
				displayName: 'User IDs',
				name: 'users',
				type: 'options',
				typeOptions: {
					loadOptionsDependsOn: [
						'workspaceId',
					],
					loadOptionsMethod: 'loadUsersForWorkspace',
				},
				default: '',
			},
			{
				displayName: 'User Status',
				name: 'user-status',
				type: 'options',
				options: [
					{
						name: 'Active',
						value: 'ACTIVE',
					},
					{
						name: 'Archived',
						value: 'ARCHIVED',
					},
				],
				default: '',
				description: 'If provided, projects will be filtered by whether they have a client.',
			},
		],
	},

	/* -------------------------------------------------------------------------- */
	/*                                 project:update                             */
	/* -------------------------------------------------------------------------- */
	{
		displayName: 'Project ID',
		name: 'projectId',
		type: 'string',
		required: true,
		default: '',
		displayOptions: {
			show: {
				resource: [
					'project',
				],
				operation: [
					'update',
				],
			},
		},
	},
	{
		displayName: 'Update Fields',
		name: 'updateFields',
		type: 'collection',
		placeholder: 'Add Field',
		displayOptions: {
			show: {
				operation: [
					'update',
				],
				resource: [
					'project',
				],
			},
		},
		default: {},
		options: [
			{
				displayName: 'Billable',
				name: 'billable',
				type: 'boolean',
				default: true,
			},
			{
				displayName: 'Color',
				name: 'color',
				type: 'color',
				default: '#0000FF',
			},
			{
				displayName: 'Client ID',
				name: 'clientId',
				type: 'options',
				typeOptions: {
					loadOptionsDependsOn: [
						'workspaceId',
					],
					loadOptionsMethod: 'loadClientsForWorkspace',
				},
				default: '',
			},
			{
				displayName: 'Estimate',
				name: 'estimateUi',
				placeholder: 'Add Estimate',
				type: 'fixedCollection',
				default: {},
				typeOptions: {
					multipleValues: false,
				},
				options: [
					{
						displayName: 'Estimate',
						name: 'estimateValues',
						values: [
							{
								displayName: 'Estimate',
								name: 'estimate',
								type: 'number',
								default: 0,
							},
							{
								displayName: 'Type',
								name: 'type',
								type: 'options',
								options: [
									{
										name: 'Auto',
										value: 'AUTO',
									},
									{
										name: 'Manual',
										value: 'MANUAL',
									},
								],
								default: 'AUTO',
							},
						],
					},
				],
			},
			{
				displayName: 'Is Public',
				name: 'isPublic',
				type: 'boolean',
				default: false,
			},
			{
				displayName: 'Name',
				name: 'name',
				type: 'string',
				default: '',
			},
			{
				displayName: 'Note',
				name: 'note',
				type: 'string',
				default: '',
				description: 'Note about the project',
			},
		],
	},

];


import {
	ICredentialType,
	INodeProperties,
} from 'n8n-workflow';

export class MatrixApi implements ICredentialType {
	name = 'matrixApi';
	displayName = 'Matrix API';
	documentationUrl = 'matrix';
	properties: INodeProperties[] = [
		{
			displayName: 'Access Token',
			name: 'accessToken',
			type: 'string',
			default: '',
		},
		{
			displayName: 'Homeserver URL',
			name: 'homeserverUrl',
			type: 'string',
			default: 'https://matrix-client.matrix.org',
		},
	];
}


import {
	FileProperties,
} from '../../Interfaces';

export const fileDownloadDescription: FileProperties = [
	{
		displayName: 'File ID',
		name: 'fileId',
		type: 'string',
		required: true,
		displayOptions: {
			show: {
				operation: [
					'download',
				],
				resource: [
					'file',
				],
			},
		},
		default: '',
		description: 'ID of the file',
	},
	{
		displayName: 'Put Output In Field',
		name: 'output',
		type: 'string',
		default: 'data',
		required: true,
		description: 'The name of the output field to put the binary file data in',
		displayOptions: {
			show: {
				operation: [
					'download',
				],
				resource: [
					'file',
				],
			},
		},
	},
];


const credentialTypes = await getCredentialTypes(context.rootGetters.getRestApiContext);

fetchAllCredentials: async (context: ActionContext<ICredentialsState, IRootState>) => {

if (context.getters.allCredentials.length > 0) {

const credentials = await getAllCredentials(context.rootGetters.getRestApiContext);

keep keep keep keep replace keep keep keep


import {
	INodeProperties,
 } from 'n8n-workflow';

export const contactOperations: INodeProperties[] = [
	{
		displayName: 'Operation',
		name: 'operation',
		type: 'options',
		displayOptions: {
			show: {
				resource: [
					'contact',
				],
			},
		},
		options: [
			{
				name: 'Create/Update',
				value: 'upsert',
				description: 'Create/update a contact',
			},
			{
				name: 'Delete',
				value: 'delete',
				description: 'Delete an contact',
			},
			{
				name: 'Get',
				value: 'get',
				description: 'Retrieve an contact',
			},
			{
				name: 'Get All',
				value: 'getAll',
				description: 'Retrieve all contacts',
			},
		],
		default: 'upsert',
		description: 'The operation to perform.',
	},
];

export const contactFields: INodeProperties[] = [

/* -------------------------------------------------------------------------- */
/*                                 contact:upsert                             */
/* -------------------------------------------------------------------------- */
	{
		displayName: 'Duplicate Option',
		name: 'duplicateOption',
		required: true,
		type: 'options',
		options: [
			{
				name: 'Email',
				value: 'email',
			},
			{
				name: 'Email And Name',
				value: 'emailAndName',
			},
		],
		displayOptions: {
			show: {
				operation: [
					'upsert',
				],
				resource: [
					'contact',
				],
			},
		},
		default: 'email',
		description: `Performs duplicate checking by one of the following options: Email, EmailAndName. If a match is found using the option provided, the existing contact will be updated`,
	},
	{
		displayName: 'Additional Fields',
		name: 'additionalFields',
		type: 'collection',
		placeholder: 'Add Field',
		default: {},
		displayOptions: {
			show: {
				operation: [
					'upsert',
				],
				resource: [
					'contact',
				],
			},
		},
		options: [
			{
				displayName: 'Anniversary',
				name: 'anniversary',
				type: 'dateTime',
				default: '',
			},
			{
				displayName: 'Company ID',
				name: 'companyId',
				type: 'number',
				typeOptions: {
					minValue: 0,
				},
				default: 0,
			},
			{
				displayName: 'Contact Type',
				name: 'contactType',
				type: 'options',
				typeOptions: {
					loadOptionsMethod: 'getContactTypes',
				},
				default: '',
			},
			{
				displayName: 'Family Name',
				name: 'familyName',
				type: 'string',
				default: '',
			},
			{
				displayName: 'Given Name',
				name: 'givenName',
				type: 'string',
				default: '',
			},
			{
				displayName: 'IP Address',
				name: 'ipAddress',
				type: 'string',
				default: '',
			},
			{
				displayName: 'Job Title',
				name: 'jobTitle',
				type: 'string',
				default: '',
			},
			{
				displayName: 'Lead Source ID',
				name: 'leadSourceId',
				type: 'number',
				default: 0,
			},
			{
				displayName: 'Middle Name',
				name: 'middleName',
				type: 'string',
				default: '',
			},
			{
				displayName: 'Opt In Reason',
				name: 'optInReason',
				type: 'string',
				default: '',
			},
			{
				displayName: 'Owner ID',
				name: 'ownerId',
				type: 'options',
				typeOptions: {
					loadOptionsMethod: 'getUsers',
				},
				default: '',
			},
			{
				displayName: 'Preferred Locale',
				name: 'preferredLocale',
				type: 'string',
				placeholder: 'en',
				default: '',
			},
			{
				displayName: 'Preferred Name',
				name: 'preferredName',
				type: 'string',
				default: '',
			},
			{
				displayName: 'Source Type',
				name: 'sourceType',
				type: 'options',
				options: [
					{
						name: 'API',
						value: 'API',
					},
					{
						name: 'Import',
						value: 'IMPORT',
					},
					{
						name: 'Landing Page',
						value: 'LANDINGPAGE',
					},
					{
						name: 'Manual',
						value: 'MANUAL',
					},
					{
						name: 'Other',
						value: 'OTHER',
					},
					{
						name: 'Unknown',
						value: 'UNKNOWN',
					},
				],
				default: '',
			},
			{
				displayName: 'Spouse Name',
				name: 'spouseName',
				type: 'string',
				default: '',
			},
			{
				displayName: 'Timezone',
				name: 'timezone',
				type: 'options',
				typeOptions: {
					loadOptionsMethod: 'getTimezones',
				},
				default: '',
			},
			{
				displayName: 'Website',
				name: 'website',
				type: 'string',
				default: '',
			},
		],
	},
	{
		displayName: 'Addresses',
		name: 'addressesUi',
		type: 'fixedCollection',
		typeOptions: {
			multipleValues: true,
		},
		default: '',
		placeholder: 'Add Address',
		displayOptions: {
			show: {
				resource: [
					'contact',
				],
				operation: [
					'upsert',
				],
			},
		},
		options: [
			{
				name: 'addressesValues',
				displayName: 'Address',
				values: [
					{
						displayName: 'Field',
						name: 'field',
						type: 'options',
						options: [
							{
								name: 'Billing',
								value: 'BILLING',
							},
							{
								name: 'Shipping',
								value: 'SHIPPING',
							},
							{
								name: 'Other',
								value: 'OTHER',
							},
						],
						default: '',
					},
					{
						displayName: 'Country Code',
						name: 'countryCode',
						type: 'options',
						typeOptions: {
							loadOptionsMethod: 'getCountries',
						},
						default: '',
					},
					{
						displayName: 'Line 1',
						name: 'line1',
						type: 'string',
						default: '',
					},
					{
						displayName: 'Line 2',
						name: 'line2',
						type: 'string',
						default: '',
					},
					{
						displayName: 'Locality',
						name: 'locality',
						type: 'string',
						default: '',
					},
					{
						displayName: 'Postal Code',
						name: 'postalCode',
						type: 'string',
						default: '',
					},
					{
						displayName: 'Region',
						name: 'region',
						type: 'string',
						default: '',
					},
					{
						displayName: 'Zip Code',
						name: 'zipCode',
						type: 'string',
						default: '',
					},
					{
						displayName: 'Zip Four',
						name: 'zipFour',
						type: 'string',
						default: '',
					},
				],
			},
		],
	},
	{
		displayName: 'Emails',
		name: 'emailsUi',
		type: 'fixedCollection',
		default: {},
		typeOptions: {
			multipleValues: true,
		},
		placeholder: 'Add Email',
		displayOptions: {
			show: {
				resource: [
					'contact',
				],
				operation: [
					'upsert',
				],
			},
		},
		options: [
			{
				name: 'emailsValues',
				displayName: 'Email',
				values: [
					{
						displayName: 'Field',
						name: 'field',
						type: 'options',
						options: [
							{
								name: 'Email 1',
								value: 'EMAIL1',
							},
							{
								name: 'Email 2',
								value: 'EMAIL2',
							},
							{
								name: 'Email 3',
								value: 'EMAIL3',
							},
						],
						default: '',
					},
					{
						displayName: 'Email',
						name: 'email',
						type: 'string',
						default: '',
					},
				],
			},
		],
	},
	{
		displayName: 'Faxes',
		name: 'faxesUi',
		type: 'fixedCollection',
		default: {},
		typeOptions: {
			multipleValues: true,
		},
		placeholder: 'Add Fax',
		displayOptions: {
			show: {
				resource: [
					'contact',
				],
				operation: [
					'upsert',
				],
			},
		},
		options: [
			{
				name: 'faxesValues',
				displayName: 'Fax',
				values: [
					{
						displayName: 'Field',
						name: 'field',
						type: 'options',
						options: [
							{
								name: 'Fax 1',
								value: 'FAX1',
							},
							{
								name: 'Fax 2',
								value: 'FAX2',
							},
						],
						default: '',
					},
					{
						displayName: 'Number',
						name: 'number',
						type: 'string',
						default: '',
					},
				],
			},
		],
	},
	{
		displayName: 'Phones',
		name: 'phonesUi',
		type: 'fixedCollection',
		default: {},
		typeOptions: {
			multipleValues: true,
		},
		placeholder: 'Add Phone',
		displayOptions: {
			show: {
				resource: [
					'contact',
				],
				operation: [
					'upsert',
				],
			},
		},
		options: [
			{
				name: 'phonesValues',
				displayName: 'Phones',
				values: [
					{
						displayName: 'Field',
						name: 'field',
						type: 'options',
						options: [
							{
								name: 'Phone 1',
								value: 'PHONE1',
							},
							{
								name: 'Phone 2',
								value: 'PHONE2',
							},
							{
								name: 'Phone 3',
								value: 'PHONE3',
							},
							{
								name: 'Phone 4',
								value: 'PHONE4',
							},
							{
								name: 'Phone 5',
								value: 'PHONE5',
							},
						],
						default: '',
					},
					{
						displayName: 'Number',
						name: 'number',
						type: 'string',
						default: '',
					},
				],
			},
		],
	},
	{
		displayName: 'Social Accounts',
		name: 'socialAccountsUi',
		type: 'fixedCollection',
		typeOptions: {
			multipleValues: true,
		},
		default: '',
		placeholder: 'Add Social Account',
		displayOptions: {
			show: {
				resource: [
					'contact',
				],
				operation: [
					'upsert',
				],
			},
		},
		options: [
			{
				name: 'socialAccountsValues',
				displayName: 'Social Account',
				values: [
					{
						displayName: 'Type',
						name: 'type',
						type: 'options',
						options: [
							{
								name: 'Facebook',
								value: 'Facebook',
							},
							{
								name: 'Twitter',
								value: 'Twitter',
							},
							{
								name: 'LinkedIn',
								value: 'LinkedIn',
							},
						],
						default: '',
					},
					{
						displayName: 'Name',
						name: 'name',
						type: 'string',
						default: '',
					},
				],
			},
		],
	},
/* -------------------------------------------------------------------------- */
/*                                 contact:delete                             */
/* -------------------------------------------------------------------------- */
	{
		displayName: 'Contact ID',
		name: 'contactId',
		type: 'string',
		required: true,
		displayOptions: {
			show: {
				operation: [
					'delete',
				],
				resource: [
					'contact',
				],
			},
		},
		default: '',
	},
/* -------------------------------------------------------------------------- */
/*                                 contact:get                                */
/* -------------------------------------------------------------------------- */
	{
		displayName: 'Contact ID',
		name: 'contactId',
		type: 'string',
		required: true,
		displayOptions: {
			show: {
				operation: [
					'get',
				],
				resource: [
					'contact',
				],
			},
		},
		default: '',
	},
	{
		displayName: 'Options',
		name: 'options',
		type: 'collection',
		placeholder: 'Add Options',
		default: {},
		displayOptions: {
			show: {
				operation: [
					'get',
				],
				resource: [
					'contact',
				],
			},
		},
		options: [
			{
				displayName: 'Fields',
				name: 'fields',
				type: 'string',
				default: '',
				description: `Comma-delimited list of Contact properties to include in the response. (Some fields such as lead_source_id, custom_fields, and job_title aren't included, by default.)`,
			},
		],
	},
/* -------------------------------------------------------------------------- */
/*                                 contact:getAll                             */
/* -------------------------------------------------------------------------- */
	{
		displayName: 'Return All',
		name: 'returnAll',
		type: 'boolean',
		displayOptions: {
			show: {
				operation: [
					'getAll',
				],
				resource: [
					'contact',
				],
			},
		},
		default: false,
		description: 'If all results should be returned or only up to a given limit.',
	},
	{
		displayName: 'Limit',
		name: 'limit',
		type: 'number',
		displayOptions: {
			show: {
				operation: [
					'getAll',
				],
				resource: [
					'contact',
				],
				returnAll: [
					false,
				],
			},
		},
		typeOptions: {
			minValue: 1,
			maxValue: 200,
		},
		default: 100,
		description: 'How many results to return.',
	},
	{
		displayName: 'Options',
		name: 'options',
		type: 'collection',
		placeholder: 'Add Option',
		default: {},
		displayOptions: {
			show: {
				operation: [
					'getAll',
				],
				resource: [
					'contact',
				],
			},
		},
		options: [
			{
				displayName: 'Email',
				name: 'email',
				type: 'string',
				default: '',
			},
			{
				displayName: 'Given Name',
				name: 'givenName',
				type: 'string',
				default: '',
			},
			{
				displayName: 'Family Name',
				name: 'familyName',
				type: 'string',
				default: '',
			},
			{
				displayName: 'Order',
				name: 'order',
				type: 'options',
				options: [
					{
						name: 'Date',
						value: 'date',
					},
					{
						name: 'Email',
						value: 'email',
					},
					{
						name: 'ID',
						value: 'id',
					},
					{
						name: 'Name',
						value: 'name',
					},
				],
				default: '',
				description: 'Attribute to order items by',
			},
			{
				displayName: 'Order Direction',
				name: 'orderDirection',
				type: 'options',
				options: [
					{
						name: 'ASC',
						value: 'ascending',
					},
					{
						name: 'DES',
						value: 'descending',
					},
				],
				default: '',
			},
			{
				displayName: 'Since',
				name: 'since',
				type: 'dateTime',
				default: '',
				description: 'Date to start searching from on LastUpdated',
			},
			{
				displayName: 'Until',
				name: 'until',
				type: 'dateTime',
				default: '',
				description: 'Date to search to on LastUpdated',
			},
		],
	},
];


<template>
	<div :class="$style.list">
		<div v-for="node in slicedNodes" :class="[$style.container, $style[size]]" :key="node.name">
			<HoverableNodeIcon :nodeType="node" :size="size === 'md'? 24: 18" :title="node.name" />
		</div>
		<div :class="[$style.button, size === 'md' ? $style.buttonMd : $style.buttonSm]" v-if="filteredCoreNodes.length > limit + 1">
			+{{ hiddenNodes }}
		</div>
	</div>
</template>

<script lang="ts">
import HoverableNodeIcon from '@/components/HoverableNodeIcon.vue';

import { genericHelpers } from '@/components/mixins/genericHelpers';
import { ITemplatesNode } from '@/Interface';

import mixins from 'vue-typed-mixins';
import { filterTemplateNodes } from './helpers';

export default mixins(genericHelpers).extend({
	name: 'NodeList',
	props: {
		nodes: {
			type: Array,
		},
		limit: {
			type: Number,
			default: 4,
		},
		size: {
			type: String,
			default: 'sm',
		},
	},
	components: {
		HoverableNodeIcon,
	},
	computed: {
		filteredCoreNodes() {
			return filterTemplateNodes(this.nodes as ITemplatesNode[]);
		},
		hiddenNodes(): number {
			return this.filteredCoreNodes.length - this.countNodesToBeSliced(this.filteredCoreNodes);
		},
		slicedNodes(): ITemplatesNode[] {
			return this.filteredCoreNodes.slice(0, this.countNodesToBeSliced(this.filteredCoreNodes));
		},
	},
	methods: {
		countNodesToBeSliced(nodes: ITemplatesNode[]): number {
			if (nodes.length > this.limit) {
				return this.limit - 1;
			} else {
				return this.limit;
			}
		},
	},
});
</script>

<style lang="scss" module>
.list {
	max-width: 100px;
	display: flex;
	flex-direction: row;
	justify-content: flex-end;
	align-items: center;
}

.container {
	position: relative;
	display: block;
}

.sm {
	margin-left: var(--spacing-2xs);
}

.md {
	margin-left: var(--spacing-xs);
}

.button {
	top: 0px;
	position: relative;
	display: flex;
	justify-content: center;
	align-items: center;
	background: var(--color-background-light);
	border: 1px var(--color-foreground-base) solid;
	border-radius: var(--border-radius-base);
	font-size: 10px;
	font-weight: var(--font-weight-bold);
	color: var(--color-text-base);
}

.buttonSm {
	margin-left: var(--spacing-2xs);
	width: 20px;
	min-width: 20px;
	height: 20px;
}

.buttonMd {
	margin-left: var(--spacing-xs);
	width: 24px;
	min-width: 24px;
	height: 24px;
}
</style>


<div class="d-flex flex-column flex-grow-1">

keep keep keep replace replace replace replace replace replace keep keep keep keep

Disabled: <VAutocomplete items={ items } disabled />,

Affixes: <VAutocomplete items={ items } prefix="prefix" suffix="suffix" />,

'Prepend/append': <VAutocomplete items={ items } prependIcon="$vuetify" appendIcon="$vuetify" />,

'Prepend/append inner': <VAutocomplete items={ items } prependInnerIcon="$vuetify" appendInnerIcon="$vuetify" />,

Placeholder: <VAutocomplete items={ items } placeholder="placeholder" persistentPlaceholder />,

/// <reference types="../../../../types/cypress" />

// Components
import { VAutocomplete } from '../VAutocomplete'
import { VForm } from '@/components/VForm'

// Utilities
import { cloneVNode, ref } from 'vue'
import { generate } from '../../../../cypress/templates'
import { keyValues } from '@/util'

const variants = ['underlined', 'outlined', 'filled', 'solo', 'plain'] as const
const densities = ['default', 'comfortable', 'compact'] as const
const items = ['California', 'Colorado', 'Florida', 'Georgia', 'Texas', 'Wyoming'] as const

const stories = Object.fromEntries(Object.entries({
  'Default input': <VAutocomplete label="label" />,
  Disabled: <VAutocomplete label="label" items={ items } disabled />,
  Affixes: <VAutocomplete label="label" items={ items } prefix="prefix" suffix="suffix" />,
  'Prepend/append': <VAutocomplete label="label" items={ items } prependIcon="$vuetify" appendIcon="$vuetify" />,
  'Prepend/append inner': <VAutocomplete label="label" items={ items } prependInnerIcon="$vuetify" appendInnerIcon="$vuetify" />,
  Placeholder: <VAutocomplete label="label" items={ items } placeholder="placeholder" persistentPlaceholder />,
}).map(([k, v]) => [k, (

    { variants.map(variant => (
      densities.map(density => (

          { cloneVNode(v, { variant, density }) }
          { cloneVNode(v, { variant, density, modelValue: ['California'] }) }
          { cloneVNode(v, { variant, density, chips: true, modelValue: ['California'] }) }
          <VAutocomplete
            variant={ variant }
            density={ density }
            modelValue={['California']}
            { ...v.props }
          >{{
            selection: ({ item }) => {
              return item.title
            },
          }}
          </VAutocomplete>

      ))
    )).flat()}

)]))

describe('VAutocomplete', () => {
  it('should close only first chip', () => {
    const items = ['Item 1', 'Item 2', 'Item 3', 'Item 4']

    const selectedItems = ['Item 1', 'Item 2', 'Item 3']

    cy.mount(() => (
      <VAutocomplete
        items={ items }
        modelValue={ selectedItems }
        chips
        closableChips
        multiple
      />
    ))

    cy.get('.v-chip__close').eq(0).click()
    cy.get('input').should('exist')
    cy.get('.v-chip').should('have.length', 2)
  })

  it('should have selected chip with array of strings', () => {
    const items = ref(['California', 'Colorado', 'Florida'])

    const selectedItems = ref(['California', 'Colorado'])

    cy.mount(() => (
      <VAutocomplete
        v-model={ selectedItems.value }
        items={ items.value }
        chips
        multiple
        closableChips
      />
    ))

    cy.get('.mdi-menu-down').click()

    cy.get('.v-list-item--active').should('have.length', 2)
    cy.get('.v-list-item--active input').eq(0).click()
    cy.then(() => {
      expect(selectedItems.value).to.deep.equal(['Colorado'])
    })

    cy.get('.v-list-item--active').should('have.length', 1)

    cy.get('.v-chip__close').eq(0).click()
    cy.get('.v-chip')
      .should('have.length', 0)
      .should(() => expect(selectedItems.value).to.be.empty)
  })

  it('should have selected chip with return-object', () => {
    const items = ref([
      {
        title: 'Item 1',
        value: 'item1',
      },
      {
        title: 'Item 2',
        value: 'item2',
      },
    ])

    const selectedItems = ref([
      {
        title: 'Item 1',
        value: 'item1',
      },
    ])

    cy.mount(() => (
      <VAutocomplete
        v-model={ selectedItems.value }
        items={ items.value }
        returnObject
        chips
        multiple
      />
    ))

    cy.get('.mdi-menu-down').click()

    cy.get('.v-list-item--active').should('have.length', 1)
    cy.get('.v-list-item--active input').click()
    cy.then(() => {
      expect(selectedItems.value).to.be.empty
    })
    cy.get('.v-list-item--active').should('have.length', 0)
  })

  it('should not be clickable when in readonly', () => {
    const items = ['Item 1', 'Item 2', 'Item 3', 'Item 4']

    const selectedItems = 'Item 1'

    cy.mount(() => (
      <VAutocomplete
        items={ items }
        modelValue={ selectedItems }
        readonly
      />
    ))

    cy.get('.v-autocomplete').click()
    cy.get('.v-list-item').should('have.length', 0)
    cy.get('.v-select--active-menu').should('have.length', 0)

    cy.get('.v-autocomplete input').as('input')
      .focus()
    cy.get('@input').type('{downarrow}', { force: true })
    cy.get('.v-list-item').should('have.length', 0)
    cy.get('.v-select--active-menu').should('have.length', 0)
  })

  it('should not be clickable when in readonly form', () => {
    const items = ['Item 1', 'Item 2', 'Item 3', 'Item 4']

    const selectedItems = 'Item 1'

    cy.mount(() => (
      <VForm readonly>
        <VAutocomplete
          items={ items }
          modelValue={ selectedItems }
          readonly
        />
      </VForm>
    ))

    cy.get('.v-autocomplete').click()
    cy.get('.v-list-item').should('have.length', 0)
    cy.get('.v-select--active-menu').should('have.length', 0)

    cy.get('.v-autocomplete input').as('input')
      .focus()
    cy.get('@input').type('{downarrow}', { force: true })
    cy.get('.v-list-item').should('have.length', 0)
    cy.get('.v-select--active-menu').should('have.length', 0)
  })

  it('should be empty when delete the selected option', () => {
    const items = ref([
      { title: 'Item 1', value: 'Item 1' },
      { title: 'Item 2', value: 'Item 2' },
    ])

    const selectedItems = ref(null)

    cy.mount(() => (
      <VAutocomplete
        v-model={ selectedItems.value }
        items={ items.value }
        returnObject
      />
    ))

    cy.get('.v-autocomplete').click()
    cy.get('.v-list-item').should('have.length', 2)
    cy.get('.v-list-item').contains('Item 1').click()

    cy.get('.v-field__input').clear()
    cy.get('body').click('bottomLeft')
    cy.get('.v-field__input').should('not.include.text', 'Item 1')
  })

  // https://github.com/vuetifyjs/vuetify/issues/16210
  it('should return item object as the argument of item-title function', () => {
    const items = [
      { id: 1, name: 'a' },
      { id: 2, name: 'b' },
    ]

    const selectedItems = ref(null)

    function itemTitleFunc (item: any) {
      return 'Item: ' + JSON.stringify(item)
    }

    const itemTitleFuncSpy = cy.spy(itemTitleFunc).as('itemTitleFunc')

    cy.mount(() => (
      <VAutocomplete
        items={ items }
        modelValue={ selectedItems }
        item-title={ itemTitleFuncSpy }
        item-value="id"
      />
    ))

    cy.get('.v-autocomplete').click()

    cy.get('.v-list-item').eq(0).click({ waitForAnimations: false }).should(() => {
      expect(selectedItems.value).to.deep.equal(1)
    })

    cy.get('@itemTitleFunc')
      .should('have.been.calledWith', { id: 1, name: 'a' })

    cy.get('.v-autocomplete__selection-text').should('have.text', `Item: {"id":1,"name":"a"}`)
  })

  describe('hide-selected', () => {
    it('should hide selected item(s)', () => {
      const items = ['Item 1', 'Item 2', 'Item 3', 'Item 4']

      const selectedItems = ['Item 1', 'Item 2']

      cy.mount(() => (
        <VAutocomplete
          items={ items }
          modelValue={ selectedItems }
          hideSelected
          multiple
        />
      ))

      cy.get('.mdi-menu-down').click()

      cy.get('.v-overlay__content .v-list-item').should('have.length', 2)
      cy.get('.v-overlay__content .v-list-item .v-list-item-title').eq(0).should('have.text', 'Item 3')
      cy.get('.v-overlay__content .v-list-item .v-list-item-title').eq(1).should('have.text', 'Item 4')
    })
  })

  // https://github.com/vuetifyjs/vuetify/issues/16055
  it('should not replicate html select hotkeys in v-autocomplete', () => {
    const items = ref(['aaa', 'foo', 'faa'])

    const selectedItems = ref(undefined)

    cy.mount(() => (
      <VAutocomplete
        v-model={ selectedItems.value }
        items={ items.value }
      />
    ))

    cy.get('.v-autocomplete')
      .click()
      .get('.v-autocomplete input')
      .focus()
      .type('f', { force: true })
      .get('.v-list-item').should('have.length', 2)
      .then(_ => {
        expect(selectedItems.value).equal(undefined)
      })
  })

  it('should conditionally show placeholder', () => {
    cy.mount(props => (
      <VAutocomplete placeholder="Placeholder" { ...props } />
    ))
      .get('.v-autocomplete input')
      .should('have.attr', 'placeholder', 'Placeholder')
      .setProps({ label: 'Label' })
      .get('.v-autocomplete input')
      .should('not.be.visible')
      .get('.v-autocomplete input')
      .focus()
      .should('have.attr', 'placeholder', 'Placeholder')
      .should('be.visible')
      .blur()
      .setProps({ persistentPlaceholder: true })
      .get('.v-autocomplete input')
      .should('have.attr', 'placeholder', 'Placeholder')
      .should('be.visible')
      .setProps({ modelValue: 'Foobar' })
      .get('.v-autocomplete input')
      .should('not.have.attr', 'placeholder')
      .setProps({ multiple: true, modelValue: ['Foobar'] })
      .get('.v-autocomplete input')
      .should('not.have.attr', 'placeholder')
  })

  it('should keep TextField focused while selecting items from open menu', () => {
    cy.mount(() => (
      <VAutocomplete
        multiple
        items={['California', 'Colorado', 'Florida', 'Georgia', 'Texas', 'Wyoming']}
      />
    ))

    cy.get('.v-autocomplete')
      .click()

    cy.get('.v-list')
      .trigger('keydown', { key: keyValues.down, waitForAnimations: false })
      .trigger('keydown', { key: keyValues.down, waitForAnimations: false })
      .trigger('keydown', { key: keyValues.down, waitForAnimations: false })

    cy.get('.v-field').should('have.class', 'v-field--focused')
  })

  it('should not open menu when closing a chip', () => {
    cy
      .mount(() => (
        <VAutocomplete
          chips
          closable-chips
          items={['foo', 'bar']}
          label="Autocomplete"
          modelValue={['foo', 'bar']}
          multiple
        />
      ))
      .get('.v-autocomplete')
      .should('not.have.class', 'v-autocomplete--active-menu')
      .get('.v-chip__close').eq(1)
      .click()
      .get('.v-autocomplete')
      .should('not.have.class', 'v-autocomplete--active-menu')
      .get('.v-chip__close')
      .click()
      .get('.v-autocomplete')
      .should('not.have.class', 'v-autocomplete--active-menu')
      .click()
      .should('have.class', 'v-autocomplete--active-menu')
      .trigger('keydown', { key: keyValues.esc })
      .should('not.have.class', 'v-autocomplete--active-menu')
  })

  it('should auto-select-first item when pressing enter', () => {
    cy
      .mount(() => (
        <VAutocomplete
          items={['California', 'Colorado', 'Florida', 'Georgia', 'Texas', 'Wyoming']}
          multiple
          autoSelectFirst
        />
      ))
      .get('.v-autocomplete')
      .click()
      .get('.v-list-item')
      .should('have.length', 6)
      .get('.v-autocomplete input')
      .type('Cal')
      .get('.v-list-item').eq(0)
      .should('have.class', 'v-list-item--active')
      .get('.v-autocomplete input')
      .trigger('keydown', { key: keyValues.enter, waitForAnimations: false })
      .get('.v-list-item')
      .should('have.length', 6)
  })

  describe('Showcase', () => {
    generate({ stories })
  })
})


<template>
  <v-card
    v-scroll.self="onScroll"
    class="overflow-y-auto"
    max-height="400"
  >
    <v-banner
      class="justify-center text-h5 font-weight-light"
      sticky
    >
      Scroll Me - Method invoked

      <span
        class="font-weight-bold"
        v-text="scrollInvoked"
      ></span>

      times
    </v-banner>

    <v-card-text>
      <div
        v-for="n in 12"
        :key="n"
        class="mb-4"
      >
        Lorem ipsum dolor sit amet consectetur adipisicing elit. Modi commodi earum tenetur. Asperiores dolorem placeat ab nobis iusto culpa, autem molestias molestiae quidem pariatur. Debitis beatae expedita nam facere perspiciatis. Lorem ipsum dolor sit amet consectetur adipisicing elit. Repellendus ducimus cupiditate rerum officiis consequuntur laborum doloremque quaerat ipsa voluptates, nobis nam quis nulla ullam at corporis, similique ratione quasi illo!
      </div>
    </v-card-text>
  </v-card>
</template>

<script>
  export default {
    data: () => ({
      scrollInvoked: 0,
    }),

    methods: {
      onScroll () {
        this.scrollInvoked++
      },
    },
  }
</script>


@mixin absolute($pseudo: false)
  @if ($pseudo)
    content: ''
  position: absolute
  top: 0
  left: 0
  width: 100%
  height: 100%


<div class="d-flex align-start" style="gap: 0.4rem; height: 100px;">

keep keep keep keep keep replace replace replace keep keep keep keep

{ cloneVNode(v, { variant, density, label: `${variant} ${density}` }) }

{ cloneVNode(v, { variant, density, label: `with value`, modelValue: ['California'] }) }

{ cloneVNode(v, { variant, density, label: `chips`, chips: true, modelValue: ['California'] }) }

/// <reference types="../../../../types/cypress" />

// Components
import { VCombobox } from '../VCombobox'
import { VForm } from '@/components/VForm'

// Utilities
import { cloneVNode, ref } from 'vue'
import { generate } from '../../../../cypress/templates'
import { keyValues } from '@/util'

const variants = ['underlined', 'outlined', 'filled', 'solo', 'plain'] as const
const densities = ['default', 'comfortable', 'compact'] as const
const items = ['California', 'Colorado', 'Florida', 'Georgia', 'Texas', 'Wyoming'] as const

const stories = Object.fromEntries(Object.entries({
  'Default input': <VCombobox label="label" />,
  Disabled: <VCombobox label="label" items={ items } disabled />,
  Affixes: <VCombobox label="label" items={ items } prefix="prefix" suffix="suffix" />,
  'Prepend/append': <VCombobox label="label" items={ items } prependIcon="$vuetify" appendIcon="$vuetify" />,
  'Prepend/append inner': <VCombobox label="label" items={ items } prependInnerIcon="$vuetify" appendInnerIcon="$vuetify" />,
  Placeholder: <VCombobox label="label" items={ items } placeholder="placeholder" persistentPlaceholder />,
}).map(([k, v]) => [k, (

    { variants.map(variant => (
      densities.map(density => (

          { cloneVNode(v, { variant, density }) }
          { cloneVNode(v, { variant, density, modelValue: ['California'] }) }
          { cloneVNode(v, { variant, density, chips: true, modelValue: ['California'] }) }
          <VCombobox
            variant={ variant }
            density={ density }
            modelValue={['California']}
            { ...v.props }
          >{{
            selection: ({ item }) => {
              return item.title
            },
          }}
          </VCombobox>

      ))
    )).flat()}

)]))

describe('VCombobox', () => {
  describe('closableChips', () => {
    it('should close only first chip', () => {
      const items = [
        'Item 1',
        'Item 2',
        'Item 3',
        'Item 4',
      ]

      const selectedItems = [
        'Item 1',
        'Item 2',
        'Item 3',
      ]

      cy.mount(() => (
        <VCombobox items={ items } modelValue={ selectedItems } multiple closableChips chips />
      ))
        .get('.v-chip__close').eq(0)
        .click()
      cy.get('input').should('exist')
      cy.get('.v-chip')
        .should('have.length', 2)
    })
  })

  describe('complex objects', () => {
    it('single', () => {
      const items = [
        { title: 'Item 1', value: 'item1' },
        { title: 'Item 2', value: 'item2' },
        { title: 'Item 3', value: 'item3' },
        { title: 'Item 4', value: 'item4' },
      ]
      const model = ref()
      const search = ref()
      const updateModel = cy.stub().as('model').callsFake(val => model.value = val)
      const updateSearch = cy.stub().as('search').callsFake(val => search.value = val)

      cy.mount(() => (
        <VCombobox
          modelValue={ model.value }
          search={ search.value }
          onUpdate:modelValue={ updateModel }
          onUpdate:search={ updateSearch }
          items={ items }
        />
      ))
        .get('input')
        .click()
      cy.get('.v-list-item').eq(0)
        .click({ waitForAnimations: false })
      cy.should(() => {
        expect(model.value).to.deep.equal(items[0])
        expect(search.value).to.deep.equal(items[0].title)
      })
      cy.get('input')
        .should('have.value', items[0].title)
        .blur()
      cy.get('.v-combobox__selection')
        .should('contain', items[0].title)

      cy.get('input').click()
      cy.get('input').clear()
      cy.get('input').type('Item 2')
      cy.should(() => {
        expect(model.value).to.equal('Item 2')
        expect(search.value).to.equal('Item 2')
      })
      cy.get('input')
        .should('have.value', 'Item 2')
        .blur()
      cy.get('.v-combobox__selection')
        .should('contain', 'Item 2')

      cy.get('input').click()
      cy.get('input').clear()
      cy.get('input').type('item3')
      cy.should(() => {
        expect(model.value).to.equal('item3')
        expect(search.value).to.equal('item3')
      })
      cy.get('input')
        .should('have.value', 'item3')
        .blur()
      cy.get('.v-combobox__selection')
        .should('contain', 'item3')
    })

    it('multiple', () => {
      const items = [
        { title: 'Item 1', value: 'item1' },
        { title: 'Item 2', value: 'item2' },
        { title: 'Item 3', value: 'item3' },
        { title: 'Item 4', value: 'item4' },
      ]
      const model = ref<(string | typeof items[number])[]>([])
      const search = ref()
      const updateModel = cy.stub().as('model').callsFake(val => model.value = val)
      const updateSearch = cy.stub().as('search').callsFake(val => search.value = val)

      cy.mount(() => (
        <VCombobox
          modelValue={ model.value }
          search={ search.value }
          onUpdate:modelValue={ updateModel }
          onUpdate:search={ updateSearch }
          multiple
          items={ items }
        />
      ))
        .get('.v-field input')
        .click()
      cy.get('.v-list-item').eq(0)
        .click({ waitForAnimations: false })
      cy.then(() => {
        expect(model.value).to.deep.equal([items[0]])
        expect(search.value).to.be.undefined
      })
      cy.get('.v-field input').as('input')
        .should('have.value', '')
      cy.get('.v-combobox__selection')
        .should('contain', items[0].title)

      cy.get('@input').click()
      cy.get('@input').type('Item 2')
      cy.get('@input').blur()
      cy.should(() => {
        expect(model.value).to.deep.equal([items[0], 'Item 2'])
        expect(search.value).to.equal('')
      })
      cy.get('@input').should('have.value', '')
      cy.get('.v-combobox__selection')
        .should('contain', 'Item 2')

      cy.get('@input').click()
      cy.get('@input').type('item3')
      cy.get('@input').blur()
      cy.should(() => {
        expect(model.value).to.deep.equal([items[0], 'Item 2', 'item3'])
        expect(search.value).to.equal('')
      })
      cy.get('@input').should('have.value', '')
      cy.get('.v-combobox__selection')
        .should('contain', 'item3')
    })
  })

  describe('search', () => {
    it('should filter items', () => {
      const items = [
        'Item 1',
        'Item 1a',
        'Item 2',
        'Item 2a',
      ]

      cy.mount(() => (
        <VCombobox items={ items } />
      ))
        .get('input')
        .type('Item')
      cy.get('.v-list-item')
        .should('have.length', 4)
      cy.get('input').clear()
      cy.get('input').type('Item 1')
      cy.get('.v-list-item')
        .should('have.length', 2)
      cy.get('input').clear()
      cy.get('input').type('Item 3')
      cy.get('input').should('have.length', 1)
    })

    it('should filter items when using multiple', () => {
      const items = [
        'Item 1',
        'Item 1a',
        'Item 2',
        'Item 2a',
      ]

      cy.mount(() => (
        <VCombobox items={ items } multiple />
      ))
        .get('input')
        .type('Item')
      cy.get('.v-list-item')
        .should('have.length', 4)
      cy.get('input:first-child').as('input')
        .clear()
      cy.get('@input').type('Item 1')
      cy.get('.v-list-item')
        .should('have.length', 2)
      cy.get('@input').clear()
      cy.get('@input').type('Item 3')
      cy.get('.v-list-item')
        .should('have.length', 0)
    })

    it('should filter with custom item shape', () => {
      const items = [
        {
          id: 1,
          name: 'Test1',
        },
        {
          id: 2,
          name: 'Antonsen PK',
        },
      ]

      cy.mount(() => (
        <VCombobox
          items={ items }
          item-value="id"
          item-title="name"
        />
      ))
        .get('input')
        .type('test')
      cy.get('.v-list-item')
        .should('have.length', 1)
        .eq(0)
        .should('have.text', 'Test1')
      cy.get('input').clear()
      cy.get('input').type('antonsen')
      cy.get('.v-list-item')
        .should('have.length', 1)
        .eq(0)
        .should('have.text', 'Antonsen PK')
    })
  })

  describe('prefilled data', () => {
    it('should work with array of strings when using multiple', () => {
      const items = ref(['California', 'Colorado', 'Florida'])

      const selectedItems = ref(['California', 'Colorado'])

      cy.mount(() => (
        <VCombobox v-model={ selectedItems.value } items={ items.value } multiple chips closableChips />
      ))

      cy.get('.v-combobox input').click()

      cy.get('.v-list-item--active').should('have.length', 2)
      cy.get('input').get('.v-chip').should('have.length', 2)

      cy.get('.v-chip__close')
        .eq(0)
        .click()
      cy.get('input').should('exist')
      cy.get('.v-chip')
        .should('have.length', 1)
      cy.should(() => expect(selectedItems.value).to.deep.equal(['Colorado']))
    })

    it('should work with objects when using multiple', () => {
      const items = ref([
        {
          title: 'Item 1',
          value: 'item1',
        },
        {
          title: 'Item 2',
          value: 'item2',
        },
        {
          title: 'Item 3',
          value: 'item3',
        },
      ])

      const selectedItems = ref(
        [
          {
            title: 'Item 1',
            value: 'item1',
          },
          {
            title: 'Item 2',
            value: 'item2',
          },
        ]
      )

      cy.mount(() => (
        <VCombobox
          v-model={ selectedItems.value }
          items={ items.value }
          multiple
          chips
          closableChips
          returnObject
        />
      ))

      cy.get('.v-combobox input').click()

      cy.get('.v-list-item--active').should('have.length', 2)
      cy.get('input').get('.v-chip').should('have.length', 2)

      cy.get('.v-chip__close')
        .eq(0)
        .click()
      cy.get('input').should('exist')
      cy.get('.v-chip')
        .should('have.length', 1)
      cy.should(() => expect(selectedItems.value).to.deep.equal([{
        title: 'Item 2',
        value: 'item2',
      }]))
    })
  })

  describe('readonly', () => {
    it('should not be clickable when in readonly', () => {
      const items = ['Item 1', 'Item 2', 'Item 3', 'Item 4']

      const selectedItems = 'Item 1'

      cy.mount(() => (
        <VCombobox
          items={ items }
          modelValue={ selectedItems }
          readonly
        />
      ))

      cy.get('.v-combobox')
        .click()
      cy.get('.v-list-item').should('have.length', 0)
        .get('.v-select--active-menu').should('have.length', 0)

      cy.get('.v-combobox input').as('input')
        .focus()
      cy.get('@input').type('{downarrow}', { force: true })
      cy.get('.v-list-item').should('have.length', 0)
        .get('.v-select--active-menu').should('have.length', 0)
    })

    it('should not be clickable when in readonly form', () => {
      const items = ['Item 1', 'Item 2', 'Item 3', 'Item 4']

      const selectedItems = 'Item 1'

      cy.mount(() => (
        <VForm readonly>
          <VCombobox
            items={ items }
            modelValue={ selectedItems }
            readonly
          />
        </VForm>
      ))

      cy.get('.v-combobox')
        .click()
      cy.get('.v-list-item').should('have.length', 0)
        .get('.v-select--active-menu').should('have.length', 0)

      cy.get('.v-combobox input').as('input')
        .focus()
      cy.get('@input').type('{downarrow}', { force: true })
      cy.get('.v-list-item').should('have.length', 0)
        .get('.v-select--active-menu').should('have.length', 0)
    })
  })

  describe('hide-selected', () => {
    it('should hide selected item(s)', () => {
      const items = [
        'Item 1',
        'Item 2',
        'Item 3',
        'Item 4',
      ]

      const selectedItems = [
        'Item 1',
        'Item 2',
      ]

      cy.mount(() => (
        <VCombobox items={ items } modelValue={ selectedItems } multiple hideSelected />
      ))

      cy.get('.v-combobox input').click()

      cy.get('.v-overlay__content .v-list-item').should('have.length', 2)
      cy.get('.v-overlay__content .v-list-item .v-list-item-title').eq(0).should('have.text', 'Item 3')
      cy.get('.v-overlay__content .v-list-item .v-list-item-title').eq(1).should('have.text', 'Item 4')
    })
  })

  // https://github.com/vuetifyjs/vuetify/issues/17120
  it('should display 0 when selected', () => {
    const items = [0, 1, 2, 3, 4]

    const selectedItems = ref(undefined)

    cy.mount(() => (
      <VCombobox
        items={ items }
        v-model={ selectedItems.value }
      />
    ))
      .get('.v-field input')
      .click()

    cy.get('.v-list-item').eq(0)
      .click({ waitForAnimations: false })

    cy.get('.v-combobox input')
      .should('have.value', '0')
  })

  it('should conditionally show placeholder', () => {
    cy.mount(props => (
      <VCombobox placeholder="Placeholder" { ...props } />
    ))
      .get('.v-combobox input')
      .should('have.attr', 'placeholder', 'Placeholder')
      .setProps({ label: 'Label' })
      .get('.v-combobox input')
      .should('not.be.visible')
      .get('.v-combobox input')
      .focus()
      .should('have.attr', 'placeholder', 'Placeholder')
      .should('be.visible')
      .blur()
      .setProps({ persistentPlaceholder: true })
      .get('.v-combobox input')
      .should('have.attr', 'placeholder', 'Placeholder')
      .should('be.visible')
      .setProps({ modelValue: 'Foobar' })
      .get('.v-combobox input')
      .should('not.have.attr', 'placeholder')
      .setProps({ multiple: true, modelValue: ['Foobar'] })
      .get('.v-combobox input')
      .should('not.have.attr', 'placeholder')
  })

  it('should keep TextField focused while selecting items from open menu', () => {
    cy.mount(() => (
      <VCombobox
        multiple
        items={['California', 'Colorado', 'Florida', 'Georgia', 'Texas', 'Wyoming']}
      />
    ))

    cy.get('.v-combobox')
      .click()

    cy.get('.v-list')
      .trigger('keydown', { key: keyValues.down, waitForAnimations: false })
      .trigger('keydown', { key: keyValues.down, waitForAnimations: false })
      .trigger('keydown', { key: keyValues.down, waitForAnimations: false })

    cy.get('.v-field').should('have.class', 'v-field--focused')
  })

  it('should not open menu when closing a chip', () => {
    cy
      .mount(() => (
        <VCombobox
          chips
          closable-chips
          items={['foo', 'bar']}
          label="Select"
          modelValue={['foo', 'bar']}
          multiple
        />
      ))
      .get('.v-combobox')
      .should('not.have.class', 'v-combobox--active-menu')
      .get('.v-chip__close').eq(1)
      .click()
      .get('.v-combobox')
      .should('not.have.class', 'v-combobox--active-menu')
      .get('.v-chip__close')
      .click()
      .get('.v-combobox')
      .should('not.have.class', 'v-combobox--active-menu')
      .click()
      .should('have.class', 'v-combobox--active-menu')
      .trigger('keydown', { key: keyValues.esc })
      .should('not.have.class', 'v-combobox--active-menu')
  })

  it('should auto-select-first item when pressing enter', () => {
    cy
      .mount(() => (
        <VCombobox
          items={['California', 'Colorado', 'Florida', 'Georgia', 'Texas', 'Wyoming']}
          multiple
          autoSelectFirst
        />
      ))
      .get('.v-combobox')
      .click()
      .get('.v-list-item')
      .should('have.length', 6)
      .get('.v-combobox input')
      .type('Cal')
      .get('.v-list-item').eq(0)
      .should('have.class', 'v-list-item--active')
      .get('.v-combobox input')
      .trigger('keydown', { key: keyValues.enter, waitForAnimations: false })
      .get('.v-list-item')
      .should('have.length', 6)
  })

  describe('Showcase', () => {
    generate({ stories })
  })
})


<template>
  <div class="d-flex align-center justify-center flex-column">
    <v-rating
      v-model="rating"
      class="ma-2"
      :item-labels="['sad', '', '', '', 'happy']"
      item-label-position="top"
    ></v-rating>

    <v-rating
      v-model="rating"
      class="ma-2"
      :item-labels="['sad', '', '', '', 'happy']"
      item-label-position="bottom"
    ></v-rating>
  </div>
</template>

<script>
  export default {
    data: () => ({ rating: 4 }),
  }
</script>


// Composables
import { VItemGroupSymbol } from './VItemGroup'
import { makeGroupItemProps, useGroupItem } from '@/composables/group'

// Utilities
import { genericComponent } from '@/util'

type VItemSlots = {
  default: {
    isSelected: boolean | undefined
    selectedClass: boolean | (string | undefined)[] | undefined
    select: ((value: boolean) => void) | undefined
    toggle: (() => void) | undefined
    value: unknown
    disabled: boolean | undefined
  }
}

export const VItem = genericComponent<VItemSlots>()({
  name: 'VItem',

  props: makeGroupItemProps(),

  emits: {
    'group:selected': (val: { value: boolean }) => true,
  },

  setup (props, { slots }) {
    const { isSelected, select, toggle, selectedClass, value, disabled } = useGroupItem(props, VItemGroupSymbol)
    return () => slots.default?.({
      isSelected: isSelected.value,
      selectedClass: selectedClass.value,
      select,
      toggle,
      value: value.value,
      disabled: disabled.value,
    })
  },
})

export type VItem = InstanceType<typeof VItem>