mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
Merge branch 'main' into 'patch-e2es'.
This commit is contained in:
+4
-4
@@ -12,7 +12,7 @@
|
||||
"clean": "rm -rf node_modules && rm -rf .svelte_kit && pnpm i --force",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-check --tsconfig ./tsconfig.json --watch",
|
||||
"format": "prettier --cache --write --cache .",
|
||||
"format": "prettier --write --cache .",
|
||||
"lint": "prettier --check . && eslint .",
|
||||
"test": "TZ=EST vitest run",
|
||||
"test:ui": "TZ=EST vitest --ui",
|
||||
@@ -22,11 +22,11 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@ai-sdk/svelte": "^1.1.24",
|
||||
"@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@5ce6dc7",
|
||||
"@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@315d6c5",
|
||||
"@appwrite.io/pink-icons": "0.25.0",
|
||||
"@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@5b26bb8",
|
||||
"@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@6916470",
|
||||
"@appwrite.io/pink-legacy": "^1.0.3",
|
||||
"@appwrite.io/pink-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@5b26bb8",
|
||||
"@appwrite.io/pink-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@33845eb",
|
||||
"@faker-js/faker": "^9.9.0",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@sentry/sveltekit": "^10.25.0",
|
||||
|
||||
Generated
+281
-274
File diff suppressed because it is too large
Load Diff
@@ -195,7 +195,10 @@ export enum Click {
|
||||
VariablesCreateClick = 'click_variable_create',
|
||||
VariablesUpdateClick = 'click_variable_update',
|
||||
VariablesImportClick = 'click_variable_import',
|
||||
WebsiteOpenClick = 'click_open_website'
|
||||
WebsiteOpenClick = 'click_open_website',
|
||||
CopyPromptStarterKitClick = 'click_copy_prompt_starter_kit',
|
||||
OpenInCursorClick = 'click_open_in_cursor',
|
||||
OpenInLovableClick = 'click_open_in_lovable'
|
||||
}
|
||||
|
||||
export enum Submit {
|
||||
@@ -357,6 +360,7 @@ export enum Submit {
|
||||
BucketUpdateSize = 'submit_bucket_update_size',
|
||||
BucketUpdateCompression = 'submit_bucket_update_compression',
|
||||
BucketUpdateExtensions = 'submit_bucket_update_extensions',
|
||||
BucketUpdateTransformations = 'submit_bucket_update_transformations',
|
||||
FileCreate = 'submit_file_create',
|
||||
FileDelete = 'submit_file_delete',
|
||||
FileUpdatePermissions = 'submit_file_update_permissions',
|
||||
|
||||
@@ -152,7 +152,7 @@
|
||||
bind:selectedAddress={billingAddress} />
|
||||
{/if}
|
||||
{#if showReplace}
|
||||
<ReplaceAddress bind:show={showReplace} />
|
||||
<ReplaceAddress bind:show={showReplace} {locale} {countryList} />
|
||||
{/if}
|
||||
{#if showRemove}
|
||||
<RemoveAddress bind:show={showRemove} />
|
||||
|
||||
@@ -11,9 +11,11 @@
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { base } from '$app/paths';
|
||||
import { Alert, Badge, Card, Layout, Skeleton } from '@appwrite.io/pink-svelte';
|
||||
import { page } from '$app/state';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
|
||||
export let show = false;
|
||||
export let locale: Models.Locale;
|
||||
export let countryList: Models.CountryList;
|
||||
let loading = true;
|
||||
let addresses: AddressesList;
|
||||
let selectedAddress: string;
|
||||
@@ -44,13 +46,9 @@
|
||||
: null
|
||||
: null;
|
||||
|
||||
const locale = await sdk.forProject(page.params.region, page.params.project).locale.get();
|
||||
if (locale?.countryCode) {
|
||||
country = locale.countryCode;
|
||||
}
|
||||
const countryList = await sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.locale.listCountries();
|
||||
options = countryList.countries.map((country) => {
|
||||
return {
|
||||
value: country.code,
|
||||
|
||||
-5
@@ -82,9 +82,4 @@
|
||||
aspect-ratio: 1/1;
|
||||
}
|
||||
}
|
||||
|
||||
:global(.ai-icon-holder.notification) {
|
||||
width: 36px !important;
|
||||
height: 32px !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
+25
@@ -251,6 +251,7 @@
|
||||
data-mode={mode}
|
||||
bind:this={spreadsheetContainer}
|
||||
class:custom-columns={customColumns.length > 0}
|
||||
class:no-custom-columns={customColumns.length <= 0}
|
||||
class="databases-spreadsheet spreadsheet-container-outer">
|
||||
<SpreadsheetContainer>
|
||||
<Spreadsheet.Root
|
||||
@@ -381,6 +382,19 @@
|
||||
width: unset;
|
||||
}
|
||||
|
||||
&.no-custom-columns {
|
||||
@media (max-width: 768px) {
|
||||
& :global(.spreadsheet-wrapper) {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
& > .spreadsheet-fade-bottom {
|
||||
top: var(--top-actions-spacing) !important;
|
||||
background: var(--bgcolor-neutral-primary) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.custom-columns) :global(.spreadsheet-container) {
|
||||
overflow-x: hidden;
|
||||
overflow-y: hidden;
|
||||
@@ -393,12 +407,16 @@
|
||||
}
|
||||
|
||||
&[data-mode='rows'] {
|
||||
--top-actions-spacing: 50%;
|
||||
|
||||
& :global([role='rowheader'] :nth-last-child(2) [role='presentation']) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&[data-mode='indexes'] {
|
||||
--top-actions-spacing: 40%;
|
||||
|
||||
& :global([role='cell']:last-child [role='presentation']) {
|
||||
display: none;
|
||||
}
|
||||
@@ -463,4 +481,11 @@
|
||||
margin-bottom: 15%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
// global but controlled properly!
|
||||
:global(main:has(.no-custom-columns) .console-container) {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
+3
-2
@@ -11,7 +11,7 @@
|
||||
import { Fieldset, Layout, Icon, Input, Tag } from '@appwrite.io/pink-svelte';
|
||||
import { IconGithub, IconPencil } from '@appwrite.io/pink-icons-svelte';
|
||||
import { onMount } from 'svelte';
|
||||
import { ID, Runtime } from '@appwrite.io/console';
|
||||
import { ID, Runtime, Type } from '@appwrite.io/console';
|
||||
import { CustomId } from '$lib/components';
|
||||
import { getIconFromRuntime } from '$lib/stores/runtimes';
|
||||
import { regionalConsoleVariables } from '$routes/(console)/project-[region]-[project]/store';
|
||||
@@ -135,7 +135,8 @@
|
||||
repository: data.repository.name,
|
||||
owner: data.repository.owner,
|
||||
rootDirectory: rootDir || '.',
|
||||
version: latestTag ?? '1.0.0',
|
||||
type: Type.Tag,
|
||||
reference: latestTag ?? '1.0.0',
|
||||
activate: true
|
||||
});
|
||||
|
||||
|
||||
+3
-2
@@ -15,7 +15,7 @@
|
||||
import { writable } from 'svelte/store';
|
||||
import ProductionBranch from '$lib/components/git/productionBranchFieldset.svelte';
|
||||
import Configuration from './configuration.svelte';
|
||||
import { ID, Runtime, type Models } from '@appwrite.io/console';
|
||||
import { ID, Runtime, Type, type Models } from '@appwrite.io/console';
|
||||
import {
|
||||
ConnectBehaviour,
|
||||
NewRepository,
|
||||
@@ -180,7 +180,8 @@
|
||||
repository: data.template.providerRepositoryId || undefined,
|
||||
owner: data.template.providerOwner || undefined,
|
||||
rootDirectory: rt?.providerRootDirectory || undefined,
|
||||
version: data.template.providerVersion || undefined,
|
||||
type: Type.Tag,
|
||||
reference: data.template.providerVersion || undefined,
|
||||
activate: true
|
||||
});
|
||||
|
||||
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { app } from '$lib/stores/app';
|
||||
import Light from '../assets/cursor-ai.svg';
|
||||
import Dark from '../assets/dark/cursor-ai.svg';
|
||||
</script>
|
||||
|
||||
<img src={$app.themeInUse === 'dark' ? Dark : Light} width="20" height="20" alt="Cursor" />
|
||||
+79
-1
@@ -17,7 +17,7 @@
|
||||
import { Card } from '$lib/components';
|
||||
import { page } from '$app/state';
|
||||
import { onMount } from 'svelte';
|
||||
import { realtime, sdk } from '$lib/stores/sdk';
|
||||
import { getApiEndpoint, realtime, sdk } from '$lib/stores/sdk';
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { fade } from 'svelte/transition';
|
||||
@@ -26,6 +26,7 @@
|
||||
import { PlatformType } from '@appwrite.io/console';
|
||||
import { project } from '../../store';
|
||||
import { getCorrectTitle, type PlatformProps } from './store';
|
||||
import LlmBanner from './llmBanner.svelte';
|
||||
|
||||
let { isConnectPlatform = false }: PlatformProps = $props();
|
||||
|
||||
@@ -35,6 +36,60 @@
|
||||
let isPlatformCreated = $state(isConnectPlatform);
|
||||
|
||||
const projectId = page.params.project;
|
||||
const VERSIONS_ENDPOINT = (() => {
|
||||
const endpoint = getApiEndpoint(page.params.region);
|
||||
const url = new URL('/versions', endpoint);
|
||||
return url.toString();
|
||||
})();
|
||||
let androidSdkVersion = $state('11.3.0');
|
||||
|
||||
function buildAndroidInstructions(version: string) {
|
||||
return `
|
||||
Confirm you're working inside the correct Android project before editing anything:
|
||||
- Navigate into the directory that contains the real Android app module (look for gradlew, settings.gradle, and the app-level build.gradle(.kts)).
|
||||
- If Cursor opens in a parent folder (like your home directory) or you see multiple Android projects, ask which one to modify before making changes.
|
||||
- Update the app-level build.gradle.kts by default, but be ready to edit a Groovy build.gradle if the project hasn't migrated to Kotlin DSL yet.
|
||||
|
||||
Prefer Version Catalogs when adding the Appwrite SDK:
|
||||
1. If ./gradle/libs.versions.toml exists, add or reuse an Appwrite entry:
|
||||
\`\`\`toml
|
||||
[libraries]
|
||||
appwrite = { module = "io.appwrite:sdk-for-android", version = "${version}" }
|
||||
\`\`\`
|
||||
2. Reference it inside the module's dependencies block:
|
||||
\`\`\`kotlin
|
||||
dependencies {
|
||||
implementation(libs.appwrite)
|
||||
}
|
||||
\`\`\`
|
||||
Only when the project lacks ./gradle/libs.versions.toml should you hardcode the dependency:
|
||||
\`\`\`kotlin
|
||||
implementation("io.appwrite:sdk-for-android:${version}")
|
||||
\`\`\`
|
||||
Legacy Groovy scripts should use:
|
||||
\`\`\`groovy
|
||||
implementation "io.appwrite:sdk-for-android:${version}"
|
||||
\`\`\`
|
||||
|
||||
Before introducing any new files, search the project (app/src, libs/, shared modules, etc.) for existing Appwrite client helpers (look for \`Client(\`, \`AppwriteClient\`, or \`.setEndpoint\`). If a client already exists, update its configuration instead of creating a duplicate.
|
||||
|
||||
Ensure the Appwrite client is initialized with the application context and current project info:
|
||||
\`\`\`kotlin
|
||||
val client = Client(applicationContext)
|
||||
.setEndpoint("${sdk.forProject(page.params.region, page.params.project).client.config.endpoint}")
|
||||
.setProject("${projectId}")
|
||||
|
||||
val account = Account(client)
|
||||
\`\`\`
|
||||
|
||||
From the app's entry point (e.g., Application class or the first launched Activity), automatically invoke a helper that pings Appwrite so the user can verify connectivity and will be reflected on the Appwrite console:
|
||||
\`\`\`kotlin
|
||||
client.ping()
|
||||
\`\`\`
|
||||
`;
|
||||
}
|
||||
|
||||
const alreadyExistsInstructions = $derived(buildAndroidInstructions(androidSdkVersion));
|
||||
|
||||
const gitCloneCode =
|
||||
'\ngit clone https://github.com/appwrite/starter-for-android\ncd starter-for-android\n';
|
||||
@@ -43,6 +98,22 @@
|
||||
const val APPWRITE_PROJECT_NAME = "${$project.name}"
|
||||
const val APPWRITE_PUBLIC_ENDPOINT = "${sdk.forProject(page.params.region, page.params.project).client.config.endpoint}"`;
|
||||
|
||||
async function fetchAndroidSdkVersion() {
|
||||
try {
|
||||
const response = await fetch(VERSIONS_ENDPOINT);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch versions: ${response.status}`);
|
||||
}
|
||||
const data = await response.json();
|
||||
const latestVersion = data?.['client-android'];
|
||||
if (typeof latestVersion === 'string' && latestVersion.trim()) {
|
||||
androidSdkVersion = latestVersion.trim();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Unable to fetch latest Android SDK version', error);
|
||||
}
|
||||
}
|
||||
|
||||
async function createAndroidPlatform() {
|
||||
try {
|
||||
isCreatingPlatform = true;
|
||||
@@ -83,6 +154,7 @@ const val APPWRITE_PUBLIC_ENDPOINT = "${sdk.forProject(page.params.region, page.
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
fetchAndroidSdkVersion();
|
||||
const unsubscribe = realtime.forConsole(page.params.region, 'console', (response) => {
|
||||
if (response.events.includes(`projects.${projectId}.ping`)) {
|
||||
connectionSuccessful = true;
|
||||
@@ -171,6 +243,12 @@ const val APPWRITE_PUBLIC_ENDPOINT = "${sdk.forProject(page.params.region, page.
|
||||
{#if isPlatformCreated}
|
||||
<Fieldset legend="Clone starter" badge="Optional">
|
||||
<Layout.Stack gap="l">
|
||||
<LlmBanner
|
||||
platform="android"
|
||||
{configCode}
|
||||
{alreadyExistsInstructions}
|
||||
openers={['cursor']} />
|
||||
|
||||
<Typography.Text variant="m-500">
|
||||
1. If you're starting a new project, you can clone our starter kit from
|
||||
GitHub using the terminal, VSCode or Android Studio.
|
||||
|
||||
+32
-1
@@ -28,6 +28,7 @@
|
||||
import { app } from '$lib/stores/app';
|
||||
import { project } from '../../store';
|
||||
import { getCorrectTitle, type PlatformProps } from './store';
|
||||
import LlmBanner from './llmBanner.svelte';
|
||||
|
||||
let { isConnectPlatform = false, platform = PlatformType.Appleios }: PlatformProps = $props();
|
||||
|
||||
@@ -38,6 +39,30 @@
|
||||
|
||||
const projectId = page.params.project;
|
||||
|
||||
const alreadyExistsInstructions = `
|
||||
Install the Appwrite iOS SDK using the following package URL:
|
||||
|
||||
\`\`\`
|
||||
https://github.com/appwrite/sdk-for-apple
|
||||
\`\`\`
|
||||
|
||||
From a suitable lib directory, export the Appwrite client as a global variable:
|
||||
|
||||
\`\`\`
|
||||
let client = Client()
|
||||
.setEndpoint("${sdk.forProject(page.params.region, page.params.project).client.config.endpoint}")
|
||||
.setProject("${projectId}")
|
||||
|
||||
let account = Account(client)
|
||||
\`\`\`
|
||||
|
||||
On the homepage of the app, create a button that says "Send a ping" and when clicked, it should call the following function:
|
||||
|
||||
\`\`\`
|
||||
client.ping()
|
||||
\`\`\`
|
||||
`;
|
||||
|
||||
const gitCloneCode =
|
||||
'\ngit clone https://github.com/appwrite/starter-for-ios\ncd starter-for-ios\n';
|
||||
|
||||
@@ -45,7 +70,7 @@
|
||||
APPWRITE_PROJECT_NAME: "${$project.name}"
|
||||
APPWRITE_PUBLIC_ENDPOINT: "${sdk.forProject(page.params.region, page.params.project).client.config.endpoint}"`;
|
||||
|
||||
let platforms: { [key: string]: PlatformType } = {
|
||||
const platforms: { [key: string]: PlatformType } = {
|
||||
iOS: PlatformType.Appleios,
|
||||
macOS: PlatformType.Applemacos,
|
||||
watchOS: PlatformType.Applewatchos,
|
||||
@@ -199,6 +224,12 @@ APPWRITE_PUBLIC_ENDPOINT: "${sdk.forProject(page.params.region, page.params.proj
|
||||
{#if isPlatformCreated}
|
||||
<Fieldset legend="Clone starter" badge="Optional">
|
||||
<Layout.Stack gap="l">
|
||||
<LlmBanner
|
||||
platform="apple"
|
||||
{configCode}
|
||||
{alreadyExistsInstructions}
|
||||
openers={['cursor']} />
|
||||
|
||||
<Typography.Text variant="m-500">
|
||||
1. If you're starting a new project, you can clone our starter kit from
|
||||
GitHub using the terminal or XCode.
|
||||
|
||||
+56
-1
@@ -18,7 +18,7 @@
|
||||
import { Card } from '$lib/components';
|
||||
import { page } from '$app/state';
|
||||
import { onMount } from 'svelte';
|
||||
import { realtime, sdk } from '$lib/stores/sdk';
|
||||
import { getApiEndpoint, realtime, sdk } from '$lib/stores/sdk';
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { fade } from 'svelte/transition';
|
||||
@@ -28,6 +28,7 @@
|
||||
import { project } from '../../store';
|
||||
import { resolvedProfile } from '$lib/profiles/index.svelte';
|
||||
import { getCorrectTitle, type PlatformProps } from './store';
|
||||
import LlmBanner from './llmBanner.svelte';
|
||||
|
||||
let { isConnectPlatform = false, platform = PlatformType.Flutterandroid }: PlatformProps =
|
||||
$props();
|
||||
@@ -38,6 +39,38 @@
|
||||
let isPlatformCreated = $state(isConnectPlatform);
|
||||
|
||||
const projectId = page.params.project;
|
||||
const VERSIONS_ENDPOINT = (() => {
|
||||
const endpoint = getApiEndpoint(page.params.region);
|
||||
const url = new URL('/versions', endpoint);
|
||||
return url.toString();
|
||||
})();
|
||||
let flutterSdkVersion = $state('20.3.0');
|
||||
|
||||
function buildFlutterInstructions(version: string) {
|
||||
return `
|
||||
Install the Appwrite Flutter SDK using the following command:
|
||||
|
||||
\`\`\`
|
||||
flutter pub add appwrite:${version}
|
||||
\`\`\`
|
||||
|
||||
From a suitable lib directory, export the Appwrite client as a global variable, hardcode the project details too:
|
||||
|
||||
\`\`\`
|
||||
final Client client = Client()
|
||||
.setProject("${projectId}")
|
||||
.setEndpoint("${sdk.forProject(page.params.region, page.params.project).client.config.endpoint}");
|
||||
\`\`\`
|
||||
|
||||
On the homepage of the app, create a button that says "Send a ping" and when clicked, it should call the following function:
|
||||
|
||||
\`\`\`
|
||||
client.ping();
|
||||
\`\`\`
|
||||
`;
|
||||
}
|
||||
|
||||
const alreadyExistsInstructions = $derived(buildFlutterInstructions(flutterSdkVersion));
|
||||
|
||||
const gitCloneCode =
|
||||
'\ngit clone https://github.com/appwrite/starter-for-flutter\ncd starter-for-flutter\n';
|
||||
@@ -111,6 +144,22 @@
|
||||
[PlatformType.Flutterwindows]: 'Package name'
|
||||
};
|
||||
|
||||
async function fetchFlutterSdkVersion() {
|
||||
try {
|
||||
const response = await fetch(VERSIONS_ENDPOINT);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch versions: ${response.status}`);
|
||||
}
|
||||
const data = await response.json();
|
||||
const latestVersion = data?.['client-flutter'];
|
||||
if (typeof latestVersion === 'string' && latestVersion.trim()) {
|
||||
flutterSdkVersion = latestVersion.trim();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Unable to fetch latest Flutter SDK version', error);
|
||||
}
|
||||
}
|
||||
|
||||
async function createFlutterPlatform() {
|
||||
try {
|
||||
isCreatingPlatform = true;
|
||||
@@ -158,6 +207,7 @@
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
fetchFlutterSdkVersion();
|
||||
const unsubscribe = realtime.forConsole(page.params.region, 'console', (response) => {
|
||||
if (response.events.includes(`projects.${projectId}.ping`)) {
|
||||
connectionSuccessful = true;
|
||||
@@ -281,6 +331,11 @@
|
||||
{#if isPlatformCreated}
|
||||
<Fieldset legend="Clone starter" badge="Optional">
|
||||
<Layout.Stack gap="l">
|
||||
<LlmBanner
|
||||
platform="flutter"
|
||||
{configCode}
|
||||
{alreadyExistsInstructions}
|
||||
openers={['cursor']} />
|
||||
<Typography.Text variant="m-500">
|
||||
1. If you're starting a new project, you can clone our starter kit from
|
||||
GitHub using the terminal, VSCode or Android Studio.
|
||||
|
||||
+35
@@ -28,6 +28,7 @@
|
||||
import { project } from '../../store';
|
||||
import { resolvedProfile } from '$lib/profiles/index.svelte';
|
||||
import { getCorrectTitle, type PlatformProps } from './store';
|
||||
import LlmBanner from './llmBanner.svelte';
|
||||
|
||||
let { isConnectPlatform = false, platform = PlatformType.Reactnativeandroid }: PlatformProps =
|
||||
$props();
|
||||
@@ -39,6 +40,28 @@
|
||||
|
||||
const projectId = page.params.project;
|
||||
|
||||
const alreadyExistsInstructions = `
|
||||
Install the Appwrite React Native SDK using the following command, respect user's package manager of choice and use the one being used in the codebase:
|
||||
|
||||
\`\`\`
|
||||
npx expo install react-native-appwrite react-native-url-polyfill
|
||||
\`\`\`
|
||||
|
||||
From a suitable lib directory, export the Appwrite client as a global variable, hardcode the project details too:
|
||||
|
||||
\`\`\`
|
||||
const client = new Client()
|
||||
.setProject("${projectId}")
|
||||
.setEndpoint("${sdk.forProject(page.params.region, page.params.project).client.config.endpoint}");
|
||||
\`\`\`
|
||||
|
||||
From the entrypoint of the app, make it so that the following function is automatically called which will ping the Appwrite backend server to verify the setup. Let the user know about this function being added
|
||||
|
||||
\`\`\`
|
||||
client.ping();
|
||||
\`\`\`
|
||||
`;
|
||||
|
||||
const gitCloneCode =
|
||||
'\ngit clone https://github.com/appwrite/starter-for-react-native\ncd starter-for-react-native\n';
|
||||
|
||||
@@ -46,6 +69,12 @@
|
||||
EXPO_PUBLIC_APPWRITE_PROJECT_NAME="${$project.name}"
|
||||
EXPO_PUBLIC_APPWRITE_ENDPOINT=${sdk.forProject(page.params.region, page.params.project).client.config.endpoint}`;
|
||||
|
||||
const promptConfigCode = `
|
||||
const client = new Client()
|
||||
.setProject("${projectId}")
|
||||
.setEndpoint("${sdk.forProject(page.params.region, page.params.project).client.config.endpoint}")
|
||||
`;
|
||||
|
||||
let platforms: { [key: string]: PlatformType } = {
|
||||
Android: PlatformType.Reactnativeandroid,
|
||||
iOS: PlatformType.Reactnativeios
|
||||
@@ -226,6 +255,12 @@ EXPO_PUBLIC_APPWRITE_ENDPOINT=${sdk.forProject(page.params.region, page.params.p
|
||||
{#if isPlatformCreated}
|
||||
<Fieldset legend="Clone starter" badge="Optional">
|
||||
<Layout.Stack gap="l">
|
||||
<LlmBanner
|
||||
platform="reactnative"
|
||||
configCode={promptConfigCode}
|
||||
{alreadyExistsInstructions}
|
||||
openers={['cursor']} />
|
||||
|
||||
<Typography.Text variant="m-500">
|
||||
1. If you're starting a new project, you can clone our starter kit from
|
||||
GitHub using the terminal or VSCode.
|
||||
|
||||
+60
-1
@@ -48,8 +48,14 @@
|
||||
} from './components/index';
|
||||
import { extendedHostnameRegex } from '$lib/helpers/string';
|
||||
import { project } from '../../store';
|
||||
import {
|
||||
type PlatformProps,
|
||||
type FrameworkType,
|
||||
type LLMPromptConfig,
|
||||
getCorrectTitle
|
||||
} from './store';
|
||||
import LlmBanner from './llmBanner.svelte';
|
||||
import { resolvedProfile } from '$lib/profiles/index.svelte';
|
||||
import { type PlatformProps, type FrameworkType, getCorrectTitle } from './store';
|
||||
|
||||
let { key, isConnectPlatform = false, platform = PlatformType.Web }: PlatformProps = $props();
|
||||
|
||||
@@ -158,6 +164,57 @@ ${prefix}APPWRITE_ENDPOINT = "${sdk.forProject(page.params.region, page.params.p
|
||||
selectedFramework ? selectedFramework.icon : NoFrameworkIcon
|
||||
);
|
||||
|
||||
const llmConfig: LLMPromptConfig = $derived({
|
||||
alreadyExistsInstructions: `
|
||||
Install the Appwrite web SDK using the following command. Respect the user's package manager of choice. Do not use NPM if the user uses Bun for example.
|
||||
|
||||
\`\`\`bash
|
||||
npm install appwrite
|
||||
\`\`\`
|
||||
|
||||
Create a new \`appwrite.js\` (or equivalent, respecting the framework and language, don't create a JS file if TS is being used in the project) file in a suitable lib directory and have the following code:
|
||||
|
||||
\`\`\`js
|
||||
import { Client, Account, Databases } from "appwrite";
|
||||
|
||||
const client = new Client()
|
||||
.setEndpoint("${sdk.forProject(page.params.region, page.params.project).client.config.endpoint}")
|
||||
.setProject("${projectId}");
|
||||
|
||||
const account = new Account(client);
|
||||
const databases = new Databases(client);
|
||||
|
||||
export { client, account, databases };
|
||||
\`\`\`
|
||||
|
||||
When the app is opened, make it so that the following function is automatically called which will ping the Appwrite backend server to verify the setup. Let the user know about this function being added
|
||||
|
||||
\`\`\`js
|
||||
client.ping();
|
||||
\`\`\`
|
||||
`,
|
||||
title: `Copy prompt: starter kit for Appwrite in ${selectedFramework?.label || 'Web'}`,
|
||||
cloneCommand: `git clone https://github.com/appwrite/starter-for-${selectedFramework?.key}\ncd starter-for-${selectedFramework?.key}`,
|
||||
configFile:
|
||||
selectedFramework?.key === 'angular'
|
||||
? 'src/environments/environment.ts'
|
||||
: 'appwrite.js',
|
||||
configCode:
|
||||
// selectedFramework?.key === 'angular'
|
||||
// ? `APPWRITE_PROJECT_ID=${projectId}\nAPPWRITE_PROJECT_NAME=${$project.name}\nAPPWRITE_ENDPOINT=${sdk.forProject(page.params.region, page.params.project).client.config.endpoint}`
|
||||
// : `
|
||||
// const client = new Client()
|
||||
// .setEndpoint("${sdk.forProject(page.params.region, page.params.project).client.config.endpoint}")
|
||||
// .setProject("${projectId}");
|
||||
// `,
|
||||
`APPWRITE_PROJECT_ID = "${projectId}"
|
||||
APPWRITE_PROJECT_NAME = "${$project.name}"
|
||||
APPWRITE_ENDPOINT = "${sdk.forProject(page.params.region, page.params.project).client.config.endpoint}"`,
|
||||
configLanguage: selectedFramework?.key === 'angular' ? 'ts' : 'dotenv',
|
||||
runInstructions: `Install project dependencies using \`npm install\`, then run the app using \`${selectedFramework?.runCommand}\`. Demo app runs on http://localhost:${selectedFramework?.portNumber}. Click the \`Send a ping\` button to verify the setup.`,
|
||||
using: 'the terminal or VSCode'
|
||||
});
|
||||
|
||||
async function createWebPlatform() {
|
||||
const hostnameRegex = new RegExp(extendedHostnameRegex);
|
||||
const finalHostname = hostname?.trim() || 'localhost';
|
||||
@@ -302,6 +359,8 @@ ${prefix}APPWRITE_ENDPOINT = "${sdk.forProject(page.params.region, page.params.p
|
||||
{#if isPlatformCreated && !isChangingFramework}
|
||||
<Fieldset legend="Clone starter" badge="Optional">
|
||||
<Layout.Stack gap="l">
|
||||
<LlmBanner config={llmConfig} openers={['cursor', 'lovable']} />
|
||||
|
||||
<Typography.Text variant="m-500">
|
||||
1. If you're starting a new project, you can clone our starter kit from
|
||||
GitHub using the terminal or VSCode.
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
<script lang="ts">
|
||||
import { copy } from '$lib/helpers/copy';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import {
|
||||
ActionMenu,
|
||||
Alert,
|
||||
Icon,
|
||||
Layout,
|
||||
Popover,
|
||||
Typography,
|
||||
Button as PinkButton
|
||||
} from '@appwrite.io/pink-svelte';
|
||||
import { IconChevronDown, IconChevronUp, IconLovable } from '@appwrite.io/pink-icons-svelte';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { buildPlatformConfig, generatePromptFromConfig, type LLMPromptConfig } from './store';
|
||||
import { Click, trackEvent } from '$lib/actions/analytics';
|
||||
import IconAINotification from '../../databases/database-[database]/(suggestions)/icon/aiNotification.svelte';
|
||||
import Avatar from '$lib/components/avatar.svelte';
|
||||
import CursorIcon from '$routes/(console)/project-[region]-[project]/overview/components/CursorIconLarge.svelte';
|
||||
import type { ComponentType } from 'svelte';
|
||||
|
||||
let {
|
||||
platform,
|
||||
configCode,
|
||||
alreadyExistsInstructions,
|
||||
config: customConfig,
|
||||
openers = [] as Array<SupportedAgents>
|
||||
}: {
|
||||
platform?: string;
|
||||
configCode?: string;
|
||||
alreadyExistsInstructions?: string;
|
||||
config?: LLMPromptConfig;
|
||||
openers?: Array<SupportedAgents>;
|
||||
} = $props();
|
||||
|
||||
const config = $derived.by(() => {
|
||||
if (customConfig) return customConfig;
|
||||
if (platform && configCode)
|
||||
return buildPlatformConfig(platform, configCode, alreadyExistsInstructions);
|
||||
throw new Error('LlmBanner: must provide either config OR (platform + configCode)');
|
||||
});
|
||||
|
||||
const prompt = $derived(generatePromptFromConfig(config));
|
||||
|
||||
let showAlert = $state(true);
|
||||
|
||||
type OpenerConfig = {
|
||||
id: SupportedAgents;
|
||||
label: string;
|
||||
description: string;
|
||||
href: (prompt: string) => string;
|
||||
icon?: ComponentType;
|
||||
imgSrc?: string;
|
||||
alt: string;
|
||||
};
|
||||
|
||||
type SupportedAgents = 'cursor' | 'lovable';
|
||||
|
||||
const openersConfig: Record<SupportedAgents, OpenerConfig> = {
|
||||
cursor: {
|
||||
id: 'cursor',
|
||||
label: 'Open in Cursor',
|
||||
description: 'Set up starter kit in Cursor',
|
||||
href: (p: string) => {
|
||||
trackEvent(Click.OpenInCursorClick, {
|
||||
platform: config.title
|
||||
});
|
||||
const u = new URL('https://cursor.com/link/prompt');
|
||||
u.searchParams.set('text', p);
|
||||
return u.toString();
|
||||
},
|
||||
icon: CursorIcon,
|
||||
alt: 'Cursor'
|
||||
},
|
||||
lovable: {
|
||||
id: 'lovable',
|
||||
label: 'Open in Lovable',
|
||||
description: 'Set up starter kit in Lovable',
|
||||
href: (p: string) => {
|
||||
trackEvent(Click.OpenInLovableClick, {
|
||||
platform: config.title
|
||||
});
|
||||
const u = new URL('https://lovable.dev/');
|
||||
u.searchParams.set('autosubmit', 'true');
|
||||
u.searchParams.set('prompt', p);
|
||||
return u.toString();
|
||||
},
|
||||
icon: IconLovable,
|
||||
alt: 'Lovable'
|
||||
}
|
||||
};
|
||||
|
||||
const validOpeners = $derived(openers.filter((id) => openersConfig[id]));
|
||||
|
||||
async function copyPrompt() {
|
||||
await copy(prompt);
|
||||
|
||||
trackEvent(Click.CopyPromptStarterKitClick, {
|
||||
platform: config.title
|
||||
});
|
||||
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: 'Prompt copied to clipboard'
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if showAlert}
|
||||
<Alert.Inline
|
||||
status="info"
|
||||
title="Set up your starter kit with AI"
|
||||
dismissible
|
||||
--bgcolor-neutral-default="var(--bgcolor-neutral-primary)"
|
||||
--fgcolor-info="var(--fgcolor-neutral-primary)"
|
||||
on:dismiss={() => (showAlert = false)}>
|
||||
<svelte:fragment slot="icon">
|
||||
<IconAINotification />
|
||||
</svelte:fragment>
|
||||
<Layout.Stack direction="column" class="alert-content" gap="l">
|
||||
<Layout.Stack direction="column" alignItems="center" gap="s">
|
||||
<Typography.Text>
|
||||
Copy the prompt or open it directly in an AI tool like Cursor or Lovable to get
|
||||
step-by-step instructions, starter code, and SDK commands for your project.
|
||||
</Typography.Text>
|
||||
</Layout.Stack>
|
||||
|
||||
<Popover let:toggle let:showing padding="none" placement="bottom-start">
|
||||
<svelte:fragment slot="tooltip" let:toggle>
|
||||
<ActionMenu.Root>
|
||||
{#each validOpeners as openerId}
|
||||
{@const o = openersConfig[openerId]}
|
||||
{#if o}
|
||||
<ActionMenu.Item.Button
|
||||
on:click={(e) => {
|
||||
window.open(
|
||||
o.href(prompt),
|
||||
'_blank',
|
||||
'noopener,noreferrer'
|
||||
);
|
||||
toggle(e);
|
||||
}}>
|
||||
<Layout.Stack direction="row" gap="s" alignItems="center">
|
||||
<Avatar size="s" alt={o.alt}>
|
||||
{#if o.icon}
|
||||
<Icon icon={o.icon} size="l" />
|
||||
{:else if o.imgSrc}
|
||||
<img src={o.imgSrc} alt={o.alt} />
|
||||
{/if}
|
||||
</Avatar>
|
||||
<Layout.Stack gap="none">
|
||||
<Typography.Text
|
||||
color="--fgcolor-neutral-secondary"
|
||||
variant="m-500">{o.label}</Typography.Text>
|
||||
<Typography.Text color="--fgcolor-neutral-tertiary">
|
||||
{o.description}
|
||||
</Typography.Text>
|
||||
</Layout.Stack>
|
||||
</Layout.Stack>
|
||||
</ActionMenu.Item.Button>
|
||||
{/if}
|
||||
{/each}
|
||||
</ActionMenu.Root>
|
||||
</svelte:fragment>
|
||||
<PinkButton.Split>
|
||||
<Button
|
||||
secondary
|
||||
size="s"
|
||||
class={validOpeners.length ? 'btn-no-right-radius' : ''}
|
||||
on:click={copyPrompt}
|
||||
disabled={!prompt || prompt.length === 0}>Copy setup prompt</Button>
|
||||
{#if validOpeners.length}
|
||||
<Button
|
||||
secondary
|
||||
size="s"
|
||||
class="btn-no-left-radius"
|
||||
icon
|
||||
on:click={toggle}
|
||||
ariaLabel="Open action menu"
|
||||
disabled={!prompt || prompt.length === 0}>
|
||||
<Icon icon={showing ? IconChevronUp : IconChevronDown} />
|
||||
</Button>
|
||||
{/if}
|
||||
</PinkButton.Split>
|
||||
</Popover>
|
||||
</Layout.Stack>
|
||||
</Alert.Inline>
|
||||
{/if}
|
||||
@@ -17,6 +17,119 @@ export type FrameworkType = {
|
||||
updateConfigCode: string;
|
||||
};
|
||||
|
||||
export type LLMPromptConfig = {
|
||||
title: string;
|
||||
alreadyExistsInstructions: string;
|
||||
cloneCommand: string;
|
||||
configFile: string;
|
||||
configCode: string;
|
||||
configLanguage: string;
|
||||
runInstructions: string;
|
||||
using: string;
|
||||
};
|
||||
|
||||
export function getCorrectTitle(isConnectPlatform: boolean, platform: string) {
|
||||
return isConnectPlatform ? `Connect your ${platform} app` : `Add ${platform} platform`;
|
||||
}
|
||||
|
||||
export function generatePromptFromConfig(config: LLMPromptConfig): string {
|
||||
return `
|
||||
Goal: Setting up Appwrite SDK in the project depending on if a project already exists or not.
|
||||
|
||||
Following are the project details:
|
||||
|
||||
\`\`\`
|
||||
${config.configCode}
|
||||
\`\`\`
|
||||
|
||||
Follow the steps depending on if a project already exists on user's working directory or not:
|
||||
|
||||
## If a project already exists:
|
||||
${config.alreadyExistsInstructions}
|
||||
|
||||
## If a project does not exist:
|
||||
|
||||
1. Clone the starter kit using ${config.using || 'the terminal'}. Make sure to clone in the current working directory so that the cloned files are directly available in the working directory.
|
||||
|
||||
\`\`\`bash
|
||||
${config.cloneCommand} .
|
||||
\`\`\`
|
||||
|
||||
2. Replace all occurrences of the environment variables described in the project details section with their corresponding values. This effectively hardcodes the project details wherever those environment variables are used. Use grep (or an equivalent search) to find and update all occurrences.
|
||||
3. ${config.runInstructions}`;
|
||||
}
|
||||
|
||||
type PlatformConfig = {
|
||||
name: string;
|
||||
title: string;
|
||||
repoName: string;
|
||||
configFile: string;
|
||||
configLanguage: string;
|
||||
runInstructions: string;
|
||||
using: string;
|
||||
};
|
||||
|
||||
const platformConfigs: Record<string, PlatformConfig> = {
|
||||
android: {
|
||||
name: 'Kotlin',
|
||||
title: 'Copy prompt: starter kit for Appwrite in Kotlin',
|
||||
repoName: 'starter-for-android',
|
||||
configFile: 'constants/AppwriteConfig.kt',
|
||||
configLanguage: 'kotlin',
|
||||
runInstructions:
|
||||
'Run the app on a connected device or emulator, then click the `Send a ping` button to verify the setup.',
|
||||
using: 'the terminal, VSCode or Android Studio'
|
||||
},
|
||||
apple: {
|
||||
name: 'Apple platforms',
|
||||
title: 'Copy prompt: starter kit for Appwrite for Apple platforms',
|
||||
repoName: 'starter-for-ios',
|
||||
configFile: 'Sources/Config.plist',
|
||||
configLanguage: 'plaintext',
|
||||
runInstructions:
|
||||
'Run the app on a connected device or simulator, then click the `Send a ping` button to verify the setup.',
|
||||
using: 'the terminal or XCode'
|
||||
},
|
||||
flutter: {
|
||||
name: 'Flutter',
|
||||
title: 'Copy prompt: starter kit for Appwrite in Flutter',
|
||||
repoName: 'starter-for-flutter',
|
||||
configFile: 'lib/config/environment.dart',
|
||||
configLanguage: 'dart',
|
||||
runInstructions:
|
||||
'Run the app on a connected device or simulator using `flutter run -d [device_name]`, then click the `Send a ping` button to verify the setup. Ask the user if the AI agent should run the command to run the app for them. Provide the full command while you ask for permission.',
|
||||
using: 'the terminal'
|
||||
},
|
||||
reactnative: {
|
||||
name: 'React Native',
|
||||
title: 'Copy prompt: starter kit for Appwrite in React Native',
|
||||
repoName: 'starter-for-react-native',
|
||||
configFile: 'index.ts',
|
||||
configLanguage: 'typescript',
|
||||
runInstructions:
|
||||
'After replacing and hardcoding project details, run the app on a connected device or simulator using `npm install` followed by `npm run ios` or `npm run android`, then click the `Send a ping` button to verify the setup. Ask the user if the AI agent should run the command to run the app for them. Provide the full command while you ask for permission.',
|
||||
using: 'the terminal or VSCode'
|
||||
}
|
||||
};
|
||||
|
||||
export function buildPlatformConfig(
|
||||
platformKey: string,
|
||||
configCode: string,
|
||||
alreadyExistsInstructions: string
|
||||
): LLMPromptConfig {
|
||||
const config = platformConfigs[platformKey];
|
||||
if (!config) {
|
||||
throw new Error(`Unknown platform: ${platformKey}`);
|
||||
}
|
||||
|
||||
return {
|
||||
title: config.title,
|
||||
alreadyExistsInstructions: alreadyExistsInstructions,
|
||||
cloneCommand: `git clone https://github.com/appwrite/${config.repoName}\ncd ${config.repoName}`,
|
||||
configFile: config.configFile,
|
||||
configCode: configCode,
|
||||
configLanguage: config.configLanguage,
|
||||
runInstructions: config.runInstructions,
|
||||
using: config.using
|
||||
};
|
||||
}
|
||||
|
||||
+3
-2
@@ -12,7 +12,7 @@
|
||||
import { IconGithub, IconPencil } from '@appwrite.io/pink-icons-svelte';
|
||||
import { onMount } from 'svelte';
|
||||
import Domain from '../domain.svelte';
|
||||
import { Adapter, BuildRuntime, Framework, ID } from '@appwrite.io/console';
|
||||
import { Adapter, BuildRuntime, Framework, ID, Type } from '@appwrite.io/console';
|
||||
import { CustomId } from '$lib/components';
|
||||
import { getFrameworkIcon } from '$lib/stores/sites';
|
||||
import { regionalConsoleVariables } from '$routes/(console)/project-[region]-[project]/store';
|
||||
@@ -173,7 +173,8 @@
|
||||
repository: data.repository.name,
|
||||
owner: data.repository.owner,
|
||||
rootDirectory: rootDir || '.',
|
||||
version: latestTag ?? '1.0.0',
|
||||
type: Type.Tag,
|
||||
reference: latestTag ?? '1.0.0',
|
||||
activate: true
|
||||
});
|
||||
|
||||
|
||||
+3
-2
@@ -24,7 +24,7 @@
|
||||
import Details from '../../details.svelte';
|
||||
import Configuration from './configuration.svelte';
|
||||
import Aside from '../../aside.svelte';
|
||||
import { Adapter, BuildRuntime, Framework, ID, type Models } from '@appwrite.io/console';
|
||||
import { Adapter, BuildRuntime, Framework, ID, Type, type Models } from '@appwrite.io/console';
|
||||
import {
|
||||
ConnectBehaviour,
|
||||
NewRepository,
|
||||
@@ -160,7 +160,8 @@
|
||||
repository: data.template.providerRepositoryId,
|
||||
owner: data.template.providerOwner,
|
||||
rootDirectory: framework.providerRootDirectory,
|
||||
version: data.template.providerVersion,
|
||||
type: Type.Tag,
|
||||
reference: data.template.providerVersion,
|
||||
activate: true
|
||||
});
|
||||
|
||||
|
||||
+38
-2
@@ -32,7 +32,8 @@
|
||||
allowedFileExtensions: values.allowedFileExtensions,
|
||||
compression: values.compression,
|
||||
encryption: values.encryption,
|
||||
antivirus: values.antivirus
|
||||
antivirus: values.antivirus,
|
||||
transformations: values.transformations
|
||||
});
|
||||
|
||||
await invalidate(Dependencies.BUCKET);
|
||||
@@ -102,7 +103,8 @@
|
||||
$permissions: permissions,
|
||||
encryption,
|
||||
antivirus,
|
||||
compression
|
||||
compression,
|
||||
transformations
|
||||
} = data.bucket;
|
||||
|
||||
const compressionOptions = [
|
||||
@@ -215,6 +217,18 @@
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function updateTransformations() {
|
||||
updateBucket(
|
||||
data.bucket,
|
||||
{
|
||||
transformations
|
||||
},
|
||||
{
|
||||
trackEventName: Submit.BucketUpdateTransformations
|
||||
}
|
||||
);
|
||||
}
|
||||
</script>
|
||||
|
||||
<Container>
|
||||
@@ -366,6 +380,28 @@
|
||||
</CardGrid>
|
||||
</Form>
|
||||
|
||||
<Form onSubmit={updateTransformations}>
|
||||
<CardGrid>
|
||||
<svelte:fragment slot="title">Image transformations</svelte:fragment>
|
||||
<svelte:fragment slot="aside">
|
||||
<Selector.Switch
|
||||
label="Image transformations"
|
||||
id="transformations"
|
||||
bind:checked={transformations}
|
||||
description="Enabling this option allows image manipulation through the API, including resizing, cropping, and format conversion." />
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button
|
||||
disabled={transformations === data.bucket.transformations ||
|
||||
($readOnly && !GRACE_PERIOD_OVERRIDE)}
|
||||
submit>
|
||||
Update
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Form>
|
||||
|
||||
<UpdateMaxFileSize currentPlan={data.currentPlan} bucket={data.bucket} />
|
||||
|
||||
<Form onSubmit={updateAllowedExtensions}>
|
||||
|
||||
Reference in New Issue
Block a user