Skip to content
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

slightly better sync, recursive rebase, list, and loading component #54

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/commands/changes/commit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ const DoChangesCommit = ({
return (
<RecursiveRebaser
baseBranch={currentBranch}
endBranch={currentBranch}
successStateNode={
<Text color="green">Committed changes successfully</Text>
}
Expand Down
116 changes: 70 additions & 46 deletions src/commands/list.tsx
Original file line number Diff line number Diff line change
@@ -1,67 +1,50 @@
import React, { useMemo } from 'react';
import React, { useCallback, useMemo } from 'react';
import { Box, Text } from 'ink';
import { CommandConfig } from '../types.js';
import { ForegroundColorName } from 'chalk';
import { LiteralUnion } from 'type-fest';
import { Loading } from '../components/loading.js';
import { SelectRootBranch } from '../components/select-root-branch.js';
import { treeToParentChildRecord } from '../utils/tree-helpers.js';
import { useAsyncValue } from '../hooks/use-async-value.js';
import { useGit } from '../hooks/use-git.js';
import { useGitHelpers } from '../hooks/use-git-helpers.js';
import { useTree } from '../hooks/use-tree.js';

interface TextStyle {
color: LiteralUnion<ForegroundColorName, string>;
dimColor?: boolean;
}

const styleMap: TextStyle[] = [
{ color: 'cyan' },
{ color: 'blue' },
{ color: 'yellow' },
{ color: 'magentaBright' },
{ color: 'green' },
{ color: 'blueBright' },
{ color: 'yellowBright' },
{ color: 'magenta' },
{ color: 'cyanBright' },
];

const DisplayElementText = ({ elements }: { elements: DisplayElement[] }) => {
return (
<>
{elements.map((element, index) => {
const style = styleMap[index % styleMap.length] as TextStyle;
return (
<Text
key={`element-${index}`}
color={style.color}
dimColor={style.dimColor}
>
{element.symbols}
</Text>
);
})}
</>
);
};

const Spaces = ({ count }: { count: number }) => {
return <>{Array(count).fill(' ')}</>;
};

export const List = () => {
const git = useGit();
const { currentBranch } = useGitHelpers();
const { get, rootBranchName } = useTree();
const { get, rootBranchName, currentTree } = useTree();
const treeParentChildRecord = useMemo(
() => treeToParentChildRecord(get()),
[]
);

const getBranchNeedsRebaseRecord = useCallback(async () => {
const record: Record<string, boolean> = {};
await Promise.all(
currentTree.map(async (_node) => {
if (!_node.parent) return null;

record[_node.key] = await git.needsRebaseOnto({
branch: _node.key,
ontoBranch: _node.parent,
});
return null;
})
);
return record;
}, [currentTree, git.needsRebaseOnto]);

const branchNeedsRebaseRecord = useAsyncValue({
getValue: getBranchNeedsRebaseRecord,
});

if (!rootBranchName) {
return <SelectRootBranch />;
}

if (currentBranch.isLoading) {
if (currentBranch.isLoading || branchNeedsRebaseRecord.isLoading) {
return <Loading />;
}

Expand Down Expand Up @@ -90,13 +73,14 @@ export const List = () => {
]}
/>
<Spaces
count={
maxWidth + 2 - node.width + (isCurrent ? 0 : 2)
}
count={maxWidth - node.width + (isCurrent ? 0 : 2)}
/>
<Text color={style.color} dimColor={style.dimColor}>
{isCurrent ? ' 👉 ' : ''}
{node.name}{' '}
{branchNeedsRebaseRecord.value?.[node.name] && (
<Text color="white">(Needs rebase)</Text>
)}
</Text>
</Text>
);
Expand All @@ -105,6 +89,46 @@ export const List = () => {
);
};

interface TextStyle {
color: LiteralUnion<ForegroundColorName, string>;
dimColor?: boolean;
}

const styleMap: TextStyle[] = [
{ color: 'cyan' },
{ color: 'blue' },
{ color: 'yellow' },
{ color: 'magentaBright' },
{ color: 'green' },
{ color: 'blueBright' },
{ color: 'yellowBright' },
{ color: 'magenta' },
{ color: 'cyanBright' },
];

const DisplayElementText = ({ elements }: { elements: DisplayElement[] }) => {
return (
<>
{elements.map((element, index) => {
const style = styleMap[index % styleMap.length] as TextStyle;
return (
<Text
key={`element-${index}`}
color={style.color}
dimColor={style.dimColor}
>
{element.symbols}
</Text>
);
})}
</>
);
};

const Spaces = ({ count }: { count: number }) => {
return <>{Array(count).fill(' ')}</>;
};

interface DisplayElement {
symbols: string;
}
Expand Down
67 changes: 65 additions & 2 deletions src/commands/sync.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,90 @@
import React from 'react';
import ErrorDisplay from '../components/error-display.js';
import React, { useCallback } from 'react';
import { Action, useAction } from '../hooks/use-action.js';
import { CommandConfig, CommandProps } from '../types.js';
import { Loading } from '../components/loading.js';
import { RecursiveRebaser } from '../components/recursive-rebaser.js';
import { SelectRootBranch } from '../components/select-root-branch.js';
import { Text } from 'ink';
import { useGit } from '../hooks/use-git.js';
import { useGitHelpers } from '../hooks/use-git-helpers.js';
import { useTree } from '../hooks/use-tree.js';

const Sync = (_: CommandProps) => {
const { currentBranch } = useGitHelpers();
const { rootBranchName } = useTree();

if (!rootBranchName) {
return <SelectRootBranch />;
}

// todo: prompt to delete branches
if (currentBranch.isLoading) {
return <Loading />;
}

return (
<DoSync
rootBranchName={rootBranchName}
currentBranchName={currentBranch.value}
/>
);
};

const DoSync = ({
rootBranchName,
currentBranchName,
}: {
rootBranchName: string;
currentBranchName: string;
}) => {
const result = useSyncAction({
rootBranchName,
currentBranchName,
});

if (result.isError) {
return <ErrorDisplay error={result.error} />;
}

if (result.isLoading) {
return <Loading />;
}

// todo: prompt to delete branches
return (
<RecursiveRebaser
baseBranch={rootBranchName}
endBranch={currentBranchName}
successStateNode={<Text color="green">Synced successfully</Text>}
/>
);
};

type UseSyncActionResult = Action;
const useSyncAction = ({
currentBranchName,
rootBranchName,
}: {
currentBranchName: string;
rootBranchName: string;
}): UseSyncActionResult => {
const git = useGit();

const performAction = useCallback(async () => {
await git.checkout(rootBranchName);
await git.pull();
await git.checkout(currentBranchName);
}, [git]);

const action = useAction({
asyncAction: performAction,
});

return {
...action,
} as UseSyncActionResult;
};

export const syncConfig: CommandConfig = {
description: 'Sync your local changes with the remote server',
usage: 'sync',
Expand Down
44 changes: 41 additions & 3 deletions src/components/loading.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,44 @@
import React from 'react';
import { Text } from 'ink';
import React, { useEffect, useMemo, useState } from 'react';
import { Box, Text } from 'ink';

export const Loading = () => {
return <Text color="cyan">Loading...</Text>;
return <InfiniteLoadingAnimation />;
};

const WIDTH = 3;
const SYMBOLS = ['◔', '◑', '◕', '●'];

export const InfiniteLoadingAnimation = () => {
const [barStartIndex, setBarStartIndex] = useState(0);
const [symbolIndex, setSymbolIndex] = useState(0);

useEffect(() => {
const loadingBarInterval = setInterval(() => {
setBarStartIndex((prev) => (prev + 1) % WIDTH);
}, 150);

const symbolInterval = setInterval(() => {
setSymbolIndex((prev) => (prev + 1) % SYMBOLS.length);
}, 200);

return () => {
clearInterval(loadingBarInterval);
clearInterval(symbolInterval);
};
}, []);

// alternative loading bar concept
const currentBar = useMemo(() => {
const blocks = Array(WIDTH).fill('█') as string[];
blocks.splice(barStartIndex, 1, ' ');
return blocks;
}, [barStartIndex]);

return (
<Box gap={1}>
<Text color="cyan">{SYMBOLS[symbolIndex]} |</Text>
{/*<Text color="cyan">{currentBar.join('')}</Text>*/}
<Text color="cyan">Loading</Text>
</Box>
);
};
3 changes: 3 additions & 0 deletions src/components/rebase-conflict.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ export const RebaseConflict = () => {
3. Continue the rebase with{' '}
<Text color="cyan">gum continue</Text>
</Text>
<Text color="yellow">
OR - Cancel with <Text color="cyan">git rebase --abort</Text>
</Text>
</Box>
);
};
4 changes: 3 additions & 1 deletion src/components/recursive-rebaser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@ import { RebaseConflict } from './rebase-conflict.js';

export const RecursiveRebaser = ({
baseBranch,
endBranch,
successStateNode,
}: {
baseBranch: string;
endBranch: string;
successStateNode: ReactNode;
}) => {
const result = useRecursiveRebase({ baseBranch });
const result = useRecursiveRebase({ baseBranch, endBranch });

if (result.isError) {
return <ErrorDisplay error={result.error} />;
Expand Down
3 changes: 3 additions & 0 deletions src/hooks/use-recursive-rebase.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ export type RebaseActionLog = RebaseAction & {

export const useRecursiveRebase = ({
baseBranch,
endBranch,
}: {
baseBranch: string;
endBranch: string;
}): UseRecursiveRebaseResult => {
const git = useGit();
const { currentTree } = useTree();
Expand All @@ -31,6 +33,7 @@ export const useRecursiveRebase = ({
await recursiveRebase({
tree: currentTree,
baseBranch: baseBranch,
endBranch: endBranch,
events: {
rebased: (action, state) => {
if (state === 'STARTED') {
Expand Down
Loading
Loading