From 9d94da022064ba9ffa9a0f0a36c5fadb13f6a736 Mon Sep 17 00:00:00 2001 From: Bart <bartolomej.kozorog@gmail.com> Date: Sat, 9 Sep 2023 12:07:49 +0200 Subject: [PATCH 01/37] open transaction in interact screen (first pass) --- .../TransactionDetailsTabs.tsx | 7 +-- .../TransactionSource.module.scss | 14 ++++++ .../TransactionSource/TransactionSource.tsx | 49 ++++++++++++++++--- 3 files changed, 56 insertions(+), 14 deletions(-) diff --git a/frontend/src/modules/transactions/TransactionDetailsTabs/TransactionDetailsTabs.tsx b/frontend/src/modules/transactions/TransactionDetailsTabs/TransactionDetailsTabs.tsx index d21832ac..bd8c9532 100644 --- a/frontend/src/modules/transactions/TransactionDetailsTabs/TransactionDetailsTabs.tsx +++ b/frontend/src/modules/transactions/TransactionDetailsTabs/TransactionDetailsTabs.tsx @@ -50,12 +50,7 @@ export function TransactionDetailsTabs( tabs.push({ id: "script", label: "Script", - content: ( - <TransactionSource - code={transaction.script} - arguments={transaction.arguments} - /> - ), + content: <TransactionSource transaction={transaction} />, }); } diff --git a/frontend/src/modules/transactions/TransactionSource/TransactionSource.module.scss b/frontend/src/modules/transactions/TransactionSource/TransactionSource.module.scss index 550929f2..7023a9b3 100644 --- a/frontend/src/modules/transactions/TransactionSource/TransactionSource.module.scss +++ b/frontend/src/modules/transactions/TransactionSource/TransactionSource.module.scss @@ -5,6 +5,20 @@ .root { display: flex; column-gap: $spacing-xl; + position: relative; + + .interactLink { + position: absolute; + top: 0; + right: 0; + display: flex; + align-items: center; + column-gap: $spacing-s; + + svg * { + fill: $blue; + } + } .left { flex: 1; diff --git a/frontend/src/modules/transactions/TransactionSource/TransactionSource.tsx b/frontend/src/modules/transactions/TransactionSource/TransactionSource.tsx index c748804d..dcea995c 100644 --- a/frontend/src/modules/transactions/TransactionSource/TransactionSource.tsx +++ b/frontend/src/modules/transactions/TransactionSource/TransactionSource.tsx @@ -1,28 +1,30 @@ import React, { FC } from "react"; import Card from "../../../components/card/Card"; import classes from "./TransactionSource.module.scss"; -import { TransactionArgument } from "@flowser/shared"; +import { Transaction } from "@flowser/shared"; import { CadenceEditor } from "../../../components/cadence-editor/CadenceEditor"; import { ParamBuilder } from "../../interactions/components/ParamBuilder/ParamBuilder"; import { SizedBox } from "../../../components/sized-box/SizedBox"; +import { ProjectLink } from "../../../components/links/ProjectLink"; +import { FlowserIcon } from "../../../components/icons/Icons"; +import { useInteractionRegistry } from "../../interactions/contexts/interaction-registry.context"; type TransactionSourceProps = { - code: string; - arguments: TransactionArgument[]; + transaction: Transaction; }; export const TransactionSource: FC<TransactionSourceProps> = ({ - code, - arguments: args, + transaction, }) => { + const { setFocused, create } = useInteractionRegistry(); return ( <Card className={classes.root}> - {args.length > 0 && ( + {transaction.arguments.length > 0 && ( <div className={classes.left}> <h3>Arguments</h3> <SizedBox height={20} /> <div className={classes.argumentsWrapper}> - {args.map((arg) => ( + {transaction.arguments.map((arg) => ( <ParamBuilder key={arg.identifier} disabled @@ -35,8 +37,39 @@ export const TransactionSource: FC<TransactionSourceProps> = ({ </div> )} <div className={classes.right}> - <CadenceEditor value={code} editable={false} /> + <CadenceEditor value={transaction.script} editable={false} /> </div> + <ProjectLink + className={classes.interactLink} + to="/interactions" + onClick={() => { + create({ + code: transaction.script, + fclValuesByIdentifier: new Map( + transaction.arguments.map((arg) => [ + arg.identifier, + JSON.parse(arg.valueAsJson), + ]) + ), + id: transaction.id, + initialOutcome: { + transaction: { + transactionId: transaction.id, + error: transaction.status?.errorMessage, + }, + }, + name: "Test", + transactionOptions: { + authorizerAddresses: transaction.authorizers, + payerAddress: transaction.payer, + proposerAddress: transaction.proposalKey!.address, + }, + }); + setFocused(transaction.id); + }} + > + <FlowserIcon.CursorClick /> Interact + </ProjectLink> </Card> ); }; From 59a6cc3348bb67b1e0572f52c2379480ee4add81 Mon Sep 17 00:00:00 2001 From: Bart <bartolomej.kozorog@gmail.com> Date: Sat, 9 Sep 2023 12:08:35 +0200 Subject: [PATCH 02/37] remove predefined interactions --- .../contexts/interaction-registry.context.tsx | 54 ------------------- 1 file changed, 54 deletions(-) diff --git a/frontend/src/modules/interactions/contexts/interaction-registry.context.tsx b/frontend/src/modules/interactions/contexts/interaction-registry.context.tsx index 38cd4103..964d494d 100644 --- a/frontend/src/modules/interactions/contexts/interaction-registry.context.tsx +++ b/frontend/src/modules/interactions/contexts/interaction-registry.context.tsx @@ -74,24 +74,6 @@ export type FlowInteractionOutcome = { const Context = createContext<InteractionsRegistry>(undefined as never); -const helloWorldScript = `pub fun main(): String { - return "Hello World" -}`; - -const helloWorldScriptWithArguments = `pub fun main(a: String, b: String): String { - return a.concat(" ").concat(b) -}`; - -const helloWorldTransaction = `transaction() { - prepare(signer: AuthAccount) { - log("Preparing") - } - execute { - log("Executing") - } -} -`; - export function InteractionRegistryProvider(props: { children: React.ReactNode; }): ReactElement { @@ -112,44 +94,8 @@ export function InteractionRegistryProvider(props: { const [customTemplates, setRawTemplates] = useLocalStorage< RawInteractionDefinitionTemplate[] >("interactions", []); - const predefinedTemplates = useMemo< - (CoreInteractionDefinition & Partial<InteractionDefinitionTemplate>)[] - >( - () => [ - { - id: "hello-world-script", - name: "Hello World", - code: helloWorldScript, - }, - { - id: "script-with-arguments", - name: "Arguments example", - code: helloWorldScriptWithArguments, - fclValuesByIdentifier: new Map([ - ["a", "Hello"], - ["b", "World"], - ]), - }, - { - id: "hello-world-transaction", - name: "Hello World", - code: helloWorldTransaction, - }, - ], - [] - ); const templates = useMemo<InteractionDefinitionTemplate[]>( () => [ - ...predefinedTemplates.map( - (template): InteractionDefinitionTemplate => ({ - createdDate: new Date(), - updatedDate: new Date(), - fclValuesByIdentifier: new Map(), - transactionOptions: undefined, - isMutable: false, - ...template, - }) - ), ...customTemplates.map( (template): InteractionDefinitionTemplate => ({ ...template, From 476f92790395bce457a2f17e17888b998623ee1d Mon Sep 17 00:00:00 2001 From: Bart <bartolomej.kozorog@gmail.com> Date: Sat, 9 Sep 2023 12:16:56 +0200 Subject: [PATCH 03/37] fix execution settings width --- frontend/src/components/links/ExternalLink.module.scss | 4 +--- .../ExecutionSettings/ExecutionSettings.module.scss | 1 - 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/frontend/src/components/links/ExternalLink.module.scss b/frontend/src/components/links/ExternalLink.module.scss index 28151c98..92ad47b3 100644 --- a/frontend/src/components/links/ExternalLink.module.scss +++ b/frontend/src/components/links/ExternalLink.module.scss @@ -13,8 +13,6 @@ fill: $blue; } .url { - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; + word-break: break-all; } } diff --git a/frontend/src/modules/interactions/components/ExecutionSettings/ExecutionSettings.module.scss b/frontend/src/modules/interactions/components/ExecutionSettings/ExecutionSettings.module.scss index 7876a8cd..04c56bdc 100644 --- a/frontend/src/modules/interactions/components/ExecutionSettings/ExecutionSettings.module.scss +++ b/frontend/src/modules/interactions/components/ExecutionSettings/ExecutionSettings.module.scss @@ -7,7 +7,6 @@ justify-content: space-between; flex-direction: column; padding: $spacing-base; - min-width: 300px; row-gap: $spacing-base; .top { From 5c622f67a8d9e9cced6d5dc3e358809b0bd8805b Mon Sep 17 00:00:00 2001 From: Bart <bartolomej.kozorog@gmail.com> Date: Sat, 9 Sep 2023 12:24:29 +0200 Subject: [PATCH 04/37] consolidate section spacings --- frontend/src/modules/interactions/InteractionsPage.module.scss | 1 + frontend/src/modules/interactions/InteractionsPage.tsx | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/frontend/src/modules/interactions/InteractionsPage.module.scss b/frontend/src/modules/interactions/InteractionsPage.module.scss index 418c5e3f..afe7e174 100644 --- a/frontend/src/modules/interactions/InteractionsPage.module.scss +++ b/frontend/src/modules/interactions/InteractionsPage.module.scss @@ -4,6 +4,7 @@ .pageRoot { height: 100%; display: flex; + column-gap: $spacing-base; flex: 1; background: #1C2128; .leftSideMenu { diff --git a/frontend/src/modules/interactions/InteractionsPage.tsx b/frontend/src/modules/interactions/InteractionsPage.tsx index 9550ea9f..fdc00a57 100644 --- a/frontend/src/modules/interactions/InteractionsPage.tsx +++ b/frontend/src/modules/interactions/InteractionsPage.tsx @@ -58,7 +58,6 @@ export function InteractionsPage(): ReactElement { onChangeTab={(tab) => setCurrentSideMenuTabId(tab.id)} tabs={sideMenuTabs} /> - <SizedBox width={20} /> <Tabs className={classes.mainContent} tabWrapperClassName={classes.interactionsTabWrapper} @@ -97,7 +96,6 @@ function InteractionBody(): ReactElement { <InteractionDetails /> </div> </div> - <LineSeparator vertical /> <ExecutionSettings /> </div> ); From 0fc26b291d5358548062c30c6456e9e95b73a1da Mon Sep 17 00:00:00 2001 From: Bart <bartolomej.kozorog@gmail.com> Date: Sat, 9 Sep 2023 12:26:42 +0200 Subject: [PATCH 05/37] tweak execution settings padding --- .../src/modules/interactions/InteractionsPage.module.scss | 8 ++++---- .../ExecutionSettings/ExecutionSettings.module.scss | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/src/modules/interactions/InteractionsPage.module.scss b/frontend/src/modules/interactions/InteractionsPage.module.scss index afe7e174..f7ec3dd8 100644 --- a/frontend/src/modules/interactions/InteractionsPage.module.scss +++ b/frontend/src/modules/interactions/InteractionsPage.module.scss @@ -6,9 +6,9 @@ display: flex; column-gap: $spacing-base; flex: 1; - background: #1C2128; + background: $gray-110; .leftSideMenu { - background: #272B32; + background: $gray-100; flex: 1; .content { margin: $spacing-base; @@ -38,11 +38,11 @@ overflow: hidden; .code { height: 60%; - background: #272B32; + background: $gray-100; } .details { height: 40%; - background: #272B32; + background: $gray-100; .error { padding: $spacing-base; } diff --git a/frontend/src/modules/interactions/components/ExecutionSettings/ExecutionSettings.module.scss b/frontend/src/modules/interactions/components/ExecutionSettings/ExecutionSettings.module.scss index 04c56bdc..e180ed9d 100644 --- a/frontend/src/modules/interactions/components/ExecutionSettings/ExecutionSettings.module.scss +++ b/frontend/src/modules/interactions/components/ExecutionSettings/ExecutionSettings.module.scss @@ -6,7 +6,7 @@ display: flex; justify-content: space-between; flex-direction: column; - padding: $spacing-base; + padding: $spacing-l; row-gap: $spacing-base; .top { From 387612daf21cb9c9d1efda230e59efcb5b9a4a0c Mon Sep 17 00:00:00 2001 From: Bart <bartolomej.kozorog@gmail.com> Date: Sat, 9 Sep 2023 12:31:32 +0200 Subject: [PATCH 06/37] fix opened logs positioning --- frontend/src/modules/logs/Logs.module.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/modules/logs/Logs.module.scss b/frontend/src/modules/logs/Logs.module.scss index e6dadcd8..906bbd9f 100644 --- a/frontend/src/modules/logs/Logs.module.scss +++ b/frontend/src/modules/logs/Logs.module.scss @@ -5,7 +5,7 @@ @mixin overlay { z-index: 99; - position: absolute; + position: absolute !important; bottom: 0; left: 0; right: 0; From ff134ea3b6d55c6192330206b8b4631ec35fdd94 Mon Sep 17 00:00:00 2001 From: Bart <bartolomej.kozorog@gmail.com> Date: Sun, 10 Sep 2023 18:39:23 +0200 Subject: [PATCH 07/37] move templates logic to a separate provider --- frontend/src/App.tsx | 25 ++-- .../InteractionTemplates.tsx | 10 +- .../contexts/interaction-registry.context.tsx | 90 +------------ .../contexts/templates.context.tsx | 126 ++++++++++++++++++ .../hooks/use-transaction-name.ts | 4 +- 5 files changed, 152 insertions(+), 103 deletions(-) create mode 100644 frontend/src/modules/interactions/contexts/templates.context.tsx diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 8edc2495..80d84353 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -49,6 +49,9 @@ import { ContractsTable } from "./modules/contracts/ContractsTable"; import { ContractDetails } from "./modules/contracts/ContractDetails/ContractDetails"; import { EventsTable } from "./modules/events/EventsTable/EventsTable"; import { createCrumbHandle } from "./components/breadcrumbs/Breadcrumbs"; +import { + TemplatesRegistryProvider +} from './modules/interactions/contexts/templates.context'; const BrowserRouterEvents = (props: { children: ReactNode }): ReactElement => { const location = useLocation(); @@ -85,16 +88,18 @@ export const FlowserClientApp = ({ <ConfirmDialogProvider> <PlatformAdapterProvider {...platformAdapter}> <InteractionRegistryProvider> - <ConsentAnalytics /> - <ProjectRequirements /> - <RouterProvider - router={useHashRouter ? hashRouter : browserRouter} - /> - <Toaster - position="bottom-center" - gutter={8} - toastOptions={toastOptions} - /> + <TemplatesRegistryProvider> + <ConsentAnalytics /> + <ProjectRequirements /> + <RouterProvider + router={useHashRouter ? hashRouter : browserRouter} + /> + <Toaster + position="bottom-center" + gutter={8} + toastOptions={toastOptions} + /> + </TemplatesRegistryProvider> </InteractionRegistryProvider> </PlatformAdapterProvider> </ConfirmDialogProvider> diff --git a/frontend/src/modules/interactions/components/InteractionTemplates/InteractionTemplates.tsx b/frontend/src/modules/interactions/components/InteractionTemplates/InteractionTemplates.tsx index 0f58b484..293fa019 100644 --- a/frontend/src/modules/interactions/components/InteractionTemplates/InteractionTemplates.tsx +++ b/frontend/src/modules/interactions/components/InteractionTemplates/InteractionTemplates.tsx @@ -8,6 +8,7 @@ import { SearchInput } from "../../../../components/inputs/search-input/SearchIn import { useConfirmDialog } from "../../../../contexts/confirm-dialog.context"; import classNames from "classnames"; import { InteractionLabel } from "../InteractionLabel/InteractionLabel"; +import { useTemplatesRegistry } from "../../contexts/templates.context"; export function InteractionTemplates(): ReactElement { return ( @@ -21,8 +22,8 @@ export function InteractionTemplates(): ReactElement { function StoredTemplates() { const { showDialog } = useConfirmDialog(); const [searchTerm, setSearchTerm] = useState(""); - const { templates, forkTemplate, removeTemplate, focusedDefinition } = - useInteractionRegistry(); + const { forkTemplate, focusedDefinition } = useInteractionRegistry(); + const { templates, removeTemplate } = useTemplatesRegistry(); const filteredTemplates = useMemo(() => { if (searchTerm === "") { return templates; @@ -84,7 +85,8 @@ function StoredTemplates() { } function FocusedDefinitionSettings() { - const { focusedDefinition, update, persist } = useInteractionRegistry(); + const { focusedDefinition, update } = useInteractionRegistry(); + const { saveTemplate } = useTemplatesRegistry(); if (!focusedDefinition) { return null; @@ -97,7 +99,7 @@ function FocusedDefinitionSettings() { value={focusedDefinition.name} onChange={(e) => update({ ...focusedDefinition, name: e.target.value })} /> - <PrimaryButton onClick={() => persist(focusedDefinition.id)}> + <PrimaryButton onClick={() => saveTemplate(focusedDefinition)}> Save </PrimaryButton> </div> diff --git a/frontend/src/modules/interactions/contexts/interaction-registry.context.tsx b/frontend/src/modules/interactions/contexts/interaction-registry.context.tsx index 964d494d..a9c98166 100644 --- a/frontend/src/modules/interactions/contexts/interaction-registry.context.tsx +++ b/frontend/src/modules/interactions/contexts/interaction-registry.context.tsx @@ -7,21 +7,16 @@ import React, { useState, } from "react"; import { FclValueLookupByIdentifier } from "./definition.context"; -import { useLocalStorage } from "usehooks-ts"; -import { FclValue, InteractionTemplate } from "@flowser/shared"; -import { useGetPollingFlowInteractionTemplates } from "../../../hooks/use-api"; +import { InteractionTemplate } from "@flowser/shared"; type InteractionsRegistry = { - templates: InteractionDefinitionTemplate[]; definitions: InteractionDefinition[]; focusedDefinition: InteractionDefinition | undefined; update: (interaction: InteractionDefinition) => void; create: (interaction: InteractionDefinition) => void; remove: (interactionId: string) => void; setFocused: (interactionId: string) => void; - persist: (interactionId: string) => void; forkTemplate: (template: InteractionDefinitionTemplate) => void; - removeTemplate: (template: InteractionDefinitionTemplate) => void; }; export type CoreInteractionDefinition = Omit< @@ -43,14 +38,6 @@ export type InteractionDefinitionTemplate = CoreInteractionDefinition & { isMutable: boolean; }; -// Internal structure that's persisted in local storage. -type RawInteractionDefinitionTemplate = CoreInteractionDefinition & { - fclValuesByIdentifier: Record<string, FclValue>; - transactionOptions: TransactionOptions | undefined; - createdDate: string; - updatedDate: string; -}; - export type TransactionOptions = { authorizerAddresses: string[]; proposerAddress: string; @@ -89,37 +76,6 @@ export function InteractionRegistryProvider(props: { payerAddress: "0xf8d6e0586b0a20c7", }, }; - const { data: projectTemplatesData } = - useGetPollingFlowInteractionTemplates(); - const [customTemplates, setRawTemplates] = useLocalStorage< - RawInteractionDefinitionTemplate[] - >("interactions", []); - const templates = useMemo<InteractionDefinitionTemplate[]>( - () => [ - ...customTemplates.map( - (template): InteractionDefinitionTemplate => ({ - ...template, - createdDate: new Date(template.createdDate), - updatedDate: new Date(template.updatedDate), - fclValuesByIdentifier: new Map( - Object.entries(template.fclValuesByIdentifier) - ), - isMutable: true, - }) - ), - ...(projectTemplatesData?.templates?.map( - (template): InteractionDefinitionTemplate => ({ - ...template, - isMutable: false, - transactionOptions: undefined, - fclValuesByIdentifier: new Map(), - createdDate: new Date(template.createdDate), - updatedDate: new Date(template.updatedDate), - }) - ) ?? []), - ], - [customTemplates, projectTemplatesData] - ); const [definitions, setDefinitions] = useState<InteractionDefinition[]>([ defaultInteraction, ]); @@ -141,29 +97,6 @@ export function InteractionRegistryProvider(props: { } }, [definitions]); - function persist(interactionId: string) { - const interaction = getById(interactionId); - - const newTemplate: RawInteractionDefinitionTemplate = { - id: interaction.name, - name: interaction.name, - code: interaction.code, - fclValuesByIdentifier: Object.fromEntries( - interaction.fclValuesByIdentifier - ), - transactionOptions: interaction.transactionOptions, - createdDate: new Date().toISOString(), - updatedDate: new Date().toISOString(), - }; - - setRawTemplates([ - ...customTemplates.filter( - (template) => template.name !== newTemplate.name - ), - newTemplate, - ]); - } - function forkTemplate(template: InteractionDefinitionTemplate) { const definition: InteractionDefinition = { id: template.id, @@ -182,14 +115,6 @@ export function InteractionRegistryProvider(props: { } } - function removeTemplate(template: InteractionDefinitionTemplate) { - setRawTemplates((rawTemplates) => - rawTemplates.filter( - (existingTemplate) => existingTemplate.name !== template.name - ) - ); - } - function update(updatedInteraction: InteractionDefinition) { setDefinitions((interactions) => interactions.map((existingInteraction) => { @@ -221,27 +146,16 @@ export function InteractionRegistryProvider(props: { } } - function getById(id: string) { - const definition = definitions.find((definition) => definition.id === id); - if (!definition) { - throw new Error(`Definition not found by id: ${id}`); - } - return definition; - } - return ( <Context.Provider value={{ - templates, definitions, focusedDefinition, setFocused: setFocusedInteractionId, forkTemplate, - removeTemplate, remove, create, update, - persist, }} > {props.children} @@ -253,7 +167,7 @@ export function useInteractionRegistry(): InteractionsRegistry { const context = useContext(Context); if (context === undefined) { - throw new Error("Interaction definitions manager provider not found"); + throw new Error("Interaction definitions registry provider not found"); } return context; diff --git a/frontend/src/modules/interactions/contexts/templates.context.tsx b/frontend/src/modules/interactions/contexts/templates.context.tsx new file mode 100644 index 00000000..bb7c2105 --- /dev/null +++ b/frontend/src/modules/interactions/contexts/templates.context.tsx @@ -0,0 +1,126 @@ +import React, { createContext, ReactElement, useContext, useMemo } from "react"; +import { FclValueLookupByIdentifier } from "./definition.context"; +import { useLocalStorage } from "usehooks-ts"; +import { FclValue } from "@flowser/shared"; +import { useGetPollingFlowInteractionTemplates } from "../../../hooks/use-api"; +import { + CoreInteractionDefinition, + InteractionDefinition, +} from "./interaction-registry.context"; + +type InteractionTemplatesRegistry = { + templates: InteractionDefinitionTemplate[]; + saveTemplate: (definition: InteractionDefinition) => void; + removeTemplate: (template: InteractionDefinitionTemplate) => void; +}; + +export type InteractionDefinitionTemplate = CoreInteractionDefinition & { + fclValuesByIdentifier: FclValueLookupByIdentifier; + transactionOptions: TransactionOptions | undefined; + createdDate: Date; + updatedDate: Date; + isMutable: boolean; +}; + +// Internal structure that's persisted in local storage. +type RawInteractionDefinitionTemplate = CoreInteractionDefinition & { + fclValuesByIdentifier: Record<string, FclValue>; + transactionOptions: TransactionOptions | undefined; + createdDate: string; + updatedDate: string; +}; + +export type TransactionOptions = { + authorizerAddresses: string[]; + proposerAddress: string; + payerAddress: string; +}; + +const Context = createContext<InteractionTemplatesRegistry>(undefined as never); + +export function TemplatesRegistryProvider(props: { + children: React.ReactNode; +}): ReactElement { + const { data: projectTemplatesData } = + useGetPollingFlowInteractionTemplates(); + const [customTemplates, setRawTemplates] = useLocalStorage< + RawInteractionDefinitionTemplate[] + >("interactions", []); + const templates = useMemo<InteractionDefinitionTemplate[]>( + () => [ + ...customTemplates.map( + (template): InteractionDefinitionTemplate => ({ + ...template, + createdDate: new Date(template.createdDate), + updatedDate: new Date(template.updatedDate), + fclValuesByIdentifier: new Map( + Object.entries(template.fclValuesByIdentifier) + ), + isMutable: true, + }) + ), + ...(projectTemplatesData?.templates?.map( + (template): InteractionDefinitionTemplate => ({ + ...template, + isMutable: false, + transactionOptions: undefined, + fclValuesByIdentifier: new Map(), + createdDate: new Date(template.createdDate), + updatedDate: new Date(template.updatedDate), + }) + ) ?? []), + ], + [customTemplates, projectTemplatesData] + ); + + function saveTemplate(interaction: InteractionDefinition) { + const newTemplate: RawInteractionDefinitionTemplate = { + id: interaction.name, + name: interaction.name, + code: interaction.code, + fclValuesByIdentifier: Object.fromEntries( + interaction.fclValuesByIdentifier + ), + transactionOptions: interaction.transactionOptions, + createdDate: new Date().toISOString(), + updatedDate: new Date().toISOString(), + }; + + setRawTemplates([ + ...customTemplates.filter( + (template) => template.name !== newTemplate.name + ), + newTemplate, + ]); + } + + function removeTemplate(template: InteractionDefinitionTemplate) { + setRawTemplates((rawTemplates) => + rawTemplates.filter( + (existingTemplate) => existingTemplate.name !== template.name + ) + ); + } + + return ( + <Context.Provider + value={{ + templates, + removeTemplate, + saveTemplate, + }} + > + {props.children} + </Context.Provider> + ); +} + +export function useTemplatesRegistry(): InteractionTemplatesRegistry { + const context = useContext(Context); + + if (context === undefined) { + throw new Error("Interaction templates registry provider not found"); + } + + return context; +} diff --git a/frontend/src/modules/interactions/hooks/use-transaction-name.ts b/frontend/src/modules/interactions/hooks/use-transaction-name.ts index b437dbb3..b8f502a9 100644 --- a/frontend/src/modules/interactions/hooks/use-transaction-name.ts +++ b/frontend/src/modules/interactions/hooks/use-transaction-name.ts @@ -1,6 +1,7 @@ import { useInteractionRegistry } from "../contexts/interaction-registry.context"; import { useMemo } from "react"; import { Transaction } from "@flowser/shared"; +import { useTemplatesRegistry } from "../contexts/templates.context"; type UseInteractionNameProps = { transaction: Transaction | undefined; @@ -59,7 +60,8 @@ export function useTransactionName( props: UseInteractionNameProps ): string | undefined { const { transaction } = props; - const { templates, definitions } = useInteractionRegistry(); + const { templates } = useTemplatesRegistry(); + const { definitions } = useInteractionRegistry(); return useMemo(() => { if (!transaction?.script) { From 4995ec56f15e2fa4b1d698a81dbb47d99faf616b Mon Sep 17 00:00:00 2001 From: Bart <bartolomej.kozorog@gmail.com> Date: Sun, 10 Sep 2023 20:32:39 +0200 Subject: [PATCH 08/37] simplify/refactor interaction structures and logic --- frontend/src/hooks/use-api.ts | 18 ++-- .../modules/interactions/InteractionsPage.tsx | 16 +-- .../ExecutionSettings/ExecutionSettings.tsx | 2 +- .../InteractionHistory/InteractionHistory.tsx | 1 - .../InteractionLabel/InteractionLabel.tsx | 9 +- .../InteractionOutcomeDisplay.module.scss} | 0 .../InteractionOutcomeDisplay.tsx} | 17 ++-- .../InteractionTemplates.tsx | 7 +- .../contexts/definition.context.tsx | 11 +-- .../contexts/interaction-registry.context.tsx | 98 ++++++------------- .../interactions/contexts/outcome.context.tsx | 9 +- .../contexts/templates.context.tsx | 30 +++--- .../modules/interactions/core/core-types.ts | 35 +++++++ .../modules/interactions/core/core-utils.ts | 31 ++++++ .../hooks/use-transaction-name.ts | 28 +++--- .../TransactionSource/TransactionSource.tsx | 5 +- 16 files changed, 156 insertions(+), 161 deletions(-) rename frontend/src/modules/interactions/components/{InteractionOutcome/InteractionOutcome.module.scss => InteractionOutcomeDisplay/InteractionOutcomeDisplay.module.scss} (100%) rename frontend/src/modules/interactions/components/{InteractionOutcome/InteractionOutcome.tsx => InteractionOutcomeDisplay/InteractionOutcomeDisplay.tsx} (90%) create mode 100644 frontend/src/modules/interactions/core/core-types.ts create mode 100644 frontend/src/modules/interactions/core/core-utils.ts diff --git a/frontend/src/hooks/use-api.ts b/frontend/src/hooks/use-api.ts index f895718a..0ea622ec 100644 --- a/frontend/src/hooks/use-api.ts +++ b/frontend/src/hooks/use-api.ts @@ -47,6 +47,7 @@ import { } from "contexts/timeout-polling.context"; import { useEffect, useState } from "react"; import { useCurrentProjectId } from "./use-current-project-id"; +import { InteractionDefinition } from "../modules/interactions/core/core-types"; const { projectsService, @@ -349,26 +350,21 @@ export function useGetFlowserVersion() { ); } -type UseGetParsedInteractionRequest = { - // Used as a cache key. - id: string; - sourceCode: string; -}; - -export function useGetParsedInteraction( - request: UseGetParsedInteractionRequest -) { +export function useGetParsedInteraction(request: InteractionDefinition) { // We are not using `sourceCode` as the cache key, // to avoid the flickering UI effect that's caused // by undefined parsed interaction every time the source code changes. const queryState = useQuery<GetParsedInteractionResponse>( `/go-bindings/get-parsed-interaction/${request.id}`, - () => goBindingsService.getParsedInteraction(request) + () => + goBindingsService.getParsedInteraction({ + sourceCode: request.code, + }) ); useEffect(() => { queryState.refetch(); - }, [request.sourceCode]); + }, [request.code]); return queryState; } diff --git a/frontend/src/modules/interactions/InteractionsPage.tsx b/frontend/src/modules/interactions/InteractionsPage.tsx index fdc00a57..83a40a66 100644 --- a/frontend/src/modules/interactions/InteractionsPage.tsx +++ b/frontend/src/modules/interactions/InteractionsPage.tsx @@ -9,16 +9,14 @@ import { useInteractionDefinitionManager, } from "./contexts/definition.context"; import { InteractionTemplates } from "./components/InteractionTemplates/InteractionTemplates"; -import { SizedBox } from "../../components/sized-box/SizedBox"; -import { LineSeparator } from "../../components/line-separator/LineSeparator"; import { ExecutionSettings } from "./components/ExecutionSettings/ExecutionSettings"; import { CadenceEditor } from "../../components/cadence-editor/CadenceEditor"; -import { InteractionOutcome } from "./components/InteractionOutcome/InteractionOutcome"; +import { InteractionOutcomeDisplay } from "./components/InteractionOutcomeDisplay/InteractionOutcomeDisplay"; import { SpinnerWithLabel } from "../../components/spinner/SpinnerWithLabel"; import { InteractionLabel } from "./components/InteractionLabel/InteractionLabel"; export function InteractionsPage(): ReactElement { - const { definitions, focusedDefinition, remove, setFocused, forkTemplate } = + const { definitions, focusedDefinition, remove, setFocused, create } = useInteractionRegistry(); const sideMenuTabs: TabItem[] = [ @@ -68,16 +66,12 @@ export function InteractionsPage(): ReactElement { tabs={openEditorTabs} onClose={(tab) => remove(tab.id)} onAddNew={() => - forkTemplate({ - id: crypto.randomUUID(), + create({ name: "New interaction", code: "", fclValuesByIdentifier: new Map(), transactionOptions: undefined, - createdDate: new Date(), - updatedDate: new Date(), - // TODO(feature-interact-screen): This should be defined in the implemented function - isMutable: true, + initialOutcome: undefined, }) } /> @@ -122,5 +116,5 @@ function InteractionDetails() { return <pre className={classes.error}>{parseError}</pre>; } - return <InteractionOutcome />; + return <InteractionOutcomeDisplay />; } diff --git a/frontend/src/modules/interactions/components/ExecutionSettings/ExecutionSettings.tsx b/frontend/src/modules/interactions/components/ExecutionSettings/ExecutionSettings.tsx index ea6b4966..8b492967 100644 --- a/frontend/src/modules/interactions/components/ExecutionSettings/ExecutionSettings.tsx +++ b/frontend/src/modules/interactions/components/ExecutionSettings/ExecutionSettings.tsx @@ -12,9 +12,9 @@ import classes from "./ExecutionSettings.module.scss"; import { LoaderButton } from "../../../../components/buttons/loader-button/LoaderButton"; import { useInteractionOutcomeManager } from "../../contexts/outcome.context"; import { useInteractionDefinitionManager } from "../../contexts/definition.context"; -import { TransactionOptions } from "../../contexts/interaction-registry.context"; import { Callout } from "../../../../components/callout/Callout"; import { ExternalLink } from "../../../../components/links/ExternalLink"; +import { TransactionOptions } from "modules/interactions/core/core-types"; export function ExecutionSettings(): ReactElement { return ( diff --git a/frontend/src/modules/interactions/components/InteractionHistory/InteractionHistory.tsx b/frontend/src/modules/interactions/components/InteractionHistory/InteractionHistory.tsx index 647b40c1..203ef3a7 100644 --- a/frontend/src/modules/interactions/components/InteractionHistory/InteractionHistory.tsx +++ b/frontend/src/modules/interactions/components/InteractionHistory/InteractionHistory.tsx @@ -61,7 +61,6 @@ function BlockItem(props: BlockItemProps) { return; } create({ - id: block.id, name: transactionName ?? `Tx from block #${block.height}`, code: firstTransaction.script, fclValuesByIdentifier: new Map( diff --git a/frontend/src/modules/interactions/components/InteractionLabel/InteractionLabel.tsx b/frontend/src/modules/interactions/components/InteractionLabel/InteractionLabel.tsx index 47e355e6..ff07e0fa 100644 --- a/frontend/src/modules/interactions/components/InteractionLabel/InteractionLabel.tsx +++ b/frontend/src/modules/interactions/components/InteractionLabel/InteractionLabel.tsx @@ -1,23 +1,20 @@ import React, { ReactElement } from "react"; import { InteractionIcon } from "../InteractionIcon/InteractionIcon"; import { SizedBox } from "../../../../components/sized-box/SizedBox"; -import { CoreInteractionDefinition } from "../../contexts/interaction-registry.context"; import { useGetParsedInteraction } from "../../../../hooks/use-api"; import { Spinner } from "../../../../components/spinner/Spinner"; import classes from "./InteractionLabel.module.scss"; import { InteractionKind } from "@flowser/shared"; +import { InteractionDefinition } from "modules/interactions/core/core-types"; type InteractionLabelProps = { - interaction: CoreInteractionDefinition; + interaction: InteractionDefinition; }; export function InteractionLabel(props: InteractionLabelProps): ReactElement { const { interaction } = props; - const { data } = useGetParsedInteraction({ - id: interaction.id, - sourceCode: interaction.code, - }); + const { data } = useGetParsedInteraction(interaction); return ( <div className={classes.root}> diff --git a/frontend/src/modules/interactions/components/InteractionOutcome/InteractionOutcome.module.scss b/frontend/src/modules/interactions/components/InteractionOutcomeDisplay/InteractionOutcomeDisplay.module.scss similarity index 100% rename from frontend/src/modules/interactions/components/InteractionOutcome/InteractionOutcome.module.scss rename to frontend/src/modules/interactions/components/InteractionOutcomeDisplay/InteractionOutcomeDisplay.module.scss diff --git a/frontend/src/modules/interactions/components/InteractionOutcome/InteractionOutcome.tsx b/frontend/src/modules/interactions/components/InteractionOutcomeDisplay/InteractionOutcomeDisplay.tsx similarity index 90% rename from frontend/src/modules/interactions/components/InteractionOutcome/InteractionOutcome.tsx rename to frontend/src/modules/interactions/components/InteractionOutcomeDisplay/InteractionOutcomeDisplay.tsx index 3b771b0c..079e5347 100644 --- a/frontend/src/modules/interactions/components/InteractionOutcome/InteractionOutcome.tsx +++ b/frontend/src/modules/interactions/components/InteractionOutcomeDisplay/InteractionOutcomeDisplay.tsx @@ -3,11 +3,7 @@ import { useInteractionOutcomeManager } from "../../contexts/outcome.context"; import { ScriptError } from "../../../../components/status/ErrorMessage"; import { JsonView } from "../../../../components/json-view/JsonView"; import { useGetTransaction } from "../../../../hooks/use-api"; -import classes from "./InteractionOutcome.module.scss"; -import { - FlowScriptOutcome, - FlowTransactionOutcome, -} from "modules/interactions/contexts/interaction-registry.context"; +import classes from "./InteractionOutcomeDisplay.module.scss"; import { TabItem } from "../../../../components/tabs/Tabs"; import { Callout } from "../../../../components/callout/Callout"; import { useInteractionDefinitionManager } from "../../contexts/definition.context"; @@ -17,14 +13,15 @@ import { LineSeparator } from "../../../../components/line-separator/LineSeparat import { SpinnerWithLabel } from "../../../../components/spinner/SpinnerWithLabel"; import { StyledTabs } from "../../../../components/tabs/StyledTabs"; import { TransactionDetailsTabs } from "../../../transactions/TransactionDetailsTabs/TransactionDetailsTabs"; +import { ScriptOutcome, TransactionOutcome } from "../../core/core-types"; -export function InteractionOutcome(): ReactElement { +export function InteractionOutcomeDisplay(): ReactElement { const { outcome } = useInteractionOutcomeManager(); return ( <div className={classes.root}> - {outcome?.script && <ScriptOutcome outcome={outcome.script} />} + {outcome?.script && <ScriptOutcomeDisplay outcome={outcome.script} />} {outcome?.transaction && ( - <TransactionOutcome outcome={outcome.transaction} /> + <TransactionOutcomeDisplay outcome={outcome.transaction} /> )} {!outcome && ( <div className={classes.emptyStateWrapper}> @@ -97,7 +94,7 @@ function EmptyState() { } } -function TransactionOutcome(props: { outcome: FlowTransactionOutcome }) { +function TransactionOutcomeDisplay(props: { outcome: TransactionOutcome }) { const { outcome } = props; const { data } = useGetTransaction(outcome.transactionId); @@ -119,7 +116,7 @@ function TransactionOutcome(props: { outcome: FlowTransactionOutcome }) { ); } -function ScriptOutcome(props: { outcome: FlowScriptOutcome }) { +function ScriptOutcomeDisplay(props: { outcome: ScriptOutcome }) { const { result, error } = props.outcome; const resultTabId = "result"; const errorTabId = "result"; diff --git a/frontend/src/modules/interactions/components/InteractionTemplates/InteractionTemplates.tsx b/frontend/src/modules/interactions/components/InteractionTemplates/InteractionTemplates.tsx index 293fa019..b8080857 100644 --- a/frontend/src/modules/interactions/components/InteractionTemplates/InteractionTemplates.tsx +++ b/frontend/src/modules/interactions/components/InteractionTemplates/InteractionTemplates.tsx @@ -22,7 +22,7 @@ export function InteractionTemplates(): ReactElement { function StoredTemplates() { const { showDialog } = useConfirmDialog(); const [searchTerm, setSearchTerm] = useState(""); - const { forkTemplate, focusedDefinition } = useInteractionRegistry(); + const { create, focusedDefinition, setFocused } = useInteractionRegistry(); const { templates, removeTemplate } = useTemplatesRegistry(); const filteredTemplates = useMemo(() => { if (searchTerm === "") { @@ -51,7 +51,10 @@ function StoredTemplates() { {filteredAndSortedTemplates.map((template) => ( <div key={template.id} - onClick={() => forkTemplate(template)} + onClick={() => { + const createdInteraction = create(template); + setFocused(createdInteraction.id); + }} className={classNames(classes.item, { [classes.focusedItem]: focusedDefinition?.id === template.id, })} diff --git a/frontend/src/modules/interactions/contexts/definition.context.tsx b/frontend/src/modules/interactions/contexts/definition.context.tsx index 638a71d8..792de358 100644 --- a/frontend/src/modules/interactions/contexts/definition.context.tsx +++ b/frontend/src/modules/interactions/contexts/definition.context.tsx @@ -4,12 +4,10 @@ import React, { ReactNode, useContext, } from "react"; -import { - InteractionDefinition, - useInteractionRegistry, -} from "./interaction-registry.context"; +import { useInteractionRegistry } from "./interaction-registry.context"; import { useGetParsedInteraction } from "../../../hooks/use-api"; import { FclValue, Interaction } from "@flowser/shared"; +import { InteractionDefinition } from "../core/core-types"; type InteractionDefinitionManager = InteractionParameterBuilder & { isParsing: boolean; @@ -34,10 +32,7 @@ export function InteractionDefinitionManagerProvider(props: { }): ReactElement { const { definition } = props; const { update } = useInteractionRegistry(); - const { data, isLoading } = useGetParsedInteraction({ - id: definition.id, - sourceCode: definition.code, - }); + const { data, isLoading } = useGetParsedInteraction(definition); const fclValuesByIdentifier = definition.fclValuesByIdentifier; function partialUpdate(newDefinition: Partial<InteractionDefinition>) { diff --git a/frontend/src/modules/interactions/contexts/interaction-registry.context.tsx b/frontend/src/modules/interactions/contexts/interaction-registry.context.tsx index a9c98166..d2ded59e 100644 --- a/frontend/src/modules/interactions/contexts/interaction-registry.context.tsx +++ b/frontend/src/modules/interactions/contexts/interaction-registry.context.tsx @@ -6,57 +6,21 @@ import React, { useMemo, useState, } from "react"; -import { FclValueLookupByIdentifier } from "./definition.context"; -import { InteractionTemplate } from "@flowser/shared"; +import { InteractionDefinition } from "../core/core-types"; +import { InteractionUtils } from "../core/core-utils"; + +type CreateInteractionDefinition = Omit< + InteractionDefinition, + "id" | "createdDate" | "updatedDate" +>; type InteractionsRegistry = { definitions: InteractionDefinition[]; focusedDefinition: InteractionDefinition | undefined; + create: (interaction: CreateInteractionDefinition) => InteractionDefinition; update: (interaction: InteractionDefinition) => void; - create: (interaction: InteractionDefinition) => void; remove: (interactionId: string) => void; setFocused: (interactionId: string) => void; - forkTemplate: (template: InteractionDefinitionTemplate) => void; -}; - -export type CoreInteractionDefinition = Omit< - InteractionTemplate, - "source" | "createdDate" | "updatedDate" ->; - -export type InteractionDefinition = CoreInteractionDefinition & { - fclValuesByIdentifier: FclValueLookupByIdentifier; - initialOutcome: FlowInteractionOutcome | undefined; - transactionOptions: TransactionOptions | undefined; -}; - -export type InteractionDefinitionTemplate = CoreInteractionDefinition & { - fclValuesByIdentifier: FclValueLookupByIdentifier; - transactionOptions: TransactionOptions | undefined; - createdDate: Date; - updatedDate: Date; - isMutable: boolean; -}; - -export type TransactionOptions = { - authorizerAddresses: string[]; - proposerAddress: string; - payerAddress: string; -}; - -export type FlowTransactionOutcome = { - transactionId?: string; - error?: string; -}; - -export type FlowScriptOutcome = { - result?: unknown; - error?: string; -}; - -export type FlowInteractionOutcome = { - transaction?: FlowTransactionOutcome; - script?: FlowScriptOutcome; }; const Context = createContext<InteractionsRegistry>(undefined as never); @@ -65,8 +29,8 @@ export function InteractionRegistryProvider(props: { children: React.ReactNode; }): ReactElement { const defaultInteraction: InteractionDefinition = { - name: "Your first interaction", id: crypto.randomUUID(), + name: "Your first interaction", code: "", fclValuesByIdentifier: new Map(), initialOutcome: undefined, @@ -75,13 +39,15 @@ export function InteractionRegistryProvider(props: { proposerAddress: "0xf8d6e0586b0a20c7", payerAddress: "0xf8d6e0586b0a20c7", }, + createdDate: new Date(), + updatedDate: new Date(), }; const [definitions, setDefinitions] = useState<InteractionDefinition[]>([ defaultInteraction, ]); const [focusedInteractionId, setFocusedInteractionId] = useState< string | undefined - >(definitions?.[0]?.id); + >(); const focusedDefinition = useMemo( () => definitions.find((definition) => definition.id === focusedInteractionId), @@ -97,24 +63,6 @@ export function InteractionRegistryProvider(props: { } }, [definitions]); - function forkTemplate(template: InteractionDefinitionTemplate) { - const definition: InteractionDefinition = { - id: template.id, - name: template.name, - code: template.code, - fclValuesByIdentifier: template.fclValuesByIdentifier, - transactionOptions: template.transactionOptions, - initialOutcome: undefined, - }; - const isAlreadyOpen = definitions.some( - (definition) => definition.id === template.id - ); - if (!isAlreadyOpen) { - setDefinitions([...definitions, definition]); - setFocusedInteractionId(definition.id); - } - } - function update(updatedInteraction: InteractionDefinition) { setDefinitions((interactions) => interactions.map((existingInteraction) => { @@ -137,12 +85,23 @@ export function InteractionRegistryProvider(props: { } } - function create(newInteraction: InteractionDefinition) { - const isExisting = definitions.some( - (definition) => definition.id === newInteraction.id + function create( + newPartialInteraction: CreateInteractionDefinition + ): InteractionDefinition { + const existingInteraction = definitions.find((definition) => + InteractionUtils.areEqual(newPartialInteraction, definition) ); - if (!isExisting) { - setDefinitions([...definitions, newInteraction]); + if (existingInteraction) { + return existingInteraction; + } else { + const newInteractionDefinition: InteractionDefinition = { + id: crypto.randomUUID(), + ...newPartialInteraction, + createdDate: new Date(), + updatedDate: new Date(), + }; + setDefinitions([...definitions, newInteractionDefinition]); + return newInteractionDefinition; } } @@ -152,7 +111,6 @@ export function InteractionRegistryProvider(props: { definitions, focusedDefinition, setFocused: setFocusedInteractionId, - forkTemplate, remove, create, update, diff --git a/frontend/src/modules/interactions/contexts/outcome.context.tsx b/frontend/src/modules/interactions/contexts/outcome.context.tsx index 9b97c0eb..dbfad4ee 100644 --- a/frontend/src/modules/interactions/contexts/outcome.context.tsx +++ b/frontend/src/modules/interactions/contexts/outcome.context.tsx @@ -1,8 +1,4 @@ import { ServiceRegistry } from "../../../services/service-registry"; -import { - FlowInteractionOutcome, - InteractionDefinition, -} from "./interaction-registry.context"; import React, { createContext, ReactElement, @@ -23,9 +19,10 @@ import { import { useInteractionDefinitionManager } from "./definition.context"; import toast from "react-hot-toast"; import { useQuery } from "react-query"; +import { InteractionDefinition, InteractionOutcome } from "../core/core-types"; type InteractionOutcomeManager = { - outcome: FlowInteractionOutcome | undefined; + outcome: InteractionOutcome | undefined; execute: () => Promise<void>; }; @@ -76,7 +73,7 @@ export function InteractionOutcomeManagerProvider(props: { async function executeTransaction( definition: InteractionDefinition - ): Promise<FlowInteractionOutcome | undefined> { + ): Promise<InteractionOutcome | undefined> { const { transactionOptions } = definition; if (!transactionOptions) { throw new Error("Transaction options must be set"); diff --git a/frontend/src/modules/interactions/contexts/templates.context.tsx b/frontend/src/modules/interactions/contexts/templates.context.tsx index bb7c2105..d6024193 100644 --- a/frontend/src/modules/interactions/contexts/templates.context.tsx +++ b/frontend/src/modules/interactions/contexts/templates.context.tsx @@ -1,12 +1,8 @@ import React, { createContext, ReactElement, useContext, useMemo } from "react"; -import { FclValueLookupByIdentifier } from "./definition.context"; import { useLocalStorage } from "usehooks-ts"; import { FclValue } from "@flowser/shared"; import { useGetPollingFlowInteractionTemplates } from "../../../hooks/use-api"; -import { - CoreInteractionDefinition, - InteractionDefinition, -} from "./interaction-registry.context"; +import { InteractionDefinition } from "../core/core-types"; type InteractionTemplatesRegistry = { templates: InteractionDefinitionTemplate[]; @@ -14,16 +10,14 @@ type InteractionTemplatesRegistry = { removeTemplate: (template: InteractionDefinitionTemplate) => void; }; -export type InteractionDefinitionTemplate = CoreInteractionDefinition & { - fclValuesByIdentifier: FclValueLookupByIdentifier; - transactionOptions: TransactionOptions | undefined; - createdDate: Date; - updatedDate: Date; +export type InteractionDefinitionTemplate = InteractionDefinition & { isMutable: boolean; }; // Internal structure that's persisted in local storage. -type RawInteractionDefinitionTemplate = CoreInteractionDefinition & { +type RawInteractionDefinitionTemplate = { + name: string; + code: string; fclValuesByIdentifier: Record<string, FclValue>; transactionOptions: TransactionOptions | undefined; createdDate: string; @@ -50,7 +44,11 @@ export function TemplatesRegistryProvider(props: { () => [ ...customTemplates.map( (template): InteractionDefinitionTemplate => ({ - ...template, + id: crypto.randomUUID(), + name: template.name, + code: template.code, + transactionOptions: undefined, + initialOutcome: undefined, createdDate: new Date(template.createdDate), updatedDate: new Date(template.updatedDate), fclValuesByIdentifier: new Map( @@ -61,12 +59,15 @@ export function TemplatesRegistryProvider(props: { ), ...(projectTemplatesData?.templates?.map( (template): InteractionDefinitionTemplate => ({ - ...template, - isMutable: false, + id: crypto.randomUUID(), + name: template.name, + code: template.code, transactionOptions: undefined, + initialOutcome: undefined, fclValuesByIdentifier: new Map(), createdDate: new Date(template.createdDate), updatedDate: new Date(template.updatedDate), + isMutable: false, }) ) ?? []), ], @@ -75,7 +76,6 @@ export function TemplatesRegistryProvider(props: { function saveTemplate(interaction: InteractionDefinition) { const newTemplate: RawInteractionDefinitionTemplate = { - id: interaction.name, name: interaction.name, code: interaction.code, fclValuesByIdentifier: Object.fromEntries( diff --git a/frontend/src/modules/interactions/core/core-types.ts b/frontend/src/modules/interactions/core/core-types.ts new file mode 100644 index 00000000..55761aad --- /dev/null +++ b/frontend/src/modules/interactions/core/core-types.ts @@ -0,0 +1,35 @@ +import { FclValue } from "@flowser/shared"; + +export type InteractionDefinition = { + id: string; + name: string; + code: string; + fclValuesByIdentifier: FclValueLookupByIdentifier; + initialOutcome: InteractionOutcome | undefined; + transactionOptions: TransactionOptions | undefined; + createdDate: Date; + updatedDate: Date; +}; + +export type FclValueLookupByIdentifier = Map<string, FclValue>; + +export type TransactionOptions = { + authorizerAddresses: string[]; + proposerAddress: string; + payerAddress: string; +}; + +export type InteractionOutcome = { + transaction?: TransactionOutcome; + script?: ScriptOutcome; +}; + +export type TransactionOutcome = { + transactionId?: string; + error?: string; +}; + +export type ScriptOutcome = { + result?: unknown; + error?: string; +}; diff --git a/frontend/src/modules/interactions/core/core-utils.ts b/frontend/src/modules/interactions/core/core-utils.ts new file mode 100644 index 00000000..fa9e3179 --- /dev/null +++ b/frontend/src/modules/interactions/core/core-utils.ts @@ -0,0 +1,31 @@ +import { InteractionDefinition } from "./core-types"; + +type IdentifiableInteractionDefinition = Pick<InteractionDefinition, "code">; + +export class InteractionUtils { + // Interaction structures that represent the same logical interaction, + // should be treated as the same entity. + // See how this is handled within FLIX standard: + // https://github.com/onflow/flips/blob/main/application/20220503-interaction-templates.md#data-structure-serialization--identifier-generation + static areEqual( + a: IdentifiableInteractionDefinition, + b: IdentifiableInteractionDefinition + ): boolean { + return ( + this.normalizeCadenceCode(a.code) === this.normalizeCadenceCode(b.code) + ); + } + + static normalizeCadenceCode(code: string): string { + // Ignore imports for comparison, + // since those can differ due to address replacement. + // See: https://developers.flow.com/tooling/fcl-js/api#address-replacement + const strippedImports = code + .split("\n") + .filter((line) => !line.startsWith("import")) + .join("\n"); + + // Replace all whitespace and newlines + return strippedImports.replaceAll(/[\n\t ]/g, ""); + } +} diff --git a/frontend/src/modules/interactions/hooks/use-transaction-name.ts b/frontend/src/modules/interactions/hooks/use-transaction-name.ts index b8f502a9..36b16132 100644 --- a/frontend/src/modules/interactions/hooks/use-transaction-name.ts +++ b/frontend/src/modules/interactions/hooks/use-transaction-name.ts @@ -2,6 +2,7 @@ import { useInteractionRegistry } from "../contexts/interaction-registry.context import { useMemo } from "react"; import { Transaction } from "@flowser/shared"; import { useTemplatesRegistry } from "../contexts/templates.context"; +import { InteractionUtils } from "../core/core-utils"; type UseInteractionNameProps = { transaction: Transaction | undefined; @@ -53,7 +54,10 @@ const hardcodedTemplates: [string, TransactionKind][] = [ ]; const transactionKindBySource = new Map<string, TransactionKind>( - hardcodedTemplates.map((entry) => [sanitizeCadenceSource(entry[0]), entry[1]]) + hardcodedTemplates.map((entry) => [ + InteractionUtils.normalizeCadenceCode(entry[0]), + entry[1], + ]) ); export function useTransactionName( @@ -68,11 +72,14 @@ export function useTransactionName( return undefined; } - const sanitizedTargetCode = sanitizeCadenceSource(transaction.script); + const sanitizedTargetCode = InteractionUtils.normalizeCadenceCode( + transaction.script + ); const matchingTemplateName = [...templates, ...definitions].find( (template) => template.code && - sanitizeCadenceSource(template.code) === sanitizedTargetCode + InteractionUtils.normalizeCadenceCode(template.code) === + sanitizedTargetCode )?.name; return matchingTemplateName ?? getDynamicName(transaction); @@ -81,7 +88,7 @@ export function useTransactionName( function getDynamicName(transaction: Transaction) { const kind = transactionKindBySource.get( - sanitizeCadenceSource(transaction.script) + InteractionUtils.normalizeCadenceCode(transaction.script) ); switch (kind) { @@ -106,16 +113,3 @@ function getArgumentValueById(transaction: Transaction, id: string) { ?.valueAsJson ?? "" ); } - -function sanitizeCadenceSource(code: string) { - // Ignore imports for comparison, - // since those can differ due to address replacement. - // See: https://developers.flow.com/tooling/fcl-js/api#address-replacement - const strippedImports = code - .split("\n") - .filter((line) => !line.startsWith("import")) - .join("\n"); - - // Replace all whitespace and newlines - return strippedImports.replaceAll(/[\n\t ]/g, ""); -} diff --git a/frontend/src/modules/transactions/TransactionSource/TransactionSource.tsx b/frontend/src/modules/transactions/TransactionSource/TransactionSource.tsx index dcea995c..2be3f303 100644 --- a/frontend/src/modules/transactions/TransactionSource/TransactionSource.tsx +++ b/frontend/src/modules/transactions/TransactionSource/TransactionSource.tsx @@ -43,7 +43,7 @@ export const TransactionSource: FC<TransactionSourceProps> = ({ className={classes.interactLink} to="/interactions" onClick={() => { - create({ + const createdInteraction = create({ code: transaction.script, fclValuesByIdentifier: new Map( transaction.arguments.map((arg) => [ @@ -51,7 +51,6 @@ export const TransactionSource: FC<TransactionSourceProps> = ({ JSON.parse(arg.valueAsJson), ]) ), - id: transaction.id, initialOutcome: { transaction: { transactionId: transaction.id, @@ -65,7 +64,7 @@ export const TransactionSource: FC<TransactionSourceProps> = ({ proposerAddress: transaction.proposalKey!.address, }, }); - setFocused(transaction.id); + setFocused(createdInteraction.id); }} > <FlowserIcon.CursorClick /> Interact From 9970cf3bd5c50169a4664e823fc5ca57aa79eeeb Mon Sep 17 00:00:00 2001 From: Bart <bartolomej.kozorog@gmail.com> Date: Sun, 10 Sep 2023 20:42:48 +0200 Subject: [PATCH 09/37] fix empty interaction creation --- .../modules/interactions/InteractionsPage.tsx | 24 ++++++++++++------- .../contexts/interaction-registry.context.tsx | 14 ++++++++--- 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/frontend/src/modules/interactions/InteractionsPage.tsx b/frontend/src/modules/interactions/InteractionsPage.tsx index 83a40a66..6eea82e7 100644 --- a/frontend/src/modules/interactions/InteractionsPage.tsx +++ b/frontend/src/modules/interactions/InteractionsPage.tsx @@ -65,15 +65,21 @@ export function InteractionsPage(): ReactElement { onChangeTab={(tab) => setFocused(tab.id)} tabs={openEditorTabs} onClose={(tab) => remove(tab.id)} - onAddNew={() => - create({ - name: "New interaction", - code: "", - fclValuesByIdentifier: new Map(), - transactionOptions: undefined, - initialOutcome: undefined, - }) - } + onAddNew={() => { + const createdInteraction = create( + { + name: "New interaction", + code: "", + fclValuesByIdentifier: new Map(), + transactionOptions: undefined, + initialOutcome: undefined, + }, + { + allowDuplicates: true, + } + ); + setFocused(createdInteraction.id); + }} /> </div> ); diff --git a/frontend/src/modules/interactions/contexts/interaction-registry.context.tsx b/frontend/src/modules/interactions/contexts/interaction-registry.context.tsx index d2ded59e..2ce6723f 100644 --- a/frontend/src/modules/interactions/contexts/interaction-registry.context.tsx +++ b/frontend/src/modules/interactions/contexts/interaction-registry.context.tsx @@ -14,10 +14,17 @@ type CreateInteractionDefinition = Omit< "id" | "createdDate" | "updatedDate" >; +type CreateInteractionOptions = { + allowDuplicates: boolean; +}; + type InteractionsRegistry = { definitions: InteractionDefinition[]; focusedDefinition: InteractionDefinition | undefined; - create: (interaction: CreateInteractionDefinition) => InteractionDefinition; + create: ( + interaction: CreateInteractionDefinition, + options?: CreateInteractionOptions + ) => InteractionDefinition; update: (interaction: InteractionDefinition) => void; remove: (interactionId: string) => void; setFocused: (interactionId: string) => void; @@ -86,12 +93,13 @@ export function InteractionRegistryProvider(props: { } function create( - newPartialInteraction: CreateInteractionDefinition + newPartialInteraction: CreateInteractionDefinition, + options?: CreateInteractionOptions ): InteractionDefinition { const existingInteraction = definitions.find((definition) => InteractionUtils.areEqual(newPartialInteraction, definition) ); - if (existingInteraction) { + if (existingInteraction && !options?.allowDuplicates) { return existingInteraction; } else { const newInteractionDefinition: InteractionDefinition = { From 06c50208850a54f33288d449b2f74826e2db758c Mon Sep 17 00:00:00 2001 From: Bart <bartolomej.kozorog@gmail.com> Date: Sun, 10 Sep 2023 20:46:41 +0200 Subject: [PATCH 10/37] fix address builder overflow --- .../ValueBuilder/AddressBuilder/AddressBuilder.module.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/modules/interactions/components/ValueBuilder/AddressBuilder/AddressBuilder.module.scss b/frontend/src/modules/interactions/components/ValueBuilder/AddressBuilder/AddressBuilder.module.scss index 30a5ab9d..91798587 100644 --- a/frontend/src/modules/interactions/components/ValueBuilder/AddressBuilder/AddressBuilder.module.scss +++ b/frontend/src/modules/interactions/components/ValueBuilder/AddressBuilder/AddressBuilder.module.scss @@ -7,7 +7,6 @@ $avatar-size: 30px; .root { width: 100%; - overflow-x: scroll; background: $gray-80; border-radius: $border-radius-input; @@ -15,6 +14,7 @@ $avatar-size: 30px; .innerWrapper { display: flex; + flex-wrap: wrap; column-gap: $spacing-base; row-gap: $spacing-base; padding: $spacing-base; From ca6ec3f10d9510bf80a795419befc6e2a6b19f52 Mon Sep 17 00:00:00 2001 From: Bart <bartolomej.kozorog@gmail.com> Date: Sun, 10 Sep 2023 20:50:34 +0200 Subject: [PATCH 11/37] temporarily only show settings for mutable templates --- .../InteractionTemplates/InteractionTemplates.tsx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/frontend/src/modules/interactions/components/InteractionTemplates/InteractionTemplates.tsx b/frontend/src/modules/interactions/components/InteractionTemplates/InteractionTemplates.tsx index b8080857..c65ff6c6 100644 --- a/frontend/src/modules/interactions/components/InteractionTemplates/InteractionTemplates.tsx +++ b/frontend/src/modules/interactions/components/InteractionTemplates/InteractionTemplates.tsx @@ -9,6 +9,7 @@ import { useConfirmDialog } from "../../../../contexts/confirm-dialog.context"; import classNames from "classnames"; import { InteractionLabel } from "../InteractionLabel/InteractionLabel"; import { useTemplatesRegistry } from "../../contexts/templates.context"; +import { InteractionUtils } from "../../core/core-utils"; export function InteractionTemplates(): ReactElement { return ( @@ -89,12 +90,20 @@ function StoredTemplates() { function FocusedDefinitionSettings() { const { focusedDefinition, update } = useInteractionRegistry(); - const { saveTemplate } = useTemplatesRegistry(); + const { templates, saveTemplate } = useTemplatesRegistry(); if (!focusedDefinition) { return null; } + const correspondingTemplate = templates.find((template) => + InteractionUtils.areEqual(template, focusedDefinition) + ); + + if (!correspondingTemplate?.isMutable) { + return null; + } + return ( <div className={classes.focusedTemplate}> <Input From 032ee93f705c73454da2159335901f08ace7bc75 Mon Sep 17 00:00:00 2001 From: Bart <bartolomej.kozorog@gmail.com> Date: Sun, 10 Sep 2023 20:55:12 +0200 Subject: [PATCH 12/37] fixed interaction tab width --- frontend/src/modules/interactions/InteractionsPage.module.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/modules/interactions/InteractionsPage.module.scss b/frontend/src/modules/interactions/InteractionsPage.module.scss index f7ec3dd8..b83a1c87 100644 --- a/frontend/src/modules/interactions/InteractionsPage.module.scss +++ b/frontend/src/modules/interactions/InteractionsPage.module.scss @@ -18,6 +18,7 @@ overflow-x: scroll; .interactionTab { max-width: 200px; + min-width: 200px; margin-right: $spacing-s; .label { text-align: left; From 291ad00b425d08c934771351330e466ab362ff97 Mon Sep 17 00:00:00 2001 From: Bart <bartolomej.kozorog@gmail.com> Date: Sun, 10 Sep 2023 21:00:43 +0200 Subject: [PATCH 13/37] fix transaction source overflow --- .../transactions/TransactionSource/TransactionSource.module.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/modules/transactions/TransactionSource/TransactionSource.module.scss b/frontend/src/modules/transactions/TransactionSource/TransactionSource.module.scss index 7023a9b3..5271bd33 100644 --- a/frontend/src/modules/transactions/TransactionSource/TransactionSource.module.scss +++ b/frontend/src/modules/transactions/TransactionSource/TransactionSource.module.scss @@ -33,5 +33,6 @@ .right { flex: 3; + overflow: scroll; } } From c759d9918d20dc52866abbc79716736b4bef5952 Mon Sep 17 00:00:00 2001 From: Bart <bartolomej.kozorog@gmail.com> Date: Sun, 10 Sep 2023 22:59:03 +0200 Subject: [PATCH 14/37] fix open transaction from history --- .../components/InteractionHistory/InteractionHistory.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/modules/interactions/components/InteractionHistory/InteractionHistory.tsx b/frontend/src/modules/interactions/components/InteractionHistory/InteractionHistory.tsx index 203ef3a7..aa06a377 100644 --- a/frontend/src/modules/interactions/components/InteractionHistory/InteractionHistory.tsx +++ b/frontend/src/modules/interactions/components/InteractionHistory/InteractionHistory.tsx @@ -60,7 +60,7 @@ function BlockItem(props: BlockItemProps) { if (!firstTransaction?.proposalKey) { return; } - create({ + const createdInteraction = create({ name: transactionName ?? `Tx from block #${block.height}`, code: firstTransaction.script, fclValuesByIdentifier: new Map( @@ -80,7 +80,7 @@ function BlockItem(props: BlockItemProps) { authorizerAddresses: firstTransaction.authorizers, }, }); - setFocused(block.id); + setFocused(createdInteraction.id); } return ( From 59a1c8af158269dd338fc75a859afa87f6c27bf0 Mon Sep 17 00:00:00 2001 From: Bart <bartolomej.kozorog@gmail.com> Date: Sun, 10 Sep 2023 23:11:48 +0200 Subject: [PATCH 15/37] make transaction error handling more consistent --- .../InteractionOutcomeDisplay/InteractionOutcomeDisplay.tsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/frontend/src/modules/interactions/components/InteractionOutcomeDisplay/InteractionOutcomeDisplay.tsx b/frontend/src/modules/interactions/components/InteractionOutcomeDisplay/InteractionOutcomeDisplay.tsx index 079e5347..aa112c67 100644 --- a/frontend/src/modules/interactions/components/InteractionOutcomeDisplay/InteractionOutcomeDisplay.tsx +++ b/frontend/src/modules/interactions/components/InteractionOutcomeDisplay/InteractionOutcomeDisplay.tsx @@ -98,10 +98,6 @@ function TransactionOutcomeDisplay(props: { outcome: TransactionOutcome }) { const { outcome } = props; const { data } = useGetTransaction(outcome.transactionId); - if (outcome.error) { - return <ScriptError errorMessage={outcome.error} />; - } - if (!data?.transaction) { return <SpinnerWithLabel label="Executing" />; } From b77d6aabd91b161f95a8241a959bd3dc1287567d Mon Sep 17 00:00:00 2001 From: Bart <bartolomej.kozorog@gmail.com> Date: Sun, 10 Sep 2023 23:19:36 +0200 Subject: [PATCH 16/37] fix transaction name when opened from source component --- .../transactions/TransactionSource/TransactionSource.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frontend/src/modules/transactions/TransactionSource/TransactionSource.tsx b/frontend/src/modules/transactions/TransactionSource/TransactionSource.tsx index 2be3f303..9eaa13b5 100644 --- a/frontend/src/modules/transactions/TransactionSource/TransactionSource.tsx +++ b/frontend/src/modules/transactions/TransactionSource/TransactionSource.tsx @@ -8,6 +8,7 @@ import { SizedBox } from "../../../components/sized-box/SizedBox"; import { ProjectLink } from "../../../components/links/ProjectLink"; import { FlowserIcon } from "../../../components/icons/Icons"; import { useInteractionRegistry } from "../../interactions/contexts/interaction-registry.context"; +import { useTransactionName } from "../../interactions/hooks/use-transaction-name"; type TransactionSourceProps = { transaction: Transaction; @@ -17,6 +18,8 @@ export const TransactionSource: FC<TransactionSourceProps> = ({ transaction, }) => { const { setFocused, create } = useInteractionRegistry(); + const transactionName = useTransactionName({ transaction }); + return ( <Card className={classes.root}> {transaction.arguments.length > 0 && ( @@ -44,6 +47,7 @@ export const TransactionSource: FC<TransactionSourceProps> = ({ to="/interactions" onClick={() => { const createdInteraction = create({ + name: transactionName ?? "Unknown", code: transaction.script, fclValuesByIdentifier: new Map( transaction.arguments.map((arg) => [ @@ -57,7 +61,6 @@ export const TransactionSource: FC<TransactionSourceProps> = ({ error: transaction.status?.errorMessage, }, }, - name: "Test", transactionOptions: { authorizerAddresses: transaction.authorizers, payerAddress: transaction.payer, From 8ae96fde1b893425da1af85dac51f22f9935ad09 Mon Sep 17 00:00:00 2001 From: Bart <bartolomej.kozorog@gmail.com> Date: Mon, 11 Sep 2023 08:33:33 +0200 Subject: [PATCH 17/37] fix layout body scrollbars --- frontend/src/components/layout/Layout.module.scss | 3 +++ 1 file changed, 3 insertions(+) diff --git a/frontend/src/components/layout/Layout.module.scss b/frontend/src/components/layout/Layout.module.scss index c5045443..74e5d1db 100644 --- a/frontend/src/components/layout/Layout.module.scss +++ b/frontend/src/components/layout/Layout.module.scss @@ -1,5 +1,6 @@ @import "styles/spacings"; @import "styles/colors"; +@import "styles/scrollbars"; $side-nav-width: 80px; @@ -34,6 +35,8 @@ $side-nav-width: 80px; display: flex; flex-direction: column; flex: 1; + + @include hiddenScrollbars(); } .bodyWithBorderSpacing { From 6fbade38f2236569b502e473df9d42a00f010364 Mon Sep 17 00:00:00 2001 From: Bart <bartolomej.kozorog@gmail.com> Date: Mon, 11 Sep 2023 08:36:39 +0200 Subject: [PATCH 18/37] fix contract details link --- .../modules/contracts/ContractDetails/ContractDetails.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/modules/contracts/ContractDetails/ContractDetails.tsx b/frontend/src/modules/contracts/ContractDetails/ContractDetails.tsx index 3010d934..7beda825 100644 --- a/frontend/src/modules/contracts/ContractDetails/ContractDetails.tsx +++ b/frontend/src/modules/contracts/ContractDetails/ContractDetails.tsx @@ -1,5 +1,4 @@ import React, { FunctionComponent } from "react"; -import { NavLink } from "react-router-dom"; import FullScreenLoading from "../../../components/fullscreen-loading/FullScreenLoading"; import { useGetContract } from "../../../hooks/use-api"; import classes from "./ContractDetails.module.scss"; @@ -10,6 +9,7 @@ import { import { SizedBox } from "../../../components/sized-box/SizedBox"; import { CadenceEditor } from "../../../components/cadence-editor/CadenceEditor"; import { DateDisplay } from "../../../components/time/DateDisplay/DateDisplay"; +import { ProjectLink } from "../../../components/links/ProjectLink"; type ContractDetailsProps = { contractId: string; @@ -35,9 +35,9 @@ export const ContractDetails: FunctionComponent<ContractDetailsProps> = ( { label: "Account", value: ( - <NavLink to={`/accounts/details/${contract.accountAddress}`}> + <ProjectLink to={`/accounts/${contract.accountAddress}`}> {contract.accountAddress} - </NavLink> + </ProjectLink> ), }, { From 17aab53539e0fe189c651186f43ac23b5508e7c0 Mon Sep 17 00:00:00 2001 From: Bart <bartolomej.kozorog@gmail.com> Date: Mon, 11 Sep 2023 08:48:31 +0200 Subject: [PATCH 19/37] fix basic layout --- frontend/src/App.tsx | 17 ++++--- .../components/breadcrumbs/Breadcrumbs.tsx | 4 +- frontend/src/components/icons/Icons.tsx | 2 + .../BasicLayout/BasicLayout.module.scss | 10 ++++ .../layout/BasicLayout/BasicLayout.tsx | 14 ++++++ frontend/src/components/layout/Layout.tsx | 48 ------------------- .../ProjectLayout.module.scss} | 0 .../layout/ProjectLayout/ProjectLayout.tsx | 32 +++++++++++++ .../AccountStorage/AccountStorage.tsx | 2 +- .../ExecutionSettings/ExecutionSettings.tsx | 5 -- 10 files changed, 72 insertions(+), 62 deletions(-) create mode 100644 frontend/src/components/layout/BasicLayout/BasicLayout.module.scss create mode 100644 frontend/src/components/layout/BasicLayout/BasicLayout.tsx delete mode 100644 frontend/src/components/layout/Layout.tsx rename frontend/src/components/layout/{Layout.module.scss => ProjectLayout/ProjectLayout.module.scss} (100%) create mode 100644 frontend/src/components/layout/ProjectLayout/ProjectLayout.tsx diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 80d84353..665aa48a 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -10,7 +10,7 @@ import { useParams, } from "react-router-dom"; import { Toaster } from "react-hot-toast"; -import { BackButtonLayout, ProjectLayout } from "./components/layout/Layout"; +import { ProjectLayout } from "./components/layout/ProjectLayout/ProjectLayout"; import "./App.scss"; import { toastOptions } from "./config/toast"; @@ -49,9 +49,8 @@ import { ContractsTable } from "./modules/contracts/ContractsTable"; import { ContractDetails } from "./modules/contracts/ContractDetails/ContractDetails"; import { EventsTable } from "./modules/events/EventsTable/EventsTable"; import { createCrumbHandle } from "./components/breadcrumbs/Breadcrumbs"; -import { - TemplatesRegistryProvider -} from './modules/interactions/contexts/templates.context'; +import { TemplatesRegistryProvider } from "./modules/interactions/contexts/templates.context"; +import { BasicLayout } from "./components/layout/BasicLayout/BasicLayout"; const BrowserRouterEvents = (props: { children: ReactNode }): ReactElement => { const location = useLocation(); @@ -122,6 +121,9 @@ const routes: RouteObject[] = [ </BrowserRouterEvents> </ProjectProvider> ), + handle: createCrumbHandle({ + crumbName: "Projects", + }), children: [ { index: true, @@ -130,10 +132,13 @@ const routes: RouteObject[] = [ { path: "create", element: ( - <BackButtonLayout> + <BasicLayout> <ProjectSettingsPage /> - </BackButtonLayout> + </BasicLayout> ), + handle: createCrumbHandle({ + crumbName: "Create", + }), }, { path: ":projectId", diff --git a/frontend/src/components/breadcrumbs/Breadcrumbs.tsx b/frontend/src/components/breadcrumbs/Breadcrumbs.tsx index 8a5edfb5..1f223406 100644 --- a/frontend/src/components/breadcrumbs/Breadcrumbs.tsx +++ b/frontend/src/components/breadcrumbs/Breadcrumbs.tsx @@ -1,8 +1,8 @@ import React, { ReactElement } from "react"; import { NavLink, useMatches, useNavigate } from "react-router-dom"; import classes from "./Breadcrumbs.module.scss"; -import { ReactComponent as IconBackButton } from "../../assets/icons/back-button.svg"; import classNames from "classnames"; +import { FlowserIcon } from "../icons/Icons"; type BreadcrumbsProps = { className?: string; @@ -54,7 +54,7 @@ export function Breadcrumbs(props: BreadcrumbsProps): ReactElement | null { <div className={classNames(classes.root, props.className)}> {breadcrumbs.length > 1 && ( <div className={classes.backButtonWrapper} onClick={() => navigate(-1)}> - <IconBackButton className={classes.backButton} /> + <FlowserIcon.Back className={classes.backButton} /> </div> )} <div className={classes.breadcrumbs}> diff --git a/frontend/src/components/icons/Icons.tsx b/frontend/src/components/icons/Icons.tsx index 1c6c3e7d..adf3acba 100644 --- a/frontend/src/components/icons/Icons.tsx +++ b/frontend/src/components/icons/Icons.tsx @@ -20,8 +20,10 @@ import { ReactComponent as Exit } from "../../assets/icons/exit.svg"; import { ReactComponent as Restart } from "../../assets/icons/restart.svg"; import { ReactComponent as Open } from "../../assets/icons/open.svg"; import { ReactComponent as Share } from "../../assets/icons/share.svg"; +import { ReactComponent as Back } from "../../assets/icons/back-button.svg"; export const FlowserIcon = { + Back: Back, Account: Account, Block: Block, Contract: Contract, diff --git a/frontend/src/components/layout/BasicLayout/BasicLayout.module.scss b/frontend/src/components/layout/BasicLayout/BasicLayout.module.scss new file mode 100644 index 00000000..c1f53d45 --- /dev/null +++ b/frontend/src/components/layout/BasicLayout/BasicLayout.module.scss @@ -0,0 +1,10 @@ +@import "styles/colors"; + +.root { + .header { + position: sticky; + top: 0; + background: $gray-110; + z-index: 100; + } +} diff --git a/frontend/src/components/layout/BasicLayout/BasicLayout.tsx b/frontend/src/components/layout/BasicLayout/BasicLayout.tsx new file mode 100644 index 00000000..cd0fe4e9 --- /dev/null +++ b/frontend/src/components/layout/BasicLayout/BasicLayout.tsx @@ -0,0 +1,14 @@ +import React, { FC, ReactNode } from "react"; +import { Breadcrumbs } from "../../breadcrumbs/Breadcrumbs"; +import classes from "./BasicLayout.module.scss"; + +export const BasicLayout: FC<{ children: ReactNode }> = (props) => { + return ( + <div className={classes.root}> + <div className={classes.header}> + <Breadcrumbs className={classes.breadcrumbs} /> + </div> + {props.children} + </div> + ); +}; diff --git a/frontend/src/components/layout/Layout.tsx b/frontend/src/components/layout/Layout.tsx deleted file mode 100644 index d9f256aa..00000000 --- a/frontend/src/components/layout/Layout.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import React, { FC, FunctionComponent, ReactNode } from "react"; -import classes from "./Layout.module.scss"; -import { Logs } from "../../modules/logs/Logs"; -import { useLocation, useNavigate } from "react-router-dom"; -import { ReactComponent as IconBackButton } from "../../assets/icons/back-button.svg"; -import classNames from "classnames"; -import { Breadcrumbs } from "../breadcrumbs/Breadcrumbs"; -import { SideNavigation } from "../side-navigation/SideNavigation"; - -export const BackButtonLayout: FC<{ children: ReactNode }> = (props) => { - const navigate = useNavigate(); - return ( - <div style={{ height: "100%" }}> - <div className={classNames(classes.backButtonWrapper)}> - <IconBackButton - onClick={() => navigate(-1)} - className={classes.backButton} - /> - </div> - {props.children} - </div> - ); -}; - -export const scrollableElementId = "flowser-scroll"; - -export const ProjectLayout: FunctionComponent = ({ children }) => { - const location = useLocation(); - const showMargin = !location.pathname.endsWith("interactions"); - - return ( - <div className={classes.root}> - <SideNavigation className={classes.sideNavigation} /> - <div className={classes.mainContent}> - <Breadcrumbs className={classes.breadcrumbs} /> - <div - id={scrollableElementId} - className={classNames(classes.body, { - [classes.bodyWithBorderSpacing]: showMargin, - })} - > - {children} - </div> - <Logs className={classes.logs} /> - </div> - </div> - ); -}; diff --git a/frontend/src/components/layout/Layout.module.scss b/frontend/src/components/layout/ProjectLayout/ProjectLayout.module.scss similarity index 100% rename from frontend/src/components/layout/Layout.module.scss rename to frontend/src/components/layout/ProjectLayout/ProjectLayout.module.scss diff --git a/frontend/src/components/layout/ProjectLayout/ProjectLayout.tsx b/frontend/src/components/layout/ProjectLayout/ProjectLayout.tsx new file mode 100644 index 00000000..8039a849 --- /dev/null +++ b/frontend/src/components/layout/ProjectLayout/ProjectLayout.tsx @@ -0,0 +1,32 @@ +import React, { FunctionComponent } from "react"; +import classes from "./ProjectLayout.module.scss"; +import { Logs } from "../../../modules/logs/Logs"; +import { useLocation } from "react-router-dom"; +import classNames from "classnames"; +import { Breadcrumbs } from "../../breadcrumbs/Breadcrumbs"; +import { SideNavigation } from "../../side-navigation/SideNavigation"; + +export const scrollableElementId = "flowser-scroll"; + +export const ProjectLayout: FunctionComponent = ({ children }) => { + const location = useLocation(); + const showMargin = !location.pathname.endsWith("interactions"); + + return ( + <div className={classes.root}> + <SideNavigation className={classes.sideNavigation} /> + <div className={classes.mainContent}> + <Breadcrumbs className={classes.breadcrumbs} /> + <div + id={scrollableElementId} + className={classNames(classes.body, { + [classes.bodyWithBorderSpacing]: showMargin, + })} + > + {children} + </div> + <Logs className={classes.logs} /> + </div> + </div> + ); +}; diff --git a/frontend/src/modules/accounts/AccountStorage/AccountStorage.tsx b/frontend/src/modules/accounts/AccountStorage/AccountStorage.tsx index 93363e09..90e7ad86 100644 --- a/frontend/src/modules/accounts/AccountStorage/AccountStorage.tsx +++ b/frontend/src/modules/accounts/AccountStorage/AccountStorage.tsx @@ -9,7 +9,7 @@ import { enableDetailsIntroAnimation } from "../../../config/common"; import { InternalStorageCard } from "../InternalStorageCard/InternalStorageCard"; import classNames from "classnames"; import classes from "./AccountStorage.module.scss"; -import { scrollableElementId } from "../../../components/layout/Layout"; +import { scrollableElementId } from "../../../components/layout/ProjectLayout/ProjectLayout"; type AccountStorageProps = { account: Account; diff --git a/frontend/src/modules/interactions/components/ExecutionSettings/ExecutionSettings.tsx b/frontend/src/modules/interactions/components/ExecutionSettings/ExecutionSettings.tsx index 8b492967..d6b143b9 100644 --- a/frontend/src/modules/interactions/components/ExecutionSettings/ExecutionSettings.tsx +++ b/frontend/src/modules/interactions/components/ExecutionSettings/ExecutionSettings.tsx @@ -112,11 +112,6 @@ function EmptyInteractionHelp() { </b>{" "} are used for reading existing state from the blockchain. </p> - <SizedBox height={10} /> - <p>To learn more about Cadence, check out the resources below.</p> - <SizedBox height={10} /> - <ExternalLink href="https://developers.flow.com/cadence/intro" /> - <ExternalLink href="https://academy.ecdao.org/en/cadence-by-example" /> </div> } /> From ff4638223b4bd027bc1c221b5224888280340569 Mon Sep 17 00:00:00 2001 From: Bart <bartolomej.kozorog@gmail.com> Date: Mon, 11 Sep 2023 08:50:42 +0200 Subject: [PATCH 20/37] collapse event table data --- frontend/src/modules/events/EventsTable/EventsTable.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/modules/events/EventsTable/EventsTable.tsx b/frontend/src/modules/events/EventsTable/EventsTable.tsx index e6a1a616..81ce7d8f 100644 --- a/frontend/src/modules/events/EventsTable/EventsTable.tsx +++ b/frontend/src/modules/events/EventsTable/EventsTable.tsx @@ -67,7 +67,7 @@ const columns = [ <Value> <JsonView name="data" - collapseAtDepth={1} + collapseAtDepth={0} data={info.getValue() as Record<string, unknown>} /> </Value> From 6222a24bdcb7ef81c8750d57c9e91b5c48142af7 Mon Sep 17 00:00:00 2001 From: Bart <bartolomej.kozorog@gmail.com> Date: Mon, 11 Sep 2023 08:52:39 +0200 Subject: [PATCH 21/37] tweak card scrollbars --- frontend/src/components/card/Card.module.scss | 3 +++ frontend/src/contexts/project.context.tsx | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/card/Card.module.scss b/frontend/src/components/card/Card.module.scss index b2e80712..a8a65c9c 100644 --- a/frontend/src/components/card/Card.module.scss +++ b/frontend/src/components/card/Card.module.scss @@ -2,6 +2,7 @@ @import 'styles/rules'; @import 'styles/spacings'; @import 'styles/animations'; +@import 'styles/scrollbars'; .root { border-radius: $border-radius-card; @@ -9,6 +10,8 @@ border: 1px solid transparent; position: relative; + @include hiddenScrollbars(); + &.introAnimation { @include addNewContentAnimation(.4s); } diff --git a/frontend/src/contexts/project.context.tsx b/frontend/src/contexts/project.context.tsx index 761dfc98..60d775ea 100644 --- a/frontend/src/contexts/project.context.tsx +++ b/frontend/src/contexts/project.context.tsx @@ -111,7 +111,7 @@ export function ProjectProvider({ body: <span>Are you sure you want to delete this project?</span>, onConfirm: () => confirmProjectRemove(project), confirmButtonLabel: "DELETE", - cancelButtonLabel: "BACK", + cancelButtonLabel: "CANCEL", }); } From 1cce838c83fd3fb20c87640678369c618f8ef4df Mon Sep 17 00:00:00 2001 From: Bart <bartolomej.kozorog@gmail.com> Date: Mon, 11 Sep 2023 09:08:29 +0200 Subject: [PATCH 22/37] fix breadcrumbs filtration --- .../src/components/breadcrumbs/Breadcrumbs.tsx | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/breadcrumbs/Breadcrumbs.tsx b/frontend/src/components/breadcrumbs/Breadcrumbs.tsx index 1f223406..c8e12737 100644 --- a/frontend/src/components/breadcrumbs/Breadcrumbs.tsx +++ b/frontend/src/components/breadcrumbs/Breadcrumbs.tsx @@ -3,6 +3,7 @@ import { NavLink, useMatches, useNavigate } from "react-router-dom"; import classes from "./Breadcrumbs.module.scss"; import classNames from "classnames"; import { FlowserIcon } from "../icons/Icons"; +import { useCurrentProjectId } from "../../hooks/use-current-project-id"; type BreadcrumbsProps = { className?: string; @@ -35,10 +36,18 @@ function isMatchWithCrumb(match: Match): match is MatchWithCrumb { export function Breadcrumbs(props: BreadcrumbsProps): ReactElement | null { const navigate = useNavigate(); - const currentUrl = window.location.pathname; + const projectId = useCurrentProjectId(); + + function shouldShowMatch(match: Match) { + // For project pages, only show matches from project-scoped routes. + // This is to avoid showing the "Projects" crumb on every page. + return !projectId || match.pathname.startsWith(`/projects/${projectId}`); + } const matches: Match[] = useMatches(); - const matchesWithCrumbs: MatchWithCrumb[] = matches.filter(isMatchWithCrumb); + const matchesWithCrumbs: MatchWithCrumb[] = matches + .filter(isMatchWithCrumb) + .filter(shouldShowMatch); const breadcrumbs = matchesWithCrumbs.map( (match): Breadcrumb => ({ to: match.pathname, @@ -60,7 +69,7 @@ export function Breadcrumbs(props: BreadcrumbsProps): ReactElement | null { <div className={classes.breadcrumbs}> {breadcrumbs .map<React.ReactNode>((item, key) => ( - <NavLink key={key} to={item.to || currentUrl}> + <NavLink key={key} to={item.to}> {item.label} </NavLink> )) From 6d922d0c4f6bcd90471400506aec3125f335bf6d Mon Sep 17 00:00:00 2001 From: Bart <bartolomej.kozorog@gmail.com> Date: Mon, 11 Sep 2023 09:16:58 +0200 Subject: [PATCH 23/37] fix project update --- backend/src/projects/projects.service.ts | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/backend/src/projects/projects.service.ts b/backend/src/projects/projects.service.ts index d065fe40..ca63803d 100644 --- a/backend/src/projects/projects.service.ts +++ b/backend/src/projects/projects.service.ts @@ -28,7 +28,7 @@ import * as fs from "fs"; import { CacheRemovalService } from "../core/services/cache-removal.service"; import { WalletService } from "../wallet/wallet.service"; import { FlowSnapshotService } from "../flow/services/snapshot.service"; -import { FlowTemplatesService } from '../flow/services/templates.service'; +import { FlowTemplatesService } from "../flow/services/templates.service"; const commandExists = require("command-exists"); const semver = require("semver"); @@ -58,7 +58,7 @@ export class ProjectsService { this.flowSnapshotsService, // Wallet service also depends on the gateway service (needs to initialize fcl). this.walletService, - this.flowTemplatesService + this.flowTemplatesService, ]; constructor( @@ -235,13 +235,21 @@ export class ProjectsService { async update(id: string, updateProjectDto: UpdateProjectDto) { const currentProject = this.getCurrentProject(); + const currentPersistedProject = await this.projectRepository.findOneBy({ + id, + }); + const project = ProjectEntity.create(updateProjectDto); project.markUpdated(); // Project can be persisted only in-memory - if (currentProject?.id === id) { + if (currentProject && currentProject.id === id) { this.currentProject = project; - return this.currentProject; + + // If this is an in-memory project, don't execute update to avoid http error. + if (!currentPersistedProject) { + return this.currentProject; + } } await this.projectRepository.update( @@ -249,6 +257,7 @@ export class ProjectsService { // Prevent overwriting existing created date { ...project, createdAt: undefined } ); + return project; } From baa5e7b7fae12623521c0f52b0ad270b406c2b8f Mon Sep 17 00:00:00 2001 From: Bart <bartolomej.kozorog@gmail.com> Date: Mon, 11 Sep 2023 09:22:44 +0200 Subject: [PATCH 24/37] hide root scrollbars --- .../components/layout/ProjectLayout/ProjectLayout.module.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frontend/src/components/layout/ProjectLayout/ProjectLayout.module.scss b/frontend/src/components/layout/ProjectLayout/ProjectLayout.module.scss index 74e5d1db..0ca04492 100644 --- a/frontend/src/components/layout/ProjectLayout/ProjectLayout.module.scss +++ b/frontend/src/components/layout/ProjectLayout/ProjectLayout.module.scss @@ -21,6 +21,8 @@ $side-nav-width: 80px; max-height: 100vh; overflow-y: scroll; + @include hiddenScrollbars(); + .breadcrumbs { position: sticky; top: 0; From 009f44cd138854d4dec714e81f08ed8b62957a60 Mon Sep 17 00:00:00 2001 From: Bart <bartolomej.kozorog@gmail.com> Date: Mon, 11 Sep 2023 09:25:28 +0200 Subject: [PATCH 25/37] move interaction providers lower down the react tree --- frontend/src/App.tsx | 30 +++++++++---------- .../src/contexts/platform-adapter.context.tsx | 9 ++++-- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 665aa48a..34ffd789 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -86,20 +86,16 @@ export const FlowserClientApp = ({ <TimeoutPollingProvider enabled={enableTimeoutPolling}> <ConfirmDialogProvider> <PlatformAdapterProvider {...platformAdapter}> - <InteractionRegistryProvider> - <TemplatesRegistryProvider> - <ConsentAnalytics /> - <ProjectRequirements /> - <RouterProvider - router={useHashRouter ? hashRouter : browserRouter} - /> - <Toaster - position="bottom-center" - gutter={8} - toastOptions={toastOptions} - /> - </TemplatesRegistryProvider> - </InteractionRegistryProvider> + <ConsentAnalytics /> + <ProjectRequirements /> + <RouterProvider + router={useHashRouter ? hashRouter : browserRouter} + /> + <Toaster + position="bottom-center" + gutter={8} + toastOptions={toastOptions} + /> </PlatformAdapterProvider> </ConfirmDialogProvider> </TimeoutPollingProvider> @@ -144,7 +140,11 @@ const routes: RouteObject[] = [ path: ":projectId", element: ( <ProjectLayout> - <Outlet /> + <InteractionRegistryProvider> + <TemplatesRegistryProvider> + <Outlet /> + </TemplatesRegistryProvider> + </InteractionRegistryProvider> </ProjectLayout> ), children: [ diff --git a/frontend/src/contexts/platform-adapter.context.tsx b/frontend/src/contexts/platform-adapter.context.tsx index f8d51d2a..f112a1cf 100644 --- a/frontend/src/contexts/platform-adapter.context.tsx +++ b/frontend/src/contexts/platform-adapter.context.tsx @@ -1,4 +1,9 @@ -import React, { createContext, ReactElement, useContext } from "react"; +import React, { + createContext, + ReactElement, + ReactNode, + useContext, +} from "react"; import { MonitoringServiceInt } from "../services/monitoring.service"; export type PlatformAdapterState = { @@ -11,7 +16,7 @@ const PlatformAdapterContext = createContext<PlatformAdapterState>( ); export type PlatformAdapterProviderProps = PlatformAdapterState & { - children: ReactElement; + children: ReactNode; }; export function PlatformAdapterProvider({ From 3458dd4b7821de991e66d7ae23dc3ca9794b2c50 Mon Sep 17 00:00:00 2001 From: Bart <bartolomej.kozorog@gmail.com> Date: Mon, 11 Sep 2023 09:37:51 +0200 Subject: [PATCH 26/37] fix interaction removal --- .../components/InteractionTemplates/InteractionTemplates.tsx | 2 +- .../interactions/contexts/interaction-registry.context.tsx | 4 ++-- frontend/src/modules/interactions/core/core-utils.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/src/modules/interactions/components/InteractionTemplates/InteractionTemplates.tsx b/frontend/src/modules/interactions/components/InteractionTemplates/InteractionTemplates.tsx index c65ff6c6..f6b177aa 100644 --- a/frontend/src/modules/interactions/components/InteractionTemplates/InteractionTemplates.tsx +++ b/frontend/src/modules/interactions/components/InteractionTemplates/InteractionTemplates.tsx @@ -97,7 +97,7 @@ function FocusedDefinitionSettings() { } const correspondingTemplate = templates.find((template) => - InteractionUtils.areEqual(template, focusedDefinition) + InteractionUtils.areLogicallyEqual(template, focusedDefinition) ); if (!correspondingTemplate?.isMutable) { diff --git a/frontend/src/modules/interactions/contexts/interaction-registry.context.tsx b/frontend/src/modules/interactions/contexts/interaction-registry.context.tsx index 2ce6723f..db203832 100644 --- a/frontend/src/modules/interactions/contexts/interaction-registry.context.tsx +++ b/frontend/src/modules/interactions/contexts/interaction-registry.context.tsx @@ -86,7 +86,7 @@ export function InteractionRegistryProvider(props: { (definition) => definition.id !== interactionId ); setDefinitions(newDefinitions); - const lastDefinition = newDefinitions.reverse()[0]; + const lastDefinition = newDefinitions[newDefinitions.length - 1]; if (lastDefinition) { setFocusedInteractionId(lastDefinition.id); } @@ -97,7 +97,7 @@ export function InteractionRegistryProvider(props: { options?: CreateInteractionOptions ): InteractionDefinition { const existingInteraction = definitions.find((definition) => - InteractionUtils.areEqual(newPartialInteraction, definition) + InteractionUtils.areLogicallyEqual(newPartialInteraction, definition) ); if (existingInteraction && !options?.allowDuplicates) { return existingInteraction; diff --git a/frontend/src/modules/interactions/core/core-utils.ts b/frontend/src/modules/interactions/core/core-utils.ts index fa9e3179..d7d99763 100644 --- a/frontend/src/modules/interactions/core/core-utils.ts +++ b/frontend/src/modules/interactions/core/core-utils.ts @@ -7,7 +7,7 @@ export class InteractionUtils { // should be treated as the same entity. // See how this is handled within FLIX standard: // https://github.com/onflow/flips/blob/main/application/20220503-interaction-templates.md#data-structure-serialization--identifier-generation - static areEqual( + static areLogicallyEqual( a: IdentifiableInteractionDefinition, b: IdentifiableInteractionDefinition ): boolean { From 071ce5761539057d37144aa4d14ca3d7c0544a66 Mon Sep 17 00:00:00 2001 From: Bart <bartolomej.kozorog@gmail.com> Date: Mon, 11 Sep 2023 09:55:01 +0200 Subject: [PATCH 27/37] improve interaction creation logic --- .../modules/interactions/InteractionsPage.tsx | 19 +++----- .../InteractionTemplates.tsx | 6 +-- .../contexts/interaction-registry.context.tsx | 20 ++++++--- .../modules/interactions/core/core-utils.ts | 2 +- .../TransactionSource/TransactionSource.tsx | 43 +++++++++++-------- 5 files changed, 50 insertions(+), 40 deletions(-) diff --git a/frontend/src/modules/interactions/InteractionsPage.tsx b/frontend/src/modules/interactions/InteractionsPage.tsx index 6eea82e7..e9220c03 100644 --- a/frontend/src/modules/interactions/InteractionsPage.tsx +++ b/frontend/src/modules/interactions/InteractionsPage.tsx @@ -66,18 +66,13 @@ export function InteractionsPage(): ReactElement { tabs={openEditorTabs} onClose={(tab) => remove(tab.id)} onAddNew={() => { - const createdInteraction = create( - { - name: "New interaction", - code: "", - fclValuesByIdentifier: new Map(), - transactionOptions: undefined, - initialOutcome: undefined, - }, - { - allowDuplicates: true, - } - ); + const createdInteraction = create({ + name: "New interaction", + code: "", + fclValuesByIdentifier: new Map(), + transactionOptions: undefined, + initialOutcome: undefined, + }); setFocused(createdInteraction.id); }} /> diff --git a/frontend/src/modules/interactions/components/InteractionTemplates/InteractionTemplates.tsx b/frontend/src/modules/interactions/components/InteractionTemplates/InteractionTemplates.tsx index f6b177aa..5d536b38 100644 --- a/frontend/src/modules/interactions/components/InteractionTemplates/InteractionTemplates.tsx +++ b/frontend/src/modules/interactions/components/InteractionTemplates/InteractionTemplates.tsx @@ -96,11 +96,11 @@ function FocusedDefinitionSettings() { return null; } - const correspondingTemplate = templates.find((template) => - InteractionUtils.areLogicallyEqual(template, focusedDefinition) + const correspondingTemplate = templates.find( + (template) => template.id === focusedDefinition.id ); - if (!correspondingTemplate?.isMutable) { + if (correspondingTemplate && !correspondingTemplate.isMutable) { return null; } diff --git a/frontend/src/modules/interactions/contexts/interaction-registry.context.tsx b/frontend/src/modules/interactions/contexts/interaction-registry.context.tsx index db203832..6aa586fd 100644 --- a/frontend/src/modules/interactions/contexts/interaction-registry.context.tsx +++ b/frontend/src/modules/interactions/contexts/interaction-registry.context.tsx @@ -12,10 +12,13 @@ import { InteractionUtils } from "../core/core-utils"; type CreateInteractionDefinition = Omit< InteractionDefinition, "id" | "createdDate" | "updatedDate" ->; +> & { + // If not provided, a random UUID will be used. + id?: string; +}; type CreateInteractionOptions = { - allowDuplicates: boolean; + deduplicateBySourceCodeSemantics: boolean; }; type InteractionsRegistry = { @@ -96,15 +99,22 @@ export function InteractionRegistryProvider(props: { newPartialInteraction: CreateInteractionDefinition, options?: CreateInteractionOptions ): InteractionDefinition { + const newPartialDefinitionId = + newPartialInteraction.id ?? crypto.randomUUID(); const existingInteraction = definitions.find((definition) => - InteractionUtils.areLogicallyEqual(newPartialInteraction, definition) + options?.deduplicateBySourceCodeSemantics + ? InteractionUtils.areSemanticallyEquivalent( + newPartialInteraction, + definition + ) + : definition.id === newPartialDefinitionId ); - if (existingInteraction && !options?.allowDuplicates) { + if (existingInteraction) { return existingInteraction; } else { const newInteractionDefinition: InteractionDefinition = { - id: crypto.randomUUID(), ...newPartialInteraction, + id: newPartialDefinitionId, createdDate: new Date(), updatedDate: new Date(), }; diff --git a/frontend/src/modules/interactions/core/core-utils.ts b/frontend/src/modules/interactions/core/core-utils.ts index d7d99763..1df48139 100644 --- a/frontend/src/modules/interactions/core/core-utils.ts +++ b/frontend/src/modules/interactions/core/core-utils.ts @@ -7,7 +7,7 @@ export class InteractionUtils { // should be treated as the same entity. // See how this is handled within FLIX standard: // https://github.com/onflow/flips/blob/main/application/20220503-interaction-templates.md#data-structure-serialization--identifier-generation - static areLogicallyEqual( + static areSemanticallyEquivalent( a: IdentifiableInteractionDefinition, b: IdentifiableInteractionDefinition ): boolean { diff --git a/frontend/src/modules/transactions/TransactionSource/TransactionSource.tsx b/frontend/src/modules/transactions/TransactionSource/TransactionSource.tsx index 9eaa13b5..487bfd34 100644 --- a/frontend/src/modules/transactions/TransactionSource/TransactionSource.tsx +++ b/frontend/src/modules/transactions/TransactionSource/TransactionSource.tsx @@ -46,27 +46,32 @@ export const TransactionSource: FC<TransactionSourceProps> = ({ className={classes.interactLink} to="/interactions" onClick={() => { - const createdInteraction = create({ - name: transactionName ?? "Unknown", - code: transaction.script, - fclValuesByIdentifier: new Map( - transaction.arguments.map((arg) => [ - arg.identifier, - JSON.parse(arg.valueAsJson), - ]) - ), - initialOutcome: { - transaction: { - transactionId: transaction.id, - error: transaction.status?.errorMessage, + const createdInteraction = create( + { + name: transactionName ?? "Unknown", + code: transaction.script, + fclValuesByIdentifier: new Map( + transaction.arguments.map((arg) => [ + arg.identifier, + JSON.parse(arg.valueAsJson), + ]) + ), + initialOutcome: { + transaction: { + transactionId: transaction.id, + error: transaction.status?.errorMessage, + }, + }, + transactionOptions: { + authorizerAddresses: transaction.authorizers, + payerAddress: transaction.payer, + proposerAddress: transaction.proposalKey!.address, }, }, - transactionOptions: { - authorizerAddresses: transaction.authorizers, - payerAddress: transaction.payer, - proposerAddress: transaction.proposalKey!.address, - }, - }); + { + deduplicateBySourceCodeSemantics: true, + } + ); setFocused(createdInteraction.id); }} > From 9fe4d085a499de07c8d61d1d7c3d17211322bd47 Mon Sep 17 00:00:00 2001 From: Bart <bartolomej.kozorog@gmail.com> Date: Mon, 11 Sep 2023 09:56:40 +0200 Subject: [PATCH 28/37] display tabs in reverse order of stored templates --- .../modules/interactions/InteractionsPage.tsx | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/frontend/src/modules/interactions/InteractionsPage.tsx b/frontend/src/modules/interactions/InteractionsPage.tsx index e9220c03..c7f255ae 100644 --- a/frontend/src/modules/interactions/InteractionsPage.tsx +++ b/frontend/src/modules/interactions/InteractionsPage.tsx @@ -35,17 +35,19 @@ export function InteractionsPage(): ReactElement { sideMenuTabs[0].id ); - const openEditorTabs: TabItem[] = definitions.map((definition) => ({ - id: definition.id, - label: <InteractionLabel interaction={definition} />, - content: ( - <InteractionDefinitionManagerProvider definition={definition}> - <InteractionOutcomeManagerProvider> - <InteractionBody /> - </InteractionOutcomeManagerProvider> - </InteractionDefinitionManagerProvider> - ), - })); + const openEditorTabs: TabItem[] = [...definitions] + .reverse() + .map((definition) => ({ + id: definition.id, + label: <InteractionLabel interaction={definition} />, + content: ( + <InteractionDefinitionManagerProvider definition={definition}> + <InteractionOutcomeManagerProvider> + <InteractionBody /> + </InteractionOutcomeManagerProvider> + </InteractionDefinitionManagerProvider> + ), + })); return ( <div className={classes.pageRoot}> From 65fa8628017dcb5de5d889a8986815a9109afe13 Mon Sep 17 00:00:00 2001 From: Bart <bartolomej.kozorog@gmail.com> Date: Wed, 13 Sep 2023 08:43:52 +0200 Subject: [PATCH 29/37] fix focused transaction tab reset --- frontend/src/components/tabs/Tabs.tsx | 4 +--- .../InteractionOutcomeDisplay/InteractionOutcomeDisplay.tsx | 3 +++ 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/tabs/Tabs.tsx b/frontend/src/components/tabs/Tabs.tsx index 1d266367..c8a15c2a 100644 --- a/frontend/src/components/tabs/Tabs.tsx +++ b/frontend/src/components/tabs/Tabs.tsx @@ -36,9 +36,7 @@ export function Tabs(props: TabsProps): ReactElement { onAddNew, } = props; - const [fallbackCurrentTabId, setFallbackCurrentTabId] = useState( - props.tabs[0]?.id - ); + const [fallbackCurrentTabId, setFallbackCurrentTabId] = useState(tabs[0]?.id); const currentTabId = props.currentTabId ?? fallbackCurrentTabId; function onChangeTab(tab: TabItem) { diff --git a/frontend/src/modules/interactions/components/InteractionOutcomeDisplay/InteractionOutcomeDisplay.tsx b/frontend/src/modules/interactions/components/InteractionOutcomeDisplay/InteractionOutcomeDisplay.tsx index aa112c67..e32be9d1 100644 --- a/frontend/src/modules/interactions/components/InteractionOutcomeDisplay/InteractionOutcomeDisplay.tsx +++ b/frontend/src/modules/interactions/components/InteractionOutcomeDisplay/InteractionOutcomeDisplay.tsx @@ -104,6 +104,9 @@ function TransactionOutcomeDisplay(props: { outcome: TransactionOutcome }) { return ( <TransactionDetailsTabs + // Re-mount this component when different transaction is used. + // This is mainly to reset the initial focused tab. + key={data.transaction.id} label="Transaction" includeOverviewTab={true} includeScriptTab={false} From d507dff9b4d7d6b62a02ff41e1995240b2f42952 Mon Sep 17 00:00:00 2001 From: Bart <bartolomej.kozorog@gmail.com> Date: Wed, 13 Sep 2023 11:47:56 +0200 Subject: [PATCH 30/37] add unsupported import syntax notice --- .../src/components/status/ErrorMessage.tsx | 71 +++++++++++++++++-- .../InteractionOutcomeDisplay.tsx | 7 +- .../TransactionDetailsTabs.tsx | 8 ++- 3 files changed, 78 insertions(+), 8 deletions(-) diff --git a/frontend/src/components/status/ErrorMessage.tsx b/frontend/src/components/status/ErrorMessage.tsx index 582b3d6f..f9e190bd 100644 --- a/frontend/src/components/status/ErrorMessage.tsx +++ b/frontend/src/components/status/ErrorMessage.tsx @@ -2,21 +2,28 @@ import React, { ReactElement } from "react"; import { FlowUtils } from "../../utils/flow-utils"; import classes from "./ErrorMessage.module.scss"; import { JsonView } from "../json-view/JsonView"; +import { ExternalLink } from "../links/ExternalLink"; +import { Callout } from "../callout/Callout"; +import { SizedBox } from "../sized-box/SizedBox"; type ScriptErrorProps = { + cadenceSource: string; errorMessage: string; }; -export function ScriptError({ errorMessage }: ScriptErrorProps): ReactElement { - const parsedMessage = FlowUtils.parseScriptError(errorMessage); +export function ScriptError(props: ScriptErrorProps): ReactElement { + const parsedMessage = FlowUtils.parseScriptError(props.errorMessage); if (parsedMessage === undefined) { - return <pre className={classes.root}>{errorMessage}</pre>; + return <pre className={classes.root}>{props.errorMessage}</pre>; } if (parsedMessage.responseBody?.message) { return ( - <pre className={classes.root}>{parsedMessage.responseBody.message}</pre> + <> + <UnsupportedImportSyntaxNotice cadenceSource={props.cadenceSource} /> + <pre className={classes.root}>{parsedMessage.responseBody.message}</pre> + </> ); } @@ -26,9 +33,63 @@ export function ScriptError({ errorMessage }: ScriptErrorProps): ReactElement { } type TransactionErrorProps = { + cadenceSource: string; errorMessage: string; }; export function TransactionError(props: TransactionErrorProps): ReactElement { - return <pre className={classes.root}>{props.errorMessage}</pre>; + return ( + <> + <UnsupportedImportSyntaxNotice cadenceSource={props.cadenceSource} /> + <pre className={classes.root}>{props.errorMessage}</pre> + </> + ); +} + +type UnsupportedImportSyntaxNoticeProps = { + cadenceSource: string; +}; + +function UnsupportedImportSyntaxNotice( + props: UnsupportedImportSyntaxNoticeProps +) { + const isUnsupportedSyntax = props.cadenceSource + .split("\n") + .some((sourceCodeLine) => /^import ".*"$/.test(sourceCodeLine.trim())); + + if (!isUnsupportedSyntax) { + return null; + } + + return ( + <> + <Callout + icon="🚨" + title="Unsupported import syntax" + description={ + <div> + <p> + It looks like the executed Cadence code uses{" "} + <ExternalLink + href="https://developers.flow.com/tools/toolchains/flow-cli/super-commands#import-schema" + inline + > + the latest import syntax + </ExternalLink>{" "} + {/* eslint-disable-next-line react/no-unescaped-entities */} + syntax, which isn't supported within Flowser yet. + </p> + <p> + For more details, see:{" "} + <ExternalLink + inline + href="https://github.com/onflow/fcl-js/issues/1765" + /> + </p> + </div> + } + /> + <SizedBox height={20} /> + </> + ); } diff --git a/frontend/src/modules/interactions/components/InteractionOutcomeDisplay/InteractionOutcomeDisplay.tsx b/frontend/src/modules/interactions/components/InteractionOutcomeDisplay/InteractionOutcomeDisplay.tsx index e32be9d1..002cf1d4 100644 --- a/frontend/src/modules/interactions/components/InteractionOutcomeDisplay/InteractionOutcomeDisplay.tsx +++ b/frontend/src/modules/interactions/components/InteractionOutcomeDisplay/InteractionOutcomeDisplay.tsx @@ -117,8 +117,9 @@ function TransactionOutcomeDisplay(props: { outcome: TransactionOutcome }) { function ScriptOutcomeDisplay(props: { outcome: ScriptOutcome }) { const { result, error } = props.outcome; + const { definition } = useInteractionDefinitionManager(); const resultTabId = "result"; - const errorTabId = "result"; + const errorTabId = "error"; const [currentTabId, setCurrentTabId] = useState(resultTabId); useEffect(() => { @@ -133,7 +134,9 @@ function ScriptOutcomeDisplay(props: { outcome: ScriptOutcome }) { tabs.push({ id: errorTabId, label: "Error", - content: <ScriptError errorMessage={error} />, + content: ( + <ScriptError errorMessage={error} cadenceSource={definition.code} /> + ), }); } else { tabs.push({ diff --git a/frontend/src/modules/transactions/TransactionDetailsTabs/TransactionDetailsTabs.tsx b/frontend/src/modules/transactions/TransactionDetailsTabs/TransactionDetailsTabs.tsx index bd8c9532..469953f2 100644 --- a/frontend/src/modules/transactions/TransactionDetailsTabs/TransactionDetailsTabs.tsx +++ b/frontend/src/modules/transactions/TransactionDetailsTabs/TransactionDetailsTabs.tsx @@ -11,6 +11,7 @@ import { import { Transaction } from "@flowser/shared"; import { useGetPollingEventsByTransaction } from "../../../hooks/use-api"; import { TransactionOverview } from "../TransactionOverview/TransactionOverview"; +import { useInteractionDefinitionManager } from "../../interactions/contexts/definition.context"; type TransactionDetailsTabsProps = Omit<StyledTabsProps, "tabs"> & { transaction: Transaction; @@ -24,6 +25,8 @@ export function TransactionDetailsTabs( const { transaction, includeOverviewTab, includeScriptTab, ...tabProps } = props; + const { definition } = useInteractionDefinitionManager(); + const { data: events } = useGetPollingEventsByTransaction(transaction.id); const tabs: TabItem[] = []; @@ -33,7 +36,10 @@ export function TransactionDetailsTabs( id: "error", label: "Error", content: ( - <TransactionError errorMessage={transaction.status.errorMessage} /> + <TransactionError + errorMessage={transaction.status.errorMessage} + cadenceSource={definition.code} + /> ), }); } From 903b5f41315f0b1ce65120c1a7c136224cc62366 Mon Sep 17 00:00:00 2001 From: Bart <bartolomej.kozorog@gmail.com> Date: Wed, 13 Sep 2023 11:57:43 +0200 Subject: [PATCH 31/37] improve error message --- .../src/components/status/ErrorMessage.tsx | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/status/ErrorMessage.tsx b/frontend/src/components/status/ErrorMessage.tsx index f9e190bd..89e33c0e 100644 --- a/frontend/src/components/status/ErrorMessage.tsx +++ b/frontend/src/components/status/ErrorMessage.tsx @@ -5,6 +5,8 @@ import { JsonView } from "../json-view/JsonView"; import { ExternalLink } from "../links/ExternalLink"; import { Callout } from "../callout/Callout"; import { SizedBox } from "../sized-box/SizedBox"; +import { CadenceEditor } from "../cadence-editor/CadenceEditor"; +import { LineSeparator } from "../line-separator/LineSeparator"; type ScriptErrorProps = { cadenceSource: string; @@ -75,10 +77,22 @@ function UnsupportedImportSyntaxNotice( inline > the latest import syntax + </ExternalLink> + {/* eslint-disable-next-line react/no-unescaped-entities */}, + which isn't supported within Flowser yet. + </p> + <SizedBox height={10} /> + <p> + Try refactoring your code to use the{" "} + <ExternalLink + inline + href="https://developers.flow.com/cadence/language/imports#docusaurus_skipToContent_fallback" + > + standard Cadence syntax </ExternalLink>{" "} - {/* eslint-disable-next-line react/no-unescaped-entities */} - syntax, which isn't supported within Flowser yet. + instead. </p> + <SizedBox height={10} /> <p> For more details, see:{" "} <ExternalLink From 49d9ac760d201c11edc4fb03b49cb863b6ccaea8 Mon Sep 17 00:00:00 2001 From: Bart <bartolomej.kozorog@gmail.com> Date: Wed, 13 Sep 2023 12:10:40 +0200 Subject: [PATCH 32/37] remove bottom border from tabs --- frontend/src/components/status/ErrorMessage.tsx | 2 -- frontend/src/components/tabs/StyledTabs.module.scss | 1 - 2 files changed, 3 deletions(-) diff --git a/frontend/src/components/status/ErrorMessage.tsx b/frontend/src/components/status/ErrorMessage.tsx index 89e33c0e..f4d349e4 100644 --- a/frontend/src/components/status/ErrorMessage.tsx +++ b/frontend/src/components/status/ErrorMessage.tsx @@ -5,8 +5,6 @@ import { JsonView } from "../json-view/JsonView"; import { ExternalLink } from "../links/ExternalLink"; import { Callout } from "../callout/Callout"; import { SizedBox } from "../sized-box/SizedBox"; -import { CadenceEditor } from "../cadence-editor/CadenceEditor"; -import { LineSeparator } from "../line-separator/LineSeparator"; type ScriptErrorProps = { cadenceSource: string; diff --git a/frontend/src/components/tabs/StyledTabs.module.scss b/frontend/src/components/tabs/StyledTabs.module.scss index 4311153a..33a2e115 100644 --- a/frontend/src/components/tabs/StyledTabs.module.scss +++ b/frontend/src/components/tabs/StyledTabs.module.scss @@ -11,7 +11,6 @@ .activeTab { background: $gray-100 !important; - border-bottom: 2px solid $violet-100 !important; } .tabLabel { From 0c72c5756aacfabf3f1ccd3b135654f5e82b4cb9 Mon Sep 17 00:00:00 2001 From: Bart <bartolomej.kozorog@gmail.com> Date: Wed, 13 Sep 2023 12:15:48 +0200 Subject: [PATCH 33/37] settings screen tweaks --- .../ProjectSettings/ProjectSettings.tsx | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/frontend/src/modules/projects/ProjectSettings/ProjectSettings.tsx b/frontend/src/modules/projects/ProjectSettings/ProjectSettings.tsx index e6c1c852..e1aa3ae1 100644 --- a/frontend/src/modules/projects/ProjectSettings/ProjectSettings.tsx +++ b/frontend/src/modules/projects/ProjectSettings/ProjectSettings.tsx @@ -298,6 +298,18 @@ export const ProjectSettings: FunctionComponent<ProjectSettingsProps> = ( /> </Card> <Card className={classes.card}> + <EmulatorToggleField + label="REST API debug" + path="enableRestDebug" + description="Enable REST API debugging output (not recommended as it slows down the app)" + formik={formik} + /> + <EmulatorToggleField + label="GRPC API debug" + path="enableGrpcDebug" + description="enable gRPC server reflection for debugging with grpc_cli" + formik={formik} + /> <EmulatorToggleField label="Verbose" path="verboseLogging" @@ -307,13 +319,13 @@ export const ProjectSettings: FunctionComponent<ProjectSettingsProps> = ( <EmulatorToggleField label="Persist" path="persist" - description="Enable persistence of the state between restarts" + description="Persist emaulator blockchain state between restarts" formik={formik} /> <EmulatorToggleField - label="Snapshot" + label="Snapshots" path="snapshot" - description="Enable snapshot support (this option automatically enables persistence)" + description="Enable blockchain state snapshots (this option automatically enables persistence)" formik={formik} /> <EmulatorToggleField From f66033d213c669a5852539821b8b7997240040d7 Mon Sep 17 00:00:00 2001 From: Bart <bartolomej.kozorog@gmail.com> Date: Wed, 13 Sep 2023 12:32:20 +0200 Subject: [PATCH 34/37] change block menu item labels --- .../components/InteractionHistory/InteractionHistory.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/modules/interactions/components/InteractionHistory/InteractionHistory.tsx b/frontend/src/modules/interactions/components/InteractionHistory/InteractionHistory.tsx index aa06a377..bd2e4ad2 100644 --- a/frontend/src/modules/interactions/components/InteractionHistory/InteractionHistory.tsx +++ b/frontend/src/modules/interactions/components/InteractionHistory/InteractionHistory.tsx @@ -97,7 +97,7 @@ function BlockItem(props: BlockItemProps) { <MenuItem onClick={() => onForkAsTemplate()}> <FlowserIcon.Share width={menuIconSize} height={menuIconSize} /> <SizedBox width={10} /> - Open + View transaction </MenuItem> <MenuItem onClick={() => checkoutBlock(block.id)}> <FlowserIcon.CircleArrowLeft @@ -105,7 +105,7 @@ function BlockItem(props: BlockItemProps) { height={menuIconSize} /> <SizedBox width={10} /> - Rollback + Rollback to block </MenuItem> </FlowserMenu> ); From dc2569f08a3be67e53fbe4be9e7b5c4d754113f0 Mon Sep 17 00:00:00 2001 From: Bart <bartolomej.kozorog@gmail.com> Date: Thu, 14 Sep 2023 09:28:43 +0200 Subject: [PATCH 35/37] custom outline color --- frontend/src/App.scss | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/frontend/src/App.scss b/frontend/src/App.scss index fb4157a6..8cd96fa6 100644 --- a/frontend/src/App.scss +++ b/frontend/src/App.scss @@ -2,9 +2,14 @@ @import "styles/typography"; * { - margin: 0; - padding: 0; - box-sizing: content-box; + margin: 0; + padding: 0; + box-sizing: content-box; + outline: none; + + &:focus-visible { + outline: 2px solid $blue; + } } html, @@ -23,6 +28,7 @@ body { height: 100%; scroll-behavior: smooth; + &::-webkit-scrollbar { display: none; } From 4993922d211b29e6fb51dc248d29b8c0ef7955a2 Mon Sep 17 00:00:00 2001 From: Bart <bartolomej.kozorog@gmail.com> Date: Fri, 15 Sep 2023 11:59:09 +0200 Subject: [PATCH 36/37] fix external link overflow --- frontend/src/components/links/ExternalLink.module.scss | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/links/ExternalLink.module.scss b/frontend/src/components/links/ExternalLink.module.scss index 92ad47b3..07d8b75b 100644 --- a/frontend/src/components/links/ExternalLink.module.scss +++ b/frontend/src/components/links/ExternalLink.module.scss @@ -13,6 +13,8 @@ fill: $blue; } .url { - word-break: break-all; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } } From c87e2999a2393391323144ddd966dec1fd7a17da Mon Sep 17 00:00:00 2001 From: Bart <bartolomej.kozorog@gmail.com> Date: Fri, 15 Sep 2023 12:08:04 +0200 Subject: [PATCH 37/37] fix missing context --- .../modules/transactions/SignaturesTable/SignaturesTable.tsx | 2 +- .../TransactionDetailsTabs/TransactionDetailsTabs.tsx | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/frontend/src/modules/transactions/SignaturesTable/SignaturesTable.tsx b/frontend/src/modules/transactions/SignaturesTable/SignaturesTable.tsx index a4a689b8..73973ef2 100644 --- a/frontend/src/modules/transactions/SignaturesTable/SignaturesTable.tsx +++ b/frontend/src/modules/transactions/SignaturesTable/SignaturesTable.tsx @@ -12,7 +12,7 @@ const columnsHelper = createColumnHelper<SignableObject>(); const columns = [ columnsHelper.accessor("address", { - header: () => <Label variant="medium">ACCOUNT ADDRESS</Label>, + header: () => <Label variant="medium">ADDRESS</Label>, cell: (info) => ( <Value> <ProjectLink to={`/accounts/${info.getValue()}`}> diff --git a/frontend/src/modules/transactions/TransactionDetailsTabs/TransactionDetailsTabs.tsx b/frontend/src/modules/transactions/TransactionDetailsTabs/TransactionDetailsTabs.tsx index 469953f2..3886040c 100644 --- a/frontend/src/modules/transactions/TransactionDetailsTabs/TransactionDetailsTabs.tsx +++ b/frontend/src/modules/transactions/TransactionDetailsTabs/TransactionDetailsTabs.tsx @@ -11,7 +11,6 @@ import { import { Transaction } from "@flowser/shared"; import { useGetPollingEventsByTransaction } from "../../../hooks/use-api"; import { TransactionOverview } from "../TransactionOverview/TransactionOverview"; -import { useInteractionDefinitionManager } from "../../interactions/contexts/definition.context"; type TransactionDetailsTabsProps = Omit<StyledTabsProps, "tabs"> & { transaction: Transaction; @@ -25,8 +24,6 @@ export function TransactionDetailsTabs( const { transaction, includeOverviewTab, includeScriptTab, ...tabProps } = props; - const { definition } = useInteractionDefinitionManager(); - const { data: events } = useGetPollingEventsByTransaction(transaction.id); const tabs: TabItem[] = []; @@ -38,7 +35,7 @@ export function TransactionDetailsTabs( content: ( <TransactionError errorMessage={transaction.status.errorMessage} - cadenceSource={definition.code} + cadenceSource={transaction.script} /> ), });