-
Notifications
You must be signed in to change notification settings - Fork 22
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: Add infra to support OutputPage (nova) * feat: Add partial support for output "nested" view and unlock conditions [WiP] * feat: Add stardust-like outputId render to OutputView --------- Co-authored-by: Begoña Álvarez de la Cruz <[email protected]>
- Loading branch information
1 parent
67c4360
commit 98e6b13
Showing
17 changed files
with
889 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import { OutputResponse } from "@iota/sdk-nova"; | ||
import { IResponse } from "./IResponse"; | ||
|
||
export interface IOutputDetailsResponse extends IResponse { | ||
/** | ||
* The output data. | ||
*/ | ||
output?: OutputResponse; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
export interface IResponse { | ||
/** | ||
* An error for the response. | ||
*/ | ||
error?: string; | ||
|
||
/** | ||
* A message for the response. | ||
*/ | ||
message?: string; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import { ServiceFactory } from "../../../factories/serviceFactory"; | ||
import { IOutputDetailsRequest } from "../../../models/api/chrysalis/IOutputDetailsRequest"; | ||
import { IOutputDetailsResponse } from "../../../models/api/nova/IOutputDetailsResponse"; | ||
import { IConfiguration } from "../../../models/configuration/IConfiguration"; | ||
import { NOVA } from "../../../models/db/protocolVersion"; | ||
import { NetworkService } from "../../../services/networkService"; | ||
import { NovaApi } from "../../../services/nova/novaApi"; | ||
import { ValidationHelper } from "../../../utils/validationHelper"; | ||
|
||
/** | ||
* Find the object from the network. | ||
* @param config The configuration. | ||
* @param request The request. | ||
* @returns The response. | ||
*/ | ||
export async function get( | ||
config: IConfiguration, | ||
request: IOutputDetailsRequest | ||
): Promise<IOutputDetailsResponse> { | ||
const networkService = ServiceFactory.get<NetworkService>("network"); | ||
const networks = networkService.networkNames(); | ||
ValidationHelper.oneOf(request.network, networks, "network"); | ||
ValidationHelper.string(request.outputId, "outputId"); | ||
|
||
const networkConfig = networkService.get(request.network); | ||
|
||
if (networkConfig.protocolVersion !== NOVA) { | ||
return {}; | ||
} | ||
|
||
return NovaApi.outputDetails(networkConfig, request.outputId); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
import { | ||
__ClientMethods__, OutputResponse, Client | ||
} from "@iota/sdk-nova"; | ||
import { ServiceFactory } from "../../factories/serviceFactory"; | ||
import { IOutputDetailsResponse } from "../../models/api/nova/IOutputDetailsResponse"; | ||
import { INetwork } from "../../models/db/INetwork"; | ||
|
||
type NameType<T> = T extends { name: infer U } ? U : never; | ||
type ExtractedMethodNames = NameType<__ClientMethods__>; | ||
|
||
/** | ||
* Class to interact with the nova API. | ||
*/ | ||
export class NovaApi { | ||
/** | ||
* Get the output details. | ||
* @param network The network to find the items on. | ||
* @param outputId The output id to get the details. | ||
* @returns The item details. | ||
*/ | ||
public static async outputDetails(network: INetwork, outputId: string): Promise<IOutputDetailsResponse> { | ||
const outputResponse = await this.tryFetchNodeThenPermanode<string, OutputResponse>( | ||
outputId, | ||
"getOutput", | ||
network | ||
); | ||
|
||
return outputResponse ? | ||
{ output: outputResponse } : | ||
{ message: "Output not found" }; | ||
} | ||
|
||
/** | ||
* Generic helper function to try fetching from node client. | ||
* On failure (or not present), we try to fetch from permanode (if configured). | ||
* @param args The argument(s) to pass to the fetch calls. | ||
* @param methodName The function to call on the client. | ||
* @param network The network config in context. | ||
* @returns The results or null if call(s) failed. | ||
*/ | ||
public static async tryFetchNodeThenPermanode<A, R>( | ||
args: A, | ||
methodName: ExtractedMethodNames, | ||
network: INetwork | ||
): Promise<R> | null { | ||
const { permaNodeEndpoint, disableApiFallback } = network; | ||
const isFallbackEnabled = !disableApiFallback; | ||
const client = ServiceFactory.get<Client>(`client-${network.network}`); | ||
|
||
try { | ||
// try fetch from node | ||
const result: Promise<R> = client[methodName](args); | ||
return await result; | ||
} catch { } | ||
|
||
if (permaNodeEndpoint && isFallbackEnabled) { | ||
const permanodeClient = ServiceFactory.get<Client>(`permanode-client-${network.network}`); | ||
try { | ||
// try fetch from permanode (chronicle) | ||
const result: Promise<R> = permanodeClient[methodName](args); | ||
return await result; | ||
} catch { } | ||
} | ||
|
||
return null; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -114,5 +114,8 @@ | |
"not dead", | ||
"not ie <= 11", | ||
"not op_mini all" | ||
] | ||
], | ||
"prettier": { | ||
"tabWidth": 4 | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import React from "react"; | ||
import { Address, AddressType } from "@iota/sdk-wasm-nova/web"; | ||
|
||
interface AddressViewProps { | ||
address: Address; | ||
} | ||
|
||
const AddressView: React.FC<AddressViewProps> = ({ address }) => { | ||
return ( | ||
<div className="address-type"> | ||
<div className="card--label"> | ||
{getAddressTypeName(address.type)} | ||
</div> | ||
<div className="card--value"> | ||
{JSON.stringify(address)} | ||
</div> | ||
</div> | ||
); | ||
}; | ||
|
||
function getAddressTypeName(type: AddressType): string { | ||
switch (type) { | ||
case AddressType.Ed25519: | ||
return "Ed25519"; | ||
case AddressType.Account: | ||
return "Account"; | ||
case AddressType.Nft: | ||
return "Nft"; | ||
case AddressType.Anchor: | ||
return "Anchor"; | ||
case AddressType.ImplicitAccountCreation: | ||
return "ImplicitAccountCreation"; | ||
case AddressType.Multi: | ||
return "Multi"; | ||
case AddressType.Restricted: | ||
return "Restricted"; | ||
} | ||
} | ||
|
||
export default AddressView; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
@import "../../../scss/media-queries"; | ||
@import "../../../scss/variables"; | ||
|
||
.card--content__output { | ||
padding: 0 30px; | ||
margin-bottom: 20px; | ||
|
||
@include phone-down { | ||
padding: 0 4px; | ||
} | ||
|
||
.card--value.card-header--wrapper { | ||
width: 100%; | ||
display: flex; | ||
margin-bottom: 0px; | ||
height: 32px; | ||
align-items: center; | ||
|
||
.output-header { | ||
display: flex; | ||
width: 100%; | ||
|
||
.output-type--name { | ||
white-space: nowrap; | ||
} | ||
|
||
.output-id--link { | ||
display: flex; | ||
margin-right: 2px; | ||
white-space: nowrap; | ||
|
||
a { | ||
margin-right: 0px; | ||
} | ||
|
||
.highlight { | ||
font-weight: 500; | ||
color: $gray-6; | ||
margin-left: 2px; | ||
} | ||
|
||
.copy-button { | ||
margin-left: 2px; | ||
} | ||
} | ||
} | ||
|
||
.amount-size { | ||
width: min-content; | ||
text-align: end; | ||
word-break: normal; | ||
white-space: nowrap; | ||
cursor: pointer; | ||
margin-bottom: 0; | ||
margin-right: 4px; | ||
|
||
span { | ||
word-break: keep-all; | ||
} | ||
} | ||
} | ||
|
||
.left-border { | ||
border-left: 1px solid var(--border-color); | ||
} | ||
} | ||
|
||
.card--content--dropdown { | ||
margin-right: 8px; | ||
cursor: pointer; | ||
|
||
svg { | ||
transition: transform 0.25s ease; | ||
|
||
path { | ||
fill: var(--card-color); | ||
} | ||
} | ||
|
||
&.opened > svg { | ||
transform: rotate(90deg); | ||
} | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
import React from "react"; | ||
import DropdownIcon from "~assets/dropdown-arrow.svg?react"; | ||
import classNames from "classnames"; | ||
import { Output, OutputType, CommonOutput } from "@iota/sdk-wasm-nova/web"; | ||
import UnlockConditionView from "./UnlockConditionView"; | ||
import CopyButton from "../CopyButton"; | ||
import { Link } from "react-router-dom"; | ||
import "./OutputView.scss"; | ||
|
||
interface OutputViewProps { | ||
outputId: string; | ||
output: Output; | ||
showCopyAmount: boolean; | ||
} | ||
|
||
const OutputView: React.FC<OutputViewProps> = ({ | ||
outputId, | ||
output, | ||
showCopyAmount, | ||
}) => { | ||
const [isExpanded, setIsExpanded] = React.useState(false); | ||
const [isFormattedBalance, setIsFormattedBalance] = React.useState(true); | ||
|
||
console.log(output); | ||
|
||
const outputIdTransactionPart = `${outputId.slice(0, 8)}....${outputId.slice(-8, -4)}`; | ||
const outputIdIndexPart = outputId.slice(-4); | ||
|
||
return ( | ||
<div className="card--content__output"> | ||
<div | ||
onClick={() => setIsExpanded(!isExpanded)} | ||
className="card--value card-header--wrapper" | ||
> | ||
<div | ||
className={classNames("card--content--dropdown", { | ||
opened: isExpanded, | ||
})} | ||
> | ||
<DropdownIcon /> | ||
</div> | ||
<div className="output-header"> | ||
<button type="button" className="output-type--name color"> | ||
{getOutputTypeName(output.type)} | ||
</button> | ||
<div className="output-id--link"> | ||
( | ||
<Link | ||
// TODO need the network context here | ||
to={`/networkContext/output/${outputId}`} | ||
className="margin-r-t" | ||
> | ||
<span>{outputIdTransactionPart}</span> | ||
<span className="highlight"> | ||
{outputIdIndexPart} | ||
</span> | ||
</Link> | ||
) | ||
<CopyButton copy={String(outputId)} /> | ||
</div> | ||
</div> | ||
{showCopyAmount && ( | ||
<div className="card--value pointer amount-size row end"> | ||
<span | ||
className="pointer" | ||
onClick={(e) => { | ||
setIsFormattedBalance(!isFormattedBalance); | ||
e.stopPropagation(); | ||
}} | ||
> | ||
{output.amount} | ||
</span> | ||
</div> | ||
)} | ||
{showCopyAmount && <CopyButton copy={output.amount} />} | ||
</div> | ||
{isExpanded && ( | ||
<div className="output padding-l-t left-border"> | ||
{(output as CommonOutput).unlockConditions?.map( | ||
(unlockCondition, idx) => ( | ||
<UnlockConditionView | ||
key={idx} | ||
unlockCondition={unlockCondition} | ||
isPreExpanded={true} | ||
/> | ||
), | ||
)} | ||
</div> | ||
)} | ||
</div> | ||
); | ||
}; | ||
|
||
function getOutputTypeName(type: OutputType): string { | ||
switch (type) { | ||
case OutputType.Basic: | ||
return "Basic"; | ||
case OutputType.Account: | ||
return "Account"; | ||
case OutputType.Anchor: | ||
return "Anchor"; | ||
case OutputType.Foundry: | ||
return "Foundry"; | ||
case OutputType.Nft: | ||
return "Nft"; | ||
case OutputType.Delegation: | ||
return "Delegation"; | ||
} | ||
} | ||
|
||
export default OutputView; |
Oops, something went wrong.