diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 0fb44f62bf..82300969ce 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -4,7 +4,15 @@ module.exports = { extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'], plugins: ['svelte3', '@typescript-eslint'], ignorePatterns: ['*.cjs'], - overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }], + overrides: [ + { + files: ['*.svelte'], + processor: 'svelte3/svelte3', + rules: { + 'no-redeclare': 'off' + } + } + ], settings: { 'svelte3/typescript': () => require('typescript') }, diff --git a/src/lib/actions/selectionStart.ts b/src/lib/actions/selectionStart.ts new file mode 100644 index 0000000000..bd42999279 --- /dev/null +++ b/src/lib/actions/selectionStart.ts @@ -0,0 +1,28 @@ +import type { Action } from 'svelte/action'; + +type Props = { + onChange: (selectionStart: number) => void; +}; + +export const selectionStart: Action = (node, { onChange }) => { + const handler = () => { + onChange(node.selectionStart); + }; + node.addEventListener('click', handler); + node.addEventListener('keydown', handler); + node.addEventListener('keyup', handler); + + return { + update({ onChange }) { + const handler = () => { + onChange(node.selectionStart); + }; + node.addEventListener('click', handler); + node.addEventListener('keydown', handler); + node.addEventListener('keyup', handler); + }, + destroy() { + onChange(-1); + } + }; +}; diff --git a/src/lib/components/copy.svelte b/src/lib/components/copy.svelte index 0e859e79c8..8271c6ebcd 100644 --- a/src/lib/components/copy.svelte +++ b/src/lib/components/copy.svelte @@ -7,6 +7,7 @@ export let value: string; export let event: string = null; + export let appendTo: Parameters['1']['appendTo'] = undefined; let content = 'Click to copy'; @@ -36,7 +37,8 @@ on:mouseenter={() => setTimeout(() => (content = 'Click to copy'))} use:tooltip={{ content, - hideOnClick: false + hideOnClick: false, + appendTo }}> diff --git a/src/lib/components/eventModal.svelte b/src/lib/components/eventModal.svelte index e80f891307..38835eaf16 100644 --- a/src/lib/components/eventModal.svelte +++ b/src/lib/components/eventModal.svelte @@ -1,187 +1,241 @@ @@ -190,134 +244,126 @@

Choose a service

- {#each services as service} + {#each available.services as service} { - selectedService = service; - inputData = null; - }}>{service.name} + on:click={() => select('service', service)}> + {service.name} + {/each}
- {#if selectedService} + + {#if !empty(available.resources)}

Choose a resource (optional)

- {#each selectedService.resources as resource} + {#each available.resources as resource} { - selectedResource === resource - ? (selectedResource = null) - : (selectedResource = resource); - inputData = null; - }}>{resource.name} + on:click={() => select('resource', resource)}> + {resource.name} + {/each}
+ {/if} + + {#if !empty(available.actions)}

Choose an action (optional)

- {#each selectedResource?.actions ?? selectedService?.actions as action} + {#each available.actions as action} { - selectedAction === action - ? (selectedAction = null) - : (selectedAction = action); - inputData = null; - }}>{action.name} + on:click={() => select('action', action)}> + {action.name} + {/each}
- {#if selectedAction?.attributes} -
-

Choose an attribute (optional)

-
- {#each selectedAction.attributes as attribute} - { - selectedAttribute === attribute - ? (selectedAttribute = null) - : (selectedAttribute = attribute); - inputData = null; - }}>{attribute} - {/each} -
+ {/if} + + {#if !empty(available.attributes)} +
+

Choose an attribute (optional)

+
+ {#each available.attributes as attribute} + select('attribute', attribute)}> + {attribute} + + {/each}
- {/if} +
{/if} + {#if showInput}
- + (customInputCursor = newValue) }} />
- -
+

{helper ?? ''}

{:else} -
-
- {#if inputData} - {inputData} - {:else} - {#each Array.from(eventString.values()) as route, i} - - - {i + 1 < eventString?.size ? '.' : ''} - - {/each} - {/if} +
+ +
+ {#each eventString as route, i} + (helper = route.description)} + on:mouseleave={() => (helper = null)}> + {route.value} + + + {i + 1 < eventString?.length ? '.' : ''} + + {/each}
- - + {#key copyParent} + + + {/key}

{helper ?? ''}

@@ -325,6 +371,7 @@ - + diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 428ae63923..d8015a1f4c 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -176,3 +176,87 @@ export const scopes: { category: 'Other' } ]; + +export type EventService = { + name: string; + resources: EventResource[]; + actions?: EventAction[]; +}; + +export type EventResource = { + name: string; + actions?: EventAction[]; +}; + +export type EventAction = { + name: string; + attributes?: string[]; +}; + +export const eventServices: Array = [ + { + name: 'buckets', + resources: [ + { + name: 'files', + actions: [{ name: 'create' }, { name: 'update' }, { name: 'delete' }] + } + ], + actions: [{ name: 'create' }, { name: 'update' }, { name: 'delete' }] + }, + { + name: 'databases', + resources: [ + { + name: 'collections', + actions: [{ name: 'create' }, { name: 'update' }, { name: 'delete' }] + }, + { + name: 'documents', + actions: [{ name: 'create' }, { name: 'update' }, { name: 'delete' }] + } + ], + actions: [{ name: 'create' }, { name: 'update' }, { name: 'delete' }] + }, + { + name: 'functions', + resources: [ + { + name: 'deployments', + actions: [{ name: 'create' }, { name: 'update' }, { name: 'delete' }] + }, + { + name: 'executions', + actions: [{ name: 'create' }, { name: 'update' }, { name: 'delete' }] + } + ], + actions: [{ name: 'create' }, { name: 'update' }, { name: 'delete' }] + }, + { + name: 'teams', + resources: [ + { + name: 'memberships', + actions: [ + { name: 'create' }, + { name: 'update', attributes: ['status'] }, + { name: 'delete' } + ] + } + ], + actions: [{ name: 'create' }, { name: 'update' }, { name: 'delete' }] + }, + { + name: 'users', + resources: [ + { name: 'recovery', actions: [{ name: 'create' }, { name: 'delete' }] }, + { name: 'sessions', actions: [{ name: 'create' }, { name: 'delete' }] }, + { name: 'verifications', actions: [{ name: 'create' }, { name: 'delete' }] } + ], + actions: [ + { name: 'create' }, + { name: 'update', attributes: ['email', 'name', 'password', 'status', 'prefs'] }, + { name: 'delete' } + ] + } +]; diff --git a/src/lib/helpers/array.ts b/src/lib/helpers/array.ts index 12cde8c4b8..67a50a180b 100644 --- a/src/lib/helpers/array.ts +++ b/src/lib/helpers/array.ts @@ -27,3 +27,49 @@ export function remove(arr: T[], index: number): T[] { // Remove the element at the given index, return a new array return [...arr.slice(0, index), ...arr.slice(index + 1)]; } + +/** + * Returns true if the array is empty, false otherwise. + * + * @export + * @param {unknown[]} arr + * @returns {boolean} + */ +export function empty(arr: unknown[]): boolean { + if (!arr) return true; + return arr.length === 0; +} + +/** + * Get nth item of Array. Negative for backward + * + * @export + * @template T + * @param {(readonly T[] | [])} array + * @param {number} index + * @returns {(T | undefined)} + */ +export function at(array: readonly [], index: number): undefined; +export function at(array: readonly T[], index: number): T; +export function at(array: readonly T[] | [], index: number): T | undefined { + const len = array.length; + if (!len) return undefined; + + if (index < 0) index += len; + + return array[index]; +} + +/** + * Get last item + * + * @export + * @template T + * @param {readonly T[]} array + * @returns {(T | undefined)} + */ +export function last(array: readonly []): undefined; +export function last(array: readonly T[]): T; +export function last(array: readonly T[]): T | undefined { + return at(array, -1); +} diff --git a/src/lib/helpers/string.ts b/src/lib/helpers/string.ts new file mode 100644 index 0000000000..564b286091 --- /dev/null +++ b/src/lib/helpers/string.ts @@ -0,0 +1,11 @@ +/** + * Given a string, returns the singular version of it, + * by removing the trailing 's'. + * + * @export + * @param {string} str + * @returns {string} + */ +export function singular(str: string): string { + return str.replace(/s$/, ''); +} diff --git a/src/routes/console/project-[project]/functions/function-[function]/settings/+page.svelte b/src/routes/console/project-[project]/functions/function-[function]/settings/+page.svelte index e25e5473bb..a6959160d9 100644 --- a/src/routes/console/project-[project]/functions/function-[function]/settings/+page.svelte +++ b/src/routes/console/project-[project]/functions/function-[function]/settings/+page.svelte @@ -1,46 +1,35 @@ @@ -372,49 +321,7 @@ -
- - Update Events -

Set the events that will trigger your function. Maximum 100 events allowed.

- - {#if $eventSet.size} -
- - {#each Array.from($eventSet) as event} -
  • - - {event} - - - - -
  • - {/each} -
    - - -
    - {:else} - (showEvents = true)}>Add an event to get started - {/if} -
    - - - - -
    -
    +
    @@ -587,5 +494,3 @@ on:created={handleVariableCreated} on:updated={handleVariableUpdated} /> {/if} - - diff --git a/src/routes/console/project-[project]/functions/function-[function]/settings/updateEvents.svelte b/src/routes/console/project-[project]/functions/function-[function]/settings/updateEvents.svelte new file mode 100644 index 0000000000..901b53c731 --- /dev/null +++ b/src/routes/console/project-[project]/functions/function-[function]/settings/updateEvents.svelte @@ -0,0 +1,144 @@ + + + + + Update Events +

    Set the events that will trigger your function. Maximum 100 events allowed.

    + + {#if $eventSet.size} + + {#each Array.from($eventSet) as event, i} +
  • + + {event} + + + + + + { + showDropdown[i] = false; + showEvents = true; + eventValue = event; + }}> + Edit + + { + $eventSet.delete(event); + eventSet.set($eventSet); + }}> + Delete + + + + +
  • + {/each} +
    + +
    + +
    + {:else} + (showEvents = true)}>Add an event to get started + {/if} +
    + + + + +
    + + + diff --git a/tsconfig.json b/tsconfig.json index d164d251ec..5987c9cb1d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,7 @@ { "extends": "./.svelte-kit/tsconfig.json", "compilerOptions": { - "lib": ["webworker"], + "lib": ["webworker", "ES2016", "DOM"], "module": "ESNext", "moduleResolution": "Node", "importsNotUsedAsValues": "error",