-
Notifications
You must be signed in to change notification settings - Fork 0
feat: Snapshots (without snapshot renaming) #58
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
fpena
wants to merge
34
commits into
main
Choose a base branch
from
fpena/41-state-snapshots
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 28 commits
Commits
Show all changes
34 commits
Select commit
Hold shift + click to select a range
21f2b45
Add snapshots tab and arrowDownUp icon
fpena 2fc5b61
Add EmptyState component and update StateScreen UI
fpena 2df391b
Moving forward with implementation
fpena 35a0c52
Add snapshot renaming with tooltip and input
fpena 01d64ac
Moving forward
fpena 7ab4041
Refactor snapshot list UI with card layout
fpena 1600827
Remove snapshot download functionality from StateScreen
fpena 7555996
Add restore logic
fpena ec41f97
Adjust style better
fpena 39f633a
Remove snapshot renaming functionality temporarily from StateScreen
fpena f78791d
Remove unused isExpanded parameter from style functions
fpena b4cd61a
Add styling to empty state text in StateScreen
fpena b0a4bba
Refactor snapshot card and header styles
fpena dd691d5
Update Icon components to use theme color and key
fpena efadc1b
Merge branch 'main' into fpena/41-state-snapshots
fpena 7be6756
Remove arrowDownUp icon and references
fpena 56d181b
Remove unnecessary console logs and error messages
fpena 2d27a26
Remove console error on missing client in StateScreen
fpena 3cd2761
Refactor StateScreen to remove unnecessary fragment
fpena 9289ee7
Refactor state backup response type check
fpena 46c216a
Refactor StateScreen to use StateSubscriptions component
fpena decd0ae
Refactor snapshot UI into StateSnapshots component
fpena e6b978d
Refactor state management in State components
fpena 2ba3bea
Move copySnapshotToClipboard to top-level function
fpena 94c8879
Fix import paths in StateSubscriptions component
fpena ee0d4de
Remove debug console logs from snapshot features
fpena 95e010f
Add themeName key to Icon components in StateSnapshots
fpena 5e1ecac
Remove pretty-printing from snapshot clipboard copy
fpena de6b7e4
Refactor snapshot state to use useSnapshots hook
fpena 24c9ca8
Refactor snapshot management into useSnapshots hook
fpena 1b6b47b
Refactor snapshot state hooks and imports
fpena b7dffc1
Fix Snapshot type import path in StateSnapshots
fpena 41258ad
Move restoreSnapshot logic to useSnapshots hook
fpena 9f1a397
Improve import statements
fpena File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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 hidden or 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,183 @@ | ||
| import { Text, ViewStyle, TextStyle, Pressable, View } from "react-native" | ||
| import { themed, useTheme, useThemeName } from "../../theme/theme" | ||
| import { TreeViewWithProvider } from "../TreeView" | ||
| import { Divider } from "../Divider" | ||
| import { Icon } from "../Icon" | ||
| import { Tooltip } from "../Tooltip" | ||
| import { useState } from "react" | ||
| import { useGlobal } from "../../state/useGlobal" | ||
| import { sendToCore } from "../../state/connectToServer" | ||
| import IRClipboard from "../../native/IRClipboard/NativeIRClipboard" | ||
| import type { Snapshot } from "app/types" | ||
|
|
||
| export function StateSnapshots() { | ||
| const theme = useTheme() | ||
| const [themeName] = useThemeName() | ||
| const [snapshots, setSnapshots] = useGlobal<Snapshot[]>("snapshots", []) | ||
|
||
| const [activeClientId, _] = useGlobal("activeClientId", "") | ||
| const [expandedSnapshotIds, setExpandedSnapshotIds] = useState<Set<string>>(new Set()) | ||
|
|
||
| const iconColor = theme.colors.mainText | ||
|
|
||
| const deleteSnapshot = (snapshotId: string) => { | ||
| setSnapshots((prev) => prev.filter((s) => s.id !== snapshotId)) | ||
| } | ||
|
|
||
| const restoreSnapshot = (snapshot: Snapshot) => { | ||
| if (!snapshot || !snapshot.state) return | ||
|
|
||
| // Use the snapshot's clientId if available, otherwise fall back to the active client | ||
| const targetClientId = snapshot.clientId || activeClientId | ||
|
|
||
| if (!targetClientId) return | ||
|
|
||
| // Send the restore command to the client | ||
| sendToCore("state.restore.request", { | ||
| clientId: targetClientId, | ||
| state: snapshot.state, | ||
| }) | ||
| } | ||
|
|
||
| const toggleSnapshotExpanded = (snapshotId: string) => { | ||
| setExpandedSnapshotIds((prev) => { | ||
| const newSet = new Set(prev) | ||
| if (newSet.has(snapshotId)) { | ||
| newSet.delete(snapshotId) | ||
| } else { | ||
| newSet.add(snapshotId) | ||
| } | ||
| return newSet | ||
| }) | ||
| } | ||
|
|
||
| if (snapshots.length === 0) { | ||
| return ( | ||
| <Text style={$emptyStateText()}> | ||
| To take a snapshot of your current redux or mobx-state-tree store, press the Create Snapshot | ||
| button in the top right corner of this window. | ||
| </Text> | ||
| ) | ||
| } | ||
|
|
||
| return ( | ||
| <> | ||
| {snapshots.map((snapshot) => ( | ||
| <View key={snapshot.id}> | ||
| <View style={$snapshotCard()}> | ||
| <Pressable | ||
| style={$snapshotHeader()} | ||
| onPress={() => toggleSnapshotExpanded(snapshot.id)} | ||
| > | ||
| <View style={$snapshotInfo()}> | ||
| <Text style={$snapshotName()}>{snapshot.name}</Text> | ||
| <Tooltip label="Copy Snapshot"> | ||
| <Pressable | ||
| style={$iconButton()} | ||
| onPress={(e) => { | ||
| e.stopPropagation() | ||
| copySnapshotToClipboard(snapshot) | ||
| }} | ||
| > | ||
| <Icon | ||
| icon="clipboard" | ||
| size={18} | ||
| color={iconColor} | ||
| key={`clipboard-${themeName}`} | ||
| /> | ||
| </Pressable> | ||
| </Tooltip> | ||
| <Tooltip label="Restore Snapshot"> | ||
| <Pressable | ||
| style={$iconButton()} | ||
| onPress={(e) => { | ||
| e.stopPropagation() | ||
| restoreSnapshot(snapshot) | ||
| }} | ||
| > | ||
| <Icon | ||
| icon="arrowUpFromLine" | ||
| size={18} | ||
| color={iconColor} | ||
| key={`restore-${themeName}`} | ||
| /> | ||
| </Pressable> | ||
| </Tooltip> | ||
| <Tooltip label="Delete Snapshot"> | ||
| <Pressable | ||
| style={$iconButton()} | ||
| onPress={(e) => { | ||
| e.stopPropagation() | ||
| deleteSnapshot(snapshot.id) | ||
| }} | ||
| > | ||
| <Icon icon="trash" size={18} color={iconColor} key={`trash-${themeName}`} /> | ||
| </Pressable> | ||
| </Tooltip> | ||
| </View> | ||
| </Pressable> | ||
| {expandedSnapshotIds.has(snapshot.id) && ( | ||
| <View style={$snapshotContent()}> | ||
| <TreeViewWithProvider data={snapshot.state} /> | ||
| </View> | ||
| )} | ||
| </View> | ||
| <Divider /> | ||
| </View> | ||
| ))} | ||
| </> | ||
| ) | ||
| } | ||
|
|
||
| function copySnapshotToClipboard(snapshot: Snapshot) { | ||
| try { | ||
| IRClipboard.setString(JSON.stringify(snapshot.state)) | ||
| } catch (error) { | ||
| console.error("Failed to copy snapshot to clipboard:", error) | ||
| } | ||
| } | ||
|
|
||
| const $snapshotCard = themed<ViewStyle>(({ colors }) => ({ | ||
| backgroundColor: colors.cardBackground, | ||
| overflow: "hidden", | ||
| })) | ||
|
|
||
| const $snapshotHeader = themed<ViewStyle>(({ spacing, colors }) => ({ | ||
| flexDirection: "row", | ||
| justifyContent: "space-between", | ||
| alignItems: "center", | ||
| padding: spacing.sm, | ||
| backgroundColor: colors.cardBackground, | ||
| cursor: "pointer", | ||
| })) | ||
|
|
||
| const $snapshotInfo = themed<ViewStyle>(({ spacing }) => ({ | ||
| flexDirection: "row", | ||
| alignItems: "center", | ||
| gap: spacing.md, | ||
| })) | ||
|
|
||
| const $snapshotName = themed<TextStyle>(({ colors, typography }) => ({ | ||
| flex: 1, | ||
| fontSize: typography.body, | ||
| fontWeight: "600", | ||
| color: colors.mainText, | ||
| fontFamily: typography.code.normal, | ||
| })) | ||
|
|
||
| const $iconButton = themed<ViewStyle>(({ spacing, colors }) => ({ | ||
| padding: spacing.xs, | ||
| borderRadius: 4, | ||
| cursor: "pointer", | ||
| backgroundColor: colors.neutralVery, | ||
| })) | ||
|
|
||
| const $snapshotContent = themed<ViewStyle>(({ spacing, colors }) => ({ | ||
| padding: spacing.md, | ||
| backgroundColor: colors.cardBackground, | ||
| })) | ||
|
|
||
| const $emptyStateText = themed<TextStyle>(({ colors, typography }) => ({ | ||
| fontSize: typography.body, | ||
| fontWeight: "400", | ||
| color: colors.mainText, | ||
| })) | ||
This file contains hidden or 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,82 @@ | ||
| import { Text, ViewStyle, TextStyle, Pressable, View } from "react-native" | ||
| import { themed, useTheme } from "../../theme/theme" | ||
| import { TreeViewWithProvider } from "../TreeView" | ||
| import { Divider } from "../Divider" | ||
| import { Icon } from "../Icon" | ||
| import type { StateSubscription } from "../../types" | ||
| import { useGlobal } from "../../state/useGlobal" | ||
| import { sendToCore } from "../../state/connectToServer" | ||
|
|
||
| export function StateSubscriptions() { | ||
| const theme = useTheme() | ||
| const [stateSubscriptionsByClientId, setStateSubscriptionsByClientId] = useGlobal<{ | ||
| [clientId: string]: StateSubscription[] | ||
| }>("stateSubscriptionsByClientId", {}) | ||
| const [activeClientId, _] = useGlobal("activeClientId", "") | ||
| const clientStateSubscriptions = stateSubscriptionsByClientId[activeClientId] || [] | ||
|
|
||
| const removeSubscription = (path: string) => { | ||
| const newStateSubscriptions = clientStateSubscriptions.filter((s) => s.path !== path) | ||
| sendToCore("state.values.subscribe", { | ||
| paths: newStateSubscriptions.map((s) => s.path), | ||
| clientId: activeClientId, | ||
| }) | ||
| setStateSubscriptionsByClientId((prev) => ({ | ||
| ...prev, | ||
| [activeClientId]: newStateSubscriptions, | ||
| })) | ||
| } | ||
|
|
||
| if (clientStateSubscriptions.length === 0) { | ||
| return <Text style={$emptyStateText()}>State is empty</Text> | ||
| } | ||
|
|
||
| return ( | ||
| <> | ||
| {clientStateSubscriptions.map((subscription, index) => ( | ||
| <View key={`${subscription.path}-${index}`} style={$stateItemContainer()}> | ||
| <Text style={$pathText()}>{subscription.path ? subscription.path : "Full State"}</Text> | ||
| <View style={$treeViewContainer()}> | ||
| <View style={$treeViewInnerContainer()}> | ||
| <TreeViewWithProvider data={subscription.value} /> | ||
| </View> | ||
| <Pressable onPress={() => removeSubscription(subscription.path)}> | ||
| <Icon icon="trash" size={20} color={theme.colors.mainText} /> | ||
| </Pressable> | ||
| </View> | ||
| {index < clientStateSubscriptions.length - 1 && <Divider extraStyles={$stateDivider()} />} | ||
| </View> | ||
| ))} | ||
| </> | ||
| ) | ||
| } | ||
|
|
||
| const $pathText = themed<TextStyle>(({ colors, typography, spacing }) => ({ | ||
| fontSize: typography.body, | ||
| fontWeight: "400", | ||
| color: colors.mainText, | ||
| marginBottom: spacing.sm, | ||
| })) | ||
|
|
||
| const $stateItemContainer = themed<ViewStyle>(({ spacing }) => ({ | ||
| marginTop: spacing.xl, | ||
| })) | ||
|
|
||
| const $treeViewInnerContainer = themed<ViewStyle>(() => ({ | ||
| flex: 1, | ||
| })) | ||
|
|
||
| const $treeViewContainer = themed<ViewStyle>(() => ({ | ||
| flexDirection: "row", | ||
| justifyContent: "space-between", | ||
| })) | ||
|
|
||
| const $stateDivider = themed<ViewStyle>(({ spacing }) => ({ | ||
| marginTop: spacing.lg, | ||
| })) | ||
|
|
||
| const $emptyStateText = themed<TextStyle>(({ colors, typography }) => ({ | ||
| fontSize: typography.body, | ||
| fontWeight: "400", | ||
| color: colors.mainText, | ||
| })) |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My editor will sometimes import things from
app/...but I've had issues with that working. I'd probably change it to../../typesto stay consistent with the above.