feat: variable editor

This commit is contained in:
Arman
2025-01-30 18:44:58 +01:00
parent ee807c4a67
commit 1f6db40305
9 changed files with 237 additions and 47 deletions
+1 -1
View File
@@ -23,7 +23,7 @@
"@appwrite.io/pink-icons": "0.25.0",
"@appwrite.io/pink-icons-svelte": "https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@5312d5d",
"@appwrite.io/pink-legacy": "^1.0.0",
"@appwrite.io/pink-svelte": "https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@5312d5d",
"@appwrite.io/pink-svelte": "https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@a8068b7",
"@popperjs/core": "^2.11.8",
"@sentry/sveltekit": "^8.38.0",
"@stripe/stripe-js": "^3.5.0",
+9 -9
View File
@@ -21,8 +21,8 @@ importers:
specifier: ^1.0.0
version: 1.0.0
'@appwrite.io/pink-svelte':
specifier: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@5312d5d
version: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@5312d5d(react-dom@18.3.1(react@18.3.1))(svelte@4.2.19)
specifier: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@a8068b7
version: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@a8068b7(react-dom@18.3.1(react@18.3.1))(svelte@4.2.19)
'@popperjs/core':
specifier: ^2.11.8
version: 2.11.8
@@ -227,8 +227,8 @@ packages:
peerDependencies:
svelte: ^4.0.0
'@appwrite.io/pink-icons-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@5312d5d32ffe17459f863a0e37d55e097ccf1cfb':
resolution: {tarball: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@5312d5d32ffe17459f863a0e37d55e097ccf1cfb}
'@appwrite.io/pink-icons-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@a8068b75f1efdadfcb9b56773fb3b5c02629452b':
resolution: {tarball: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@a8068b75f1efdadfcb9b56773fb3b5c02629452b}
version: 1.0.0-next.7
peerDependencies:
svelte: ^4.0.0
@@ -242,8 +242,8 @@ packages:
'@appwrite.io/pink-legacy@1.0.0':
resolution: {integrity: sha512-EZk/wX8oDhIsCXf2A0oQjdNOaHvh5MnWbLQI4pAiGrdy5qQGPoU19AK6iVX6z3QoOq7geoEu0zVwAowBy5hZbQ==}
'@appwrite.io/pink-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@5312d5d':
resolution: {tarball: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@5312d5d}
'@appwrite.io/pink-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@a8068b7':
resolution: {tarball: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@a8068b7}
version: 1.0.0-next.85
peerDependencies:
react-dom: ^18.0.0
@@ -4096,7 +4096,7 @@ snapshots:
dependencies:
svelte: 4.2.19
'@appwrite.io/pink-icons-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@5312d5d32ffe17459f863a0e37d55e097ccf1cfb(svelte@4.2.19)':
'@appwrite.io/pink-icons-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@a8068b75f1efdadfcb9b56773fb3b5c02629452b(svelte@4.2.19)':
dependencies:
svelte: 4.2.19
@@ -4109,9 +4109,9 @@ snapshots:
'@appwrite.io/pink-icons': 1.0.0
the-new-css-reset: 1.11.3
'@appwrite.io/pink-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@5312d5d(react-dom@18.3.1(react@18.3.1))(svelte@4.2.19)':
'@appwrite.io/pink-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@a8068b7(react-dom@18.3.1(react@18.3.1))(svelte@4.2.19)':
dependencies:
'@appwrite.io/pink-icons-svelte': https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@5312d5d32ffe17459f863a0e37d55e097ccf1cfb(svelte@4.2.19)
'@appwrite.io/pink-icons-svelte': https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@a8068b75f1efdadfcb9b56773fb3b5c02629452b(svelte@4.2.19)
'@floating-ui/dom': 1.6.13
'@melt-ui/pp': 0.3.2(@melt-ui/svelte@0.86.2(svelte@4.2.19))(svelte@4.2.19)
'@melt-ui/svelte': 0.86.2(svelte@4.2.19)
+2 -1
View File
@@ -15,6 +15,7 @@
};
export let title = '';
export let hideFooter = false;
export let submitOnEnter = true;
let alert: HTMLElement;
let formComponent: Form;
@@ -24,7 +25,7 @@
});
function handleKeydown(event: KeyboardEvent) {
if (event.key === 'Enter') {
if (event.key === 'Enter' && submitOnEnter) {
event.preventDefault();
if (show) {
formComponent.triggerSubmit();
+3 -1
View File
@@ -1,7 +1,7 @@
<script lang="ts">
import { Input } from '@appwrite.io/pink-svelte';
export let label: string;
export let label = '';
export let id: string;
export let name: string = id;
export let value = '';
@@ -12,6 +12,7 @@
export let readonly = false;
export let autofocus = false;
export let maxlength: number = null;
export let rows = 3;
let error: string;
@@ -39,6 +40,7 @@
{maxlength}
{label}
{nullable}
{rows}
autofocus={autofocus || undefined}
helper={error}
state={error ? 'error' : 'default'}
@@ -3,7 +3,7 @@
import { base } from '$app/paths';
import { page } from '$app/stores';
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
import { Button, Form } from '$lib/elements/forms';
import { Button, Form, Label } from '$lib/elements/forms';
import { Wizard } from '$lib/layout';
import { addNotification } from '$lib/stores/notifications';
import { sdk } from '$lib/stores/sdk';
@@ -129,26 +129,29 @@
confirmExit>
<Form bind:this={formComponent} onSubmit={create} bind:isSubmitting>
<Layout.Stack gap="xl">
<Upload.Dropzone folder bind:files on:change={handleChange}>
<Layout.Stack alignItems="center" gap="s">
<Layout.Stack
alignItems="center"
justifyContent="center"
direction="row"
gap="s">
<Typography.Text variant="l-500">
Drag and drop files here or click to upload
</Typography.Text>
<Tooltip>
<Icon icon={IconInfo} size="s" />
<svelte:fragment slot="tooltip">
Only PNG, JPEG, PDF files allowed
</svelte:fragment>
</Tooltip>
<Layout.Stack gap="xxs">
<Label>Files or folder</Label>
<Upload.Dropzone folder bind:files on:change={handleChange}>
<Layout.Stack alignItems="center" gap="s">
<Layout.Stack
alignItems="center"
justifyContent="center"
direction="row"
gap="s">
<Typography.Text variant="l-500">
Drag and drop files here or click to upload
</Typography.Text>
<Tooltip>
<Icon icon={IconInfo} size="s" />
<svelte:fragment slot="tooltip">
Only PNG, JPEG, PDF files allowed
</svelte:fragment>
</Tooltip>
</Layout.Stack>
<Typography.Caption variant="400">Max file size: 10MB</Typography.Caption>
</Layout.Stack>
<Typography.Caption variant="400">Max file size: 10MB</Typography.Caption>
</Layout.Stack>
</Upload.Dropzone>
</Upload.Dropzone>
</Layout.Stack>
<Details bind:name bind:id />
<Configuration
@@ -1,7 +1,36 @@
<script lang="ts">
import { Card } from '$lib/components';
import { Button, InputSelect } from '$lib/elements/forms';
import { IconDownload } from '@appwrite.io/pink-icons-svelte';
import { Icon, Image, Layout, Typography } from '@appwrite.io/pink-svelte';
let framework: 'react' | 'vue' | 'angular' | 'svelte' | 'web' = 'web';
</script>
<Card padding="xs">
<slot />
<Layout.Stack gap="xxs">
<Typography.Text variant="m-500">Don't have a site yet?</Typography.Text>
<Typography.Text variant="m-400">
Download our starter kit, and deploy in seconds.
</Typography.Text>
</Layout.Stack>
<Layout.Stack>
<InputSelect
id="framework"
options={[
{ label: 'React', value: 'react' },
{ label: 'Vue', value: 'vue' },
{ label: 'Angular', value: 'angular' },
{ label: 'Svelte', value: 'svelte' },
{ label: 'Web', value: 'web' }
]}
bind:value={framework} />
<Image src="https://placehold.co/349x201/111/bbb?text=Preview&font=inter" alt="preview" />
<Button size="s" fullWidth secondary href="#">
<Icon icon={IconDownload} /> Starter kit
</Button>
</Layout.Stack>
</Card>
@@ -24,6 +24,7 @@
import SecretVariableModal from '../secretVariableModal.svelte';
import ImportSiteVariablesModal from '../importSiteVariablesModal.svelte';
import type { Models } from '@appwrite.io/console';
import VariableEditorModal from '../variableEditorModal.svelte';
export let frameworks: Models.Framework[];
export let selectedFramework: Models.Framework;
@@ -206,13 +207,7 @@
</Fieldset>
{#if showEditorModal}
<!-- <RawVariableEditor
{isGlobal}
{sdkCreateVariable}
{sdkUpdateVariable}
{sdkDeleteVariable}
{variableList}
bind:showEditor={showEditorModal} /> -->
<VariableEditorModal bind:variables bind:showEditor={showEditorModal} />
{/if}
{#if showSecretModal}
@@ -26,6 +26,7 @@
import type { Models } from '@appwrite.io/console';
import { getFrameworkIcon } from '../../../store';
import { iconPath } from '$lib/stores/app';
import VariableEditorModal from '../../variableEditorModal.svelte';
export let frameworks: Models.Framework[];
export let selectedFramework: Models.Framework;
@@ -212,13 +213,7 @@
</Fieldset>
{#if showEditorModal}
<!-- <RawVariableEditor
{isGlobal}
{sdkCreateVariable}
{sdkUpdateVariable}
{sdkDeleteVariable}
{variableList}
bind:showEditor={showEditorModal} /> -->
<VariableEditorModal bind:variables bind:showEditor={showEditorModal} />
{/if}
{#if showSecretModal}
@@ -0,0 +1,165 @@
<script lang="ts">
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
import Copy from '$lib/components/copy.svelte';
import Modal from '$lib/components/modal.svelte';
import Button from '$lib/elements/forms/button.svelte';
import { addNotification } from '$lib/stores/notifications';
import { parse } from 'envfile';
import { Icon, Layout, Tabs } from '@appwrite.io/pink-svelte';
import { IconDownload, IconDuplicate } from '@appwrite.io/pink-icons-svelte';
import { InputTextarea } from '$lib/elements/forms';
import type { Models } from '@appwrite.io/console';
export let showEditor = false;
export let variables: Partial<Models.Variable>[];
let error = '';
let envCode = variables.map((variable) => `${variable.key}=${variable.value}`).join('\n');
let jsonCode = JSON.stringify(
JSON.parse(
`{${variables
.map((variable) => `"${variable.key}":"${variable.value.split('"').join('\\"')}"`)
.join(',')}}`
),
null,
2
);
const baseEnvCode = envCode;
const baseJsonCode = jsonCode;
if (jsonCode === '{}') {
jsonCode = '';
}
let tab: 'env' | 'json' = 'env';
async function handleSubmit() {
try {
const vars = tab === 'env' ? parse(envCode) : JSON.parse(jsonCode ? jsonCode : '{}');
const entries = Object.entries(vars);
for (const [key, value] of entries) {
if (('' + value).length > 8192) {
throw new Error(`Variable ${key} is longer than 8192 allowed characters`);
}
}
variables.map((variable) => {
const newValue = vars[variable.key] ?? null;
if (newValue === null) {
variables = variables.filter((v) => v.key !== variable.key);
} else if (newValue !== variable.value) {
variable.value = newValue;
variables = [...variables];
}
delete vars[variable.key];
});
Object.keys(vars).map(async (key) => {
const value = vars[key];
variables.push({ key, value });
});
showEditor = false;
addNotification({
type: 'success',
message: `Variables have been updated.`
});
trackEvent(Submit.VariableEditor);
} catch (e) {
error = e.message;
trackError(e, Submit.VariableEditor);
}
}
function downloadVariables() {
const content = tab === 'json' ? jsonCode : envCode;
const fileName = tab === 'json' ? 'vars.json' : '.env';
const type = tab === 'json' ? 'application/json' : 'application/x-envoy';
const file = new File([content], fileName, {
type
});
const link = document.createElement('a');
const url = URL.createObjectURL(file);
link.href = url;
link.download = file.name;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
}
function handleKeydown(event: KeyboardEvent) {
if (event.key === 'Enter') {
if (tab === 'env') {
envCode += '\n';
} else {
jsonCode += '\n';
}
}
}
$: isButtonDisabled =
(tab === 'env' && baseEnvCode === envCode) || (tab === 'json' && baseJsonCode === jsonCode);
</script>
<svelte:window on:keydown={handleKeydown} />
<Modal
title="Editor"
bind:show={showEditor}
onSubmit={handleSubmit}
bind:error
submitOnEnter={false}>
<p slot="description">Add, edit, or delete environment variables using a raw editor.</p>
<Layout.Stack gap="s">
<Tabs.Root>
<Tabs.Item.Button on:click={() => (tab = 'env')} active={tab === 'env'}>
ENV
</Tabs.Item.Button>
<Tabs.Item.Button on:click={() => (tab = 'json')} active={tab === 'json'}>
JSON
</Tabs.Item.Button>
</Tabs.Root>
{#if tab === 'env'}
<InputTextarea
id="variables"
bind:value={envCode}
rows={10}
placeholder={`SECRET_KEY=dQw4w9WgXcQ...`} />
{:else if tab === 'json'}
<InputTextarea
id="variables"
bind:value={jsonCode}
rows={10}
placeholder={`{\n "SECRET_KEY": "dQw4w9WgXcQ..."\n}`} />
{/if}
<Layout.Stack direction="row" gap="xs">
<Button on:click={() => downloadVariables()} text>
<Icon icon={IconDownload} />
Download
</Button>
<Copy value={tab == 'json' ? jsonCode : envCode}>
<Button text>
<Icon icon={IconDuplicate} />
Copy
</Button>
</Copy>
</Layout.Stack>
</Layout.Stack>
<svelte:fragment slot="footer">
<Button secondary on:click={() => (showEditor = false)}>Cancel</Button>
<Button submit disabled={isButtonDisabled}>Save</Button>
</svelte:fragment>
</Modal>