From 9aae29d1465ccc97e84caf9cbb14576140467c20 Mon Sep 17 00:00:00 2001 From: Sebastian Jeltsch Date: Wed, 26 Feb 2025 23:14:23 +0100 Subject: [PATCH] Fix ui issues and improve config validation. --- client/testfixture/config.textproto | 2 +- trailbase-core/js/admin/package.json | 2 +- trailbase-core/js/admin/proto/config.ts | 129 ++--- trailbase-core/js/admin/proto/config_api.ts | 8 +- .../admin/proto/google/protobuf/descriptor.ts | 271 ++++----- .../js/admin/src/components/FormFields.tsx | 517 +++++++++--------- .../js/admin/src/components/NavBar.tsx | 2 +- .../js/admin/src/components/SafeSheet.tsx | 2 + .../js/admin/src/components/auth/AddUser.tsx | 4 +- .../admin/src/components/auth/AuthButton.tsx | 6 +- .../admin/src/components/auth/UserTable.tsx | 24 +- .../src/components/settings/AuthSettings.tsx | 11 +- .../src/components/settings/EmailSettings.tsx | 82 ++- .../src/components/settings/SettingsPage.tsx | 6 +- .../components/tables/CreateAlterIndex.tsx | 1 - .../components/tables/CreateAlterTable.tsx | 14 +- .../src/components/tables/InsertAlterRow.tsx | 51 +- trailbase-core/js/admin/src/lib/schema.ts | 16 +- trailbase-core/js/admin/src/lib/user.ts | 2 - trailbase-core/src/app_state.rs | 2 +- trailbase-core/src/config.rs | 59 +- 21 files changed, 603 insertions(+), 608 deletions(-) diff --git a/client/testfixture/config.textproto b/client/testfixture/config.textproto index bfd7fefb..372beb9a 100644 --- a/client/testfixture/config.textproto +++ b/client/testfixture/config.textproto @@ -19,8 +19,8 @@ record_apis: [{ }, { name: "simple_strict_table" table_name: "simple_strict_table" - enable_subscriptions: true acl_authenticated: [CREATE, READ, UPDATE, DELETE] + enable_subscriptions: true }, { name: "simple_complete_view" table_name: "simple_complete_view" diff --git a/trailbase-core/js/admin/package.json b/trailbase-core/js/admin/package.json index d3070e90..4baa58a0 100644 --- a/trailbase-core/js/admin/package.json +++ b/trailbase-core/js/admin/package.json @@ -8,7 +8,7 @@ "build": "vite build", "serve": "vite preview", "format": "prettier -w src", - "proto": "protoc --plugin=protoc-gen-ts=${PWD}/node_modules/ts-proto/protoc-gen-ts_proto ../../proto/*.proto -I../../proto -I/usr/include --ts_out=./proto/ --ts_opt=esModuleInterop=true", + "proto": "protoc --plugin=protoc-gen-ts=${PWD}/node_modules/ts-proto/protoc-gen-ts_proto ../../proto/*.proto -I../../proto -I/usr/include --ts_out=./proto/ --ts_opt=esModuleInterop=true,initializeFieldsAsUndefined=false", "check": "tsc --noEmit --skipLibCheck && eslint && vitest run", "test": "vitest run" }, diff --git a/trailbase-core/js/admin/proto/config.ts b/trailbase-core/js/admin/proto/config.ts index f9975859..9091e17d 100644 --- a/trailbase-core/js/admin/proto/config.ts +++ b/trailbase-core/js/admin/proto/config.ts @@ -380,7 +380,7 @@ export interface Config { } function createBaseEmailTemplate(): EmailTemplate { - return { subject: "", body: "" }; + return {}; } export const EmailTemplate: MessageFns = { @@ -428,8 +428,8 @@ export const EmailTemplate: MessageFns = { fromJSON(object: any): EmailTemplate { return { - subject: isSet(object.subject) ? globalThis.String(object.subject) : "", - body: isSet(object.body) ? globalThis.String(object.body) : "", + subject: isSet(object.subject) ? globalThis.String(object.subject) : undefined, + body: isSet(object.body) ? globalThis.String(object.body) : undefined, }; }, @@ -456,17 +456,7 @@ export const EmailTemplate: MessageFns = { }; function createBaseEmailConfig(): EmailConfig { - return { - smtpHost: "", - smtpPort: 0, - smtpUsername: "", - smtpPassword: "", - senderName: "", - senderAddress: "", - userVerificationTemplate: undefined, - passwordResetTemplate: undefined, - changeEmailTemplate: undefined, - }; + return {}; } export const EmailConfig: MessageFns = { @@ -591,12 +581,12 @@ export const EmailConfig: MessageFns = { fromJSON(object: any): EmailConfig { return { - smtpHost: isSet(object.smtpHost) ? globalThis.String(object.smtpHost) : "", - smtpPort: isSet(object.smtpPort) ? globalThis.Number(object.smtpPort) : 0, - smtpUsername: isSet(object.smtpUsername) ? globalThis.String(object.smtpUsername) : "", - smtpPassword: isSet(object.smtpPassword) ? globalThis.String(object.smtpPassword) : "", - senderName: isSet(object.senderName) ? globalThis.String(object.senderName) : "", - senderAddress: isSet(object.senderAddress) ? globalThis.String(object.senderAddress) : "", + smtpHost: isSet(object.smtpHost) ? globalThis.String(object.smtpHost) : undefined, + smtpPort: isSet(object.smtpPort) ? globalThis.Number(object.smtpPort) : undefined, + smtpUsername: isSet(object.smtpUsername) ? globalThis.String(object.smtpUsername) : undefined, + smtpPassword: isSet(object.smtpPassword) ? globalThis.String(object.smtpPassword) : undefined, + senderName: isSet(object.senderName) ? globalThis.String(object.senderName) : undefined, + senderAddress: isSet(object.senderAddress) ? globalThis.String(object.senderAddress) : undefined, userVerificationTemplate: isSet(object.userVerificationTemplate) ? EmailTemplate.fromJSON(object.userVerificationTemplate) : undefined, @@ -668,16 +658,7 @@ export const EmailConfig: MessageFns = { }; function createBaseOAuthProviderConfig(): OAuthProviderConfig { - return { - clientId: "", - clientSecret: "", - providerId: 0, - displayName: "", - authUrl: "", - tokenUrl: "", - userApiUrl: "", - pkce: false, - }; + return {}; } export const OAuthProviderConfig: MessageFns = { @@ -791,14 +772,14 @@ export const OAuthProviderConfig: MessageFns = { fromJSON(object: any): OAuthProviderConfig { return { - clientId: isSet(object.clientId) ? globalThis.String(object.clientId) : "", - clientSecret: isSet(object.clientSecret) ? globalThis.String(object.clientSecret) : "", - providerId: isSet(object.providerId) ? oAuthProviderIdFromJSON(object.providerId) : 0, - displayName: isSet(object.displayName) ? globalThis.String(object.displayName) : "", - authUrl: isSet(object.authUrl) ? globalThis.String(object.authUrl) : "", - tokenUrl: isSet(object.tokenUrl) ? globalThis.String(object.tokenUrl) : "", - userApiUrl: isSet(object.userApiUrl) ? globalThis.String(object.userApiUrl) : "", - pkce: isSet(object.pkce) ? globalThis.Boolean(object.pkce) : false, + clientId: isSet(object.clientId) ? globalThis.String(object.clientId) : undefined, + clientSecret: isSet(object.clientSecret) ? globalThis.String(object.clientSecret) : undefined, + providerId: isSet(object.providerId) ? oAuthProviderIdFromJSON(object.providerId) : undefined, + displayName: isSet(object.displayName) ? globalThis.String(object.displayName) : undefined, + authUrl: isSet(object.authUrl) ? globalThis.String(object.authUrl) : undefined, + tokenUrl: isSet(object.tokenUrl) ? globalThis.String(object.tokenUrl) : undefined, + userApiUrl: isSet(object.userApiUrl) ? globalThis.String(object.userApiUrl) : undefined, + pkce: isSet(object.pkce) ? globalThis.Boolean(object.pkce) : undefined, }; }, @@ -849,7 +830,7 @@ export const OAuthProviderConfig: MessageFns = { }; function createBaseAuthConfig(): AuthConfig { - return { authTokenTtlSec: 0, refreshTokenTtlSec: 0, oauthProviders: {} }; + return { oauthProviders: {} }; } export const AuthConfig: MessageFns = { @@ -911,8 +892,8 @@ export const AuthConfig: MessageFns = { fromJSON(object: any): AuthConfig { return { - authTokenTtlSec: isSet(object.authTokenTtlSec) ? globalThis.Number(object.authTokenTtlSec) : 0, - refreshTokenTtlSec: isSet(object.refreshTokenTtlSec) ? globalThis.Number(object.refreshTokenTtlSec) : 0, + authTokenTtlSec: isSet(object.authTokenTtlSec) ? globalThis.Number(object.authTokenTtlSec) : undefined, + refreshTokenTtlSec: isSet(object.refreshTokenTtlSec) ? globalThis.Number(object.refreshTokenTtlSec) : undefined, oauthProviders: isObject(object.oauthProviders) ? Object.entries(object.oauthProviders).reduce<{ [key: string]: OAuthProviderConfig }>((acc, [key, value]) => { acc[key] = OAuthProviderConfig.fromJSON(value); @@ -1043,7 +1024,7 @@ export const AuthConfig_OauthProvidersEntry: MessageFns = { @@ -1124,11 +1105,11 @@ export const S3StorageConfig: MessageFns = { fromJSON(object: any): S3StorageConfig { return { - endpoint: isSet(object.endpoint) ? globalThis.String(object.endpoint) : "", - region: isSet(object.region) ? globalThis.String(object.region) : "", - bucketName: isSet(object.bucketName) ? globalThis.String(object.bucketName) : "", - accessKey: isSet(object.accessKey) ? globalThis.String(object.accessKey) : "", - secretAccessKey: isSet(object.secretAccessKey) ? globalThis.String(object.secretAccessKey) : "", + endpoint: isSet(object.endpoint) ? globalThis.String(object.endpoint) : undefined, + region: isSet(object.region) ? globalThis.String(object.region) : undefined, + bucketName: isSet(object.bucketName) ? globalThis.String(object.bucketName) : undefined, + accessKey: isSet(object.accessKey) ? globalThis.String(object.accessKey) : undefined, + secretAccessKey: isSet(object.secretAccessKey) ? globalThis.String(object.secretAccessKey) : undefined, }; }, @@ -1167,7 +1148,7 @@ export const S3StorageConfig: MessageFns = { }; function createBaseServerConfig(): ServerConfig { - return { applicationName: "", siteUrl: "", logsRetentionSec: 0, backupIntervalSec: 0, s3StorageConfig: undefined }; + return {}; } export const ServerConfig: MessageFns = { @@ -1248,10 +1229,10 @@ export const ServerConfig: MessageFns = { fromJSON(object: any): ServerConfig { return { - applicationName: isSet(object.applicationName) ? globalThis.String(object.applicationName) : "", - siteUrl: isSet(object.siteUrl) ? globalThis.String(object.siteUrl) : "", - logsRetentionSec: isSet(object.logsRetentionSec) ? globalThis.Number(object.logsRetentionSec) : 0, - backupIntervalSec: isSet(object.backupIntervalSec) ? globalThis.Number(object.backupIntervalSec) : 0, + applicationName: isSet(object.applicationName) ? globalThis.String(object.applicationName) : undefined, + siteUrl: isSet(object.siteUrl) ? globalThis.String(object.siteUrl) : undefined, + logsRetentionSec: isSet(object.logsRetentionSec) ? globalThis.Number(object.logsRetentionSec) : undefined, + backupIntervalSec: isSet(object.backupIntervalSec) ? globalThis.Number(object.backupIntervalSec) : undefined, s3StorageConfig: isSet(object.s3StorageConfig) ? S3StorageConfig.fromJSON(object.s3StorageConfig) : undefined, }; }, @@ -1293,21 +1274,7 @@ export const ServerConfig: MessageFns = { }; function createBaseRecordApiConfig(): RecordApiConfig { - return { - name: "", - tableName: "", - conflictResolution: 0, - autofillMissingUserIdColumns: false, - enableSubscriptions: false, - aclWorld: [], - aclAuthenticated: [], - createAccessRule: "", - readAccessRule: "", - updateAccessRule: "", - deleteAccessRule: "", - schemaAccessRule: "", - expand: [], - }; + return { aclWorld: [], aclAuthenticated: [], expand: [] }; } export const RecordApiConfig: MessageFns = { @@ -1500,26 +1467,28 @@ export const RecordApiConfig: MessageFns = { fromJSON(object: any): RecordApiConfig { return { - name: isSet(object.name) ? globalThis.String(object.name) : "", - tableName: isSet(object.tableName) ? globalThis.String(object.tableName) : "", + name: isSet(object.name) ? globalThis.String(object.name) : undefined, + tableName: isSet(object.tableName) ? globalThis.String(object.tableName) : undefined, conflictResolution: isSet(object.conflictResolution) ? conflictResolutionStrategyFromJSON(object.conflictResolution) - : 0, + : undefined, autofillMissingUserIdColumns: isSet(object.autofillMissingUserIdColumns) ? globalThis.Boolean(object.autofillMissingUserIdColumns) - : false, - enableSubscriptions: isSet(object.enableSubscriptions) ? globalThis.Boolean(object.enableSubscriptions) : false, + : undefined, + enableSubscriptions: isSet(object.enableSubscriptions) + ? globalThis.Boolean(object.enableSubscriptions) + : undefined, aclWorld: globalThis.Array.isArray(object?.aclWorld) ? object.aclWorld.map((e: any) => permissionFlagFromJSON(e)) : [], aclAuthenticated: globalThis.Array.isArray(object?.aclAuthenticated) ? object.aclAuthenticated.map((e: any) => permissionFlagFromJSON(e)) : [], - createAccessRule: isSet(object.createAccessRule) ? globalThis.String(object.createAccessRule) : "", - readAccessRule: isSet(object.readAccessRule) ? globalThis.String(object.readAccessRule) : "", - updateAccessRule: isSet(object.updateAccessRule) ? globalThis.String(object.updateAccessRule) : "", - deleteAccessRule: isSet(object.deleteAccessRule) ? globalThis.String(object.deleteAccessRule) : "", - schemaAccessRule: isSet(object.schemaAccessRule) ? globalThis.String(object.schemaAccessRule) : "", + createAccessRule: isSet(object.createAccessRule) ? globalThis.String(object.createAccessRule) : undefined, + readAccessRule: isSet(object.readAccessRule) ? globalThis.String(object.readAccessRule) : undefined, + updateAccessRule: isSet(object.updateAccessRule) ? globalThis.String(object.updateAccessRule) : undefined, + deleteAccessRule: isSet(object.deleteAccessRule) ? globalThis.String(object.deleteAccessRule) : undefined, + schemaAccessRule: isSet(object.schemaAccessRule) ? globalThis.String(object.schemaAccessRule) : undefined, expand: globalThis.Array.isArray(object?.expand) ? object.expand.map((e: any) => globalThis.String(e)) : [], }; }, @@ -1591,7 +1560,7 @@ export const RecordApiConfig: MessageFns = { }; function createBaseJsonSchemaConfig(): JsonSchemaConfig { - return { name: "", schema: "" }; + return {}; } export const JsonSchemaConfig: MessageFns = { @@ -1639,8 +1608,8 @@ export const JsonSchemaConfig: MessageFns = { fromJSON(object: any): JsonSchemaConfig { return { - name: isSet(object.name) ? globalThis.String(object.name) : "", - schema: isSet(object.schema) ? globalThis.String(object.schema) : "", + name: isSet(object.name) ? globalThis.String(object.name) : undefined, + schema: isSet(object.schema) ? globalThis.String(object.schema) : undefined, }; }, diff --git a/trailbase-core/js/admin/proto/config_api.ts b/trailbase-core/js/admin/proto/config_api.ts index 9881b31a..614ea499 100644 --- a/trailbase-core/js/admin/proto/config_api.ts +++ b/trailbase-core/js/admin/proto/config_api.ts @@ -21,7 +21,7 @@ export interface UpdateConfigRequest { } function createBaseGetConfigResponse(): GetConfigResponse { - return { config: undefined, hash: "" }; + return {}; } export const GetConfigResponse: MessageFns = { @@ -70,7 +70,7 @@ export const GetConfigResponse: MessageFns = { fromJSON(object: any): GetConfigResponse { return { config: isSet(object.config) ? Config.fromJSON(object.config) : undefined, - hash: isSet(object.hash) ? globalThis.String(object.hash) : "", + hash: isSet(object.hash) ? globalThis.String(object.hash) : undefined, }; }, @@ -99,7 +99,7 @@ export const GetConfigResponse: MessageFns = { }; function createBaseUpdateConfigRequest(): UpdateConfigRequest { - return { config: undefined, hash: "" }; + return {}; } export const UpdateConfigRequest: MessageFns = { @@ -148,7 +148,7 @@ export const UpdateConfigRequest: MessageFns = { fromJSON(object: any): UpdateConfigRequest { return { config: isSet(object.config) ? Config.fromJSON(object.config) : undefined, - hash: isSet(object.hash) ? globalThis.String(object.hash) : "", + hash: isSet(object.hash) ? globalThis.String(object.hash) : undefined, }; }, diff --git a/trailbase-core/js/admin/proto/google/protobuf/descriptor.ts b/trailbase-core/js/admin/proto/google/protobuf/descriptor.ts index b4e0d866..a3368072 100644 --- a/trailbase-core/js/admin/proto/google/protobuf/descriptor.ts +++ b/trailbase-core/js/admin/proto/google/protobuf/descriptor.ts @@ -1299,8 +1299,6 @@ export const FileDescriptorSet: MessageFns = { function createBaseFileDescriptorProto(): FileDescriptorProto { return { - name: "", - package: "", dependency: [], publicDependency: [], weakDependency: [], @@ -1308,9 +1306,6 @@ function createBaseFileDescriptorProto(): FileDescriptorProto { enumType: [], service: [], extension: [], - options: undefined, - sourceCodeInfo: undefined, - syntax: "", }; } @@ -1493,8 +1488,8 @@ export const FileDescriptorProto: MessageFns = { fromJSON(object: any): FileDescriptorProto { return { - name: isSet(object.name) ? globalThis.String(object.name) : "", - package: isSet(object.package) ? globalThis.String(object.package) : "", + name: isSet(object.name) ? globalThis.String(object.name) : undefined, + package: isSet(object.package) ? globalThis.String(object.package) : undefined, dependency: globalThis.Array.isArray(object?.dependency) ? object.dependency.map((e: any) => globalThis.String(e)) : [], @@ -1518,7 +1513,7 @@ export const FileDescriptorProto: MessageFns = { : [], options: isSet(object.options) ? FileOptions.fromJSON(object.options) : undefined, sourceCodeInfo: isSet(object.sourceCodeInfo) ? SourceCodeInfo.fromJSON(object.sourceCodeInfo) : undefined, - syntax: isSet(object.syntax) ? globalThis.String(object.syntax) : "", + syntax: isSet(object.syntax) ? globalThis.String(object.syntax) : undefined, }; }, @@ -1590,14 +1585,12 @@ export const FileDescriptorProto: MessageFns = { function createBaseDescriptorProto(): DescriptorProto { return { - name: "", field: [], extension: [], nestedType: [], enumType: [], extensionRange: [], oneofDecl: [], - options: undefined, reservedRange: [], reservedName: [], }; @@ -1736,7 +1729,7 @@ export const DescriptorProto: MessageFns = { fromJSON(object: any): DescriptorProto { return { - name: isSet(object.name) ? globalThis.String(object.name) : "", + name: isSet(object.name) ? globalThis.String(object.name) : undefined, field: globalThis.Array.isArray(object?.field) ? object.field.map((e: any) => FieldDescriptorProto.fromJSON(e)) : [], @@ -1822,7 +1815,7 @@ export const DescriptorProto: MessageFns = { }; function createBaseDescriptorProto_ExtensionRange(): DescriptorProto_ExtensionRange { - return { start: 0, end: 0, options: undefined }; + return {}; } export const DescriptorProto_ExtensionRange: MessageFns = { @@ -1881,8 +1874,8 @@ export const DescriptorProto_ExtensionRange: MessageFns = { @@ -1966,8 +1959,8 @@ export const DescriptorProto_ReservedRange: MessageFns = { }; function createBaseFieldDescriptorProto(): FieldDescriptorProto { - return { - name: "", - number: 0, - label: 1, - type: 1, - typeName: "", - extendee: "", - defaultValue: "", - oneofIndex: 0, - jsonName: "", - options: undefined, - proto3Optional: false, - }; + return {}; } export const FieldDescriptorProto: MessageFns = { @@ -2217,17 +2198,17 @@ export const FieldDescriptorProto: MessageFns = { fromJSON(object: any): FieldDescriptorProto { return { - name: isSet(object.name) ? globalThis.String(object.name) : "", - number: isSet(object.number) ? globalThis.Number(object.number) : 0, - label: isSet(object.label) ? fieldDescriptorProto_LabelFromJSON(object.label) : 1, - type: isSet(object.type) ? fieldDescriptorProto_TypeFromJSON(object.type) : 1, - typeName: isSet(object.typeName) ? globalThis.String(object.typeName) : "", - extendee: isSet(object.extendee) ? globalThis.String(object.extendee) : "", - defaultValue: isSet(object.defaultValue) ? globalThis.String(object.defaultValue) : "", - oneofIndex: isSet(object.oneofIndex) ? globalThis.Number(object.oneofIndex) : 0, - jsonName: isSet(object.jsonName) ? globalThis.String(object.jsonName) : "", + name: isSet(object.name) ? globalThis.String(object.name) : undefined, + number: isSet(object.number) ? globalThis.Number(object.number) : undefined, + label: isSet(object.label) ? fieldDescriptorProto_LabelFromJSON(object.label) : undefined, + type: isSet(object.type) ? fieldDescriptorProto_TypeFromJSON(object.type) : undefined, + typeName: isSet(object.typeName) ? globalThis.String(object.typeName) : undefined, + extendee: isSet(object.extendee) ? globalThis.String(object.extendee) : undefined, + defaultValue: isSet(object.defaultValue) ? globalThis.String(object.defaultValue) : undefined, + oneofIndex: isSet(object.oneofIndex) ? globalThis.Number(object.oneofIndex) : undefined, + jsonName: isSet(object.jsonName) ? globalThis.String(object.jsonName) : undefined, options: isSet(object.options) ? FieldOptions.fromJSON(object.options) : undefined, - proto3Optional: isSet(object.proto3Optional) ? globalThis.Boolean(object.proto3Optional) : false, + proto3Optional: isSet(object.proto3Optional) ? globalThis.Boolean(object.proto3Optional) : undefined, }; }, @@ -2292,7 +2273,7 @@ export const FieldDescriptorProto: MessageFns = { }; function createBaseOneofDescriptorProto(): OneofDescriptorProto { - return { name: "", options: undefined }; + return {}; } export const OneofDescriptorProto: MessageFns = { @@ -2340,7 +2321,7 @@ export const OneofDescriptorProto: MessageFns = { fromJSON(object: any): OneofDescriptorProto { return { - name: isSet(object.name) ? globalThis.String(object.name) : "", + name: isSet(object.name) ? globalThis.String(object.name) : undefined, options: isSet(object.options) ? OneofOptions.fromJSON(object.options) : undefined, }; }, @@ -2370,7 +2351,7 @@ export const OneofDescriptorProto: MessageFns = { }; function createBaseEnumDescriptorProto(): EnumDescriptorProto { - return { name: "", value: [], options: undefined, reservedRange: [], reservedName: [] }; + return { value: [], reservedRange: [], reservedName: [] }; } export const EnumDescriptorProto: MessageFns = { @@ -2451,7 +2432,7 @@ export const EnumDescriptorProto: MessageFns = { fromJSON(object: any): EnumDescriptorProto { return { - name: isSet(object.name) ? globalThis.String(object.name) : "", + name: isSet(object.name) ? globalThis.String(object.name) : undefined, value: globalThis.Array.isArray(object?.value) ? object.value.map((e: any) => EnumValueDescriptorProto.fromJSON(e)) : [], @@ -2503,7 +2484,7 @@ export const EnumDescriptorProto: MessageFns = { }; function createBaseEnumDescriptorProto_EnumReservedRange(): EnumDescriptorProto_EnumReservedRange { - return { start: 0, end: 0 }; + return {}; } export const EnumDescriptorProto_EnumReservedRange: MessageFns = { @@ -2551,8 +2532,8 @@ export const EnumDescriptorProto_EnumReservedRange: MessageFns = { @@ -2642,8 +2623,8 @@ export const EnumValueDescriptorProto: MessageFns = { fromJSON(object: any): EnumValueDescriptorProto { return { - name: isSet(object.name) ? globalThis.String(object.name) : "", - number: isSet(object.number) ? globalThis.Number(object.number) : 0, + name: isSet(object.name) ? globalThis.String(object.name) : undefined, + number: isSet(object.number) ? globalThis.Number(object.number) : undefined, options: isSet(object.options) ? EnumValueOptions.fromJSON(object.options) : undefined, }; }, @@ -2677,7 +2658,7 @@ export const EnumValueDescriptorProto: MessageFns = { }; function createBaseServiceDescriptorProto(): ServiceDescriptorProto { - return { name: "", method: [], options: undefined }; + return { method: [] }; } export const ServiceDescriptorProto: MessageFns = { @@ -2736,7 +2717,7 @@ export const ServiceDescriptorProto: MessageFns = { fromJSON(object: any): ServiceDescriptorProto { return { - name: isSet(object.name) ? globalThis.String(object.name) : "", + name: isSet(object.name) ? globalThis.String(object.name) : undefined, method: globalThis.Array.isArray(object?.method) ? object.method.map((e: any) => MethodDescriptorProto.fromJSON(e)) : [], @@ -2773,14 +2754,7 @@ export const ServiceDescriptorProto: MessageFns = { }; function createBaseMethodDescriptorProto(): MethodDescriptorProto { - return { - name: "", - inputType: "", - outputType: "", - options: undefined, - clientStreaming: false, - serverStreaming: false, - }; + return {}; } export const MethodDescriptorProto: MessageFns = { @@ -2872,12 +2846,12 @@ export const MethodDescriptorProto: MessageFns = { fromJSON(object: any): MethodDescriptorProto { return { - name: isSet(object.name) ? globalThis.String(object.name) : "", - inputType: isSet(object.inputType) ? globalThis.String(object.inputType) : "", - outputType: isSet(object.outputType) ? globalThis.String(object.outputType) : "", + name: isSet(object.name) ? globalThis.String(object.name) : undefined, + inputType: isSet(object.inputType) ? globalThis.String(object.inputType) : undefined, + outputType: isSet(object.outputType) ? globalThis.String(object.outputType) : undefined, options: isSet(object.options) ? MethodOptions.fromJSON(object.options) : undefined, - clientStreaming: isSet(object.clientStreaming) ? globalThis.Boolean(object.clientStreaming) : false, - serverStreaming: isSet(object.serverStreaming) ? globalThis.Boolean(object.serverStreaming) : false, + clientStreaming: isSet(object.clientStreaming) ? globalThis.Boolean(object.clientStreaming) : undefined, + serverStreaming: isSet(object.serverStreaming) ? globalThis.Boolean(object.serverStreaming) : undefined, }; }, @@ -2922,29 +2896,7 @@ export const MethodDescriptorProto: MessageFns = { }; function createBaseFileOptions(): FileOptions { - return { - javaPackage: "", - javaOuterClassname: "", - javaMultipleFiles: false, - javaGenerateEqualsAndHash: false, - javaStringCheckUtf8: false, - optimizeFor: 1, - goPackage: "", - ccGenericServices: false, - javaGenericServices: false, - pyGenericServices: false, - phpGenericServices: false, - deprecated: false, - ccEnableArenas: true, - objcClassPrefix: "", - csharpNamespace: "", - swiftPrefix: "", - phpClassPrefix: "", - phpNamespace: "", - phpMetadataNamespace: "", - rubyPackage: "", - uninterpretedOption: [], - }; + return { uninterpretedOption: [] }; } export const FileOptions: MessageFns = { @@ -3201,28 +3153,34 @@ export const FileOptions: MessageFns = { fromJSON(object: any): FileOptions { return { - javaPackage: isSet(object.javaPackage) ? globalThis.String(object.javaPackage) : "", - javaOuterClassname: isSet(object.javaOuterClassname) ? globalThis.String(object.javaOuterClassname) : "", - javaMultipleFiles: isSet(object.javaMultipleFiles) ? globalThis.Boolean(object.javaMultipleFiles) : false, + javaPackage: isSet(object.javaPackage) ? globalThis.String(object.javaPackage) : undefined, + javaOuterClassname: isSet(object.javaOuterClassname) ? globalThis.String(object.javaOuterClassname) : undefined, + javaMultipleFiles: isSet(object.javaMultipleFiles) ? globalThis.Boolean(object.javaMultipleFiles) : undefined, javaGenerateEqualsAndHash: isSet(object.javaGenerateEqualsAndHash) ? globalThis.Boolean(object.javaGenerateEqualsAndHash) - : false, - javaStringCheckUtf8: isSet(object.javaStringCheckUtf8) ? globalThis.Boolean(object.javaStringCheckUtf8) : false, - optimizeFor: isSet(object.optimizeFor) ? fileOptions_OptimizeModeFromJSON(object.optimizeFor) : 1, - goPackage: isSet(object.goPackage) ? globalThis.String(object.goPackage) : "", - ccGenericServices: isSet(object.ccGenericServices) ? globalThis.Boolean(object.ccGenericServices) : false, - javaGenericServices: isSet(object.javaGenericServices) ? globalThis.Boolean(object.javaGenericServices) : false, - pyGenericServices: isSet(object.pyGenericServices) ? globalThis.Boolean(object.pyGenericServices) : false, - phpGenericServices: isSet(object.phpGenericServices) ? globalThis.Boolean(object.phpGenericServices) : false, - deprecated: isSet(object.deprecated) ? globalThis.Boolean(object.deprecated) : false, - ccEnableArenas: isSet(object.ccEnableArenas) ? globalThis.Boolean(object.ccEnableArenas) : true, - objcClassPrefix: isSet(object.objcClassPrefix) ? globalThis.String(object.objcClassPrefix) : "", - csharpNamespace: isSet(object.csharpNamespace) ? globalThis.String(object.csharpNamespace) : "", - swiftPrefix: isSet(object.swiftPrefix) ? globalThis.String(object.swiftPrefix) : "", - phpClassPrefix: isSet(object.phpClassPrefix) ? globalThis.String(object.phpClassPrefix) : "", - phpNamespace: isSet(object.phpNamespace) ? globalThis.String(object.phpNamespace) : "", - phpMetadataNamespace: isSet(object.phpMetadataNamespace) ? globalThis.String(object.phpMetadataNamespace) : "", - rubyPackage: isSet(object.rubyPackage) ? globalThis.String(object.rubyPackage) : "", + : undefined, + javaStringCheckUtf8: isSet(object.javaStringCheckUtf8) + ? globalThis.Boolean(object.javaStringCheckUtf8) + : undefined, + optimizeFor: isSet(object.optimizeFor) ? fileOptions_OptimizeModeFromJSON(object.optimizeFor) : undefined, + goPackage: isSet(object.goPackage) ? globalThis.String(object.goPackage) : undefined, + ccGenericServices: isSet(object.ccGenericServices) ? globalThis.Boolean(object.ccGenericServices) : undefined, + javaGenericServices: isSet(object.javaGenericServices) + ? globalThis.Boolean(object.javaGenericServices) + : undefined, + pyGenericServices: isSet(object.pyGenericServices) ? globalThis.Boolean(object.pyGenericServices) : undefined, + phpGenericServices: isSet(object.phpGenericServices) ? globalThis.Boolean(object.phpGenericServices) : undefined, + deprecated: isSet(object.deprecated) ? globalThis.Boolean(object.deprecated) : undefined, + ccEnableArenas: isSet(object.ccEnableArenas) ? globalThis.Boolean(object.ccEnableArenas) : undefined, + objcClassPrefix: isSet(object.objcClassPrefix) ? globalThis.String(object.objcClassPrefix) : undefined, + csharpNamespace: isSet(object.csharpNamespace) ? globalThis.String(object.csharpNamespace) : undefined, + swiftPrefix: isSet(object.swiftPrefix) ? globalThis.String(object.swiftPrefix) : undefined, + phpClassPrefix: isSet(object.phpClassPrefix) ? globalThis.String(object.phpClassPrefix) : undefined, + phpNamespace: isSet(object.phpNamespace) ? globalThis.String(object.phpNamespace) : undefined, + phpMetadataNamespace: isSet(object.phpMetadataNamespace) + ? globalThis.String(object.phpMetadataNamespace) + : undefined, + rubyPackage: isSet(object.rubyPackage) ? globalThis.String(object.rubyPackage) : undefined, uninterpretedOption: globalThis.Array.isArray(object?.uninterpretedOption) ? object.uninterpretedOption.map((e: any) => UninterpretedOption.fromJSON(e)) : [], @@ -3328,13 +3286,7 @@ export const FileOptions: MessageFns = { }; function createBaseMessageOptions(): MessageOptions { - return { - messageSetWireFormat: false, - noStandardDescriptorAccessor: false, - deprecated: false, - mapEntry: false, - uninterpretedOption: [], - }; + return { uninterpretedOption: [] }; } export const MessageOptions: MessageFns = { @@ -3417,12 +3369,12 @@ export const MessageOptions: MessageFns = { return { messageSetWireFormat: isSet(object.messageSetWireFormat) ? globalThis.Boolean(object.messageSetWireFormat) - : false, + : undefined, noStandardDescriptorAccessor: isSet(object.noStandardDescriptorAccessor) ? globalThis.Boolean(object.noStandardDescriptorAccessor) - : false, - deprecated: isSet(object.deprecated) ? globalThis.Boolean(object.deprecated) : false, - mapEntry: isSet(object.mapEntry) ? globalThis.Boolean(object.mapEntry) : false, + : undefined, + deprecated: isSet(object.deprecated) ? globalThis.Boolean(object.deprecated) : undefined, + mapEntry: isSet(object.mapEntry) ? globalThis.Boolean(object.mapEntry) : undefined, uninterpretedOption: globalThis.Array.isArray(object?.uninterpretedOption) ? object.uninterpretedOption.map((e: any) => UninterpretedOption.fromJSON(e)) : [], @@ -3464,16 +3416,7 @@ export const MessageOptions: MessageFns = { }; function createBaseFieldOptions(): FieldOptions { - return { - ctype: 0, - packed: false, - jstype: 0, - lazy: false, - unverifiedLazy: false, - deprecated: false, - weak: false, - uninterpretedOption: [], - }; + return { uninterpretedOption: [] }; } export const FieldOptions: MessageFns = { @@ -3587,13 +3530,13 @@ export const FieldOptions: MessageFns = { fromJSON(object: any): FieldOptions { return { - ctype: isSet(object.ctype) ? fieldOptions_CTypeFromJSON(object.ctype) : 0, - packed: isSet(object.packed) ? globalThis.Boolean(object.packed) : false, - jstype: isSet(object.jstype) ? fieldOptions_JSTypeFromJSON(object.jstype) : 0, - lazy: isSet(object.lazy) ? globalThis.Boolean(object.lazy) : false, - unverifiedLazy: isSet(object.unverifiedLazy) ? globalThis.Boolean(object.unverifiedLazy) : false, - deprecated: isSet(object.deprecated) ? globalThis.Boolean(object.deprecated) : false, - weak: isSet(object.weak) ? globalThis.Boolean(object.weak) : false, + ctype: isSet(object.ctype) ? fieldOptions_CTypeFromJSON(object.ctype) : undefined, + packed: isSet(object.packed) ? globalThis.Boolean(object.packed) : undefined, + jstype: isSet(object.jstype) ? fieldOptions_JSTypeFromJSON(object.jstype) : undefined, + lazy: isSet(object.lazy) ? globalThis.Boolean(object.lazy) : undefined, + unverifiedLazy: isSet(object.unverifiedLazy) ? globalThis.Boolean(object.unverifiedLazy) : undefined, + deprecated: isSet(object.deprecated) ? globalThis.Boolean(object.deprecated) : undefined, + weak: isSet(object.weak) ? globalThis.Boolean(object.weak) : undefined, uninterpretedOption: globalThis.Array.isArray(object?.uninterpretedOption) ? object.uninterpretedOption.map((e: any) => UninterpretedOption.fromJSON(e)) : [], @@ -3709,7 +3652,7 @@ export const OneofOptions: MessageFns = { }; function createBaseEnumOptions(): EnumOptions { - return { allowAlias: false, deprecated: false, uninterpretedOption: [] }; + return { uninterpretedOption: [] }; } export const EnumOptions: MessageFns = { @@ -3768,8 +3711,8 @@ export const EnumOptions: MessageFns = { fromJSON(object: any): EnumOptions { return { - allowAlias: isSet(object.allowAlias) ? globalThis.Boolean(object.allowAlias) : false, - deprecated: isSet(object.deprecated) ? globalThis.Boolean(object.deprecated) : false, + allowAlias: isSet(object.allowAlias) ? globalThis.Boolean(object.allowAlias) : undefined, + deprecated: isSet(object.deprecated) ? globalThis.Boolean(object.deprecated) : undefined, uninterpretedOption: globalThis.Array.isArray(object?.uninterpretedOption) ? object.uninterpretedOption.map((e: any) => UninterpretedOption.fromJSON(e)) : [], @@ -3803,7 +3746,7 @@ export const EnumOptions: MessageFns = { }; function createBaseEnumValueOptions(): EnumValueOptions { - return { deprecated: false, uninterpretedOption: [] }; + return { uninterpretedOption: [] }; } export const EnumValueOptions: MessageFns = { @@ -3851,7 +3794,7 @@ export const EnumValueOptions: MessageFns = { fromJSON(object: any): EnumValueOptions { return { - deprecated: isSet(object.deprecated) ? globalThis.Boolean(object.deprecated) : false, + deprecated: isSet(object.deprecated) ? globalThis.Boolean(object.deprecated) : undefined, uninterpretedOption: globalThis.Array.isArray(object?.uninterpretedOption) ? object.uninterpretedOption.map((e: any) => UninterpretedOption.fromJSON(e)) : [], @@ -3881,7 +3824,7 @@ export const EnumValueOptions: MessageFns = { }; function createBaseServiceOptions(): ServiceOptions { - return { deprecated: false, uninterpretedOption: [] }; + return { uninterpretedOption: [] }; } export const ServiceOptions: MessageFns = { @@ -3929,7 +3872,7 @@ export const ServiceOptions: MessageFns = { fromJSON(object: any): ServiceOptions { return { - deprecated: isSet(object.deprecated) ? globalThis.Boolean(object.deprecated) : false, + deprecated: isSet(object.deprecated) ? globalThis.Boolean(object.deprecated) : undefined, uninterpretedOption: globalThis.Array.isArray(object?.uninterpretedOption) ? object.uninterpretedOption.map((e: any) => UninterpretedOption.fromJSON(e)) : [], @@ -3959,7 +3902,7 @@ export const ServiceOptions: MessageFns = { }; function createBaseMethodOptions(): MethodOptions { - return { deprecated: false, idempotencyLevel: 0, uninterpretedOption: [] }; + return { uninterpretedOption: [] }; } export const MethodOptions: MessageFns = { @@ -4018,10 +3961,10 @@ export const MethodOptions: MessageFns = { fromJSON(object: any): MethodOptions { return { - deprecated: isSet(object.deprecated) ? globalThis.Boolean(object.deprecated) : false, + deprecated: isSet(object.deprecated) ? globalThis.Boolean(object.deprecated) : undefined, idempotencyLevel: isSet(object.idempotencyLevel) ? methodOptions_IdempotencyLevelFromJSON(object.idempotencyLevel) - : 0, + : undefined, uninterpretedOption: globalThis.Array.isArray(object?.uninterpretedOption) ? object.uninterpretedOption.map((e: any) => UninterpretedOption.fromJSON(e)) : [], @@ -4055,15 +3998,7 @@ export const MethodOptions: MessageFns = { }; function createBaseUninterpretedOption(): UninterpretedOption { - return { - name: [], - identifierValue: "", - positiveIntValue: 0, - negativeIntValue: 0, - doubleValue: 0, - stringValue: new Uint8Array(0), - aggregateValue: "", - }; + return { name: [] }; } export const UninterpretedOption: MessageFns = { @@ -4169,12 +4104,12 @@ export const UninterpretedOption: MessageFns = { name: globalThis.Array.isArray(object?.name) ? object.name.map((e: any) => UninterpretedOption_NamePart.fromJSON(e)) : [], - identifierValue: isSet(object.identifierValue) ? globalThis.String(object.identifierValue) : "", - positiveIntValue: isSet(object.positiveIntValue) ? globalThis.Number(object.positiveIntValue) : 0, - negativeIntValue: isSet(object.negativeIntValue) ? globalThis.Number(object.negativeIntValue) : 0, - doubleValue: isSet(object.doubleValue) ? globalThis.Number(object.doubleValue) : 0, - stringValue: isSet(object.stringValue) ? bytesFromBase64(object.stringValue) : new Uint8Array(0), - aggregateValue: isSet(object.aggregateValue) ? globalThis.String(object.aggregateValue) : "", + identifierValue: isSet(object.identifierValue) ? globalThis.String(object.identifierValue) : undefined, + positiveIntValue: isSet(object.positiveIntValue) ? globalThis.Number(object.positiveIntValue) : undefined, + negativeIntValue: isSet(object.negativeIntValue) ? globalThis.Number(object.negativeIntValue) : undefined, + doubleValue: isSet(object.doubleValue) ? globalThis.Number(object.doubleValue) : undefined, + stringValue: isSet(object.stringValue) ? bytesFromBase64(object.stringValue) : undefined, + aggregateValue: isSet(object.aggregateValue) ? globalThis.String(object.aggregateValue) : undefined, }; }, @@ -4359,7 +4294,7 @@ export const SourceCodeInfo: MessageFns = { }; function createBaseSourceCodeInfo_Location(): SourceCodeInfo_Location { - return { path: [], span: [], leadingComments: "", trailingComments: "", leadingDetachedComments: [] }; + return { path: [], span: [], leadingDetachedComments: [] }; } export const SourceCodeInfo_Location: MessageFns = { @@ -4466,8 +4401,8 @@ export const SourceCodeInfo_Location: MessageFns = { return { path: globalThis.Array.isArray(object?.path) ? object.path.map((e: any) => globalThis.Number(e)) : [], span: globalThis.Array.isArray(object?.span) ? object.span.map((e: any) => globalThis.Number(e)) : [], - leadingComments: isSet(object.leadingComments) ? globalThis.String(object.leadingComments) : "", - trailingComments: isSet(object.trailingComments) ? globalThis.String(object.trailingComments) : "", + leadingComments: isSet(object.leadingComments) ? globalThis.String(object.leadingComments) : undefined, + trailingComments: isSet(object.trailingComments) ? globalThis.String(object.trailingComments) : undefined, leadingDetachedComments: globalThis.Array.isArray(object?.leadingDetachedComments) ? object.leadingDetachedComments.map((e: any) => globalThis.String(e)) : [], @@ -4571,7 +4506,7 @@ export const GeneratedCodeInfo: MessageFns = { }; function createBaseGeneratedCodeInfo_Annotation(): GeneratedCodeInfo_Annotation { - return { path: [], sourceFile: "", begin: 0, end: 0 }; + return { path: [] }; } export const GeneratedCodeInfo_Annotation: MessageFns = { @@ -4654,9 +4589,9 @@ export const GeneratedCodeInfo_Annotation: MessageFns globalThis.Number(e)) : [], - sourceFile: isSet(object.sourceFile) ? globalThis.String(object.sourceFile) : "", - begin: isSet(object.begin) ? globalThis.Number(object.begin) : 0, - end: isSet(object.end) ? globalThis.Number(object.end) : 0, + sourceFile: isSet(object.sourceFile) ? globalThis.String(object.sourceFile) : undefined, + begin: isSet(object.begin) ? globalThis.Number(object.begin) : undefined, + end: isSet(object.end) ? globalThis.Number(object.end) : undefined, }; }, diff --git a/trailbase-core/js/admin/src/components/FormFields.tsx b/trailbase-core/js/admin/src/components/FormFields.tsx index c0d5e774..cdcb5ad2 100644 --- a/trailbase-core/js/admin/src/components/FormFields.tsx +++ b/trailbase-core/js/admin/src/components/FormFields.tsx @@ -1,5 +1,4 @@ -import { createSignal } from "solid-js"; -import type { Accessor, Setter, JSXElement } from "solid-js"; +import { createSignal, type JSX } from "solid-js"; import { createForm, type FieldApi } from "@tanstack/solid-form"; import { TbInfoCircle, TbEye } from "solid-icons/tb"; @@ -31,67 +30,128 @@ import type { ColumnDataType } from "@/lib/bindings"; // eslint-disable-next-line @typescript-eslint/no-explicit-any export type AnyFieldApi = FieldApi; // eslint-disable-next-line @typescript-eslint/no-explicit-any -export type FieldApiT = FieldApi; +export type FieldApiT = FieldApi; // eslint-disable-next-line @typescript-eslint/no-explicit-any export type FormType = ReturnType>; type TextFieldOptions = { disabled?: boolean; type?: TextFieldType; - onKeyUp?: Setter; - required?: boolean; - label: () => JSXElement; - info?: JSXElement; + label: () => JSX.Element; + info?: JSX.Element; autocomplete?: string; // Optional placeholder string for absent values, e.g. "NULL". Optional only option. - nullPlaceholder?: string; + placeholder?: string; }; -export function buildTextFormField(opts: TextFieldOptions) { - const keyUp = opts.onKeyUp; +export function buildTextFormFieldT( + opts: TextFieldOptions, +) { + const externDisable = opts.disabled ?? false; - return (field: () => AnyFieldApi) => ( - -
- {opts.label()} + function builder(field: () => FieldApiT) { + return ( + +
+ {opts.label()} - { - const v = (e.target as HTMLInputElement).value; - field().handleChange(v); - if (keyUp) { - keyUp(v); + + field().handleChange((e.target as HTMLInputElement).value as T) } - }} - /> + /> -
- {field && } +
+ {field && } +
+ +
{opts.info}
+ + ); + } -
{opts.info}
-
-
- ); + return builder; +} + +/// Note that we make not-required/optional explicit by having a checkbox, since there's +/// a difference between empty string and not set. +export function buildTextFormField(opts: TextFieldOptions) { + return buildTextFormFieldT(opts); +} + +export function buildOptionalTextFormField(opts: TextFieldOptions) { + const externDisable = opts.disabled ?? false; + + function builder(field: () => FieldApiT) { + const initialValue = field().state.value; + const [enabled, setEnabled] = createSignal( + !externDisable && initialValue !== undefined && initialValue !== null, + ); + + return ( + +
+ {opts.label()} + +
+ + field().handleChange((e.target as HTMLInputElement).value) + } + /> + + { + setEnabled(enabled); + // NOTE: null is critical here to actively unset a cell, undefined + // would merely take it out of the patch set. + field().handleChange(enabled ? initialValue : undefined); + }} + /> +
+ +
+ {field && } +
+ +
{opts.info}
+
+
+ ); + } + + return builder; } export function buildSecretFormField(opts: Omit) { - const keyUp = opts.onKeyUp; const [type, setType] = createSignal("password"); - return (field: () => AnyFieldApi) => ( + return (field: () => FieldApiT) => (
) {
{ - const v = (e.target as HTMLInputElement).value; - field().handleChange(v); - if (keyUp) { - keyUp(v); - } + field().handleChange((e.target as HTMLInputElement).value); }} /> @@ -138,153 +193,54 @@ export function buildSecretFormField(opts: Omit) { ); } -export function OptionalTextFormField(props: { - label: () => JSXElement; - info?: JSXElement; - field?: () => AnyFieldApi; - - type?: TextFieldType; - onKeyUp?: Setter; - - initial?: string; - initialEnabled?: boolean; - disabled?: boolean; - - nullPlaceholder?: string; - - handleBlur?: () => void; - handleChange?: (v: string | undefined) => void; -}) { - const [text, setText] = createSignal(props.initial ?? ""); - const [enabled, setEnabled] = createSignal( - props.initialEnabled ?? props.initial !== undefined, - ); - - const externDisable = props.disabled ?? false; - const effEnabled = () => enabled() && !externDisable; - - const keyUp = props.onKeyUp; - - return ( - -
- - {props.label()} - - -
- { - const v = (e.target as HTMLInputElement).value; - setText(v); - props.handleChange?.(v); - if (keyUp) { - keyUp(v); - } - }} - onChange={(e: Event) => { - const v: string = (e.currentTarget as HTMLInputElement).value; - props.handleChange?.(v); - }} - /> - - { - setEnabled(value); - // NOTE: null is critical here to actively unset a cell, undefined - // would merely take it out of the patch set. - props.handleChange?.(value ? text() : undefined); - }} - /> -
- -
- {props.field && } -
- -
{props.info}
-
-
- ); -} - -export function buildOptionalTextFormField(opts: TextFieldOptions) { - return (field: () => AnyFieldApi) => { +export function buildTextAreaFormField( + opts: Omit, + rows?: number, +) { + return (field: () => FieldApiT) => { return ( - + +
+ {opts.label()} + + { + field().handleChange((e.target as HTMLInputElement).value); + }} + /> + +
+ {field && } +
+ +
{opts.info}
+
+
); }; } -export function buildTextAreaFormField(opts: TextFieldOptions, rows?: number) { - const keyUp = opts?.onKeyUp; - - return (field: () => AnyFieldApi) => ( - -
- {opts.label()} - - { - const v = (e.target as HTMLInputElement).value; - field().handleChange(v); - if (keyUp) { - keyUp(v); - } - }} - /> - -
- {field && } -
- -
{opts.info}
-
-
- ); -} - type NumberFieldOptions = { disabled?: boolean; - label: () => JSXElement; + label: () => JSX.Element; - info?: JSXElement; + info?: JSX.Element; integer?: boolean; }; -export function buildNumberFormField(opts: NumberFieldOptions) { +export function buildNumberFormFieldT< + T extends number | (number | undefined) | string, +>(opts: NumberFieldOptions) { const isInt = opts.integer ?? false; - return (field: () => AnyFieldApi) => { + return (field: () => FieldApiT) => { return (
{opts.label()} { const v = (e.target as HTMLInputElement).value; - const i = parseInt(v); - field().handleChange(i); + if (v) { + const i = parseInt(v); + field().handleChange(i as T); + } }} /> @@ -319,8 +276,12 @@ export function buildNumberFormField(opts: NumberFieldOptions) { }; } -export function buildBoolFormField(props: { label: () => JSXElement }) { - return (field: () => AnyFieldApi) => ( +export function buildNumberFormField(opts: NumberFieldOptions) { + return buildNumberFormFieldT(opts); +} + +export function buildBoolFormField(props: { label: () => JSX.Element }) { + return (field: () => FieldApiT) => (
+ ); + }; } function FieldInfo(props: { field: FieldApiT }) { @@ -385,6 +350,24 @@ export function notEmptyValidator() { return { onChange: ({ value }: { value: string | undefined }) => { if (!value) { + if (import.meta.env.DEV) { + return `Must not be empty. Undefined: ${value === undefined}`; + } + return "Must not be empty"; + } + }, + }; +} + +export function unsetOrNotEmptyValidator() { + return { + onChange: ({ value }: { value: string | undefined }) => { + if (value === undefined) return undefined; + + if (!value) { + if (import.meta.env.DEV) { + return `Must not be empty. Undefined: ${value === undefined}`; + } return "Must not be empty"; } }, @@ -401,54 +384,96 @@ export function largerThanZero() { }; } -export function formFieldBuilder( - type: ColumnDataType, - labelText: string, - optional: boolean, - nullPlaceholder?: string, -) { +export function unsetOrLargerThanZero() { + return { + onChange: ({ value }: { value: number | undefined }) => { + if (value === undefined) return; + + if (value <= 0) { + return "Must be positive"; + } + }, + }; +} + +function BinaryBlobHoverCard() { + return ( + + } + variant="link" + > + + + + + Binary blobs can be entered encoded as url-safe Base64. + + + ); +} + +export function buildDBCellField(props: { + type: ColumnDataType; + label: string; + optional: boolean; + placeholder?: string; +}) { const label = () => ( -
- {type === "Blob" && ( - - } - variant="link" - > - - - - - Binary blobs can be entered encoded as url-safe Base64. - - - )} - - {labelText} +
+ {props.type === "Blob" && } + {props.label}
); + const type = props.type; + const optional = props.optional; + const placeholder = props.placeholder; if (type === "Text" || type === "Blob") { if (optional) { - return buildOptionalTextFormField({ label, nullPlaceholder }); - } else { - return buildTextFormField({ label }); + return buildOptionalTextFormField({ label, placeholder }); } + return buildTextFormFieldT({ label, placeholder }); } if (type === "Integer" && !optional) { - return buildNumberFormField({ label }); + return buildNumberFormFieldT({ label }); } console.debug( - `Custom FormFields not yet implemented for (${type}, ${optional}). Falling back to textfields`, + `Custom FormFields not implemented for '${type}'. Falling back to text field`, ); + if (optional) { - return buildOptionalTextFormField({ label, nullPlaceholder }); - } else { - return buildTextFormField({ label }); + return buildOptionalTextFormField({ label, placeholder }); } + return buildTextFormFieldT({ label, placeholder }); +} + +export function buildOptionalDBCellField(props: { + type: ColumnDataType; + label: string; + placeholder?: string; +}) { + const label = () => ( +
+ {props.type === "Blob" && } + {props.label} +
+ ); + + const type = props.type; + const placeholder = props.placeholder; + if (type === "Text" || type === "Blob") { + return buildOptionalTextFormField({ label, placeholder }); + } + + console.debug( + `Custom FormFields not yet implemented for '${type}'. Falling back to textfields`, + ); + + return buildOptionalTextFormField({ label, placeholder }); } export const gapStyle = "gap-x-2 gap-y-1"; diff --git a/trailbase-core/js/admin/src/components/NavBar.tsx b/trailbase-core/js/admin/src/components/NavBar.tsx index be6bc25f..4fbaa5f8 100644 --- a/trailbase-core/js/admin/src/components/NavBar.tsx +++ b/trailbase-core/js/admin/src/components/NavBar.tsx @@ -32,7 +32,7 @@ export function NavBar(props: { location: Location }) {