mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
feat: add attribute types to input labels
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
import { FormItem, Helper } from '.';
|
||||
|
||||
export let label: string;
|
||||
export let optionalText: string | undefined = undefined;
|
||||
export let showLabel = true;
|
||||
export let id: string;
|
||||
export let value = false;
|
||||
@@ -27,6 +28,8 @@
|
||||
|
||||
<FormItem>
|
||||
<label class:u-hide={!showLabel} class="label" for={id}>{label}</label>
|
||||
<span class:u-hide={!showLabel || !optionalText} class="optional">{optionalText}</span>
|
||||
|
||||
<div class="input-text-wrapper">
|
||||
<input
|
||||
{id}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
export let label: string;
|
||||
export let showLabel = true;
|
||||
export let optionalText: string | undefined = undefined;
|
||||
export let id: string;
|
||||
export let value = '';
|
||||
export let required = false;
|
||||
@@ -39,6 +40,8 @@
|
||||
|
||||
<FormItem>
|
||||
<label class:u-hide={!showLabel} class="label" for={id}>{label}</label>
|
||||
<span class:u-hide={!showLabel || !optionalText} class="optional">{optionalText}</span>
|
||||
|
||||
<div class="input-text-wrapper">
|
||||
<input
|
||||
{id}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { FormItem, Helper } from '.';
|
||||
|
||||
export let label: string;
|
||||
export let optionalText: string | undefined = undefined;
|
||||
export let showLabel = true;
|
||||
export let id: string;
|
||||
export let value: number = null;
|
||||
@@ -52,6 +53,8 @@
|
||||
|
||||
<FormItem>
|
||||
<label class:u-hide={!showLabel} class="label" for={id}>{label}</label>
|
||||
<span class:u-hide={!showLabel || !optionalText} class="optional">{optionalText}</span>
|
||||
|
||||
<div class="input-text-wrapper">
|
||||
<input
|
||||
{id}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
export let id: string;
|
||||
export let label: string;
|
||||
export let optionalText: string | undefined = undefined;
|
||||
export let showLabel = true;
|
||||
export let value: string | number | boolean;
|
||||
export let placeholder = '';
|
||||
@@ -33,6 +34,8 @@
|
||||
|
||||
<FormItem>
|
||||
<label class:u-hide={!showLabel} class="label" for={id}>{label}</label>
|
||||
<span class:u-hide={!showLabel || !optionalText} class="optional">{optionalText}</span>
|
||||
|
||||
<div class="select">
|
||||
<select
|
||||
{id}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import { FormItem, Helper } from '.';
|
||||
|
||||
export let label: string;
|
||||
export let optionalText: string | undefined = undefined;
|
||||
export let showLabel = true;
|
||||
export let id: string;
|
||||
export let value = '';
|
||||
@@ -42,8 +43,8 @@
|
||||
</script>
|
||||
|
||||
<FormItem>
|
||||
<label class:u-hide={!showLabel} class="label" for={id}
|
||||
>{label}
|
||||
<label class:u-hide={!showLabel} class="label" for={id}>
|
||||
{label}
|
||||
{#if tooltip}
|
||||
<span
|
||||
class="icon-info"
|
||||
@@ -53,6 +54,8 @@
|
||||
}} />
|
||||
{/if}
|
||||
</label>
|
||||
<span class:u-hide={!showLabel || !optionalText} class="optional">{optionalText}</span>
|
||||
|
||||
<div class="input-text-wrapper">
|
||||
<input
|
||||
{id}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* Strict typed `Object.entries`
|
||||
* Extracted from https://github.com/antfu/utils
|
||||
*
|
||||
* @category Object
|
||||
*/
|
||||
export function objectEntries<T extends object>(obj: T) {
|
||||
return Object.entries(obj) as Array<[keyof T, T[keyof T]]>;
|
||||
}
|
||||
|
||||
type KeyTypesMap<T> = Record<keyof T, Array<string> | ((v: unknown) => boolean) | string>;
|
||||
/**
|
||||
* Checks if the given value is an object of type T, given a map of keys and
|
||||
* their types/validators.
|
||||
*
|
||||
* @category Object
|
||||
*
|
||||
* @template T
|
||||
* @param {unknown} object
|
||||
* @param {KeyTypesMap} keyTypesMap
|
||||
* @returns {value is T}
|
||||
*
|
||||
* @example
|
||||
* ```
|
||||
* type ExampleType = {
|
||||
* a: string;
|
||||
* b: number;
|
||||
* c: string | number;
|
||||
* d: string[]
|
||||
* e: {
|
||||
* f: string;
|
||||
* g: number;
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* export const isExampleType = (value: unknown): value is ExampleType => {
|
||||
* return isObjectType(value, {
|
||||
* a: 'string',
|
||||
* b: 'number',
|
||||
* c: ['string', 'number'],
|
||||
* d: (v) => Array.isArray(v) && v.every((v) => typeof v === 'string'),
|
||||
* e: (v) => isObjectType(v, {
|
||||
* f: 'string',
|
||||
* g: 'number',
|
||||
* }),
|
||||
* });
|
||||
* };
|
||||
* ```
|
||||
*/
|
||||
export const isObjectType = <T>(
|
||||
object: unknown,
|
||||
keyTypesMap: Partial<KeyTypesMap<T>>
|
||||
): object is T => {
|
||||
if (typeof object !== 'object' || object === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const [key, check] of objectEntries(keyTypesMap)) {
|
||||
const value = (object as Record<keyof T, unknown>)[key];
|
||||
|
||||
if (typeof check === 'function') {
|
||||
if (!check(value)) {
|
||||
return false;
|
||||
}
|
||||
} else if (typeof check === 'string') {
|
||||
if (typeof value !== check) {
|
||||
return false;
|
||||
}
|
||||
} else if (!check.includes(typeof value)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* Capitalizes the first letter of a string
|
||||
*
|
||||
* @export
|
||||
* @param {string} str
|
||||
* @returns {string}
|
||||
*/
|
||||
export function capitalize(str: string): string {
|
||||
return str.charAt(0).toUpperCase() + str.slice(1);
|
||||
}
|
||||
+3
@@ -9,6 +9,7 @@
|
||||
export let id: string;
|
||||
export let label: string;
|
||||
export let value: string | number | boolean;
|
||||
export let optionalText: string | undefined = undefined;
|
||||
export let attribute:
|
||||
| Models.AttributeBoolean
|
||||
| Models.AttributeEmail
|
||||
@@ -43,6 +44,7 @@
|
||||
{id}
|
||||
{label}
|
||||
{attribute}
|
||||
{optionalText}
|
||||
bind:value />
|
||||
{:else}
|
||||
<svelte:component
|
||||
@@ -50,6 +52,7 @@
|
||||
{id}
|
||||
{label}
|
||||
{attribute}
|
||||
{optionalText}
|
||||
bind:value />
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
+36
-19
@@ -3,7 +3,8 @@
|
||||
import { FormList } from '$lib/elements/forms';
|
||||
import Button from '$lib/elements/forms/button.svelte';
|
||||
import Pill from '$lib/elements/pill.svelte';
|
||||
import type { Attributes } from '../store';
|
||||
import { capitalize } from '$lib/helpers/string';
|
||||
import { isAttributeEnum, type Attributes } from '../store';
|
||||
import Attribute from './attribute.svelte';
|
||||
|
||||
export let attributes: Attributes[] = [];
|
||||
@@ -26,6 +27,11 @@
|
||||
[key]: [...formValues[key], null]
|
||||
};
|
||||
}
|
||||
|
||||
function getAttributeType(attribute: Attributes) {
|
||||
if (isAttributeEnum(attribute)) return 'Enum';
|
||||
return `${capitalize(attribute.type)}${attribute.array ? '[]' : ''}`;
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if attributes.length}
|
||||
@@ -35,7 +41,11 @@
|
||||
{#if attribute.array}
|
||||
{#if formValues[attribute.key].length === 0}
|
||||
<div class="u-flex u-cross-center u-main-space-between">
|
||||
<span class="label u-margin-0">{label}</span>
|
||||
<div class="u-flex u-cross-center u-gap-4">
|
||||
<span class="label u-margin-0">{label}</span>
|
||||
<span class="optional">{getAttributeType(attribute)}</span>
|
||||
</div>
|
||||
|
||||
<Button text noMargin on:click={() => addArrayItem(attribute.key)}>
|
||||
<span class="icon-plus" aria-hidden="true" />
|
||||
<span class="text"> Add item</span>
|
||||
@@ -43,24 +53,30 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#each [...formValues[attribute.key].keys()] as index}
|
||||
<li class="form-item is-multiple">
|
||||
<div class="form-item-part u-stretch">
|
||||
<Attribute
|
||||
{attribute}
|
||||
id={`${attribute.key}-${index}`}
|
||||
label={index === 0 ? label : ''}
|
||||
bind:value={formValues[attribute.key][index]} />
|
||||
</div>
|
||||
<div class="form-item-part u-cross-child-end">
|
||||
<Button text on:click={() => removeArrayItem(attribute.key, index)}>
|
||||
<span class="icon-x" aria-hidden="true" />
|
||||
</Button>
|
||||
</div>
|
||||
</li>
|
||||
{/each}
|
||||
|
||||
{#if formValues[attribute.key].length !== 0}
|
||||
<ul class="u-grid u-gap-8">
|
||||
{#each [...formValues[attribute.key].keys()] as index}
|
||||
<li class="form-item is-multiple">
|
||||
<div class="form-item-part u-stretch">
|
||||
<Attribute
|
||||
{attribute}
|
||||
optionalText={index === 0
|
||||
? getAttributeType(attribute)
|
||||
: undefined}
|
||||
id={`${attribute.key}-${index}`}
|
||||
label={index === 0 ? label : ''}
|
||||
bind:value={formValues[attribute.key][index]} />
|
||||
</div>
|
||||
<div class="form-item-part u-cross-child-end">
|
||||
<Button
|
||||
text
|
||||
on:click={() => removeArrayItem(attribute.key, index)}>
|
||||
<span class="icon-x" aria-hidden="true" />
|
||||
</Button>
|
||||
</div>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
<Button text noMargin on:click={() => addArrayItem(attribute.key)}>
|
||||
<span class="icon-plus" aria-hidden="true" />
|
||||
<span class="text"> Add item</span>
|
||||
@@ -71,6 +87,7 @@
|
||||
<Attribute
|
||||
{attribute}
|
||||
{label}
|
||||
optionalText={getAttributeType(attribute)}
|
||||
id={attribute.key}
|
||||
bind:value={formValues[attribute.key]} />
|
||||
</FormList>
|
||||
|
||||
+3
-1
@@ -6,12 +6,14 @@
|
||||
export let label: string;
|
||||
export let value: boolean;
|
||||
export let attribute: Models.AttributeBoolean;
|
||||
export let optionalText: string | undefined = undefined;
|
||||
</script>
|
||||
|
||||
{#if attribute?.array}
|
||||
<InputSelect
|
||||
{id}
|
||||
{label}
|
||||
{optionalText}
|
||||
showLabel={!!label?.length}
|
||||
placeholder="Select a value"
|
||||
required={attribute.required}
|
||||
@@ -21,5 +23,5 @@
|
||||
]}
|
||||
bind:value />
|
||||
{:else}
|
||||
<InputCheckbox {id} {label} required={attribute.required} bind:value />
|
||||
<InputCheckbox {id} {label} {optionalText} required={attribute.required} bind:value />
|
||||
{/if}
|
||||
|
||||
+8
-1
@@ -6,6 +6,13 @@
|
||||
export let label: string;
|
||||
export let value: string;
|
||||
export let attribute: Models.AttributeDatetime;
|
||||
export let optionalText: string | undefined = undefined;
|
||||
</script>
|
||||
|
||||
<InputDateTime {id} {label} showLabel={!!label?.length} required={attribute.required} bind:value />
|
||||
<InputDateTime
|
||||
{id}
|
||||
{label}
|
||||
{optionalText}
|
||||
showLabel={!!label?.length}
|
||||
required={attribute.required}
|
||||
bind:value />
|
||||
|
||||
+2
@@ -6,6 +6,7 @@
|
||||
export let label: string;
|
||||
export let value: string;
|
||||
export let attribute: Models.AttributeEnum;
|
||||
export let optionalText: string | undefined = undefined;
|
||||
|
||||
$: options = attribute.elements.map((element) => {
|
||||
return {
|
||||
@@ -20,5 +21,6 @@
|
||||
{options}
|
||||
{id}
|
||||
{label}
|
||||
{optionalText}
|
||||
placeholder="Select a value"
|
||||
showLabel={!!label?.length} />
|
||||
|
||||
+2
@@ -6,11 +6,13 @@
|
||||
export let label: string;
|
||||
export let value: number;
|
||||
export let attribute: Models.AttributeInteger;
|
||||
export let optionalText: string | undefined = undefined;
|
||||
</script>
|
||||
|
||||
<InputNumber
|
||||
{id}
|
||||
{label}
|
||||
{optionalText}
|
||||
required={attribute.required}
|
||||
min={attribute.min}
|
||||
max={attribute.max}
|
||||
|
||||
+2
@@ -6,11 +6,13 @@
|
||||
export let label: string;
|
||||
export let value: string;
|
||||
export let attribute: Models.AttributeString;
|
||||
export let optionalText: string | undefined = undefined;
|
||||
</script>
|
||||
|
||||
<InputText
|
||||
{id}
|
||||
{label}
|
||||
{optionalText}
|
||||
showLabel={!!label?.length}
|
||||
required={attribute.required}
|
||||
maxlength={attribute.size}
|
||||
|
||||
+7
@@ -1,6 +1,7 @@
|
||||
import { derived } from 'svelte/store';
|
||||
import { page } from '$app/stores';
|
||||
import type { Models } from '@aw-labs/appwrite-console';
|
||||
import { isObjectType } from '$lib/helpers/object';
|
||||
|
||||
export type Attributes =
|
||||
| Models.AttributeBoolean
|
||||
@@ -12,6 +13,12 @@ export type Attributes =
|
||||
| Models.AttributeString
|
||||
| Models.AttributeUrl;
|
||||
|
||||
export function isAttributeEnum(attribute: unknown): attribute is Models.AttributeEnum {
|
||||
return isObjectType<Models.AttributeEnum>(attribute, {
|
||||
format: (v) => v === 'enum'
|
||||
});
|
||||
}
|
||||
|
||||
type Collection = Omit<Models.Collection, 'attributes'> & {
|
||||
attributes: Array<Attributes>;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
import { isObjectType, objectEntries } from '$lib/helpers/object';
|
||||
|
||||
test('objectEntries', () => {
|
||||
const object = {
|
||||
a: 1,
|
||||
b: 2,
|
||||
c: 3
|
||||
};
|
||||
|
||||
expect(objectEntries(object)).toEqual(Object.entries(object));
|
||||
});
|
||||
|
||||
test('isObjectType', () => {
|
||||
type TestType = {
|
||||
a: string;
|
||||
b: number;
|
||||
c: string | number;
|
||||
d: string[];
|
||||
e: {
|
||||
f: string;
|
||||
g: number;
|
||||
};
|
||||
h: undefined | null | string;
|
||||
};
|
||||
|
||||
const valid_type = {
|
||||
a: '1',
|
||||
b: 1,
|
||||
c: '1',
|
||||
d: ['1', '2'],
|
||||
e: {
|
||||
f: '1',
|
||||
g: 1
|
||||
},
|
||||
h: null
|
||||
};
|
||||
|
||||
const invalid_type = {
|
||||
a: 1,
|
||||
b: '1',
|
||||
c: true
|
||||
};
|
||||
|
||||
const invalid_type_2 = {
|
||||
...valid_type,
|
||||
d: [1, '2']
|
||||
};
|
||||
|
||||
const invalid_type_3 = {
|
||||
...valid_type,
|
||||
c: null
|
||||
};
|
||||
|
||||
const isTestType = (value: unknown): value is TestType => {
|
||||
return isObjectType(value, {
|
||||
a: 'string',
|
||||
b: 'number',
|
||||
c: ['string', 'number'],
|
||||
d: (v) => Array.isArray(v) && v.every((v) => typeof v === 'string'),
|
||||
e: (v) =>
|
||||
isObjectType(v, {
|
||||
f: 'string',
|
||||
g: 'number'
|
||||
}),
|
||||
h: (v) => v === null || v === undefined || typeof v === 'string'
|
||||
});
|
||||
};
|
||||
|
||||
expect(isTestType(valid_type)).toBe(true);
|
||||
expect(isTestType(invalid_type)).toBe(false);
|
||||
expect(isTestType(invalid_type_2)).toBe(false);
|
||||
expect(isTestType(invalid_type_3)).toBe(false);
|
||||
expect(isTestType('12331')).toBe(false);
|
||||
});
|
||||
Reference in New Issue
Block a user