Starting Over with Ink

This commit is contained in:
2025-08-10 14:54:47 -05:00
parent f38eff12cd
commit ec6d49e37e
14 changed files with 3280 additions and 557 deletions

44
src/tui-entry.js Normal file
View File

@@ -0,0 +1,44 @@
#!/usr/bin/env node
/**
* TUI Entry Point
* Initializes the Ink-based Terminal User Interface
* Requirements: 2.2, 2.5
*/
const React = require("react");
const { render } = require("ink");
const TuiApplication = require("./tui/TuiApplication.jsx");
// Initialize the TUI application
const main = () => {
try {
// Render the main TUI application
const { waitUntilExit } = render(React.createElement(TuiApplication));
// Wait for the application to exit
return waitUntilExit();
} catch (error) {
console.error("Failed to start TUI application:", error);
process.exit(1);
}
};
// Handle process signals gracefully
process.on("SIGINT", () => {
process.exit(0);
});
process.on("SIGTERM", () => {
process.exit(0);
});
// Start the application
if (require.main === module) {
main().catch((error) => {
console.error("TUI application error:", error);
process.exit(1);
});
}
module.exports = main;

View File

@@ -0,0 +1,25 @@
const React = require("react");
const { Box, Text } = require("ink");
const AppProvider = require("./providers/AppProvider");
const Router = require("./components/Router");
const StatusBar = require("./components/StatusBar");
/**
* Main TUI Application Component
* Root component that sets up the application structure
* Requirements: 2.2, 2.5
*/
const TuiApplication = () => {
return React.createElement(
AppProvider,
null,
React.createElement(
Box,
{ flexDirection: "column", height: "100%" },
React.createElement(StatusBar),
React.createElement(Router)
)
);
};
module.exports = TuiApplication;

View File

@@ -0,0 +1,71 @@
const React = require("react");
const { Box, Text } = require("ink");
const { useAppState } = require("../providers/AppProvider");
// Import screen components (will be created in later tasks)
// const MainMenuScreen = require('./screens/MainMenuScreen');
// const ConfigurationScreen = require('./screens/ConfigurationScreen');
// const OperationScreen = require('./screens/OperationScreen');
// const SchedulingScreen = require('./screens/SchedulingScreen');
// const LogViewerScreen = require('./screens/LogViewerScreen');
// const TagAnalysisScreen = require('./screens/TagAnalysisScreen');
/**
* Router Component
* Manages screen navigation and renders the current screen
* Requirements: 5.1, 5.3, 7.1
*/
const Router = () => {
const { appState } = useAppState();
// Temporary placeholder screens until actual screens are implemented
const screens = {
"main-menu": () =>
React.createElement(
Box,
{ padding: 2 },
React.createElement(Text, null, "Main Menu Screen - Coming Soon")
),
configuration: () =>
React.createElement(
Box,
{ padding: 2 },
React.createElement(Text, null, "Configuration Screen - Coming Soon")
),
operation: () =>
React.createElement(
Box,
{ padding: 2 },
React.createElement(Text, null, "Operation Screen - Coming Soon")
),
scheduling: () =>
React.createElement(
Box,
{ padding: 2 },
React.createElement(Text, null, "Scheduling Screen - Coming Soon")
),
logs: () =>
React.createElement(
Box,
{ padding: 2 },
React.createElement(Text, null, "Log Viewer Screen - Coming Soon")
),
"tag-analysis": () =>
React.createElement(
Box,
{ padding: 2 },
React.createElement(Text, null, "Tag Analysis Screen - Coming Soon")
),
};
// Get the current screen component
const CurrentScreen = screens[appState.currentScreen] || screens["main-menu"];
return React.createElement(
Box,
{ flexGrow: 1 },
React.createElement(CurrentScreen)
);
};
module.exports = Router;

View File

@@ -0,0 +1,56 @@
const React = require("react");
const { Box, Text } = require("ink");
const { useAppState } = require("../providers/AppProvider");
/**
* StatusBar Component
* Displays global status information at the top of the application
* Requirements: 8.1, 8.2, 8.3
*/
const StatusBar = () => {
const { appState } = useAppState();
// Get connection status (placeholder for now)
const connectionStatus = "Connected"; // Will be dynamic later
const connectionColor = "green";
// Get operation progress
const operationProgress = appState.operationState?.progress || 0;
// Get current screen name for display
const screenNames = {
"main-menu": "Main Menu",
configuration: "Configuration",
operation: "Operation",
scheduling: "Scheduling",
logs: "Logs",
"tag-analysis": "Tag Analysis",
};
const currentScreenName = screenNames[appState.currentScreen] || "Unknown";
return React.createElement(
Box,
{
borderStyle: "single",
paddingX: 1,
justifyContent: "space-between",
},
React.createElement(
Box,
null,
React.createElement(Text, { color: connectionColor }, "● "),
React.createElement(Text, null, connectionStatus),
React.createElement(Text, null, " | "),
React.createElement(Text, null, `Screen: ${currentScreenName}`)
),
React.createElement(
Box,
null,
appState.operationState &&
React.createElement(Text, null, `Progress: ${operationProgress}%`)
)
);
};
module.exports = StatusBar;

View File

@@ -0,0 +1,137 @@
const React = require("react");
const { useState, createContext, useContext } = React;
/**
* Application Context for global state management
* Requirements: 5.1, 5.3, 7.1
*/
const AppContext = createContext();
/**
* Initial application state
*/
const initialState = {
currentScreen: "main-menu",
navigationHistory: [],
configuration: {
shopifyDomain: "",
accessToken: "",
targetTag: "",
priceAdjustment: 0,
operationMode: "update",
isValid: false,
lastTested: null,
},
operationState: null,
uiState: {
focusedComponent: "menu",
modalOpen: false,
selectedMenuIndex: 0,
scrollPosition: 0,
},
};
/**
* AppProvider Component
* Provides global state management using React Context
*/
const AppProvider = ({ children }) => {
const [appState, setAppState] = useState(initialState);
/**
* Navigate to a new screen
*/
const navigateTo = (screen) => {
setAppState((prevState) => ({
...prevState,
navigationHistory: [
...prevState.navigationHistory,
prevState.currentScreen,
],
currentScreen: screen,
}));
};
/**
* Navigate back to previous screen
*/
const navigateBack = () => {
setAppState((prevState) => {
const history = [...prevState.navigationHistory];
const previousScreen = history.pop() || "main-menu";
return {
...prevState,
currentScreen: previousScreen,
navigationHistory: history,
};
});
};
/**
* Update configuration
*/
const updateConfiguration = (updates) => {
setAppState((prevState) => ({
...prevState,
configuration: {
...prevState.configuration,
...updates,
},
}));
};
/**
* Update operation state
*/
const updateOperationState = (operationState) => {
setAppState((prevState) => ({
...prevState,
operationState,
}));
};
/**
* Update UI state
*/
const updateUIState = (updates) => {
setAppState((prevState) => ({
...prevState,
uiState: {
...prevState.uiState,
...updates,
},
}));
};
const contextValue = {
appState,
setAppState,
navigateTo,
navigateBack,
updateConfiguration,
updateOperationState,
updateUIState,
};
return React.createElement(
AppContext.Provider,
{ value: contextValue },
children
);
};
/**
* Custom hook to use app context
*/
const useAppState = () => {
const context = useContext(AppContext);
if (!context) {
throw new Error("useAppState must be used within an AppProvider");
}
return context;
};
module.exports = AppProvider;
module.exports.useAppState = useAppState;
module.exports.AppContext = AppContext;