Invitations screen changes. Scrollable list. Details. And role selection on import
This commit is contained in:
259
src/utils/history-utils.ts
Normal file
259
src/utils/history-utils.ts
Normal file
@@ -0,0 +1,259 @@
|
||||
/**
|
||||
* History utility functions.
|
||||
*
|
||||
* Pure functions for parsing and formatting wallet history data.
|
||||
* These functions have no React dependencies and can be used
|
||||
* in both TUI and CLI contexts.
|
||||
*/
|
||||
|
||||
import type { HistoryItem, HistoryItemType } from '../services/history.js';
|
||||
|
||||
/**
|
||||
* Color names for history item types.
|
||||
* These are semantic color names that can be mapped to actual colors
|
||||
* by the consuming application (TUI or CLI).
|
||||
*/
|
||||
export type HistoryColorName = 'info' | 'warning' | 'success' | 'error' | 'muted' | 'text';
|
||||
|
||||
/**
|
||||
* Formatted history list item data.
|
||||
*/
|
||||
export interface FormattedHistoryItem {
|
||||
/** The display label for the history item */
|
||||
label: string;
|
||||
/** Optional secondary description */
|
||||
description?: string;
|
||||
/** The formatted date string */
|
||||
dateStr?: string;
|
||||
/** The semantic color name for this item type */
|
||||
color: HistoryColorName;
|
||||
/** The history item type */
|
||||
type: HistoryItemType;
|
||||
/** Whether the item data is valid */
|
||||
isValid: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the semantic color name for a history item type.
|
||||
*
|
||||
* @param type - The history item type
|
||||
* @param isSelected - Whether the item is currently selected
|
||||
* @returns A semantic color name
|
||||
*/
|
||||
export function getHistoryItemColorName(type: HistoryItemType, isSelected: boolean = false): HistoryColorName {
|
||||
if (isSelected) return 'info'; // Use focus color when selected
|
||||
|
||||
switch (type) {
|
||||
case 'invitation_created':
|
||||
return 'text';
|
||||
case 'utxo_reserved':
|
||||
return 'warning';
|
||||
case 'utxo_received':
|
||||
return 'success';
|
||||
default:
|
||||
return 'text';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a satoshi value for display.
|
||||
*
|
||||
* @param satoshis - The value in satoshis
|
||||
* @returns Formatted string with BCH amount
|
||||
*/
|
||||
export function formatSatoshisValue(satoshis: bigint | number): string {
|
||||
const value = typeof satoshis === 'bigint' ? satoshis : BigInt(satoshis);
|
||||
const bch = Number(value) / 100_000_000;
|
||||
return `${bch.toFixed(8)} BCH (${value.toLocaleString()} sats)`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a timestamp for display.
|
||||
*
|
||||
* @param timestamp - Unix timestamp in milliseconds
|
||||
* @returns Formatted date string or undefined
|
||||
*/
|
||||
export function formatHistoryDate(timestamp?: number): string | undefined {
|
||||
if (!timestamp) return undefined;
|
||||
return new Date(timestamp).toLocaleDateString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a history item for display in a list.
|
||||
*
|
||||
* @param item - The history item to format
|
||||
* @param isSelected - Whether the item is currently selected
|
||||
* @returns Formatted item data for display
|
||||
*/
|
||||
export function formatHistoryListItem(
|
||||
item: HistoryItem | null | undefined,
|
||||
isSelected: boolean = false
|
||||
): FormattedHistoryItem {
|
||||
if (!item) {
|
||||
return {
|
||||
label: '',
|
||||
description: undefined,
|
||||
dateStr: undefined,
|
||||
color: 'muted',
|
||||
type: 'utxo_received',
|
||||
isValid: false,
|
||||
};
|
||||
}
|
||||
|
||||
const dateStr = formatHistoryDate(item.timestamp);
|
||||
const color = getHistoryItemColorName(item.type, isSelected);
|
||||
|
||||
switch (item.type) {
|
||||
case 'invitation_created':
|
||||
return {
|
||||
label: `[Invitation] ${item.description}`,
|
||||
description: undefined,
|
||||
dateStr,
|
||||
color,
|
||||
type: item.type,
|
||||
isValid: true,
|
||||
};
|
||||
|
||||
case 'utxo_reserved': {
|
||||
const satsStr = item.valueSatoshis !== undefined
|
||||
? formatSatoshisValue(item.valueSatoshis)
|
||||
: 'Unknown amount';
|
||||
return {
|
||||
label: `[Reserved] ${satsStr}`,
|
||||
description: item.description,
|
||||
dateStr,
|
||||
color,
|
||||
type: item.type,
|
||||
isValid: true,
|
||||
};
|
||||
}
|
||||
|
||||
case 'utxo_received': {
|
||||
const satsStr = item.valueSatoshis !== undefined
|
||||
? formatSatoshisValue(item.valueSatoshis)
|
||||
: 'Unknown amount';
|
||||
const reservedTag = item.reserved ? ' [Reserved]' : '';
|
||||
return {
|
||||
label: satsStr,
|
||||
description: `${item.description}${reservedTag}`,
|
||||
dateStr,
|
||||
color,
|
||||
type: item.type,
|
||||
isValid: true,
|
||||
};
|
||||
}
|
||||
|
||||
default:
|
||||
return {
|
||||
label: `${item.type}: ${item.description}`,
|
||||
description: undefined,
|
||||
dateStr,
|
||||
color: 'text',
|
||||
type: item.type,
|
||||
isValid: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a type label for display.
|
||||
*
|
||||
* @param type - The history item type
|
||||
* @returns Human-readable type label
|
||||
*/
|
||||
export function getHistoryTypeLabel(type: HistoryItemType): string {
|
||||
switch (type) {
|
||||
case 'invitation_created':
|
||||
return 'Invitation';
|
||||
case 'utxo_reserved':
|
||||
return 'Reserved';
|
||||
case 'utxo_received':
|
||||
return 'Received';
|
||||
default:
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate scrolling window indices for a list.
|
||||
*
|
||||
* @param selectedIndex - Currently selected index
|
||||
* @param totalItems - Total number of items
|
||||
* @param maxVisible - Maximum visible items
|
||||
* @returns Start and end indices for the visible window
|
||||
*/
|
||||
export function calculateScrollWindow(
|
||||
selectedIndex: number,
|
||||
totalItems: number,
|
||||
maxVisible: number
|
||||
): { startIndex: number; endIndex: number } {
|
||||
const halfWindow = Math.floor(maxVisible / 2);
|
||||
let startIndex = Math.max(0, selectedIndex - halfWindow);
|
||||
const endIndex = Math.min(totalItems, startIndex + maxVisible);
|
||||
|
||||
// Adjust start if we're near the end
|
||||
if (endIndex - startIndex < maxVisible) {
|
||||
startIndex = Math.max(0, endIndex - maxVisible);
|
||||
}
|
||||
|
||||
return { startIndex, endIndex };
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a history item is a UTXO-related event.
|
||||
*
|
||||
* @param item - The history item to check
|
||||
* @returns True if the item is UTXO-related
|
||||
*/
|
||||
export function isUtxoEvent(item: HistoryItem): boolean {
|
||||
return item.type === 'utxo_received' || item.type === 'utxo_reserved';
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter history items by type.
|
||||
*
|
||||
* @param items - Array of history items
|
||||
* @param types - Types to include
|
||||
* @returns Filtered array
|
||||
*/
|
||||
export function filterHistoryByType(
|
||||
items: HistoryItem[],
|
||||
types: HistoryItemType[]
|
||||
): HistoryItem[] {
|
||||
return items.filter(item => types.includes(item.type));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get summary statistics for history items.
|
||||
*
|
||||
* @param items - Array of history items
|
||||
* @returns Summary statistics
|
||||
*/
|
||||
export function getHistorySummary(items: HistoryItem[]): {
|
||||
totalReceived: bigint;
|
||||
totalReserved: bigint;
|
||||
invitationCount: number;
|
||||
utxoCount: number;
|
||||
} {
|
||||
let totalReceived = 0n;
|
||||
let totalReserved = 0n;
|
||||
let invitationCount = 0;
|
||||
let utxoCount = 0;
|
||||
|
||||
for (const item of items) {
|
||||
switch (item.type) {
|
||||
case 'invitation_created':
|
||||
invitationCount++;
|
||||
break;
|
||||
case 'utxo_reserved':
|
||||
totalReserved += item.valueSatoshis ?? 0n;
|
||||
break;
|
||||
case 'utxo_received':
|
||||
totalReceived += item.valueSatoshis ?? 0n;
|
||||
utxoCount++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return { totalReceived, totalReserved, invitationCount, utxoCount };
|
||||
}
|
||||
264
src/utils/invitation-utils.ts
Normal file
264
src/utils/invitation-utils.ts
Normal file
@@ -0,0 +1,264 @@
|
||||
/**
|
||||
* Invitation utility functions.
|
||||
*
|
||||
* Pure functions for parsing and formatting invitation data.
|
||||
* These functions have no React dependencies and can be used
|
||||
* in both TUI and CLI contexts.
|
||||
*/
|
||||
|
||||
import type { Invitation } from '../services/invitation.js';
|
||||
import type { XOTemplate } from '@xo-cash/types';
|
||||
|
||||
/**
|
||||
* Color names for invitation states.
|
||||
* These are semantic color names that can be mapped to actual colors
|
||||
* by the consuming application (TUI or CLI).
|
||||
*/
|
||||
export type StateColorName = 'info' | 'warning' | 'success' | 'error' | 'muted';
|
||||
|
||||
/**
|
||||
* Input data extracted from invitation commits.
|
||||
*/
|
||||
export interface InvitationInput {
|
||||
inputIdentifier?: string;
|
||||
roleIdentifier?: string;
|
||||
entityIdentifier: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Output data extracted from invitation commits.
|
||||
*/
|
||||
export interface InvitationOutput {
|
||||
outputIdentifier?: string;
|
||||
roleIdentifier?: string;
|
||||
valueSatoshis?: bigint;
|
||||
entityIdentifier: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Variable data extracted from invitation commits.
|
||||
*/
|
||||
export interface InvitationVariable {
|
||||
variableIdentifier: string;
|
||||
value: unknown;
|
||||
roleIdentifier?: string;
|
||||
entityIdentifier: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formatted invitation list item data.
|
||||
*/
|
||||
export interface FormattedInvitationItem {
|
||||
/** The display label for the invitation */
|
||||
label: string;
|
||||
/** The current status of the invitation */
|
||||
status: string;
|
||||
/** The semantic color name for the status */
|
||||
statusColor: StateColorName;
|
||||
/** Whether the invitation data is valid */
|
||||
isValid: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current state/status of an invitation.
|
||||
*
|
||||
* @param invitation - The invitation to get state for
|
||||
* @returns The status string
|
||||
*/
|
||||
export function getInvitationState(invitation: Invitation): string {
|
||||
return invitation.status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the semantic color name for an invitation state.
|
||||
*
|
||||
* @param state - The invitation state string
|
||||
* @returns A semantic color name
|
||||
*/
|
||||
export function getStateColorName(state: string): StateColorName {
|
||||
switch (state) {
|
||||
case 'created':
|
||||
case 'published':
|
||||
return 'info';
|
||||
case 'pending':
|
||||
return 'warning';
|
||||
case 'ready':
|
||||
case 'signed':
|
||||
case 'broadcast':
|
||||
case 'completed':
|
||||
return 'success';
|
||||
case 'expired':
|
||||
case 'error':
|
||||
return 'error';
|
||||
default:
|
||||
return 'muted';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract all inputs from invitation commits.
|
||||
*
|
||||
* @param invitation - The invitation to extract inputs from
|
||||
* @returns Array of input data
|
||||
*/
|
||||
export function getInvitationInputs(invitation: Invitation): InvitationInput[] {
|
||||
const inputs: InvitationInput[] = [];
|
||||
for (const commit of invitation.data.commits || []) {
|
||||
for (const input of commit.data?.inputs || []) {
|
||||
inputs.push({
|
||||
inputIdentifier: input.inputIdentifier,
|
||||
roleIdentifier: input.roleIdentifier,
|
||||
entityIdentifier: commit.entityIdentifier,
|
||||
});
|
||||
}
|
||||
}
|
||||
return inputs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract all outputs from invitation commits.
|
||||
*
|
||||
* @param invitation - The invitation to extract outputs from
|
||||
* @returns Array of output data
|
||||
*/
|
||||
export function getInvitationOutputs(invitation: Invitation): InvitationOutput[] {
|
||||
const outputs: InvitationOutput[] = [];
|
||||
for (const commit of invitation.data.commits || []) {
|
||||
for (const output of commit.data?.outputs || []) {
|
||||
outputs.push({
|
||||
outputIdentifier: output.outputIdentifier,
|
||||
roleIdentifier: output.roleIdentifier,
|
||||
valueSatoshis: output.valueSatoshis,
|
||||
entityIdentifier: commit.entityIdentifier,
|
||||
});
|
||||
}
|
||||
}
|
||||
return outputs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract all variables from invitation commits.
|
||||
*
|
||||
* @param invitation - The invitation to extract variables from
|
||||
* @returns Array of variable data
|
||||
*/
|
||||
export function getInvitationVariables(invitation: Invitation): InvitationVariable[] {
|
||||
const variables: InvitationVariable[] = [];
|
||||
for (const commit of invitation.data.commits || []) {
|
||||
for (const variable of commit.data?.variables || []) {
|
||||
variables.push({
|
||||
variableIdentifier: variable.variableIdentifier,
|
||||
value: variable.value,
|
||||
roleIdentifier: variable.roleIdentifier,
|
||||
entityIdentifier: commit.entityIdentifier,
|
||||
});
|
||||
}
|
||||
}
|
||||
return variables;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user's role from commits (the role they have accepted).
|
||||
*
|
||||
* @param invitation - The invitation to check
|
||||
* @param userEntityId - The user's entity identifier
|
||||
* @returns The role identifier if found, null otherwise
|
||||
*/
|
||||
export function getUserRole(invitation: Invitation, userEntityId: string | null): string | null {
|
||||
if (!userEntityId) return null;
|
||||
|
||||
for (const commit of invitation.data.commits || []) {
|
||||
if (commit.entityIdentifier === userEntityId) {
|
||||
// Check inputs for role
|
||||
for (const input of commit.data?.inputs || []) {
|
||||
if (input.roleIdentifier) return input.roleIdentifier;
|
||||
}
|
||||
// Check outputs for role
|
||||
for (const output of commit.data?.outputs || []) {
|
||||
if (output.roleIdentifier) return output.roleIdentifier;
|
||||
}
|
||||
// Check variables for role
|
||||
for (const variable of commit.data?.variables || []) {
|
||||
if (variable.roleIdentifier) return variable.roleIdentifier;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format an invitation for display in a list.
|
||||
*
|
||||
* @param invitation - The invitation to format
|
||||
* @param template - Optional template for additional info (name)
|
||||
* @returns Formatted item data for display
|
||||
*/
|
||||
export function formatInvitationListItem(
|
||||
invitation: Invitation,
|
||||
template?: XOTemplate | null
|
||||
): FormattedInvitationItem {
|
||||
// Validate that we have the minimum required data
|
||||
const invitationId = invitation?.data?.invitationIdentifier;
|
||||
const actionId = invitation?.data?.actionIdentifier;
|
||||
|
||||
if (!invitationId || !actionId) {
|
||||
return {
|
||||
label: '',
|
||||
status: 'error',
|
||||
statusColor: 'error',
|
||||
isValid: false,
|
||||
};
|
||||
}
|
||||
|
||||
const state = getInvitationState(invitation);
|
||||
const templateName = template?.name ?? 'Unknown';
|
||||
const shortId = formatInvitationId(invitationId, 8);
|
||||
|
||||
return {
|
||||
label: `[${state}] ${templateName}-${actionId} (${shortId})`,
|
||||
status: state,
|
||||
statusColor: getStateColorName(state),
|
||||
isValid: true,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Format an invitation ID for display (truncated).
|
||||
*
|
||||
* @param id - The full invitation ID
|
||||
* @param maxLength - Maximum length for display
|
||||
* @returns Truncated ID string
|
||||
*/
|
||||
export function formatInvitationId(id: string, maxLength: number = 16): string {
|
||||
if (id.length <= maxLength) return id;
|
||||
const half = Math.floor((maxLength - 3) / 2);
|
||||
return `${id.slice(0, half)}...${id.slice(-half)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all unique entity identifiers from an invitation's commits.
|
||||
*
|
||||
* @param invitation - The invitation to check
|
||||
* @returns Array of unique entity identifiers
|
||||
*/
|
||||
export function getInvitationParticipants(invitation: Invitation): string[] {
|
||||
const participants = new Set<string>();
|
||||
for (const commit of invitation.data.commits || []) {
|
||||
if (commit.entityIdentifier) {
|
||||
participants.add(commit.entityIdentifier);
|
||||
}
|
||||
}
|
||||
return Array.from(participants);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a user is a participant in an invitation.
|
||||
*
|
||||
* @param invitation - The invitation to check
|
||||
* @param userEntityId - The user's entity identifier
|
||||
* @returns True if the user has made at least one commit
|
||||
*/
|
||||
export function isUserParticipant(invitation: Invitation, userEntityId: string | null): boolean {
|
||||
if (!userEntityId) return false;
|
||||
return getInvitationParticipants(invitation).includes(userEntityId);
|
||||
}
|
||||
246
src/utils/template-utils.ts
Normal file
246
src/utils/template-utils.ts
Normal file
@@ -0,0 +1,246 @@
|
||||
/**
|
||||
* Template utility functions.
|
||||
*
|
||||
* Pure functions for parsing and formatting template data.
|
||||
* These functions have no React dependencies and can be used
|
||||
* in both TUI and CLI contexts.
|
||||
*/
|
||||
|
||||
import type { XOTemplate, XOTemplateAction } from '@xo-cash/types';
|
||||
|
||||
/**
|
||||
* Formatted template list item data.
|
||||
*/
|
||||
export interface FormattedTemplateItem {
|
||||
/** The display label for the template */
|
||||
label: string;
|
||||
/** The template description */
|
||||
description?: string;
|
||||
/** Whether the template data is valid */
|
||||
isValid: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formatted action list item data.
|
||||
*/
|
||||
export interface FormattedActionItem {
|
||||
/** The display label for the action */
|
||||
label: string;
|
||||
/** The action description */
|
||||
description?: string;
|
||||
/** Number of roles that can start this action */
|
||||
roleCount: number;
|
||||
/** Whether the action data is valid */
|
||||
isValid: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* A unique starting action (deduplicated by action identifier).
|
||||
* Multiple roles that can start the same action are counted
|
||||
* but not shown as separate entries.
|
||||
*/
|
||||
export interface UniqueStartingAction {
|
||||
actionIdentifier: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
roleCount: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Role information from a template.
|
||||
*/
|
||||
export interface TemplateRole {
|
||||
roleId: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a template for display in a list.
|
||||
*
|
||||
* @param template - The template to format
|
||||
* @param index - Optional index for numbered display
|
||||
* @returns Formatted item data for display
|
||||
*/
|
||||
export function formatTemplateListItem(
|
||||
template: XOTemplate | null | undefined,
|
||||
index?: number
|
||||
): FormattedTemplateItem {
|
||||
if (!template) {
|
||||
return {
|
||||
label: '',
|
||||
description: undefined,
|
||||
isValid: false,
|
||||
};
|
||||
}
|
||||
|
||||
const name = template.name || 'Unnamed Template';
|
||||
const prefix = index !== undefined ? `${index + 1}. ` : '';
|
||||
|
||||
return {
|
||||
label: `${prefix}${name}`,
|
||||
description: template.description,
|
||||
isValid: true,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Format an action for display in a list.
|
||||
*
|
||||
* @param actionId - The action identifier
|
||||
* @param action - The action definition from the template
|
||||
* @param roleCount - Number of roles that can start this action
|
||||
* @param index - Optional index for numbered display
|
||||
* @returns Formatted item data for display
|
||||
*/
|
||||
export function formatActionListItem(
|
||||
actionId: string,
|
||||
action: XOTemplateAction | null | undefined,
|
||||
roleCount: number = 1,
|
||||
index?: number
|
||||
): FormattedActionItem {
|
||||
if (!actionId) {
|
||||
return {
|
||||
label: '',
|
||||
description: undefined,
|
||||
roleCount: 0,
|
||||
isValid: false,
|
||||
};
|
||||
}
|
||||
|
||||
const name = action?.name || actionId;
|
||||
const prefix = index !== undefined ? `${index + 1}. ` : '';
|
||||
const roleSuffix = roleCount > 1 ? ` (${roleCount} roles)` : '';
|
||||
|
||||
return {
|
||||
label: `${prefix}${name}${roleSuffix}`,
|
||||
description: action?.description,
|
||||
roleCount,
|
||||
isValid: true,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Deduplicate starting actions from a template.
|
||||
* Multiple roles that can start the same action are counted
|
||||
* but returned as a single entry.
|
||||
*
|
||||
* @param template - The template to process
|
||||
* @param startingActions - Array of { action, role } pairs
|
||||
* @returns Array of unique starting actions with role counts
|
||||
*/
|
||||
export function deduplicateStartingActions(
|
||||
template: XOTemplate,
|
||||
startingActions: Array<{ action: string; role: string }>
|
||||
): UniqueStartingAction[] {
|
||||
const actionMap = new Map<string, UniqueStartingAction>();
|
||||
|
||||
for (const sa of startingActions) {
|
||||
if (actionMap.has(sa.action)) {
|
||||
actionMap.get(sa.action)!.roleCount++;
|
||||
} else {
|
||||
const actionDef = template.actions?.[sa.action];
|
||||
actionMap.set(sa.action, {
|
||||
actionIdentifier: sa.action,
|
||||
name: actionDef?.name || sa.action,
|
||||
description: actionDef?.description,
|
||||
roleCount: 1,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(actionMap.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all roles from a template.
|
||||
*
|
||||
* @param template - The template to process
|
||||
* @returns Array of role information
|
||||
*/
|
||||
export function getTemplateRoles(template: XOTemplate): TemplateRole[] {
|
||||
if (!template.roles) return [];
|
||||
|
||||
return Object.entries(template.roles).map(([roleId, role]) => {
|
||||
// Handle case where role might be a string instead of object
|
||||
const roleObj = typeof role === 'object' ? role : null;
|
||||
return {
|
||||
roleId,
|
||||
name: roleObj?.name || roleId,
|
||||
description: roleObj?.description,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get roles that can start a specific action.
|
||||
*
|
||||
* @param template - The template to check
|
||||
* @param actionIdentifier - The action to check
|
||||
* @returns Array of role information for roles that can start this action
|
||||
*/
|
||||
export function getRolesForAction(
|
||||
template: XOTemplate,
|
||||
actionIdentifier: string
|
||||
): TemplateRole[] {
|
||||
const startEntries = (template.start ?? [])
|
||||
.filter((s) => s.action === actionIdentifier);
|
||||
|
||||
return startEntries.map((entry) => {
|
||||
const roleDef = template.roles?.[entry.role];
|
||||
const roleObj = typeof roleDef === 'object' ? roleDef : null;
|
||||
return {
|
||||
roleId: entry.role,
|
||||
name: roleObj?.name || entry.role,
|
||||
description: roleObj?.description,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get template name safely.
|
||||
*
|
||||
* @param template - The template
|
||||
* @returns The template name or a default
|
||||
*/
|
||||
export function getTemplateName(template: XOTemplate | null | undefined): string {
|
||||
return template?.name || 'Unknown Template';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get template description safely.
|
||||
*
|
||||
* @param template - The template
|
||||
* @returns The template description or undefined
|
||||
*/
|
||||
export function getTemplateDescription(template: XOTemplate | null | undefined): string | undefined {
|
||||
return template?.description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get action name safely.
|
||||
*
|
||||
* @param template - The template containing the action
|
||||
* @param actionId - The action identifier
|
||||
* @returns The action name or the action ID as fallback
|
||||
*/
|
||||
export function getActionName(
|
||||
template: XOTemplate | null | undefined,
|
||||
actionId: string
|
||||
): string {
|
||||
return template?.actions?.[actionId]?.name || actionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get action description safely.
|
||||
*
|
||||
* @param template - The template containing the action
|
||||
* @param actionId - The action identifier
|
||||
* @returns The action description or undefined
|
||||
*/
|
||||
export function getActionDescription(
|
||||
template: XOTemplate | null | undefined,
|
||||
actionId: string
|
||||
): string | undefined {
|
||||
return template?.actions?.[actionId]?.description;
|
||||
}
|
||||
Reference in New Issue
Block a user