Fix receive and send
This commit is contained in:
@@ -21,10 +21,7 @@ import type { XOTemplate } from '@xo-cash/types';
|
||||
import {
|
||||
formatTemplateListItem,
|
||||
formatActionListItem,
|
||||
deduplicateStartingActions,
|
||||
getTemplateRoles,
|
||||
getRolesForAction,
|
||||
type UniqueStartingAction,
|
||||
} from '../../utils/template-utils.js';
|
||||
|
||||
/**
|
||||
@@ -33,7 +30,7 @@ import {
|
||||
interface TemplateItem {
|
||||
template: XOTemplate;
|
||||
templateIdentifier: string;
|
||||
startingActions: UniqueStartingAction[];
|
||||
availableActions: TemplateActionItem[];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -42,9 +39,17 @@ interface TemplateItem {
|
||||
type TemplateListItem = ListItemData<TemplateItem>;
|
||||
|
||||
/**
|
||||
* Action list item with UniqueStartingAction value.
|
||||
* Action list item with available action value.
|
||||
*/
|
||||
type ActionListItem = ListItemData<UniqueStartingAction>;
|
||||
type ActionListItem = ListItemData<TemplateActionItem>;
|
||||
|
||||
interface TemplateActionItem {
|
||||
actionIdentifier: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
roles: string[];
|
||||
source: 'starting' | 'next' | 'starting+next';
|
||||
}
|
||||
|
||||
/**
|
||||
* Template List Screen Component.
|
||||
@@ -76,19 +81,90 @@ export function TemplateListScreen(): React.ReactElement {
|
||||
setStatus('Loading templates...');
|
||||
|
||||
const templateList = await appService.engine.listImportedTemplates();
|
||||
const allUtxos = await appService.engine.listUnspentOutputsData();
|
||||
|
||||
const ownedOutputsByTemplate = new Map<string, Set<string>>();
|
||||
for (const utxo of allUtxos) {
|
||||
const existing = ownedOutputsByTemplate.get(utxo.templateIdentifier) ?? new Set<string>();
|
||||
existing.add(utxo.outputIdentifier);
|
||||
ownedOutputsByTemplate.set(utxo.templateIdentifier, existing);
|
||||
}
|
||||
|
||||
const loadedTemplates = await Promise.all(
|
||||
templateList.map(async (template) => {
|
||||
const templateIdentifier = generateTemplateIdentifier(template);
|
||||
const rawStartingActions = await appService.engine.listStartingActions(templateIdentifier);
|
||||
const actionMap = new Map<string, TemplateActionItem>();
|
||||
|
||||
// Use utility function to deduplicate actions
|
||||
const startingActions = deduplicateStartingActions(template, rawStartingActions);
|
||||
for (const startingAction of rawStartingActions) {
|
||||
const existing = actionMap.get(startingAction.action);
|
||||
if (existing) {
|
||||
if (!existing.roles.includes(startingAction.role)) {
|
||||
existing.roles.push(startingAction.role);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
const actionDef = template.actions?.[startingAction.action];
|
||||
actionMap.set(startingAction.action, {
|
||||
actionIdentifier: startingAction.action,
|
||||
name: actionDef?.name || startingAction.action,
|
||||
description: actionDef?.description,
|
||||
roles: [startingAction.role],
|
||||
source: 'starting',
|
||||
});
|
||||
}
|
||||
|
||||
const ownedOutputIdentifiers = ownedOutputsByTemplate.get(templateIdentifier) ?? new Set<string>();
|
||||
for (const outputIdentifier of ownedOutputIdentifiers) {
|
||||
const outputDef = template.outputs?.[outputIdentifier];
|
||||
if (!outputDef || typeof outputDef.lockscript !== 'string') continue;
|
||||
|
||||
const lockingScriptDefinition = (template.lockingScripts as Record<string, unknown> | undefined)?.[outputDef.lockscript] as
|
||||
| { roles?: Record<string, { actions?: Array<{ action?: string; role?: string } | string> }> }
|
||||
| undefined;
|
||||
if (!lockingScriptDefinition?.roles) continue;
|
||||
|
||||
for (const [lockscriptRoleId, lockscriptRoleDef] of Object.entries(lockingScriptDefinition.roles)) {
|
||||
for (const actionSpec of lockscriptRoleDef.actions ?? []) {
|
||||
const actionIdentifier = typeof actionSpec === 'string'
|
||||
? actionSpec
|
||||
: actionSpec.action;
|
||||
if (!actionIdentifier) continue;
|
||||
|
||||
const roleIdentifier = typeof actionSpec === 'string'
|
||||
? lockscriptRoleId
|
||||
: (actionSpec.role ?? lockscriptRoleId);
|
||||
|
||||
const existing = actionMap.get(actionIdentifier);
|
||||
if (existing) {
|
||||
if (!existing.roles.includes(roleIdentifier)) {
|
||||
existing.roles.push(roleIdentifier);
|
||||
}
|
||||
if (existing.source === 'starting') {
|
||||
existing.source = 'starting+next';
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
const actionDef = template.actions?.[actionIdentifier];
|
||||
actionMap.set(actionIdentifier, {
|
||||
actionIdentifier,
|
||||
name: actionDef?.name || actionIdentifier,
|
||||
description: actionDef?.description,
|
||||
roles: [roleIdentifier],
|
||||
source: 'next',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const availableActions = Array.from(actionMap.values()).sort((a, b) => a.name.localeCompare(b.name));
|
||||
|
||||
return {
|
||||
template,
|
||||
templateIdentifier,
|
||||
startingActions,
|
||||
availableActions,
|
||||
};
|
||||
})
|
||||
);
|
||||
@@ -111,7 +187,7 @@ export function TemplateListScreen(): React.ReactElement {
|
||||
|
||||
// Get current template and its actions
|
||||
const currentTemplate = templates[selectedTemplateIndex];
|
||||
const currentActions = currentTemplate?.startingActions ?? [];
|
||||
const currentActions = currentTemplate?.availableActions ?? [];
|
||||
|
||||
/**
|
||||
* Build template list items for ScrollableList.
|
||||
@@ -137,12 +213,17 @@ export function TemplateListScreen(): React.ReactElement {
|
||||
const formatted = formatActionListItem(
|
||||
action.actionIdentifier,
|
||||
currentTemplate?.template?.actions?.[action.actionIdentifier],
|
||||
action.roleCount,
|
||||
action.roles.length,
|
||||
index
|
||||
);
|
||||
const sourceSuffix = action.source === 'next'
|
||||
? ' [next]'
|
||||
: action.source === 'starting+next'
|
||||
? ' [start+next]'
|
||||
: '';
|
||||
return {
|
||||
key: action.actionIdentifier,
|
||||
label: formatted.label,
|
||||
label: `${formatted.label}${sourceSuffix}`,
|
||||
description: formatted.description,
|
||||
value: action,
|
||||
hidden: !formatted.isValid,
|
||||
@@ -171,6 +252,7 @@ export function TemplateListScreen(): React.ReactElement {
|
||||
navigate('wizard', {
|
||||
templateIdentifier: currentTemplate.templateIdentifier,
|
||||
actionIdentifier: action.actionIdentifier,
|
||||
actionRoles: action.roles,
|
||||
template: currentTemplate.template,
|
||||
});
|
||||
}, [currentTemplate, navigate]);
|
||||
@@ -267,7 +349,7 @@ export function TemplateListScreen(): React.ReactElement {
|
||||
paddingX={1}
|
||||
flexGrow={1}
|
||||
>
|
||||
<Text color={colors.primary} bold> Starting Actions </Text>
|
||||
<Text color={colors.primary} bold> Available Actions </Text>
|
||||
{isLoading ? (
|
||||
<Box marginTop={1}>
|
||||
<Text color={colors.textMuted}>Loading...</Text>
|
||||
@@ -283,7 +365,7 @@ export function TemplateListScreen(): React.ReactElement {
|
||||
onSelect={setSelectedActionIndex}
|
||||
onActivate={handleActionActivate}
|
||||
focus={focusedPanel === 'actions'}
|
||||
emptyMessage="No starting actions available"
|
||||
emptyMessage="No actions available"
|
||||
renderItem={renderActionItem}
|
||||
/>
|
||||
)}
|
||||
@@ -339,9 +421,6 @@ export function TemplateListScreen(): React.ReactElement {
|
||||
const action = currentActions[selectedActionIndex];
|
||||
if (!action) return null;
|
||||
|
||||
// Get roles that can start this action using utility function
|
||||
const availableRoles = getRolesForAction(currentTemplate.template, action.actionIdentifier);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Text color={colors.text} bold>
|
||||
@@ -351,16 +430,24 @@ export function TemplateListScreen(): React.ReactElement {
|
||||
{action.description || 'No description available'}
|
||||
</Text>
|
||||
|
||||
{/* List available roles for this action */}
|
||||
{availableRoles.length > 0 && (
|
||||
{/* List roles available for this action in current context */}
|
||||
{action.roles.length > 0 && (
|
||||
<Box marginTop={1} flexDirection="column">
|
||||
<Text color={colors.text}>Available Roles:</Text>
|
||||
{availableRoles.map((role) => (
|
||||
<Text key={role.roleId} color={colors.textMuted}>
|
||||
{' '}- {role.name}
|
||||
{role.description ? `: ${role.description}` : ''}
|
||||
</Text>
|
||||
))}
|
||||
{action.roles.map((roleId) => {
|
||||
const roleDef = currentTemplate.template.roles?.[roleId];
|
||||
const roleName = typeof roleDef === 'object' ? roleDef?.name ?? roleId : roleId;
|
||||
const roleDescription = typeof roleDef === 'object' ? roleDef?.description : undefined;
|
||||
return (
|
||||
<Text key={roleId} color={colors.textMuted}>
|
||||
{' '}- {roleName}
|
||||
{roleDescription ? `: ${roleDescription}` : ''}
|
||||
</Text>
|
||||
);
|
||||
})}
|
||||
<Text color={colors.textMuted}>
|
||||
{' '}Source: {action.source}
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
</>
|
||||
@@ -370,7 +457,7 @@ export function TemplateListScreen(): React.ReactElement {
|
||||
) : focusedPanel === 'actions' && !currentTemplate ? (
|
||||
<Text color={colors.textMuted}>Select a template first</Text>
|
||||
) : focusedPanel === 'actions' && currentActions.length === 0 ? (
|
||||
<Text color={colors.textMuted}>No starting actions available</Text>
|
||||
<Text color={colors.textMuted}>No actions available</Text>
|
||||
) : null}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
Reference in New Issue
Block a user