Starting Over with Ink
This commit is contained in:
44
src/tui-entry.js
Normal file
44
src/tui-entry.js
Normal 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;
|
||||
25
src/tui/TuiApplication.jsx
Normal file
25
src/tui/TuiApplication.jsx
Normal 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;
|
||||
71
src/tui/components/Router.jsx
Normal file
71
src/tui/components/Router.jsx
Normal 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;
|
||||
56
src/tui/components/StatusBar.jsx
Normal file
56
src/tui/components/StatusBar.jsx
Normal 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;
|
||||
137
src/tui/providers/AppProvider.jsx
Normal file
137
src/tui/providers/AppProvider.jsx
Normal 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;
|
||||
Reference in New Issue
Block a user