From a9c443eb0a37a5b8cae911d811b83b4fa66be8b9 Mon Sep 17 00:00:00 2001 From: Bart Date: Sat, 11 Nov 2023 20:04:16 +0100 Subject: [PATCH 01/23] display all flix templates --- packages/ui/src/api.ts | 4 +- .../contexts/templates.context.tsx | 52 +++++++++++++++++-- 2 files changed, 50 insertions(+), 6 deletions(-) diff --git a/packages/ui/src/api.ts b/packages/ui/src/api.ts index 020348ca..96443eb1 100644 --- a/packages/ui/src/api.ts +++ b/packages/ui/src/api.ts @@ -249,12 +249,12 @@ export function useGetParsedInteraction( return state; } -export function useGetInteractionTemplates(): SWRResponse< +export function useGetWorkspaceInteractionTemplates(): SWRResponse< InteractionTemplate[] > { const { interactionsService } = useServiceRegistry(); - return useSWR(`interaction-templates`, () => + return useSWR(`workspace-interaction-templates`, () => interactionsService.getTemplates(), ); } diff --git a/packages/ui/src/interactions/contexts/templates.context.tsx b/packages/ui/src/interactions/contexts/templates.context.tsx index 732c06b8..99caba04 100644 --- a/packages/ui/src/interactions/contexts/templates.context.tsx +++ b/packages/ui/src/interactions/contexts/templates.context.tsx @@ -2,7 +2,10 @@ import React, { createContext, ReactElement, useContext, useMemo } from "react"; import { InteractionDefinition } from "../core/core-types"; import { FclValue } from "@onflowser/core"; import { useLocalStorage } from "@uidotdev/usehooks"; -import { useGetInteractionTemplates } from "../../api"; +import { useGetWorkspaceInteractionTemplates } from "../../api"; +import useSWR, { SWRResponse } from "swr"; +import { InteractionTemplate } from "@onflowser/api"; +import { useServiceRegistry } from "../../contexts/service-registry.context"; type InteractionTemplatesRegistry = { templates: InteractionDefinitionTemplate[]; @@ -39,7 +42,8 @@ const Context = createContext(undefined as never); export function TemplatesRegistryProvider(props: { children: React.ReactNode; }): ReactElement { - const { data: projectTemplatesData } = useGetInteractionTemplates(); + const { data: workspaceTemplates } = useGetWorkspaceInteractionTemplates(); + const {data: flixTemplates} = useGetFlixInteractionTemplates(); const [customTemplates, setRawTemplates] = useLocalStorage< RawInteractionDefinitionTemplate[] >("interactions", []); @@ -63,7 +67,7 @@ export function TemplatesRegistryProvider(props: { filePath: undefined, }), ), - ...(projectTemplatesData?.map( + ...(workspaceTemplates?.map( (template): InteractionDefinitionTemplate => ({ id: randomId(), name: template.name, @@ -76,8 +80,19 @@ export function TemplatesRegistryProvider(props: { filePath: template.source?.filePath, }), ) ?? []), + ...(flixTemplates?.map((template): InteractionDefinitionTemplate => ({ + id: template.id, + name: template?.data?.messages?.title?.i18n?.["en-US"] ?? "Unknown", + code: template.data.cadence, + transactionOptions: undefined, + initialOutcome: undefined, + fclValuesByIdentifier: new Map(), + createdDate: new Date(), + updatedDate: new Date(), + filePath: undefined, + })) ?? []), ], - [customTemplates, projectTemplatesData], + [customTemplates, workspaceTemplates], ); function saveTemplate(interaction: InteractionDefinition) { @@ -130,3 +145,32 @@ export function useTemplatesRegistry(): InteractionTemplatesRegistry { return context; } + +// https://github.com/onflow/flips/blob/main/application/20220503-interaction-templates.md#interaction-interfaces +type FlixTemplate = { + id: string; + f_type: "InteractionTemplate"; + f_version: string; + data: { + messages: { + title?: FlixMessage; + description?: FlixMessage; + } + cadence: string; + // TODO: Add other fields + } +} + +type FlixMessage = { + i18n: { + "en-US"?: string + } +} + +function useGetFlixInteractionTemplates(): SWRResponse< + FlixTemplate[] +> { + return useSWR(`flix-interaction-templates`, () => + fetch("http://localhost:3333/v1/templates").then(res => res.json()) + ); +} From e47760ee07ea0e322ac91ea5e467c80673230ae3 Mon Sep 17 00:00:00 2001 From: Bart Date: Sat, 11 Nov 2023 20:29:57 +0100 Subject: [PATCH 02/23] improve flix name formatting --- .../src/interactions/contexts/templates.context.tsx | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/interactions/contexts/templates.context.tsx b/packages/ui/src/interactions/contexts/templates.context.tsx index 99caba04..187c8bb4 100644 --- a/packages/ui/src/interactions/contexts/templates.context.tsx +++ b/packages/ui/src/interactions/contexts/templates.context.tsx @@ -82,7 +82,7 @@ export function TemplatesRegistryProvider(props: { ) ?? []), ...(flixTemplates?.map((template): InteractionDefinitionTemplate => ({ id: template.id, - name: template?.data?.messages?.title?.i18n?.["en-US"] ?? "Unknown", + name: getFlixTemplateName(template), code: template.data.cadence, transactionOptions: undefined, initialOutcome: undefined, @@ -174,3 +174,14 @@ function useGetFlixInteractionTemplates(): SWRResponse< fetch("http://localhost:3333/v1/templates").then(res => res.json()) ); } + +function getFlixTemplateName(template: FlixTemplate) { + const englishTitle = template.data.messages?.title?.i18n?.["en-US"]; + if (englishTitle) { + // Transactions generated with NFT catalog have this necessary prefix in titles. + // https://github.com/onflow/nft-catalog + return englishTitle.replace("This transaction ", "") + } else { + return "Unknown" + } +} From a6111f1dcf756e9750a7496f7a929614693a9b6f Mon Sep 17 00:00:00 2001 From: Bart Date: Sat, 11 Nov 2023 21:18:04 +0100 Subject: [PATCH 03/23] format template source code --- .../contexts/templates.context.tsx | 34 ++++++++++++++++--- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/packages/ui/src/interactions/contexts/templates.context.tsx b/packages/ui/src/interactions/contexts/templates.context.tsx index 187c8bb4..5c8e4c6a 100644 --- a/packages/ui/src/interactions/contexts/templates.context.tsx +++ b/packages/ui/src/interactions/contexts/templates.context.tsx @@ -2,10 +2,8 @@ import React, { createContext, ReactElement, useContext, useMemo } from "react"; import { InteractionDefinition } from "../core/core-types"; import { FclValue } from "@onflowser/core"; import { useLocalStorage } from "@uidotdev/usehooks"; -import { useGetWorkspaceInteractionTemplates } from "../../api"; +import { useGetContracts, useGetWorkspaceInteractionTemplates } from "../../api"; import useSWR, { SWRResponse } from "swr"; -import { InteractionTemplate } from "@onflowser/api"; -import { useServiceRegistry } from "../../contexts/service-registry.context"; type InteractionTemplatesRegistry = { templates: InteractionDefinitionTemplate[]; @@ -44,12 +42,20 @@ export function TemplatesRegistryProvider(props: { }): ReactElement { const { data: workspaceTemplates } = useGetWorkspaceInteractionTemplates(); const {data: flixTemplates} = useGetFlixInteractionTemplates(); + const {data: contracts} = useGetContracts(); + const workspaceContractsLookupByName = new Set(contracts?.map(contract => contract.name)) const [customTemplates, setRawTemplates] = useLocalStorage< RawInteractionDefinitionTemplate[] >("interactions", []); const randomId = () => String(Math.random() * 1000000); + // TODO: Improve this + function isFlixTemplateUseful(template: FlixTemplate) { + const importedContractNames = Object.values(template.data.dependencies).map(dependency => Object.keys(dependency)).flat(); + return importedContractNames.some(contractName => workspaceContractsLookupByName.has(contractName)) + } + const templates = useMemo( () => [ ...customTemplates.map( @@ -80,10 +86,10 @@ export function TemplatesRegistryProvider(props: { filePath: template.source?.filePath, }), ) ?? []), - ...(flixTemplates?.map((template): InteractionDefinitionTemplate => ({ + ...(flixTemplates?.filter(isFlixTemplateUseful)?.map((template): InteractionDefinitionTemplate => ({ id: template.id, name: getFlixTemplateName(template), - code: template.data.cadence, + code: getFlixTemplateFormattedCode(template), transactionOptions: undefined, initialOutcome: undefined, fclValuesByIdentifier: new Map(), @@ -156,11 +162,24 @@ type FlixTemplate = { title?: FlixMessage; description?: FlixMessage; } + dependencies: Record; cadence: string; // TODO: Add other fields } } +type FlixDependency = Record + +type FlixDependencyOnNetwork = { + address: string; + fq_address: string; + pin: string; + pin_block_height: number; +} + type FlixMessage = { i18n: { "en-US"?: string @@ -175,6 +194,11 @@ function useGetFlixInteractionTemplates(): SWRResponse< ); } +function getFlixTemplateFormattedCode(template: FlixTemplate) { + const replacementPatterns = Object.keys(template.data.dependencies); + return replacementPatterns.reduce((cadence, pattern) => cadence.replace(`from ${pattern}`, ""), template.data.cadence) +} + function getFlixTemplateName(template: FlixTemplate) { const englishTitle = template.data.messages?.title?.i18n?.["en-US"]; if (englishTitle) { From 7283e8d5fb62ab23ddb1b2a93fc89e69b161c2f9 Mon Sep 17 00:00:00 2001 From: Bart Date: Sun, 12 Nov 2023 11:22:24 +0100 Subject: [PATCH 04/23] improve template filtering --- .../contexts/templates.context.tsx | 33 +++++++++++++++++-- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/packages/ui/src/interactions/contexts/templates.context.tsx b/packages/ui/src/interactions/contexts/templates.context.tsx index 5c8e4c6a..1b652b02 100644 --- a/packages/ui/src/interactions/contexts/templates.context.tsx +++ b/packages/ui/src/interactions/contexts/templates.context.tsx @@ -43,17 +43,44 @@ export function TemplatesRegistryProvider(props: { const { data: workspaceTemplates } = useGetWorkspaceInteractionTemplates(); const {data: flixTemplates} = useGetFlixInteractionTemplates(); const {data: contracts} = useGetContracts(); - const workspaceContractsLookupByName = new Set(contracts?.map(contract => contract.name)) const [customTemplates, setRawTemplates] = useLocalStorage< RawInteractionDefinitionTemplate[] >("interactions", []); const randomId = () => String(Math.random() * 1000000); - // TODO: Improve this + const flowCoreContractNames = new Set([ + "FlowStorageFees", + "MetadataViews", + "NonFungibleToken", + "ViewResolver", + "FungibleToken", + "FungibleTokenMetadataViews", + "FlowToken", + "FlowClusterQC", + "FlowDKG", + "FlowEpoch", + "FlowIDTableStaking", + "FlowServiceAccount", + "FlowStakingCollection", + "LockedTokens", + "NFTStorefront", + "NFTStorefrontV2", + "StakingProxy", + "FlowFees" + ]) + function isFlixTemplateUseful(template: FlixTemplate) { const importedContractNames = Object.values(template.data.dependencies).map(dependency => Object.keys(dependency)).flat(); - return importedContractNames.some(contractName => workspaceContractsLookupByName.has(contractName)) + const nonCoreImportedContractNames = importedContractNames.filter(name => !flowCoreContractNames.has(name)); + const nonCoreDeployedContractNamesLookup = new Set(contracts?.filter(contract => !flowCoreContractNames.has(contract.name)).map(contract => contract.name)) + + // Interactions that only import core contracts are most likely generic ones, + // not tailored specifically to some third party contract/project. + const onlyImportsCoreContracts = nonCoreImportedContractNames.length === 0; + const importsSomeNonCoreDeployedContract = nonCoreImportedContractNames.some(contractName => nonCoreDeployedContractNamesLookup.has(contractName)); + + return onlyImportsCoreContracts || importsSomeNonCoreDeployedContract; } const templates = useMemo( From 4d52d2af7775e73e95c370aa36c980d032db3ddc Mon Sep 17 00:00:00 2001 From: Bart Date: Sun, 12 Nov 2023 11:22:44 +0100 Subject: [PATCH 05/23] run format --- .../links/ExternalLink/ExternalLink.tsx | 5 +- .../contexts/templates.context.tsx | 96 +++++++++++-------- 2 files changed, 62 insertions(+), 39 deletions(-) diff --git a/packages/ui/src/common/links/ExternalLink/ExternalLink.tsx b/packages/ui/src/common/links/ExternalLink/ExternalLink.tsx index 4927ede1..978f661c 100644 --- a/packages/ui/src/common/links/ExternalLink/ExternalLink.tsx +++ b/packages/ui/src/common/links/ExternalLink/ExternalLink.tsx @@ -30,5 +30,8 @@ export function ExternalLink({ } function prettifyUrl(url: string) { - return url.replace(/https?:\/\//, "").replace(/www\./, "").replace(/\/$/, "") + return url + .replace(/https?:\/\//, "") + .replace(/www\./, "") + .replace(/\/$/, ""); } diff --git a/packages/ui/src/interactions/contexts/templates.context.tsx b/packages/ui/src/interactions/contexts/templates.context.tsx index 1b652b02..434d9301 100644 --- a/packages/ui/src/interactions/contexts/templates.context.tsx +++ b/packages/ui/src/interactions/contexts/templates.context.tsx @@ -2,7 +2,10 @@ import React, { createContext, ReactElement, useContext, useMemo } from "react"; import { InteractionDefinition } from "../core/core-types"; import { FclValue } from "@onflowser/core"; import { useLocalStorage } from "@uidotdev/usehooks"; -import { useGetContracts, useGetWorkspaceInteractionTemplates } from "../../api"; +import { + useGetContracts, + useGetWorkspaceInteractionTemplates, +} from "../../api"; import useSWR, { SWRResponse } from "swr"; type InteractionTemplatesRegistry = { @@ -41,8 +44,8 @@ export function TemplatesRegistryProvider(props: { children: React.ReactNode; }): ReactElement { const { data: workspaceTemplates } = useGetWorkspaceInteractionTemplates(); - const {data: flixTemplates} = useGetFlixInteractionTemplates(); - const {data: contracts} = useGetContracts(); + const { data: flixTemplates } = useGetFlixInteractionTemplates(); + const { data: contracts } = useGetContracts(); const [customTemplates, setRawTemplates] = useLocalStorage< RawInteractionDefinitionTemplate[] >("interactions", []); @@ -67,18 +70,29 @@ export function TemplatesRegistryProvider(props: { "NFTStorefront", "NFTStorefrontV2", "StakingProxy", - "FlowFees" - ]) + "FlowFees", + ]); function isFlixTemplateUseful(template: FlixTemplate) { - const importedContractNames = Object.values(template.data.dependencies).map(dependency => Object.keys(dependency)).flat(); - const nonCoreImportedContractNames = importedContractNames.filter(name => !flowCoreContractNames.has(name)); - const nonCoreDeployedContractNamesLookup = new Set(contracts?.filter(contract => !flowCoreContractNames.has(contract.name)).map(contract => contract.name)) + const importedContractNames = Object.values(template.data.dependencies) + .map((dependency) => Object.keys(dependency)) + .flat(); + const nonCoreImportedContractNames = importedContractNames.filter( + (name) => !flowCoreContractNames.has(name), + ); + const nonCoreDeployedContractNamesLookup = new Set( + contracts + ?.filter((contract) => !flowCoreContractNames.has(contract.name)) + .map((contract) => contract.name), + ); // Interactions that only import core contracts are most likely generic ones, // not tailored specifically to some third party contract/project. const onlyImportsCoreContracts = nonCoreImportedContractNames.length === 0; - const importsSomeNonCoreDeployedContract = nonCoreImportedContractNames.some(contractName => nonCoreDeployedContractNamesLookup.has(contractName)); + const importsSomeNonCoreDeployedContract = + nonCoreImportedContractNames.some((contractName) => + nonCoreDeployedContractNamesLookup.has(contractName), + ); return onlyImportsCoreContracts || importsSomeNonCoreDeployedContract; } @@ -113,17 +127,19 @@ export function TemplatesRegistryProvider(props: { filePath: template.source?.filePath, }), ) ?? []), - ...(flixTemplates?.filter(isFlixTemplateUseful)?.map((template): InteractionDefinitionTemplate => ({ - id: template.id, - name: getFlixTemplateName(template), - code: getFlixTemplateFormattedCode(template), - transactionOptions: undefined, - initialOutcome: undefined, - fclValuesByIdentifier: new Map(), - createdDate: new Date(), - updatedDate: new Date(), - filePath: undefined, - })) ?? []), + ...(flixTemplates?.filter(isFlixTemplateUseful)?.map( + (template): InteractionDefinitionTemplate => ({ + id: template.id, + name: getFlixTemplateName(template), + code: getFlixTemplateFormattedCode(template), + transactionOptions: undefined, + initialOutcome: undefined, + fclValuesByIdentifier: new Map(), + createdDate: new Date(), + updatedDate: new Date(), + filePath: undefined, + }), + ) ?? []), ], [customTemplates, workspaceTemplates], ); @@ -188,42 +204,46 @@ type FlixTemplate = { messages: { title?: FlixMessage; description?: FlixMessage; - } + }; dependencies: Record; cadence: string; // TODO: Add other fields - } -} + }; +}; -type FlixDependency = Record +type FlixDependency = Record< + string, + { + mainnet: FlixDependencyOnNetwork; + testnet: FlixDependencyOnNetwork; + } +>; type FlixDependencyOnNetwork = { address: string; fq_address: string; pin: string; pin_block_height: number; -} +}; type FlixMessage = { i18n: { - "en-US"?: string - } -} + "en-US"?: string; + }; +}; -function useGetFlixInteractionTemplates(): SWRResponse< - FlixTemplate[] -> { +function useGetFlixInteractionTemplates(): SWRResponse { return useSWR(`flix-interaction-templates`, () => - fetch("http://localhost:3333/v1/templates").then(res => res.json()) + fetch("http://localhost:3333/v1/templates").then((res) => res.json()), ); } function getFlixTemplateFormattedCode(template: FlixTemplate) { const replacementPatterns = Object.keys(template.data.dependencies); - return replacementPatterns.reduce((cadence, pattern) => cadence.replace(`from ${pattern}`, ""), template.data.cadence) + return replacementPatterns.reduce( + (cadence, pattern) => cadence.replace(`from ${pattern}`, ""), + template.data.cadence, + ); } function getFlixTemplateName(template: FlixTemplate) { @@ -231,8 +251,8 @@ function getFlixTemplateName(template: FlixTemplate) { if (englishTitle) { // Transactions generated with NFT catalog have this necessary prefix in titles. // https://github.com/onflow/nft-catalog - return englishTitle.replace("This transaction ", "") + return englishTitle.replace("This transaction ", ""); } else { - return "Unknown" + return "Unknown"; } } From 892206c5bc63e8a072ffdaec06c11bff0812dab0 Mon Sep 17 00:00:00 2001 From: Bart Date: Sun, 12 Nov 2023 12:45:49 +0100 Subject: [PATCH 06/23] fix source detection --- .../components/InteractionTemplates/InteractionTemplates.tsx | 2 +- packages/ui/src/interactions/contexts/templates.context.tsx | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/interactions/components/InteractionTemplates/InteractionTemplates.tsx b/packages/ui/src/interactions/components/InteractionTemplates/InteractionTemplates.tsx index 8316b96e..72809458 100644 --- a/packages/ui/src/interactions/components/InteractionTemplates/InteractionTemplates.tsx +++ b/packages/ui/src/interactions/components/InteractionTemplates/InteractionTemplates.tsx @@ -61,7 +61,7 @@ function StoredTemplates() { })} > - {!template.filePath && ( + {template.source === "session" && ( { diff --git a/packages/ui/src/interactions/contexts/templates.context.tsx b/packages/ui/src/interactions/contexts/templates.context.tsx index 434d9301..ef94140e 100644 --- a/packages/ui/src/interactions/contexts/templates.context.tsx +++ b/packages/ui/src/interactions/contexts/templates.context.tsx @@ -20,6 +20,7 @@ export type InteractionDefinitionTemplate = InteractionDefinition & { // since that would require making changes to the file system, // which is not implemented yet. filePath: string | undefined; + source: "filesystem" | "flix" | "session"; }; // Internal structure that's persisted in local storage. @@ -112,6 +113,7 @@ export function TemplatesRegistryProvider(props: { Object.entries(template.fclValuesByIdentifier), ), filePath: undefined, + source: "session" }), ), ...(workspaceTemplates?.map( @@ -125,6 +127,7 @@ export function TemplatesRegistryProvider(props: { createdDate: new Date(template.createdAt), updatedDate: new Date(template.updatedAt), filePath: template.source?.filePath, + source: "filesystem" }), ) ?? []), ...(flixTemplates?.filter(isFlixTemplateUseful)?.map( @@ -138,6 +141,7 @@ export function TemplatesRegistryProvider(props: { createdDate: new Date(), updatedDate: new Date(), filePath: undefined, + source: "flix" }), ) ?? []), ], From 8d5fd67e31bb6a51760370b6102bcefafe715018 Mon Sep 17 00:00:00 2001 From: Bart Date: Mon, 4 Dec 2023 09:51:21 +0100 Subject: [PATCH 07/23] small refactor --- packages/api/src/resources.ts | 6 +-- .../nodejs/src/flow-interactions.service.ts | 27 +++---------- packages/ui/src/api.ts | 4 +- .../src/contexts/service-registry.context.tsx | 4 +- .../InteractionTemplates.tsx | 12 +++--- .../contexts/templates.context.tsx | 40 ++++++++++--------- 6 files changed, 39 insertions(+), 54 deletions(-) diff --git a/packages/api/src/resources.ts b/packages/api/src/resources.ts index 3aa936e0..2ded912b 100644 --- a/packages/api/src/resources.ts +++ b/packages/api/src/resources.ts @@ -328,13 +328,11 @@ export interface FlowEmulatorConfig { snapshot: boolean; } -export interface InteractionTemplate extends TimestampedResource { +export interface WorkspaceTemplate extends TimestampedResource { id: string; name: string; code: string; - source: { - filePath?: string; - }; + filePath: string; } export interface FlowCliInfo { diff --git a/packages/nodejs/src/flow-interactions.service.ts b/packages/nodejs/src/flow-interactions.service.ts index 44e4101d..8d52f443 100644 --- a/packages/nodejs/src/flow-interactions.service.ts +++ b/packages/nodejs/src/flow-interactions.service.ts @@ -2,22 +2,9 @@ import * as fs from "fs/promises"; import * as path from "path"; import { GoBindingsService } from "./go-bindings.service"; import { isDefined } from "@onflowser/core"; -import { InteractionKind, ParsedInteractionOrError } from "@onflowser/api"; +import { InteractionKind, ParsedInteractionOrError, WorkspaceTemplate } from "@onflowser/api"; import { IFlowInteractions } from "@onflowser/core"; -export interface InteractionTemplate { - id: string; - name: string; - code: string; - source: InteractionTemplate_Source | undefined; - createdDate: string; - updatedDate: string; -} - -interface InteractionTemplate_Source { - filePath: string; -} - type GetInteractionTemplatesOptions = { workspacePath: string; }; @@ -31,7 +18,7 @@ export class FlowInteractionsService implements IFlowInteractions { public async getTemplates( options: GetInteractionTemplatesOptions, - ): Promise { + ): Promise { const potentialCadenceFilePaths = await this.findAllCadenceFiles( options.workspacePath, ); @@ -45,7 +32,7 @@ export class FlowInteractionsService implements IFlowInteractions { private async buildMaybeTemplate( filePath: string, - ): Promise { + ): Promise { const [fileContent, fileStats] = await Promise.all([ fs.readFile(filePath), fs.stat(filePath), @@ -67,11 +54,9 @@ export class FlowInteractionsService implements IFlowInteractions { id: filePath, name: path.basename(filePath), code, - updatedDate: fileStats.mtime.toISOString(), - createdDate: fileStats.ctime.toISOString(), - source: { - filePath, - }, + updatedAt: fileStats.mtime, + createdAt: fileStats.ctime, + filePath }; } else { return undefined; diff --git a/packages/ui/src/api.ts b/packages/ui/src/api.ts index 96443eb1..1503a0bb 100644 --- a/packages/ui/src/api.ts +++ b/packages/ui/src/api.ts @@ -10,7 +10,7 @@ import { FlowserWorkspace, FlowStateSnapshot, FlowTransaction, - InteractionTemplate, + WorkspaceTemplate, ManagedProcessOutput, ParsedInteractionOrError, ManagedKeyPair, @@ -250,7 +250,7 @@ export function useGetParsedInteraction( } export function useGetWorkspaceInteractionTemplates(): SWRResponse< - InteractionTemplate[] + WorkspaceTemplate[] > { const { interactionsService } = useServiceRegistry(); diff --git a/packages/ui/src/contexts/service-registry.context.tsx b/packages/ui/src/contexts/service-registry.context.tsx index fccaf4c4..70ceb43a 100644 --- a/packages/ui/src/contexts/service-registry.context.tsx +++ b/packages/ui/src/contexts/service-registry.context.tsx @@ -11,7 +11,7 @@ import { FlowserWorkspace, FlowStateSnapshot, FlowTransaction, - InteractionTemplate, + WorkspaceTemplate, IResourceIndexReader, ManagedProcessOutput, ParsedInteractionOrError, @@ -38,7 +38,7 @@ export interface IWorkspaceService { export interface IInteractionService { parse(sourceCode: string): Promise; - getTemplates(): Promise; + getTemplates(): Promise; } export type SendTransactionRequest = { diff --git a/packages/ui/src/interactions/components/InteractionTemplates/InteractionTemplates.tsx b/packages/ui/src/interactions/components/InteractionTemplates/InteractionTemplates.tsx index 72809458..ed674eae 100644 --- a/packages/ui/src/interactions/components/InteractionTemplates/InteractionTemplates.tsx +++ b/packages/ui/src/interactions/components/InteractionTemplates/InteractionTemplates.tsx @@ -15,7 +15,7 @@ export function InteractionTemplates(): ReactElement { return (
- +
); } @@ -88,7 +88,7 @@ function StoredTemplates() { ); } -function FocusedTemplateSettings() { +function FocusedInteraction() { const { focusedDefinition, update } = useInteractionRegistry(); const { templates, saveTemplate } = useTemplatesRegistry(); @@ -100,14 +100,14 @@ function FocusedTemplateSettings() { (template) => template.id === focusedDefinition.id, ); - if (correspondingTemplate && correspondingTemplate.filePath) { + if (correspondingTemplate && correspondingTemplate.workspace) { return (
Open in:
- - - + + +
); diff --git a/packages/ui/src/interactions/contexts/templates.context.tsx b/packages/ui/src/interactions/contexts/templates.context.tsx index ef94140e..d283d8fa 100644 --- a/packages/ui/src/interactions/contexts/templates.context.tsx +++ b/packages/ui/src/interactions/contexts/templates.context.tsx @@ -7,6 +7,7 @@ import { useGetWorkspaceInteractionTemplates, } from "../../api"; import useSWR, { SWRResponse } from "swr"; +import { WorkspaceTemplate } from "@onflowser/api"; type InteractionTemplatesRegistry = { templates: InteractionDefinitionTemplate[]; @@ -15,16 +16,14 @@ type InteractionTemplatesRegistry = { }; export type InteractionDefinitionTemplate = InteractionDefinition & { - // Specified for project-based interaction templates. - // These templates can't be updated, - // since that would require making changes to the file system, - // which is not implemented yet. - filePath: string | undefined; - source: "filesystem" | "flix" | "session"; + source: "workspace" | "flix" | "session"; + + flix: FlixTemplate | undefined; + workspace: WorkspaceTemplate | undefined; }; // Internal structure that's persisted in local storage. -type RawInteractionDefinitionTemplate = { +type SerializedSessionTemplate = { name: string; code: string; fclValuesByIdentifier: Record; @@ -47,8 +46,8 @@ export function TemplatesRegistryProvider(props: { const { data: workspaceTemplates } = useGetWorkspaceInteractionTemplates(); const { data: flixTemplates } = useGetFlixInteractionTemplates(); const { data: contracts } = useGetContracts(); - const [customTemplates, setRawTemplates] = useLocalStorage< - RawInteractionDefinitionTemplate[] + const [sessionTemplates, setSessionTemplates] = useLocalStorage< + SerializedSessionTemplate[] >("interactions", []); const randomId = () => String(Math.random() * 1000000); @@ -100,7 +99,7 @@ export function TemplatesRegistryProvider(props: { const templates = useMemo( () => [ - ...customTemplates.map( + ...sessionTemplates.map( (template): InteractionDefinitionTemplate => ({ id: randomId(), name: template.name, @@ -112,7 +111,8 @@ export function TemplatesRegistryProvider(props: { fclValuesByIdentifier: new Map( Object.entries(template.fclValuesByIdentifier), ), - filePath: undefined, + workspace: undefined, + flix: undefined, source: "session" }), ), @@ -126,8 +126,9 @@ export function TemplatesRegistryProvider(props: { fclValuesByIdentifier: new Map(), createdDate: new Date(template.createdAt), updatedDate: new Date(template.updatedAt), - filePath: template.source?.filePath, - source: "filesystem" + workspace: template, + flix: undefined, + source: "workspace" }), ) ?? []), ...(flixTemplates?.filter(isFlixTemplateUseful)?.map( @@ -140,16 +141,17 @@ export function TemplatesRegistryProvider(props: { fclValuesByIdentifier: new Map(), createdDate: new Date(), updatedDate: new Date(), - filePath: undefined, + workspace: undefined, + flix: undefined, source: "flix" }), ) ?? []), ], - [customTemplates, workspaceTemplates], + [sessionTemplates, workspaceTemplates], ); function saveTemplate(interaction: InteractionDefinition) { - const newTemplate: RawInteractionDefinitionTemplate = { + const newTemplate: SerializedSessionTemplate = { name: interaction.name, code: interaction.code, fclValuesByIdentifier: Object.fromEntries( @@ -160,8 +162,8 @@ export function TemplatesRegistryProvider(props: { updatedDate: new Date().toISOString(), }; - setRawTemplates([ - ...customTemplates.filter( + setSessionTemplates([ + ...sessionTemplates.filter( (template) => template.name !== newTemplate.name, ), newTemplate, @@ -169,7 +171,7 @@ export function TemplatesRegistryProvider(props: { } function removeTemplate(template: InteractionDefinitionTemplate) { - setRawTemplates((rawTemplates) => + setSessionTemplates((rawTemplates) => rawTemplates.filter( (existingTemplate) => existingTemplate.name !== template.name, ), From 0c83064993a09990dc208c182b75d1c0014de8d2 Mon Sep 17 00:00:00 2001 From: Bart Date: Mon, 4 Dec 2023 11:07:38 +0100 Subject: [PATCH 08/23] show verified by flix ui, move flix hooks to hooks/flix --- packages/ui/src/common/icons/FlowserIcon.tsx | 2 + .../links/ExternalLink/ExternalLink.tsx | 5 +- .../LineSeparator/LineSeparator.module.scss | 4 +- packages/ui/src/hooks/flix.ts | 62 ++++++++++++ .../InteractionTemplates.module.scss | 24 +++++ .../InteractionTemplates.tsx | 97 ++++++++++++++----- .../contexts/templates.context.tsx | 49 +--------- 7 files changed, 172 insertions(+), 71 deletions(-) create mode 100644 packages/ui/src/hooks/flix.ts diff --git a/packages/ui/src/common/icons/FlowserIcon.tsx b/packages/ui/src/common/icons/FlowserIcon.tsx index 98810022..e88a6ce8 100644 --- a/packages/ui/src/common/icons/FlowserIcon.tsx +++ b/packages/ui/src/common/icons/FlowserIcon.tsx @@ -32,6 +32,7 @@ import Shrink from "./assets/shrink.svg"; import Logs from "./assets/logs.svg"; import { TbBrandVscode } from "react-icons/tb"; import { SiWebstorm, SiIntellijidea } from "react-icons/si"; +import { RiVerifiedBadgeFill } from "react-icons/ri"; export const FlowserIcon = { Logs: Logs, @@ -69,4 +70,5 @@ export const FlowserIcon = { WebStorm: SiWebstorm, VsCode: TbBrandVscode, IntellijIdea: SiIntellijidea, + VerifiedCheck: RiVerifiedBadgeFill }; diff --git a/packages/ui/src/common/links/ExternalLink/ExternalLink.tsx b/packages/ui/src/common/links/ExternalLink/ExternalLink.tsx index 978f661c..94e8f136 100644 --- a/packages/ui/src/common/links/ExternalLink/ExternalLink.tsx +++ b/packages/ui/src/common/links/ExternalLink/ExternalLink.tsx @@ -1,12 +1,14 @@ import React, { ReactNode, ReactElement, CSSProperties } from "react"; import { FlowserIcon } from "../../icons/FlowserIcon"; import classes from "./ExternalLink.module.scss"; +import classNames from "classnames"; export type ExternalLinkProps = { children?: ReactNode; href: string; inline?: boolean; style?: CSSProperties; + className?: string; }; export function ExternalLink({ @@ -14,13 +16,14 @@ export function ExternalLink({ children, inline, style, + className }: ExternalLinkProps): ReactElement { return ( {!inline && } diff --git a/packages/ui/src/common/misc/LineSeparator/LineSeparator.module.scss b/packages/ui/src/common/misc/LineSeparator/LineSeparator.module.scss index 60e78e2d..5dbed149 100644 --- a/packages/ui/src/common/misc/LineSeparator/LineSeparator.module.scss +++ b/packages/ui/src/common/misc/LineSeparator/LineSeparator.module.scss @@ -9,11 +9,11 @@ .horizontal { width: 100%; height: 1px; - margin: $spacing-l 0; + margin: $spacing-base 0; } .vertical { height: 100%; width: 1px; - margin: 0 $spacing-l; + margin: 0 $spacing-base; } diff --git a/packages/ui/src/hooks/flix.ts b/packages/ui/src/hooks/flix.ts new file mode 100644 index 00000000..de5a4def --- /dev/null +++ b/packages/ui/src/hooks/flix.ts @@ -0,0 +1,62 @@ +import useSWR, { SWRResponse } from "swr"; + +// https://github.com/onflow/flips/blob/main/application/20220503-interaction-templates.md#interaction-interfaces +export type FlixTemplate = { + id: string; + f_type: "InteractionTemplate"; + f_version: string; + data: { + messages: { + title?: FlixMessage; + description?: FlixMessage; + }; + dependencies: Record; + cadence: string; + // TODO: Add other fields + }; +}; + +type FlixDependency = Record< + string, + { + mainnet: FlixDependencyOnNetwork; + testnet: FlixDependencyOnNetwork; + } +>; + +type FlixDependencyOnNetwork = { + address: string; + fq_address: string; + pin: string; + pin_block_height: number; +}; + +type FlixMessage = { + i18n: { + "en-US"?: string; + }; +}; + +export const FLOW_FLIX_URL = "https://flix.flow.com"; +export const FLOWSER_FLIX_URL = "http://localhost:3333" + +export function useListFlixTemplates(): SWRResponse { + return useSWR(`flix/templates`, () => + fetch(`${FLOWSER_FLIX_URL}/v1/templates`).then((res) => res.json()), + ); +} + +export function useFlixSearch(sourceCode: string): SWRResponse { + return useSWR(`flix/templates/${sourceCode}`, () => + fetch(`${FLOWSER_FLIX_URL}/v1/templates/search`, { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ + cadence_base64: btoa(sourceCode), + network: "mainnet" + }) + }).then((res) => res.json()), + ); +} diff --git a/packages/ui/src/interactions/components/InteractionTemplates/InteractionTemplates.module.scss b/packages/ui/src/interactions/components/InteractionTemplates/InteractionTemplates.module.scss index 8f8ce5cc..faaea994 100644 --- a/packages/ui/src/interactions/components/InteractionTemplates/InteractionTemplates.module.scss +++ b/packages/ui/src/interactions/components/InteractionTemplates/InteractionTemplates.module.scss @@ -3,6 +3,7 @@ @import "../../../styles/typography"; @import "../../../styles/animations"; @import "../../../styles/scrollbars"; +@import "../../../styles/rules"; .root { height: 100%; @@ -65,4 +66,27 @@ column-gap: $spacing-base; } } + + .flixInfo { + border-radius: $border-radius-input; + background: $gray-80; + padding: $spacing-base; + .title { + font-weight: bold; + .link { + color: $white; + } + .nameAndLogo { + color: $strong-green; + display: inline-flex !important; + align-items: center; + column-gap: 3px; + } + } + .body { + display: flex; + flex-direction: column; + row-gap: $spacing-base; + } + } } diff --git a/packages/ui/src/interactions/components/InteractionTemplates/InteractionTemplates.tsx b/packages/ui/src/interactions/components/InteractionTemplates/InteractionTemplates.tsx index ed674eae..743d7c95 100644 --- a/packages/ui/src/interactions/components/InteractionTemplates/InteractionTemplates.tsx +++ b/packages/ui/src/interactions/components/InteractionTemplates/InteractionTemplates.tsx @@ -10,6 +10,10 @@ import classNames from "classnames"; import { InteractionLabel } from "../InteractionLabel/InteractionLabel"; import { useTemplatesRegistry } from "../../contexts/templates.context"; import { IdeLink } from "../../../common/links/IdeLink"; +import { WorkspaceTemplate } from "@onflowser/api"; +import { FLOW_FLIX_URL, useFlixSearch } from "../../../hooks/flix"; +import { ExternalLink } from "../../../common/links/ExternalLink/ExternalLink"; +import { LineSeparator } from "../../../common/misc/LineSeparator/LineSeparator"; export function InteractionTemplates(): ReactElement { return ( @@ -34,9 +38,9 @@ function StoredTemplates() { const filteredAndSortedTemplates = useMemo( () => filteredTemplates.sort( - (a, b) => b.updatedDate.getTime() - a.updatedDate.getTime(), + (a, b) => b.updatedDate.getTime() - a.updatedDate.getTime() ), - [filteredTemplates], + [filteredTemplates] ); return ( @@ -57,7 +61,7 @@ function StoredTemplates() { setFocused(createdInteraction.id); }} className={classNames(classes.item, { - [classes.focusedItem]: focusedDefinition?.id === template.id, + [classes.focusedItem]: focusedDefinition?.id === template.id })} > @@ -76,7 +80,7 @@ function StoredTemplates() { ), confirmButtonLabel: "REMOVE", cancelButtonLabel: "CANCEL", - onConfirm: () => removeTemplate(template), + onConfirm: () => removeTemplate(template) }); }} /> @@ -89,32 +93,38 @@ function StoredTemplates() { } function FocusedInteraction() { - const { focusedDefinition, update } = useInteractionRegistry(); - const { templates, saveTemplate } = useTemplatesRegistry(); - - if (!focusedDefinition) { - return null; - } + const { focusedDefinition } = useInteractionRegistry(); + const { templates } = useTemplatesRegistry(); const correspondingTemplate = templates.find( - (template) => template.id === focusedDefinition.id, + (template) => template.id === focusedDefinition?.id ); - if (correspondingTemplate && correspondingTemplate.workspace) { - return ( -
- Open in: -
- - - -
-
- ); + if (!correspondingTemplate) { + return ; + } + + switch (correspondingTemplate.source) { + case "workspace": + return ; + case "flix": + return ; + case "session": + return ; + } +} + +function SessionTemplateSettings() { + const { focusedDefinition, update } = useInteractionRegistry(); + const { saveTemplate } = useTemplatesRegistry(); + + if (!focusedDefinition) { + return null; } return (
+ ); } + +function WorkspaceTemplateInfo(props: { workspaceTemplate: WorkspaceTemplate }) { + const { workspaceTemplate } = props; + + return ( +
+ + Open in: +
+ + + +
+
+ ); +} + +function FlixInfo(props: { sourceCode: string }) { + const { data } = useFlixSearch(props.sourceCode); + + if (!data) { + return null; + } + + return ( +
+
+ + Verified by +
+ FLIX + +
+
+
+ +
+

{data.data.messages.description?.i18n["en-US"]}

+ +
+
+ ); +} diff --git a/packages/ui/src/interactions/contexts/templates.context.tsx b/packages/ui/src/interactions/contexts/templates.context.tsx index d283d8fa..cf0f443d 100644 --- a/packages/ui/src/interactions/contexts/templates.context.tsx +++ b/packages/ui/src/interactions/contexts/templates.context.tsx @@ -6,8 +6,8 @@ import { useGetContracts, useGetWorkspaceInteractionTemplates, } from "../../api"; -import useSWR, { SWRResponse } from "swr"; import { WorkspaceTemplate } from "@onflowser/api"; +import { FlixTemplate, useListFlixTemplates } from "../../hooks/flix"; type InteractionTemplatesRegistry = { templates: InteractionDefinitionTemplate[]; @@ -44,7 +44,7 @@ export function TemplatesRegistryProvider(props: { children: React.ReactNode; }): ReactElement { const { data: workspaceTemplates } = useGetWorkspaceInteractionTemplates(); - const { data: flixTemplates } = useGetFlixInteractionTemplates(); + const { data: flixTemplates } = useListFlixTemplates(); const { data: contracts } = useGetContracts(); const [sessionTemplates, setSessionTemplates] = useLocalStorage< SerializedSessionTemplate[] @@ -142,7 +142,7 @@ export function TemplatesRegistryProvider(props: { createdDate: new Date(), updatedDate: new Date(), workspace: undefined, - flix: undefined, + flix: template, source: "flix" }), ) ?? []), @@ -201,49 +201,6 @@ export function useTemplatesRegistry(): InteractionTemplatesRegistry { return context; } -// https://github.com/onflow/flips/blob/main/application/20220503-interaction-templates.md#interaction-interfaces -type FlixTemplate = { - id: string; - f_type: "InteractionTemplate"; - f_version: string; - data: { - messages: { - title?: FlixMessage; - description?: FlixMessage; - }; - dependencies: Record; - cadence: string; - // TODO: Add other fields - }; -}; - -type FlixDependency = Record< - string, - { - mainnet: FlixDependencyOnNetwork; - testnet: FlixDependencyOnNetwork; - } ->; - -type FlixDependencyOnNetwork = { - address: string; - fq_address: string; - pin: string; - pin_block_height: number; -}; - -type FlixMessage = { - i18n: { - "en-US"?: string; - }; -}; - -function useGetFlixInteractionTemplates(): SWRResponse { - return useSWR(`flix-interaction-templates`, () => - fetch("http://localhost:3333/v1/templates").then((res) => res.json()), - ); -} - function getFlixTemplateFormattedCode(template: FlixTemplate) { const replacementPatterns = Object.keys(template.data.dependencies); return replacementPatterns.reduce( From 409aec73a027351adb0fe4e3699b6595fcb561a6 Mon Sep 17 00:00:00 2001 From: Bart Date: Mon, 4 Dec 2023 11:28:19 +0100 Subject: [PATCH 09/23] search interaction names in lowercase --- .../components/InteractionTemplates/InteractionTemplates.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/src/interactions/components/InteractionTemplates/InteractionTemplates.tsx b/packages/ui/src/interactions/components/InteractionTemplates/InteractionTemplates.tsx index 743d7c95..9ef84e60 100644 --- a/packages/ui/src/interactions/components/InteractionTemplates/InteractionTemplates.tsx +++ b/packages/ui/src/interactions/components/InteractionTemplates/InteractionTemplates.tsx @@ -33,7 +33,7 @@ function StoredTemplates() { if (searchTerm === "") { return templates; } - return templates.filter((template) => template.name.includes(searchTerm)); + return templates.filter((template) => template.name.toLowerCase().includes(searchTerm.toLowerCase())); }, [searchTerm, templates]); const filteredAndSortedTemplates = useMemo( () => From f699b7a95800afc8702b7f1b1cef9ecbd92ccc8b Mon Sep 17 00:00:00 2001 From: Bart Date: Mon, 4 Dec 2023 11:30:03 +0100 Subject: [PATCH 10/23] wrap "open in ide" section in gray card --- .../InteractionTemplates.module.scss | 8 ++++++++ .../InteractionTemplates/InteractionTemplates.tsx | 12 +++++++----- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/packages/ui/src/interactions/components/InteractionTemplates/InteractionTemplates.module.scss b/packages/ui/src/interactions/components/InteractionTemplates/InteractionTemplates.module.scss index faaea994..2f3753c7 100644 --- a/packages/ui/src/interactions/components/InteractionTemplates/InteractionTemplates.module.scss +++ b/packages/ui/src/interactions/components/InteractionTemplates/InteractionTemplates.module.scss @@ -60,6 +60,14 @@ padding-top: $spacing-base; background: $gray-100; bottom: 0; + } + + .workspaceInfo { + border-radius: $border-radius-input; + background: $gray-80; + padding: $spacing-base; + display: flex; + column-gap: $spacing-base; .actionButtons { display: flex; diff --git a/packages/ui/src/interactions/components/InteractionTemplates/InteractionTemplates.tsx b/packages/ui/src/interactions/components/InteractionTemplates/InteractionTemplates.tsx index 9ef84e60..b1aafd3a 100644 --- a/packages/ui/src/interactions/components/InteractionTemplates/InteractionTemplates.tsx +++ b/packages/ui/src/interactions/components/InteractionTemplates/InteractionTemplates.tsx @@ -143,11 +143,13 @@ function WorkspaceTemplateInfo(props: { workspaceTemplate: WorkspaceTemplate }) return (
- Open in: -
- - - +
+ Open in: +
+ + + +
); From 6fa56bd17d565db65559874c8c2a9bdcea50ce5d Mon Sep 17 00:00:00 2001 From: Bart Date: Tue, 5 Dec 2023 11:07:53 +0100 Subject: [PATCH 11/23] first pass at non-verified state --- .../InteractionTemplates.module.scss | 3 +++ .../InteractionTemplates.tsx | 24 +++++++++++++++++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/interactions/components/InteractionTemplates/InteractionTemplates.module.scss b/packages/ui/src/interactions/components/InteractionTemplates/InteractionTemplates.module.scss index 2f3753c7..8dcac063 100644 --- a/packages/ui/src/interactions/components/InteractionTemplates/InteractionTemplates.module.scss +++ b/packages/ui/src/interactions/components/InteractionTemplates/InteractionTemplates.module.scss @@ -81,6 +81,9 @@ padding: $spacing-base; .title { font-weight: bold; + display: flex; + column-gap: 3px; + align-items: center; .link { color: $white; } diff --git a/packages/ui/src/interactions/components/InteractionTemplates/InteractionTemplates.tsx b/packages/ui/src/interactions/components/InteractionTemplates/InteractionTemplates.tsx index b1aafd3a..03cc5666 100644 --- a/packages/ui/src/interactions/components/InteractionTemplates/InteractionTemplates.tsx +++ b/packages/ui/src/interactions/components/InteractionTemplates/InteractionTemplates.tsx @@ -108,7 +108,11 @@ function FocusedInteraction() { case "workspace": return ; case "flix": - return ; + return ( +
+ +
+ ) case "session": return ; } @@ -159,7 +163,23 @@ function FlixInfo(props: { sourceCode: string }) { const { data } = useFlixSearch(props.sourceCode); if (!data) { - return null; + return ( +
+
+ Unverified + +
+ +
+

+ This interaction is not yet verified by FLIX. +

+ + Submit for verification + +
+
+ ) } return ( From 4ebc8f6ee44510450609016bcec47af2f5221cdb Mon Sep 17 00:00:00 2001 From: Bart Date: Tue, 5 Dec 2023 11:25:17 +0100 Subject: [PATCH 12/23] fix hidden trash icons due to long labels --- .../components/InteractionLabel/InteractionLabel.module.scss | 1 - .../InteractionTemplates/InteractionTemplates.module.scss | 2 ++ .../components/InteractionTemplates/InteractionTemplates.tsx | 2 ++ 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/interactions/components/InteractionLabel/InteractionLabel.module.scss b/packages/ui/src/interactions/components/InteractionLabel/InteractionLabel.module.scss index 87aa9f6d..61b7c8cc 100644 --- a/packages/ui/src/interactions/components/InteractionLabel/InteractionLabel.module.scss +++ b/packages/ui/src/interactions/components/InteractionLabel/InteractionLabel.module.scss @@ -11,7 +11,6 @@ justify-content: center; } .label { - white-space: nowrap; text-overflow: ellipsis; overflow: hidden; } diff --git a/packages/ui/src/interactions/components/InteractionTemplates/InteractionTemplates.module.scss b/packages/ui/src/interactions/components/InteractionTemplates/InteractionTemplates.module.scss index 8dcac063..ee33f858 100644 --- a/packages/ui/src/interactions/components/InteractionTemplates/InteractionTemplates.module.scss +++ b/packages/ui/src/interactions/components/InteractionTemplates/InteractionTemplates.module.scss @@ -30,6 +30,7 @@ .item { cursor: pointer; display: flex; + align-items: center; justify-content: space-between; font-size: $font-size-normal; color: $gray-20; @@ -37,6 +38,7 @@ .trash { @include scaleOnHover(); + min-width: 20px; * { fill: $gray-50; } diff --git a/packages/ui/src/interactions/components/InteractionTemplates/InteractionTemplates.tsx b/packages/ui/src/interactions/components/InteractionTemplates/InteractionTemplates.tsx index 03cc5666..dfe23a90 100644 --- a/packages/ui/src/interactions/components/InteractionTemplates/InteractionTemplates.tsx +++ b/packages/ui/src/interactions/components/InteractionTemplates/InteractionTemplates.tsx @@ -126,6 +126,8 @@ function SessionTemplateSettings() { return null; } + console.log(focusedDefinition) + return (
From 0af41e625c764f923b39565e0e0e1e1a04eeee15 Mon Sep 17 00:00:00 2001 From: Bart Date: Tue, 5 Dec 2023 12:16:48 +0100 Subject: [PATCH 13/23] fix emulator flix template retrieval https://github.com/onflowser/flow-interaction-template-service/pull/4 --- packages/ui/src/hooks/flix.ts | 11 +++++++---- .../InteractionTemplates/InteractionTemplates.tsx | 14 ++++++++++---- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/packages/ui/src/hooks/flix.ts b/packages/ui/src/hooks/flix.ts index de5a4def..6e875bb4 100644 --- a/packages/ui/src/hooks/flix.ts +++ b/packages/ui/src/hooks/flix.ts @@ -46,16 +46,19 @@ export function useListFlixTemplates(): SWRResponse { ); } -export function useFlixSearch(sourceCode: string): SWRResponse { - return useSWR(`flix/templates/${sourceCode}`, () => +export function useFlixSearch(options: { + sourceCode: string; + network: "emulator" | "testnet" | "mainnet"; +}): SWRResponse { + return useSWR(`flix/templates/${options.sourceCode}`, () => fetch(`${FLOWSER_FLIX_URL}/v1/templates/search`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ - cadence_base64: btoa(sourceCode), - network: "mainnet" + cadence_base64: btoa(options.sourceCode), + network: options.network }) }).then((res) => res.json()), ); diff --git a/packages/ui/src/interactions/components/InteractionTemplates/InteractionTemplates.tsx b/packages/ui/src/interactions/components/InteractionTemplates/InteractionTemplates.tsx index dfe23a90..47253524 100644 --- a/packages/ui/src/interactions/components/InteractionTemplates/InteractionTemplates.tsx +++ b/packages/ui/src/interactions/components/InteractionTemplates/InteractionTemplates.tsx @@ -14,6 +14,7 @@ import { WorkspaceTemplate } from "@onflowser/api"; import { FLOW_FLIX_URL, useFlixSearch } from "../../../hooks/flix"; import { ExternalLink } from "../../../common/links/ExternalLink/ExternalLink"; import { LineSeparator } from "../../../common/misc/LineSeparator/LineSeparator"; +import { Shimmer } from "../../../common/loaders/Shimmer/Shimmer"; export function InteractionTemplates(): ReactElement { return ( @@ -110,7 +111,7 @@ function FocusedInteraction() { case "flix": return (
- +
) case "session": @@ -126,8 +127,6 @@ function SessionTemplateSettings() { return null; } - console.log(focusedDefinition) - return (
@@ -162,7 +161,14 @@ function WorkspaceTemplateInfo(props: { workspaceTemplate: WorkspaceTemplate }) } function FlixInfo(props: { sourceCode: string }) { - const { data } = useFlixSearch(props.sourceCode); + const { data, isLoading } = useFlixSearch({ + sourceCode: props.sourceCode, + network: "emulator" + }); + + if (isLoading) { + return + } if (!data) { return ( From 22d30454534e9bc6b6e9c627df4429e3ca603542 Mon Sep 17 00:00:00 2001 From: Bart Date: Tue, 5 Dec 2023 12:25:04 +0100 Subject: [PATCH 14/23] update comment --- packages/ui/src/hooks/flix.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/ui/src/hooks/flix.ts b/packages/ui/src/hooks/flix.ts index 6e875bb4..dc103f25 100644 --- a/packages/ui/src/hooks/flix.ts +++ b/packages/ui/src/hooks/flix.ts @@ -48,6 +48,8 @@ export function useListFlixTemplates(): SWRResponse { export function useFlixSearch(options: { sourceCode: string; + // Supports emulator as of: + // https://github.com/onflowser/flow-interaction-template-service/pull/4 network: "emulator" | "testnet" | "mainnet"; }): SWRResponse { return useSWR(`flix/templates/${options.sourceCode}`, () => From 93218232fcbe4d84e6f7d07c73438acb2a1d84b0 Mon Sep 17 00:00:00 2001 From: Bart Date: Tue, 5 Dec 2023 12:27:08 +0100 Subject: [PATCH 15/23] disable polling of flix templates --- packages/ui/src/hooks/flix.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/ui/src/hooks/flix.ts b/packages/ui/src/hooks/flix.ts index dc103f25..ccd1bfec 100644 --- a/packages/ui/src/hooks/flix.ts +++ b/packages/ui/src/hooks/flix.ts @@ -63,5 +63,8 @@ export function useFlixSearch(options: { network: options.network }) }).then((res) => res.json()), + { + refreshInterval: 0 + } ); } From 11eeb654e1315fa58ac3000a14cc5854f8e11ba3 Mon Sep 17 00:00:00 2001 From: Bart Date: Tue, 5 Dec 2023 12:36:27 +0100 Subject: [PATCH 16/23] update to "any" network name --- packages/ui/src/hooks/flix.ts | 4 ++-- .../components/InteractionTemplates/InteractionTemplates.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/ui/src/hooks/flix.ts b/packages/ui/src/hooks/flix.ts index ccd1bfec..de84be09 100644 --- a/packages/ui/src/hooks/flix.ts +++ b/packages/ui/src/hooks/flix.ts @@ -48,9 +48,9 @@ export function useListFlixTemplates(): SWRResponse { export function useFlixSearch(options: { sourceCode: string; - // Supports emulator as of: + // Supports "any" network as of: // https://github.com/onflowser/flow-interaction-template-service/pull/4 - network: "emulator" | "testnet" | "mainnet"; + network: "any" | "testnet" | "mainnet"; }): SWRResponse { return useSWR(`flix/templates/${options.sourceCode}`, () => fetch(`${FLOWSER_FLIX_URL}/v1/templates/search`, { diff --git a/packages/ui/src/interactions/components/InteractionTemplates/InteractionTemplates.tsx b/packages/ui/src/interactions/components/InteractionTemplates/InteractionTemplates.tsx index 47253524..725e2a8a 100644 --- a/packages/ui/src/interactions/components/InteractionTemplates/InteractionTemplates.tsx +++ b/packages/ui/src/interactions/components/InteractionTemplates/InteractionTemplates.tsx @@ -163,7 +163,7 @@ function WorkspaceTemplateInfo(props: { workspaceTemplate: WorkspaceTemplate }) function FlixInfo(props: { sourceCode: string }) { const { data, isLoading } = useFlixSearch({ sourceCode: props.sourceCode, - network: "emulator" + network: "any" }); if (isLoading) { From 22b0d052861fa12a65d5f88bfbd035dcf31ee313 Mon Sep 17 00:00:00 2001 From: Bart Date: Tue, 5 Dec 2023 12:38:20 +0100 Subject: [PATCH 17/23] stop retrying on error --- packages/ui/src/hooks/flix.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/hooks/flix.ts b/packages/ui/src/hooks/flix.ts index de84be09..646a1e47 100644 --- a/packages/ui/src/hooks/flix.ts +++ b/packages/ui/src/hooks/flix.ts @@ -64,7 +64,8 @@ export function useFlixSearch(options: { }) }).then((res) => res.json()), { - refreshInterval: 0 + refreshInterval: 0, + shouldRetryOnError: false } ); } From 8594b6cab9f53d9367cc1dcb6c413fabe4b0bfd2 Mon Sep 17 00:00:00 2001 From: Bart Date: Tue, 5 Dec 2023 12:54:45 +0100 Subject: [PATCH 18/23] show argument descriptions --- packages/ui/src/hooks/flix.ts | 20 +++++++++++++------ .../ExecutionSettings/ExecutionSettings.tsx | 3 ++- .../ParamBuilder/ParamBuilder.module.scss | 7 ++++++- .../components/ParamBuilder/ParamBuilder.tsx | 15 ++++++++++++-- .../contexts/definition.context.tsx | 7 +++++++ 5 files changed, 42 insertions(+), 10 deletions(-) diff --git a/packages/ui/src/hooks/flix.ts b/packages/ui/src/hooks/flix.ts index 646a1e47..3cac4010 100644 --- a/packages/ui/src/hooks/flix.ts +++ b/packages/ui/src/hooks/flix.ts @@ -6,16 +6,19 @@ export type FlixTemplate = { f_type: "InteractionTemplate"; f_version: string; data: { - messages: { - title?: FlixMessage; - description?: FlixMessage; - }; + messages: FlixMessages; dependencies: Record; cadence: string; - // TODO: Add other fields + arguments: Record; }; }; +export type FlixArgument = { + index: number; + type: string; + messages: FlixMessages; +} + type FlixDependency = Record< string, { @@ -31,7 +34,12 @@ type FlixDependencyOnNetwork = { pin_block_height: number; }; -type FlixMessage = { +type FlixMessages = { + title?: FlixI18nMessage; + description?: FlixI18nMessage; +}; + +type FlixI18nMessage = { i18n: { "en-US"?: string; }; diff --git a/packages/ui/src/interactions/components/ExecutionSettings/ExecutionSettings.tsx b/packages/ui/src/interactions/components/ExecutionSettings/ExecutionSettings.tsx index 0ce614db..7dba3290 100644 --- a/packages/ui/src/interactions/components/ExecutionSettings/ExecutionSettings.tsx +++ b/packages/ui/src/interactions/components/ExecutionSettings/ExecutionSettings.tsx @@ -44,7 +44,7 @@ function ExecuteButton() { } function TopContent() { - const { setFclValue, fclValuesByIdentifier, definition, parsedInteraction } = + const { flixTemplate, setFclValue, fclValuesByIdentifier, definition, parsedInteraction } = useInteractionDefinitionManager(); if (definition.code === "") { @@ -70,6 +70,7 @@ function TopContent() { parameters={parsedInteraction?.parameters ?? []} setFclValue={setFclValue} fclValuesByIdentifier={fclValuesByIdentifier} + flixTemplate={flixTemplate} /> {parsedInteraction?.kind === InteractionKind.INTERACTION_TRANSACTION && ( diff --git a/packages/ui/src/interactions/components/ParamBuilder/ParamBuilder.module.scss b/packages/ui/src/interactions/components/ParamBuilder/ParamBuilder.module.scss index f1fc408c..f718e527 100644 --- a/packages/ui/src/interactions/components/ParamBuilder/ParamBuilder.module.scss +++ b/packages/ui/src/interactions/components/ParamBuilder/ParamBuilder.module.scss @@ -9,7 +9,6 @@ .paramRoot { .label { - margin-bottom: $spacing-base; .identifier { margin-right: $spacing-s; } @@ -17,4 +16,10 @@ color: $gray-10; } } + .description { + color: $gray-20; + } + .valueBuilder { + margin-top: $spacing-base; + } } diff --git a/packages/ui/src/interactions/components/ParamBuilder/ParamBuilder.tsx b/packages/ui/src/interactions/components/ParamBuilder/ParamBuilder.tsx index 96ce3293..4d401ceb 100644 --- a/packages/ui/src/interactions/components/ParamBuilder/ParamBuilder.tsx +++ b/packages/ui/src/interactions/components/ParamBuilder/ParamBuilder.tsx @@ -5,15 +5,18 @@ import { InteractionParameterBuilder } from "../../contexts/definition.context"; import classes from "./ParamBuilder.module.scss"; import { CadenceValueBuilder } from "../ValueBuilder/interface"; import { CadenceParameter } from "@onflowser/api"; +import { FlixArgument, FlixTemplate } from "../../../hooks/flix"; +import { SizedBox } from "../../../common/misc/SizedBox/SizedBox"; export type ParameterListBuilderProps = InteractionParameterBuilder & { parameters: CadenceParameter[]; + flixTemplate: FlixTemplate | undefined; }; export function ParamListBuilder( props: ParameterListBuilderProps, ): ReactElement { - const { parameters, fclValuesByIdentifier, setFclValue } = props; + const { flixTemplate, parameters, fclValuesByIdentifier, setFclValue } = props; return (
{parameters.map((parameter) => ( @@ -22,6 +25,7 @@ export function ParamListBuilder( parameter={parameter} value={fclValuesByIdentifier.get(parameter.identifier)} setValue={(value) => setFclValue(parameter.identifier, value)} + flixArgument={flixTemplate?.data?.arguments?.[parameter.identifier]} /> ))}
@@ -30,14 +34,17 @@ export function ParamListBuilder( export type ParameterBuilderProps = Omit & { parameter: CadenceParameter; + flixArgument: FlixArgument | undefined; }; export function ParamBuilder(props: ParameterBuilderProps): ReactElement { - const { parameter, ...valueBuilderProps } = props; + const { parameter, flixArgument, ...valueBuilderProps } = props; if (!parameter.type) { throw new Error("Expected parameter.type"); } + const description = flixArgument?.messages?.title?.i18n?.["en-US"]; + return (
@@ -45,6 +52,10 @@ export function ParamBuilder(props: ParameterBuilderProps): ReactElement { {" "} {parameter.type.rawType}
+ {description && ( + {description} + )} +
); diff --git a/packages/ui/src/interactions/contexts/definition.context.tsx b/packages/ui/src/interactions/contexts/definition.context.tsx index b4aaa5b7..72c87024 100644 --- a/packages/ui/src/interactions/contexts/definition.context.tsx +++ b/packages/ui/src/interactions/contexts/definition.context.tsx @@ -12,10 +12,12 @@ import { import { ParsedInteraction } from "@onflowser/api"; import { FclValue } from "@onflowser/core"; import { useGetParsedInteraction } from "../../api"; +import { FlixTemplate, useFlixSearch } from "../../hooks/flix"; type InteractionDefinitionManager = InteractionParameterBuilder & { isParsing: boolean; parseError: string | undefined; + flixTemplate: FlixTemplate | undefined; parsedInteraction: ParsedInteraction | undefined; definition: InteractionDefinition; partialUpdate: (definition: Partial) => void; @@ -36,6 +38,10 @@ export function InteractionDefinitionManagerProvider(props: { const { update } = useInteractionRegistry(); const { data, isLoading } = useGetParsedInteraction(definition); const fclValuesByIdentifier = definition.fclValuesByIdentifier; + const { data: flixTemplate } = useFlixSearch({ + sourceCode: definition.code, + network: "any" + }) function partialUpdate(newDefinition: Partial) { update({ ...definition, ...newDefinition }); @@ -55,6 +61,7 @@ export function InteractionDefinitionManagerProvider(props: { Date: Tue, 5 Dec 2023 13:08:05 +0100 Subject: [PATCH 19/23] use interaction definition instead of template for searching flix --- .../InteractionTemplates.tsx | 54 ++++++++++--------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/packages/ui/src/interactions/components/InteractionTemplates/InteractionTemplates.tsx b/packages/ui/src/interactions/components/InteractionTemplates/InteractionTemplates.tsx index 725e2a8a..4a88a37a 100644 --- a/packages/ui/src/interactions/components/InteractionTemplates/InteractionTemplates.tsx +++ b/packages/ui/src/interactions/components/InteractionTemplates/InteractionTemplates.tsx @@ -101,22 +101,28 @@ function FocusedInteraction() { (template) => template.id === focusedDefinition?.id ); - if (!correspondingTemplate) { - return ; - } - - switch (correspondingTemplate.source) { - case "workspace": - return ; - case "flix": - return ( -
- -
- ) - case "session": + function renderContent() { + if (!correspondingTemplate) { return ; + } + + switch (correspondingTemplate.source) { + case "workspace": + return ; + case "flix": + // Already shown at the top. + return null; + case "session": + return ; + } } + + return ( +
+ {focusedDefinition && } + {renderContent()} +
+ ) } function SessionTemplateSettings() { @@ -128,8 +134,7 @@ function SessionTemplateSettings() { } return ( -
- + <> saveTemplate(focusedDefinition)}> Save -
+ ); } @@ -146,15 +151,12 @@ function WorkspaceTemplateInfo(props: { workspaceTemplate: WorkspaceTemplate }) const { workspaceTemplate } = props; return ( -
- -
- Open in: -
- - - -
+
+ Open in: +
+ + +
); From d2b4df450c0af035bb810f43a134298106ad8cc8 Mon Sep 17 00:00:00 2001 From: Bart Date: Tue, 5 Dec 2023 13:18:29 +0100 Subject: [PATCH 20/23] fix transformation to new import syntax --- .../ui/src/interactions/contexts/templates.context.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/interactions/contexts/templates.context.tsx b/packages/ui/src/interactions/contexts/templates.context.tsx index cf0f443d..06b0bd46 100644 --- a/packages/ui/src/interactions/contexts/templates.context.tsx +++ b/packages/ui/src/interactions/contexts/templates.context.tsx @@ -204,7 +204,13 @@ export function useTemplatesRegistry(): InteractionTemplatesRegistry { function getFlixTemplateFormattedCode(template: FlixTemplate) { const replacementPatterns = Object.keys(template.data.dependencies); return replacementPatterns.reduce( - (cadence, pattern) => cadence.replace(`from ${pattern}`, ""), + (cadence, pattern) => { + const contractName = Object.keys(template.data.dependencies[pattern])[0]; + + return cadence + .replace(new RegExp(`from\\s+${pattern}`), "") + .replace(new RegExp(`import\\s+${contractName}`), `import "${contractName}"`) + }, template.data.cadence, ); } From 197a5adaf5cba32ac66f417889de7c3f5710f5f2 Mon Sep 17 00:00:00 2001 From: Bart Date: Thu, 7 Dec 2023 12:28:23 +0100 Subject: [PATCH 21/23] use flowser flix api url --- packages/ui/src/hooks/flix.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/src/hooks/flix.ts b/packages/ui/src/hooks/flix.ts index 3cac4010..2323dde9 100644 --- a/packages/ui/src/hooks/flix.ts +++ b/packages/ui/src/hooks/flix.ts @@ -46,7 +46,7 @@ type FlixI18nMessage = { }; export const FLOW_FLIX_URL = "https://flix.flow.com"; -export const FLOWSER_FLIX_URL = "http://localhost:3333" +export const FLOWSER_FLIX_URL = "https://flowser-flix-368a32c94da2.herokuapp.com" export function useListFlixTemplates(): SWRResponse { return useSWR(`flix/templates`, () => From a0a8aed180dc8c0e50d9b5ca50508f1d1dddcab5 Mon Sep 17 00:00:00 2001 From: Bart Date: Sun, 10 Dec 2023 20:33:45 +0100 Subject: [PATCH 22/23] update comment --- .../ui/src/interactions/contexts/templates.context.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/interactions/contexts/templates.context.tsx b/packages/ui/src/interactions/contexts/templates.context.tsx index 06b0bd46..e2e5936d 100644 --- a/packages/ui/src/interactions/contexts/templates.context.tsx +++ b/packages/ui/src/interactions/contexts/templates.context.tsx @@ -135,7 +135,7 @@ export function TemplatesRegistryProvider(props: { (template): InteractionDefinitionTemplate => ({ id: template.id, name: getFlixTemplateName(template), - code: getFlixTemplateFormattedCode(template), + code: getCadenceWithNewImportSyntax(template), transactionOptions: undefined, initialOutcome: undefined, fclValuesByIdentifier: new Map(), @@ -201,7 +201,10 @@ export function useTemplatesRegistry(): InteractionTemplatesRegistry { return context; } -function getFlixTemplateFormattedCode(template: FlixTemplate) { +// Transform imports with replacement patterns to the new import syntax, +// since FLIX v1.0 doesn't support new import syntax yet. +// https://github.com/onflow/flow-interaction-template-tools/issues/12 +function getCadenceWithNewImportSyntax(template: FlixTemplate) { const replacementPatterns = Object.keys(template.data.dependencies); return replacementPatterns.reduce( (cadence, pattern) => { From 510f5777dd0b6e11cb883add4f1fa5c19d7f645a Mon Sep 17 00:00:00 2001 From: Bart Date: Tue, 12 Dec 2023 09:44:45 +0100 Subject: [PATCH 23/23] consolidate verified/unverified layout --- .../InteractionTemplates.module.scss | 51 ++++++++------ .../InteractionTemplates.tsx | 67 ++++++++++--------- 2 files changed, 65 insertions(+), 53 deletions(-) diff --git a/packages/ui/src/interactions/components/InteractionTemplates/InteractionTemplates.module.scss b/packages/ui/src/interactions/components/InteractionTemplates/InteractionTemplates.module.scss index ee33f858..b9925003 100644 --- a/packages/ui/src/interactions/components/InteractionTemplates/InteractionTemplates.module.scss +++ b/packages/ui/src/interactions/components/InteractionTemplates/InteractionTemplates.module.scss @@ -12,7 +12,7 @@ justify-content: space-between; position: relative; - .header { + & > .header { background: $gray-100; position: sticky; top: 0; @@ -76,30 +76,37 @@ column-gap: $spacing-base; } } +} - .flixInfo { - border-radius: $border-radius-input; - background: $gray-80; - padding: $spacing-base; +.flixInfo { + border-radius: $border-radius-input; + background: $gray-80; + padding: $spacing-base; + .header { + font-weight: bold; + display: flex; + align-items: center; + column-gap: 3px; .title { - font-weight: bold; - display: flex; - column-gap: 3px; - align-items: center; - .link { - color: $white; - } - .nameAndLogo { - color: $strong-green; - display: inline-flex !important; - align-items: center; - column-gap: 3px; - } + color: $strong-green; + display: inline-flex !important; } - .body { - display: flex; - flex-direction: column; - row-gap: $spacing-base; + @mixin icon { + height: 15px; + width: 15px; + } + .verifiedIcon { + color: $strong-green; + @include icon; + } + .unverifiedIcon { + color: $color-red; + @include icon; } } + .body { + display: flex; + flex-direction: column; + row-gap: $spacing-base; + } } diff --git a/packages/ui/src/interactions/components/InteractionTemplates/InteractionTemplates.tsx b/packages/ui/src/interactions/components/InteractionTemplates/InteractionTemplates.tsx index 4a88a37a..8a372681 100644 --- a/packages/ui/src/interactions/components/InteractionTemplates/InteractionTemplates.tsx +++ b/packages/ui/src/interactions/components/InteractionTemplates/InteractionTemplates.tsx @@ -1,4 +1,4 @@ -import React, { ReactElement, useMemo, useState } from "react"; +import React, { Fragment, ReactElement, useMemo, useState } from "react"; import { useInteractionRegistry } from "../../contexts/interaction-registry.context"; import classes from "./InteractionTemplates.module.scss"; import { FlowserIcon } from "../../../common/icons/FlowserIcon"; @@ -122,7 +122,7 @@ function FocusedInteraction() { {focusedDefinition && } {renderContent()}
- ) + ); } function SessionTemplateSettings() { @@ -169,44 +169,49 @@ function FlixInfo(props: { sourceCode: string }) { }); if (isLoading) { - return + return ; } - if (!data) { - return ( -
-
- Unverified - -
- -
-

- This interaction is not yet verified by FLIX. -

- - Submit for verification - -
-
- ) - } + const isVerified = data !== undefined; return (
-
- - Verified by -
- FLIX - -
+
+ + FLIX: + {isVerified ? ( + + verified + + + ) : ( + + unverified + + + )}
-

{data.data.messages.description?.i18n["en-US"]}

- + {isVerified ? ( + +

{data.data.messages.description?.i18n["en-US"]}

+ +
+ ) : ( + +

+ This interaction is not yet verified by FLIX. +

+ + Submit for verification + +
+ )}
);