Merge branch 'git4refactor2' of https://github.com/ethereum/remix-project into git4refactor2
commit
dff89e191d
@ -0,0 +1,36 @@ |
||||
'use strict' |
||||
import { ViewPlugin } from '@remixproject/engine-web'; |
||||
import React from 'react' // eslint-disable-line
|
||||
import { gitState, GitUI } from '@remix-ui/git'; |
||||
import * as packageJson from '../../../../../package.json' |
||||
|
||||
const profile = { |
||||
name: 'dgit', |
||||
desciption: 'Git plugin for Remix', |
||||
methods: ['pull', 'track', 'diff', 'clone', 'open'], |
||||
events: [''], |
||||
version: packageJson.version, |
||||
maintainedBy: 'Remix', |
||||
permission: true, |
||||
description: 'Use this plugin to interact with your git repositories', |
||||
location: 'sidePanel', |
||||
icon: "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTIxLjAwNyA4LjIyMTY4QzIxLjAxMDUgNy41Mjc5MiAyMC44MjA3IDYuODQ2ODkgMjAuNDU5MSA2LjI1NDg1QzIwLjA5NzQgNS42NjI4MSAxOS41NzggNS4xODMxNSAxOC45NTkyIDQuODY5NTdDMTguMzQwMyA0LjU1NiAxNy42NDYzIDQuNDIwOTEgMTYuOTU1MSA0LjQ3OTQxQzE2LjI2MzcgNC41Mzc5MyAxNS42MDI1IDQuNzg3NzMgMTUuMDQ1IDUuMjAwODVDMTQuNDg3NyA1LjYxMzk3IDE0LjA1NjMgNi4xNzQwOSAxMy43OTkzIDYuODE4NUMxMy41NDI0IDcuNDYyOSAxMy40Njk3IDguMTY2MTMgMTMuNTg5OCA4Ljg0OTQ0QzEzLjcwOTkgOS41MzI3NCAxNC4wMTc3IDEwLjE2OTIgMTQuNDc4OSAxMC42ODc0QzE0Ljk0MDIgMTEuMjA1NiAxNS41MzY3IDExLjU4NTIgMTYuMjAxNSAxMS43ODM2QzE1Ljk1NiAxMi4yODI0IDE1LjU3NjMgMTIuNzAzIDE1LjEwNDkgMTIuOTk3OUMxNC42MzM2IDEzLjI5MjkgMTQuMDg5NCAxMy40NTA1IDEzLjUzMzQgMTMuNDUzMkgxMC41NDRDOS40MzcyNiAxMy40NTcxIDguMzcxNjMgMTMuODcyNyA3LjU1NDUxIDE0LjYxOTFWNy4zOTgwOUM4LjQ2MTg0IDcuMjEyODggOS4yNjgwOCA2LjY5NzM3IDkuODE2OTIgNS45NTE1MUMxMC4zNjU4IDUuMjA1NjUgMTAuNjE4MSA0LjI4MjU2IDEwLjUyNSAzLjM2MTIxQzEwLjQzMTkgMi40Mzk4NyAxMC4wMDAxIDEuNTg1OSA5LjMxMzE2IDAuOTY0ODczQzguNjI2MjQgMC4zNDM4NDUgNy43MzMxOSAwIDYuODA3MTYgMEM1Ljg4MTEyIDAgNC45ODgwNyAwLjM0Mzg0NSA0LjMwMTE0IDAuOTY0ODczQzMuNjE0MjIgMS41ODU5IDMuMTgyMzYgMi40Mzk4NyAzLjA4OTI4IDMuMzYxMjFDMi45OTYyIDQuMjgyNTYgMy4yNDg1NSA1LjIwNTY1IDMuNzk3MzkgNS45NTE1MUM0LjM0NjIzIDYuNjk3MzcgNS4xNTI0NyA3LjIxMjg4IDYuMDU5OCA3LjM5ODA5VjE2LjUxNTlDNS4xNTQxOCAxNi42ODkxIDQuMzQzMjMgMTcuMTg3NyAzLjc3OTkzIDE3LjkxNzZDMy4yMTY2MyAxOC42NDc2IDIuOTM5OTIgMTkuNTU4NSAzLjAwMTk3IDIwLjQ3ODVDMy4wNjQwMyAyMS4zOTg0IDMuNDYwNTcgMjIuMjYzOSA0LjExNjggMjIuOTExNUM0Ljc3MzAzIDIzLjU1OTIgNS42NDM2IDIzLjk0NDQgNi41NjQyNyAyMy45OTQ0QzcuNDg0OTYgMjQuMDQ0NSA4LjM5MjExIDIzLjc1NTggOS4xMTQ2NCAyMy4xODNDOS44MzcxOCAyMi42MTAyIDEwLjMyNTEgMjEuNzkyOCAxMC40ODY1IDIwLjg4NUMxMC42NDc4IDE5Ljk3NzEgMTAuNDcxNCAxOS4wNDE3IDkuOTkwNDggMTguMjU1QzkuNTA5NTcgMTcuNDY4MyA4Ljc1NzQxIDE2Ljg4NDggNy44NzU4OCAxNi42MTQ1QzguMTIxNzYgMTYuMTE2MiA4LjUwMTY3IDE1LjY5NjMgOC45NzI5NiAxNS40MDE5QzkuNDQ0MjYgMTUuMTA3NCA5Ljk4ODI3IDE0Ljk1MDMgMTAuNTQ0IDE0Ljk0NzlIMTMuNTMzNEMxNC40NjYxIDE0Ljk0MzYgMTUuMzc0MiAxNC42NDg2IDE2LjEzMTMgMTQuMTAzOUMxNi44ODg0IDEzLjU1OTIgMTcuNDU2OCAxMi43OTIgMTcuNzU3NSAxMS45MDkxQzE4LjY1MzQgMTEuNzkxNCAxOS40NzYzIDExLjM1MjggMjAuMDczOCAxMC42NzQ4QzIwLjY3MTMgOS45OTY4IDIxLjAwMjggOS4xMjUzMyAyMS4wMDcgOC4yMjE2OFpNNC41NjUwOCAzLjczNzUyQzQuNTY1MDggMy4yOTQwOCA0LjY5NjU3IDIuODYwNiA0Ljk0MjkzIDIuNDkxOUM1LjE4OTMgMi4xMjMxOSA1LjUzOTQ3IDEuODM1ODEgNS45NDkxNSAxLjY2NjExQzYuMzU4ODQgMS40OTY0MiA2LjgwOTY1IDEuNDUyMDIgNy4yNDQ1NiAxLjUzODU0QzcuNjc5NDggMS42MjUwNCA4LjA3ODk4IDEuODM4NTcgOC4zOTI1NCAyLjE1MjE0QzguNzA2MTEgMi40NjU3IDguOTE5NjQgMi44NjUyIDkuMDA2MTUgMy4zMDAxMkM5LjA5MjY2IDMuNzM1MDQgOS4wNDgyNyA0LjE4NTg1IDguODc4NTcgNC41OTU1M0M4LjcwODg3IDUuMDA1MjEgOC40MjE0OSA1LjM1NTM5IDguMDUyNzggNS42MDE3NUM3LjY4NDA4IDUuODQ4MTEgNy4yNTA2IDUuOTc5NiA2LjgwNzE2IDUuOTc5NkM2LjIxMjUyIDUuOTc5NiA1LjY0MjI0IDUuNzQzMzkgNS4yMjE3NyA1LjMyMjkxQzQuODAxMjkgNC45MDI0NSA0LjU2NTA4IDQuMzMyMTYgNC41NjUwOCAzLjczNzUyWk05LjA0OTIzIDIwLjE3OTRDOS4wNDkyMyAyMC42MjI5IDguOTE3NzQgMjEuMDU2MyA4LjY3MTM4IDIxLjQyNUM4LjQyNTAxIDIxLjc5MzcgOC4wNzQ4NSAyMi4wODExIDcuNjY1MTYgMjIuMjUwOEM3LjI1NTQ3IDIyLjQyMDUgNi44MDQ2NiAyMi40NjQ5IDYuMzY5NzUgMjIuMzc4NEM1LjkzNDgzIDIyLjI5MiA1LjUzNTMzIDIyLjA3ODQgNS4yMjE3NyAyMS43NjQ4QzQuOTA4MjEgMjEuNDUxMiA0LjY5NDY3IDIxLjA1MTcgNC42MDgxNiAyMC42MTY5QzQuNTIxNjUgMjAuMTgxOSA0LjU2NjA1IDE5LjczMTEgNC43MzU3NSAxOS4zMjE0QzQuOTA1NDUgMTguOTExNyA1LjE5MjgyIDE4LjU2MTUgNS41NjE1MyAxOC4zMTUyQzUuOTMwMjMgMTguMDY4OSA2LjM2MzcxIDE3LjkzNzMgNi44MDcxNiAxNy45MzczQzcuNDAxNzkgMTcuOTM3MyA3Ljk3MjA3IDE4LjE3MzYgOC4zOTI1NCAxOC41OTRDOC44MTMwMiAxOS4wMTQ1IDkuMDQ5MjMgMTkuNTg0OCA5LjA0OTIzIDIwLjE3OTRaTTE3LjI3MDIgMTAuNDYzOEMxNi44MjY3IDEwLjQ2MzggMTYuMzkzMyAxMC4zMzIyIDE2LjAyNDYgMTAuMDg1OUMxNS42NTU5IDkuODM5NTQgMTUuMzY4NSA5LjQ4OTM3IDE1LjE5ODggOS4wNzk2OUMxNS4wMjkxIDguNjcgMTQuOTg0NyA4LjIxOTIgMTUuMDcxMiA3Ljc4NDI3QzE1LjE1NzYgNy4zNDkzNSAxNS4zNzEyIDYuOTQ5ODUgMTUuNjg0OCA2LjYzNjI5QzE1Ljk5ODQgNi4zMjI3MyAxNi4zOTc5IDYuMTA5MTkgMTYuODMyNyA2LjAyMjY4QzE3LjI2NzcgNS45MzYxNyAxNy43MTg1IDUuOTgwNTggMTguMTI4MSA2LjE1MDI3QzE4LjUzNzkgNi4zMTk5NyAxOC44ODgxIDYuNjA3MzQgMTkuMTM0NCA2Ljk3NjA1QzE5LjM4MDcgNy4zNDQ3NiAxOS41MTIzIDcuNzc4MjMgMTkuNTEyMyA4LjIyMTY4QzE5LjUxMjMgOC44MTYzMiAxOS4yNzYgOS4zODY2IDE4Ljg1NTYgOS44MDcwNkMxOC40MzUxIDEwLjIyNzUgMTcuODY0OCAxMC40NjM4IDE3LjI3MDIgMTAuNDYzOFoiIGZpbGw9IiM0MjQyNDIiLz4KPC9zdmc+Cg==" |
||||
} |
||||
|
||||
export class GitPlugin extends ViewPlugin { |
||||
|
||||
constructor() { |
||||
|
||||
super(profile) |
||||
} |
||||
|
||||
onDeactivation(): void { |
||||
this.call('fileDecorator', 'clearFileDecorators') |
||||
this.call('manager', 'activatePlugin', 'dgitApi') |
||||
} |
||||
|
||||
render() { |
||||
return <div id='gitTab'><GitUI plugin={this} /></div> |
||||
} |
||||
|
||||
} |
@ -0,0 +1,20 @@ |
||||
{ |
||||
"git.push": "push", |
||||
"git.pull": "pull", |
||||
"git.commit": "commit", |
||||
"git.sync": "sync", |
||||
"git.syncchanges": "sync changes", |
||||
"git.publish": "publish", |
||||
"git.ignore": "ignore", |
||||
"git.createBranch": "create branch", |
||||
"git.deleteBranch": "delete branch", |
||||
"git.mergeBranch": "merge branch", |
||||
"git.rebaseBranch": "rebase branch", |
||||
"git.checkout": "checkout", |
||||
"git.fetch": "fetch", |
||||
"git.refresh": "refresh", |
||||
"git.unstageall": "unstage all", |
||||
"git.stageall": "stage all", |
||||
"git.noremote": "this repo has no remotes", |
||||
"git.init": "Initialize repository" |
||||
} |
@ -0,0 +1,9 @@ |
||||
export type branch = { |
||||
name: string |
||||
remote: remote |
||||
} |
||||
|
||||
export type remote = { |
||||
name: string |
||||
url: string |
||||
} |
@ -0,0 +1 @@ |
||||
export * from './lib/remix-api' |
@ -0,0 +1,11 @@ |
||||
import { StatusEvents } from "@remixproject/plugin-utils" |
||||
|
||||
export interface IConfigApi { |
||||
events: { |
||||
configChanged: () => void |
||||
} & StatusEvents, |
||||
methods: { |
||||
getAppParameter(key: string): Promise<any>, |
||||
setAppParameter(key: string, value: any): Promise<void> |
||||
} |
||||
} |
@ -0,0 +1,10 @@ |
||||
import { commitChange } from "@remix-ui/git"; |
||||
import { IFileSystem } from "@remixproject/plugin-api" |
||||
|
||||
// Extended interface with 'diff' method
|
||||
export interface IExtendedFileSystem extends IFileSystem { |
||||
methods: IFileSystem['methods'] & { |
||||
/** Compare the differences between two files */ |
||||
diff(change: commitChange): Promise<void> |
||||
}; |
||||
} |
@ -0,0 +1,11 @@ |
||||
import { fileDecoration } from '@remix-ui/file-decorators' |
||||
import { StatusEvents } from '@remixproject/plugin-utils' |
||||
|
||||
export interface IFileDecoratorApi { |
||||
events: { |
||||
} & StatusEvents |
||||
methods: { |
||||
clearFileDecorators(path?: string): void |
||||
setFileDecorators(decorators: fileDecoration[]): void |
||||
} |
||||
} |
@ -0,0 +1,29 @@ |
||||
import { ModalTypes } from "@remix-ui/app" |
||||
import { StatusEvents } from "@remixproject/plugin-utils" |
||||
|
||||
export interface INotificationApi { |
||||
events: { |
||||
|
||||
} & StatusEvents, |
||||
methods: { |
||||
toast(key: string): Promise<void>, |
||||
alert({ |
||||
title, |
||||
message |
||||
}:{ |
||||
title: string, |
||||
message: string, |
||||
}): Promise<void>, |
||||
modal({ |
||||
title, |
||||
message, |
||||
okLabel, |
||||
type |
||||
}:{ |
||||
title: string, |
||||
message: string, |
||||
okLabel: string, |
||||
type: ModalTypes |
||||
}): Promise<void>, |
||||
} |
||||
} |
@ -0,0 +1,11 @@ |
||||
import { StatusEvents } from '@remixproject/plugin-utils' |
||||
|
||||
export interface ISettings { |
||||
events: { |
||||
configChanged: () => void, |
||||
} & StatusEvents |
||||
methods: { |
||||
getGithubAccessToken(): string |
||||
get(key: string): Promise<any> |
||||
} |
||||
} |
@ -0,0 +1,19 @@ |
||||
import { IGitApi } from "@remix-ui/git" |
||||
import { IRemixApi } from "@remixproject/plugin-api" |
||||
import { StatusEvents } from "@remixproject/plugin-utils" |
||||
import { IConfigApi } from "./plugins/config-api" |
||||
import { IFileDecoratorApi } from "./plugins/filedecorator-api" |
||||
import { IExtendedFileSystem } from "./plugins/fileSystem-api" |
||||
import { INotificationApi } from "./plugins/notification-api" |
||||
import { ISettings } from "./plugins/settings-api" |
||||
|
||||
export interface ICustomRemixApi extends IRemixApi { |
||||
dgitApi: IGitApi |
||||
config: IConfigApi |
||||
notification: INotificationApi |
||||
settings: ISettings |
||||
fileDecorator: IFileDecoratorApi |
||||
fileManager: IExtendedFileSystem |
||||
} |
||||
|
||||
export declare type CustomRemixApi = Readonly<ICustomRemixApi> |
@ -0,0 +1,33 @@ |
||||
import React, { useEffect, useReducer, useState } from 'react' |
||||
import { gitActionsContext } from '../state/context' |
||||
import { gitPluginContext } from './gitui' |
||||
export const BranchHeader = () => { |
||||
|
||||
const context = React.useContext(gitPluginContext) |
||||
const actions = React.useContext(gitActionsContext) |
||||
const [changed, setChanged] = useState(false) |
||||
|
||||
useEffect(() => { |
||||
if (context.currentBranch) { |
||||
console.log('GET BRANCH COMMITS', context.currentBranch) |
||||
actions.getBranchDifferences(context.currentBranch, null, context) |
||||
} |
||||
}, [context.currentBranch, context.commits, context.branches, context.remotes]) |
||||
|
||||
useEffect(() => { |
||||
if (context.fileStatusResult) { |
||||
const total = context.allchangesnotstaged.length |
||||
const badges = total + context.staged.length |
||||
setChanged((context.deleted.length > 0 || context.staged.length > 0 || context.untracked.length > 0 || context.modified.length > 0)) |
||||
} |
||||
}, [context.fileStatusResult, context.modified, context.allchangesnotstaged, context.untracked, context.deleted]) |
||||
|
||||
return (<> |
||||
<div className='text-sm w-100'> |
||||
<div className='text-secondary long-and-truncated'> |
||||
<i className="fa fa-code-branch mr-1 pl-2"></i> |
||||
{changed?'*':''}{context.currentBranch && context.currentBranch.name}</div> |
||||
</div> |
||||
<hr></hr> |
||||
</>) |
||||
} |
@ -0,0 +1,139 @@ |
||||
import React, { useEffect } from "react" |
||||
import { useState } from "react" |
||||
import { gitActionsContext } from "../../state/context" |
||||
import { gitPluginContext } from "../gitui" |
||||
import { faArrowDown, faArrowUp, faCheck, faCloudArrowUp, faSync } from "@fortawesome/free-solid-svg-icons"; |
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; |
||||
import { syncStateContext } from "./sourceControlBase"; |
||||
|
||||
enum buttonStateValues { |
||||
Commit , |
||||
Sync = 1, |
||||
PublishBranch = 2 |
||||
} |
||||
|
||||
export const CommitMessage = () => { |
||||
const context = React.useContext(gitPluginContext) |
||||
const actions = React.useContext(gitActionsContext) |
||||
const syncState = React.useContext(syncStateContext) |
||||
const [buttonState, setButtonState] = useState<buttonStateValues>(buttonStateValues.Commit) |
||||
|
||||
const [message, setMessage] = useState({ value: '' }) |
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { |
||||
setMessage({ value: e.currentTarget.value }) |
||||
} |
||||
|
||||
useEffect(() => { |
||||
if (context.fileStatusResult) { |
||||
console.log(context.staged.length + ' staged') |
||||
} |
||||
}, [context.fileStatusResult]) |
||||
|
||||
const commit = async() => { |
||||
if (context.staged.length === 0 && context.allchangesnotstaged.length == 0) return |
||||
if (context.staged.length === 0) |
||||
await actions.addall(context.allchangesnotstaged) |
||||
await actions.commit(message.value) |
||||
setMessage({ value: '' }) |
||||
} |
||||
|
||||
const getRemote = () => { |
||||
return context.upstream ? context.upstream : context.defaultRemote ? context.defaultRemote : null |
||||
} |
||||
|
||||
const sync = async() => { |
||||
await actions.pull({ |
||||
remote: getRemote(), |
||||
ref: context.currentBranch |
||||
}) |
||||
await actions.push({ |
||||
remote: getRemote(), |
||||
ref: context.currentBranch |
||||
}) |
||||
} |
||||
|
||||
const commitNotAllowed = () => { |
||||
return context.canCommit === false || message.value === "" || ( context.staged.length === 0 && context.allchangesnotstaged.length == 0 ) |
||||
} |
||||
|
||||
const commitMessagePlaceholder = () => { |
||||
if (context.currentBranch === undefined || context.currentBranch.name === "") |
||||
return `message` |
||||
return `message ( commit on ${context.currentBranch.name} )` |
||||
} |
||||
|
||||
const syncEnabled = () => { |
||||
return syncState.commitsAhead.length > 0 || syncState.commitsBehind.length > 0 |
||||
} |
||||
|
||||
const upDownArrows = () => { |
||||
return ( |
||||
<> |
||||
{syncState.commitsBehind && syncState.commitsBehind.length ? <>{syncState.commitsBehind.length}<FontAwesomeIcon icon={faArrowDown} className="ml-1" /></>: null} |
||||
{syncState.commitsAhead && syncState.commitsAhead.length ? <>{syncState.commitsAhead.length}<FontAwesomeIcon icon={faArrowUp} className="ml-1" /></>: null} |
||||
</> |
||||
) |
||||
} |
||||
|
||||
const publishEnabled = () => { |
||||
const remoteEquivalentBranch = context.branches.find((b) => b.name === context.currentBranch.name && b.remote) |
||||
return remoteEquivalentBranch === undefined && getRemote()!== null |
||||
} |
||||
|
||||
const publishBranch = async () => { |
||||
if (context.currentBranch === undefined || context.currentBranch.name === "") |
||||
return |
||||
//await actions.push(context.currentBranch.name)
|
||||
} |
||||
|
||||
const messageEnabled = () => { |
||||
return context.canCommit && (context.allchangesnotstaged.length > 0 || context.staged.length > 0) |
||||
} |
||||
|
||||
const setButtonStateValues = () => { |
||||
console.log('setButtonStateValues', context) |
||||
if (!commitNotAllowed() || context.allchangesnotstaged.length > 0 || context.staged.length > 0){ |
||||
if (context.allchangesnotstaged.length == 0 && context.staged.length == 0 && message.value === "" && publishEnabled()){ |
||||
setButtonState(buttonStateValues.PublishBranch) |
||||
return |
||||
} |
||||
setButtonState(buttonStateValues.Commit) |
||||
return |
||||
} |
||||
if (syncEnabled()){ |
||||
setButtonState(buttonStateValues.Sync) |
||||
return |
||||
} |
||||
if (publishEnabled()){ |
||||
setButtonState(buttonStateValues.PublishBranch) |
||||
return |
||||
} |
||||
setButtonState(buttonStateValues.Commit) |
||||
} |
||||
|
||||
useEffect(() => { |
||||
setButtonStateValues() |
||||
},[context.canCommit, context.staged, context.allchangesnotstaged, context.currentBranch, syncState.commitsAhead, syncState.commitsBehind, message.value]) |
||||
|
||||
return ( |
||||
<> |
||||
<div className="form-group"> |
||||
<input placeholder={commitMessagePlaceholder()} data-id='commitMessage' disabled={!messageEnabled()} className="form-control" type="text" onChange={handleChange} value={message.value} /> |
||||
</div> |
||||
<button data-id='commitButton' className={`btn btn-primary w-100 ${buttonState === buttonStateValues.Commit ?'':'d-none'}`} disabled={commitNotAllowed()} onClick={async () => await commit()} > |
||||
<FontAwesomeIcon icon={faCheck} className="mr-1" /> |
||||
Commit |
||||
</button> |
||||
<button data-id='syncButton' className={`btn btn-primary w-100 ${buttonState === buttonStateValues.Sync ?'':'d-none'}`} disabled={!syncEnabled()} onClick={async () => await sync()} > |
||||
<FontAwesomeIcon icon={faSync} className="mr-1" aria-hidden="true" /> |
||||
Sync Changes {upDownArrows()} |
||||
</button> |
||||
<button data-id='publishBranchButton' className={`btn btn-primary w-100 ${buttonState === buttonStateValues.PublishBranch ?'':'d-none'}`} onClick={async () => await publishBranch()} > |
||||
<FontAwesomeIcon icon={faCloudArrowUp} className="mr-1" aria-hidden="true" /> |
||||
Publish Branch |
||||
</button> |
||||
<hr></hr> |
||||
</> |
||||
); |
||||
} |
@ -0,0 +1,24 @@ |
||||
import React, { useContext } from 'react' |
||||
import { gitPluginContext } from '../gitui' |
||||
|
||||
interface ButtonWithContextProps { |
||||
onClick: React.MouseEventHandler<HTMLButtonElement>; |
||||
children: React.ReactNode; |
||||
disabledCondition?: boolean; // Optional additional disabling condition
|
||||
// You can add other props if needed, like 'type', 'className', etc.
|
||||
[key: string]: any; // Allow additional props to be passed
|
||||
} |
||||
|
||||
// This component extends a button, disabling it when loading is true
|
||||
const GitUIButton = ({ children, disabledCondition = false, ...rest }:ButtonWithContextProps) => { |
||||
const { loading } = React.useContext(gitPluginContext) |
||||
|
||||
const isDisabled = loading || disabledCondition |
||||
return ( |
||||
<button disabled={isDisabled} {...rest}> |
||||
{children} |
||||
</button> |
||||
); |
||||
}; |
||||
|
||||
export default GitUIButton; |
@ -0,0 +1,90 @@ |
||||
import { faArrowDown, faArrowUp, faArrowsUpDown, faArrowRotateRight } from "@fortawesome/free-solid-svg-icons" |
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome" |
||||
import { CustomTooltip } from "@remix-ui/helper" |
||||
import { ReadCommitResult } from "isomorphic-git" |
||||
import React, { createContext, useEffect, useState } from "react" |
||||
import { FormattedMessage } from "react-intl" |
||||
import { gitActionsContext } from "../../state/context" |
||||
import { branch, remote } from "../../types" |
||||
import { gitPluginContext } from "../gitui" |
||||
import GitUIButton from "./gituibutton" |
||||
|
||||
interface SourceControlButtonsProps { |
||||
remote?: remote, |
||||
branch?: branch, |
||||
children: React.ReactNode |
||||
} |
||||
|
||||
export const syncStateContext = createContext<{ |
||||
commitsAhead: ReadCommitResult[], |
||||
commitsBehind: ReadCommitResult[] |
||||
branch: branch, |
||||
remote: remote |
||||
}> |
||||
({ commitsAhead: [], commitsBehind: [], branch: undefined, remote: undefined }) |
||||
|
||||
export const SourceControlBase = (props: SourceControlButtonsProps) => { |
||||
const [branch, setBranch] = useState(props.branch) |
||||
const [remote, setRemote] = useState(props.remote) |
||||
const context = React.useContext(gitPluginContext) |
||||
const actions = React.useContext(gitActionsContext) |
||||
const [commitsAhead, setCommitsAhead] = useState<ReadCommitResult[]>([]) |
||||
const [commitsBehind, setCommitsBehind] = useState<ReadCommitResult[]>([]) |
||||
|
||||
useEffect(() => { |
||||
//console.log('BRANCH DIFF SourceControlButtons',branch, remote, context.branchDifferences, context.currentBranch)
|
||||
setDefaultRemote() |
||||
if (remote && branch && context.branchDifferences && context.branchDifferences[`${remote.name}/${branch.name}`]) { |
||||
setCommitsAhead(context.branchDifferences[`${remote.name}/${branch.name}`]?.uniqueHeadCommits) |
||||
setCommitsBehind(context.branchDifferences[`${remote.name}/${branch.name}`]?.uniqueRemoteCommits) |
||||
} else { |
||||
setCommitsAhead([]) |
||||
setCommitsBehind([]) |
||||
} |
||||
}, [context.branchDifferences, context.currentBranch, branch, remote]) |
||||
|
||||
const setDefaultRemote = () => { |
||||
|
||||
if (context.remotes.length > 0) { |
||||
// find remote called origin
|
||||
const origin = context.remotes.find(remote => remote.name === 'origin') |
||||
if (origin) { |
||||
setRemote(origin) |
||||
} else { |
||||
setRemote(context.remotes[0]) |
||||
} |
||||
return origin |
||||
} |
||||
return null |
||||
} |
||||
|
||||
useEffect(() => { |
||||
if (!props.branch) { |
||||
setBranch(context.currentBranch) |
||||
} |
||||
if (!props.remote) { |
||||
setRemote(context.defaultRemote) |
||||
} else { |
||||
setDefaultRemote() |
||||
} |
||||
}, []) |
||||
|
||||
useEffect(() => { |
||||
console.log('context', context.defaultRemote, context.currentBranch) |
||||
if (!props.branch) { |
||||
setBranch(context.currentBranch) |
||||
} |
||||
if (!props.remote) { |
||||
setRemote(context.defaultRemote) |
||||
} else { |
||||
setDefaultRemote() |
||||
} |
||||
}, [context.defaultRemote, context.currentBranch]) |
||||
|
||||
return (<> |
||||
<syncStateContext.Provider value={{ commitsAhead, commitsBehind, branch, remote }}> |
||||
{props.children} |
||||
</syncStateContext.Provider> |
||||
</>) |
||||
|
||||
} |
@ -0,0 +1,101 @@ |
||||
import { faArrowDown, faArrowUp, faArrowsUpDown, faArrowRotateRight } from "@fortawesome/free-solid-svg-icons" |
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome" |
||||
import { CustomTooltip } from "@remix-ui/helper" |
||||
import React, { useEffect, useState } from "react" |
||||
import { FormattedMessage } from "react-intl" |
||||
import { gitActionsContext } from "../../state/context" |
||||
import { branch, remote } from "../../types" |
||||
import { gitPluginContext } from "../gitui" |
||||
import GitUIButton from "./gituibutton" |
||||
import { syncStateContext } from "./sourceControlBase" |
||||
|
||||
export const SourceControlButtons = () => { |
||||
const context = React.useContext(gitPluginContext) |
||||
const actions = React.useContext(gitActionsContext) |
||||
const syncState = React.useContext(syncStateContext) |
||||
const [branch, setBranch] = useState<branch>(syncState.branch) |
||||
const [remote, setRemote] = useState<remote>(syncState.remote) |
||||
|
||||
useEffect(() => { |
||||
console.log('SC BUTTONS', branch, remote) |
||||
}, []) |
||||
|
||||
const getRemote = () => { |
||||
return remote ? remote : context.upstream ? context.upstream : context.defaultRemote ? context.defaultRemote : null |
||||
} |
||||
|
||||
const getRemoteName = () => { |
||||
return getRemote() ? getRemote().name : '' |
||||
} |
||||
|
||||
const pull = async () => { |
||||
await actions.pull({ |
||||
remote: getRemote(), |
||||
ref: branch ? branch : context.currentBranch |
||||
}) |
||||
} |
||||
|
||||
const push = async () => { |
||||
await actions.push({ |
||||
remote: getRemote(), |
||||
ref: branch ? branch : context.currentBranch |
||||
}) |
||||
await actions.fetch({ |
||||
remote: getRemote(), |
||||
ref: branch ? branch : context.currentBranch, |
||||
relative: false, |
||||
depth: 1, |
||||
singleBranch: true |
||||
}) |
||||
} |
||||
|
||||
const sync = async () => { |
||||
await pull() |
||||
await push() |
||||
} |
||||
|
||||
const refresh = async() => { |
||||
actions.getFileStatusMatrix(null) |
||||
} |
||||
|
||||
const buttonsDisabled = () => { |
||||
return (!context.upstream) || context.remotes.length === 0 |
||||
} |
||||
|
||||
const getTooltipText = (id: string) => { |
||||
if (buttonsDisabled()) return <FormattedMessage id="git.noremote" /> |
||||
return <><FormattedMessage id={id} /> {getRemoteName()}</> |
||||
} |
||||
|
||||
return ( |
||||
<span className='d-flex justify-content-end align-items-center'> |
||||
<CustomTooltip tooltipText={getTooltipText('git.pull')}> |
||||
<GitUIButton disabledCondition={buttonsDisabled()} onClick={pull} className='btn btn-sm pl-0 pr-2'> |
||||
<div className="d-flex align-items-baseline"> |
||||
{syncState.commitsBehind.length ? <div className="badge badge-pill pl-0"> |
||||
{syncState.commitsBehind.length} |
||||
</div> : null} |
||||
<FontAwesomeIcon icon={faArrowDown} className="" /> |
||||
</div> |
||||
</GitUIButton> |
||||
</CustomTooltip> |
||||
<CustomTooltip tooltipText={getTooltipText('git.push')}> |
||||
<GitUIButton disabledCondition={buttonsDisabled()} onClick={push} className='btn btn-sm pl-0 pr-2'> |
||||
<div className="d-flex align-items-baseline"> |
||||
{syncState.commitsAhead.length ? <div className="badge badge-pill pl-0"> |
||||
{syncState.commitsAhead.length} |
||||
</div> : null} |
||||
<FontAwesomeIcon icon={faArrowUp} className="" /> |
||||
</div> |
||||
</GitUIButton> |
||||
</CustomTooltip> |
||||
<CustomTooltip tooltipText={getTooltipText('git.sync')}> |
||||
<GitUIButton disabledCondition={buttonsDisabled()} onClick={sync} className='btn btn-sm pl-0 pr-2'><FontAwesomeIcon icon={faArrowsUpDown} className="" /></GitUIButton> |
||||
</CustomTooltip> |
||||
<CustomTooltip tooltipText={<FormattedMessage id="git.refresh" />}> |
||||
<GitUIButton onClick={refresh} className='btn btn-sm'><FontAwesomeIcon icon={faArrowRotateRight} className="" /></GitUIButton> |
||||
</CustomTooltip> |
||||
</span> |
||||
|
||||
) |
||||
} |
@ -0,0 +1,49 @@ |
||||
import React, { useState, useCallback, useEffect } from 'react'; |
||||
import Select from 'react-select'; |
||||
import { gitActionsContext } from '../../state/context'; |
||||
import { selectStyles, selectTheme } from '../../types/styles'; |
||||
import { gitPluginContext } from '../gitui'; |
||||
|
||||
interface BranchySelectProps { |
||||
select: (branch:{ name: string }) => void; |
||||
} |
||||
|
||||
export const BranchSelect = (props: BranchySelectProps) => { |
||||
const context = React.useContext(gitPluginContext) |
||||
const actions = React.useContext(gitActionsContext) |
||||
const [branchOptions, setBranchOptions] = useState<any>([]); |
||||
|
||||
useEffect(() => { |
||||
if (context.remoteBranches && context.remoteBranches.length > 0) { |
||||
const options = context.remoteBranches && context.remoteBranches.length > 0 && context.remoteBranches.map(branch => { |
||||
return { value: branch.name, label: branch.name } |
||||
} |
||||
) |
||||
setBranchOptions(options) |
||||
} else { |
||||
setBranchOptions(null) |
||||
} |
||||
}, [context.remoteBranches]) |
||||
|
||||
const selectRemoteBranch = async (e: any) => { |
||||
if (!e || !e.value) { |
||||
props.select(null) |
||||
return |
||||
} |
||||
const value = e && e.value |
||||
props.select({ name: value.toString() }) |
||||
} |
||||
|
||||
return (<>{branchOptions && branchOptions.length ? |
||||
<Select |
||||
options={branchOptions} |
||||
className="mt-1" |
||||
onChange={(e: any) =>selectRemoteBranch(e)} |
||||
theme={selectTheme} |
||||
styles={selectStyles} |
||||
isClearable={true} |
||||
placeholder="Type to search for a branch..." |
||||
/> : null} |
||||
</>) |
||||
|
||||
} |
@ -0,0 +1,145 @@ |
||||
import React, { useEffect } from "react"; |
||||
import { gitActionsContext, pluginActionsContext } from "../../state/context"; |
||||
import { gitPluginContext } from "../gitui"; |
||||
import axios from "axios"; |
||||
import { CopyToClipboard } from "@remix-ui/clipboard"; |
||||
import { Card } from "react-bootstrap"; |
||||
|
||||
export const GetDeviceCode = () => { |
||||
const context = React.useContext(gitPluginContext) |
||||
const actions = React.useContext(gitActionsContext) |
||||
const pluginActions = React.useContext(pluginActionsContext) |
||||
const [gitHubResponse, setGitHubResponse] = React.useState<any>(null) |
||||
const [authorized, setAuthorized] = React.useState<boolean>(false) |
||||
|
||||
const getDeviceCodeFromGitHub = async () => { |
||||
|
||||
setAuthorized(false) |
||||
// Send a POST request
|
||||
const response = await axios({ |
||||
method: 'post', |
||||
url: 'https://github.remixproject.org/login/device/code', |
||||
data: { |
||||
client_id: '2795b4e41e7197d6ea11', |
||||
scope: 'repo gist user:email read:user' |
||||
}, |
||||
headers: { |
||||
'Content-Type': 'application/json', |
||||
'Accept': 'application/json' |
||||
}, |
||||
}); |
||||
|
||||
// convert response to json
|
||||
const githubrespone = await response.data; |
||||
console.log('json', githubrespone) |
||||
|
||||
setGitHubResponse(githubrespone) |
||||
} |
||||
|
||||
const connectApp = async () => { |
||||
// poll https://github.com/login/oauth/access_token
|
||||
const accestokenresponse = await axios({ |
||||
method: 'post', |
||||
url: 'https://github.remixproject.org/login/oauth/access_token', |
||||
data: { |
||||
client_id: '2795b4e41e7197d6ea11', |
||||
device_code: gitHubResponse.device_code, |
||||
grant_type: 'urn:ietf:params:oauth:grant-type:device_code' |
||||
}, |
||||
headers: { |
||||
'Content-Type': 'application/json', |
||||
'Accept': 'application/json' |
||||
}, |
||||
}); |
||||
|
||||
// convert response to json
|
||||
const response = await accestokenresponse.data; |
||||
console.log('json2', response) |
||||
|
||||
if (response.error) { |
||||
|
||||
} |
||||
|
||||
if (response.access_token) { |
||||
setAuthorized(true) |
||||
await pluginActions.saveToken(response.access_token) |
||||
await actions.loadGitHubUserFromToken() |
||||
} |
||||
|
||||
} |
||||
|
||||
const disconnect = async () => { |
||||
setAuthorized(false) |
||||
setGitHubResponse(null) |
||||
await pluginActions.saveToken(null) |
||||
await actions.loadGitHubUserFromToken() |
||||
} |
||||
|
||||
const checkConnection = async () => { |
||||
//await actions.loadGitHubUserFromToken()
|
||||
} |
||||
|
||||
useEffect(() => { |
||||
|
||||
}, []) |
||||
|
||||
useEffect(() => { |
||||
console.log('context.rateLimit', context.rateLimit) |
||||
}, [context.rateLimit]) |
||||
|
||||
return ( |
||||
<> |
||||
{(context.gitHubUser && context.gitHubUser.login) ? null : |
||||
<button className='btn btn-primary mt-1 w-100' onClick={async () => { |
||||
getDeviceCodeFromGitHub(); |
||||
}}><i className="fab fa-github mr-1"></i>Login in with github</button> |
||||
} |
||||
{gitHubResponse && !authorized && |
||||
<div className="pt-2"> |
||||
|
||||
Step 1: Copy this code: |
||||
<div className="input-group text-secondary mb-0 h6"> |
||||
<input disabled type="text" className="form-control" value={gitHubResponse.user_code} /> |
||||
<div className="input-group-append"> |
||||
<CopyToClipboard content={gitHubResponse.user_code} data-id='copyToClipboardCopyIcon' className='far fa-copy ml-1 p-2 mt-1' direction={"top"} /> |
||||
</div> |
||||
</div> |
||||
<br></br> |
||||
Step 2: Authorize the app here |
||||
<br></br><a target="_blank" href={gitHubResponse.verification_uri}>{gitHubResponse.verification_uri}</a> |
||||
<br /><br></br> |
||||
Step 3: When you are done, click on the button below: |
||||
<button className='btn btn-primary mt-1 w-100' onClick={async () => { |
||||
connectApp() |
||||
}}>Connect</button> |
||||
</div> |
||||
} |
||||
{ |
||||
(context.gitHubUser && context.gitHubUser.login) ? |
||||
<div className="pt-2"> |
||||
<button className='btn btn-primary mt-1 w-100' onClick={async () => { |
||||
disconnect() |
||||
}}>Disconnect</button> |
||||
</div> : null |
||||
} |
||||
{ |
||||
(context.gitHubUser && context.gitHubUser.login) ? |
||||
<div className="pt-2"> |
||||
<Card> |
||||
<Card.Body> |
||||
<Card.Title>Connected as {context.gitHubUser.login}</Card.Title> |
||||
<Card.Text> |
||||
<img src={context.gitHubUser.avatar_url} className="w-100" /> |
||||
<a target="_blank" href={context.gitHubUser.html_url}>{context.gitHubUser.html_url}</a> |
||||
{context.userEmails && context.userEmails.filter((email: any) => email.primary).map((email: any) => { |
||||
return <span key={email.email}><br></br>{email.email}</span> |
||||
})} |
||||
</Card.Text> |
||||
</Card.Body> |
||||
</Card> |
||||
|
||||
</div> : null |
||||
} |
||||
|
||||
</>) |
||||
} |
@ -0,0 +1,86 @@ |
||||
import React, { useState, useEffect } from 'react'; |
||||
import { Button } from 'react-bootstrap'; |
||||
import Select from 'react-select'; |
||||
import { gitActionsContext } from '../../state/context'; |
||||
import { repository } from '../../types'; |
||||
import { selectStyles, selectTheme } from '../../types/styles'; |
||||
import { gitPluginContext } from '../gitui'; |
||||
import { TokenWarning } from '../panels/tokenWarning'; |
||||
|
||||
interface RepositorySelectProps { |
||||
select: (repo: repository) => void; |
||||
} |
||||
|
||||
const RepositorySelect = (props: RepositorySelectProps) => { |
||||
const [repoOtions, setRepoOptions] = useState<any>([]); |
||||
const context = React.useContext(gitPluginContext) |
||||
const actions = React.useContext(gitActionsContext) |
||||
const [loading, setLoading] = useState(false) |
||||
const [show, setShow] = useState(false) |
||||
|
||||
useEffect(() => { |
||||
if (context.repositories && context.repositories.length > 0) { |
||||
// map context.repositories to options
|
||||
const options = context.repositories && context.repositories.length > 0 && context.repositories.map(repo => { |
||||
return { value: repo.id, label: repo.full_name } |
||||
}) |
||||
|
||||
setRepoOptions(options) |
||||
setShow(options.length > 0) |
||||
} else { |
||||
setRepoOptions(null) |
||||
setShow(false) |
||||
} |
||||
setLoading(false) |
||||
|
||||
}, [context.repositories]) |
||||
|
||||
const selectRepo = async (e: any) => { |
||||
if (!e || !e.value) { |
||||
props.select(null) |
||||
return |
||||
} |
||||
const value = e && e.value |
||||
|
||||
const repo = context.repositories.find(repo => { |
||||
return repo.id.toString() === value.toString() |
||||
}) |
||||
|
||||
if (repo) { |
||||
props.select(repo) |
||||
await actions.remoteBranches(repo.owner.login, repo.name) |
||||
} |
||||
} |
||||
|
||||
const fetchRepositories = async () => { |
||||
try { |
||||
setShow(true) |
||||
setLoading(true) |
||||
setRepoOptions([]) |
||||
console.log(await actions.repositories()) |
||||
} catch (e) { |
||||
// do nothing
|
||||
} |
||||
}; |
||||
|
||||
return ( |
||||
<><Button onClick={fetchRepositories} className="w-100 mt-1"> |
||||
<i className="fab fa-github mr-1"></i>Fetch Repositories from GitHub |
||||
</Button> |
||||
{ |
||||
show ? |
||||
<Select |
||||
options={repoOtions} |
||||
className="mt-1" |
||||
onChange={(e: any) => selectRepo(e)} |
||||
theme={selectTheme} |
||||
styles={selectStyles} |
||||
isClearable={true} |
||||
placeholder="Type to search for a repository..." |
||||
isLoading={loading} |
||||
/> : null |
||||
}</> |
||||
); |
||||
}; |
||||
|
||||
export default RepositorySelect; |
@ -0,0 +1,59 @@ |
||||
import React, { useEffect, useState } from "react"; |
||||
import { gitActionsContext } from "../../state/context"; |
||||
import { repository } from "../../types"; |
||||
import { gitPluginContext } from "../gitui"; |
||||
import RepositorySelect from "./repositoryselect"; |
||||
import { BranchSelect } from "./branchselect"; |
||||
import { TokenWarning } from "../panels/tokenWarning"; |
||||
|
||||
interface RepositoriesProps { |
||||
cloneDepth?: number |
||||
cloneAllBranches?: boolean |
||||
} |
||||
|
||||
export const SelectAndCloneRepositories = (props: RepositoriesProps) => { |
||||
const { cloneDepth, cloneAllBranches } = props |
||||
const context = React.useContext(gitPluginContext) |
||||
const actions = React.useContext(gitActionsContext) |
||||
const [branch, setBranch] = useState({ name: "" }); |
||||
const [repo, setRepo] = useState<repository>(null); |
||||
|
||||
const selectRemoteBranch = async (branch:{ name: string }) => { |
||||
setBranch(branch) |
||||
} |
||||
|
||||
const selectRepo = async (repo: repository) => { |
||||
setBranch(null) |
||||
setRepo(repo) |
||||
} |
||||
|
||||
const clone = async () => { |
||||
try { |
||||
await actions.clone({ |
||||
url: repo.html_url, |
||||
branch: branch.name, |
||||
depth: cloneDepth, |
||||
singleBranch: !cloneAllBranches |
||||
}) |
||||
//actions.clone(repo.html_url, branch.name, cloneDepth, !cloneAllBranches)
|
||||
} catch (e) { |
||||
// do nothing
|
||||
} |
||||
}; |
||||
|
||||
return ( |
||||
<> |
||||
<TokenWarning /> |
||||
<RepositorySelect select={selectRepo} /> |
||||
|
||||
{repo &&<BranchSelect select={selectRemoteBranch} />} |
||||
|
||||
{repo && branch && branch.name && branch.name !== '0' ? |
||||
<button data-id='clonebtn' className='btn btn-primary mt-1 w-100' onClick={async () => { |
||||
await clone() |
||||
}}>clone {repo.full_name}:{branch.name}</button> : null} |
||||
|
||||
</> |
||||
) |
||||
} |
||||
|
@ -0,0 +1,234 @@ |
||||
import React, { useEffect, useReducer, useState } from 'react' |
||||
import { add, addall, checkout, checkoutfile, clone, commit, createBranch, remoteBranches, repositories, rm, getCommitChanges, diff, resolveRef, getBranchCommits, setUpstreamRemote, loadGitHubUserFromToken, getBranches, getRemotes, remoteCommits, saveGitHubCredentials, getGitHubCredentialsFromLocalStorage, fetch, pull, push, setDefaultRemote, addRemote, removeRemote, sendToGitLog, clearGitLog, getBranchDifferences, getFileStatusMatrix, init } from '../lib/gitactions' |
||||
import { loadFiles, setCallBacks } from '../lib/listeners' |
||||
import { openDiff, openFile, saveToken, setModifiedDecorator, setPlugin, setUntrackedDecorator, statusChanged } from '../lib/pluginActions' |
||||
import { gitActionsContext, pluginActionsContext } from '../state/context' |
||||
import { gitReducer } from '../state/gitreducer' |
||||
import { defaultGitState, defaultLoaderState, gitState, loaderState } from '../types' |
||||
import { Accordion } from "react-bootstrap"; |
||||
import { CommitMessage } from './buttons/commitmessage' |
||||
import { Commits } from './panels/commits' |
||||
import { Branches } from './panels/branches' |
||||
import { SourceControlNavigation } from './navigation/sourcecontrol' |
||||
import { BranchesNavigation } from './navigation/branches' |
||||
import { CommitsNavigation } from './navigation/commits' |
||||
import '../style/index.css' |
||||
import { CloneNavigation } from './navigation/clone' |
||||
import { Clone } from './panels/clone' |
||||
import { Commands } from './panels/commands' |
||||
import { CommandsNavigation } from './navigation/commands' |
||||
import { RemotesNavigation } from './navigation/remotes' |
||||
import { Remotes } from './panels/remotes' |
||||
import { ViewPlugin } from '@remixproject/engine-web' |
||||
import { GitHubNavigation } from './navigation/github' |
||||
import { loaderReducer } from '../state/loaderReducer' |
||||
import { GetDeviceCode } from './github/devicecode' |
||||
import { LogNavigation } from './navigation/log' |
||||
import LogViewer from './panels/log' |
||||
import { SourceControlBase } from './buttons/sourceControlBase' |
||||
import { BranchHeader } from './branchHeader' |
||||
import { SourceControl } from './panels/sourcontrol' |
||||
import { GitHubCredentials } from './panels/githubcredentials' |
||||
import { Setup } from './panels/setup' |
||||
import { Init } from './panels/init' |
||||
import { CustomRemixApi } from "@remix-api"; |
||||
import { Plugin } from "@remixproject/engine"; |
||||
|
||||
export const gitPluginContext = React.createContext<gitState>(defaultGitState) |
||||
export const loaderContext = React.createContext<loaderState>(defaultLoaderState) |
||||
|
||||
interface IGitUi { |
||||
plugin: Plugin<any, CustomRemixApi> |
||||
} |
||||
|
||||
export const GitUI = (props: IGitUi) => { |
||||
const plugin = props.plugin |
||||
const [gitState, gitDispatch] = useReducer(gitReducer, defaultGitState) |
||||
const [loaderState, loaderDispatch] = useReducer(loaderReducer, defaultLoaderState) |
||||
const [activePanel, setActivePanel] = useState<string>("0") |
||||
const [setup, setSetup] = useState<boolean>(false) |
||||
const [needsInit, setNeedsInit] = useState<boolean>(true) |
||||
const [appLoaded, setAppLoaded] = useState<boolean>(false) |
||||
|
||||
useEffect(() => { |
||||
plugin.emit('statusChanged', { |
||||
key:'loading', |
||||
type: 'info', |
||||
title: 'Loading Git Plugin' |
||||
}) |
||||
setTimeout(() => { |
||||
setAppLoaded(true) |
||||
}, 2000) |
||||
},[]) |
||||
|
||||
useEffect(() => { |
||||
if (!appLoaded) return |
||||
setCallBacks(plugin, gitDispatch, loaderDispatch) |
||||
setPlugin(plugin, gitDispatch, loaderDispatch) |
||||
loaderDispatch({ type: 'plugin', payload: true }) |
||||
console.log(props) |
||||
}, [appLoaded]) |
||||
|
||||
useEffect(() => { |
||||
if (!appLoaded) return |
||||
async function checkconfig() { |
||||
|
||||
const username = await plugin.call('settings', 'get', 'settings/github-user-name') |
||||
const email = await plugin.call('settings', 'get', 'settings/github-email') |
||||
const token = await plugin.call('settings', 'get', 'settings/gist-access-token') |
||||
console.log('gitState', gitState, username, email, token) |
||||
setSetup(!(username && email)) |
||||
} |
||||
checkconfig() |
||||
}, [gitState.gitHubAccessToken, gitState.gitHubUser, gitState.userEmails]) |
||||
|
||||
useEffect(() => { |
||||
if (!appLoaded) return |
||||
async function setDecorators(gitState: gitState) { |
||||
await plugin.call('fileDecorator', 'clearFileDecorators') |
||||
await setModifiedDecorator(gitState.modified) |
||||
await setUntrackedDecorator(gitState.untracked) |
||||
} |
||||
|
||||
setTimeout(() => { |
||||
setDecorators(gitState) |
||||
}) |
||||
|
||||
}, [gitState.fileStatusResult]) |
||||
|
||||
useEffect(() => { |
||||
if (!appLoaded) return |
||||
async function updatestate() { |
||||
console.log('updatestate', gitState) |
||||
if (gitState.currentBranch && gitState.currentBranch.remote && gitState.currentBranch.remote.url) { |
||||
remoteCommits(gitState.currentBranch.remote.url, gitState.currentBranch.name, 1) |
||||
} |
||||
} |
||||
setTimeout(() => { |
||||
updatestate() |
||||
}) |
||||
|
||||
setNeedsInit(!(gitState.currentBranch && gitState.currentBranch.name !== '')) |
||||
|
||||
}, [gitState.gitHubUser, gitState.currentBranch, gitState.remotes, gitState.gitHubAccessToken]) |
||||
|
||||
const gitActionsProviderValue = { |
||||
commit, |
||||
addall, |
||||
add, |
||||
checkoutfile, |
||||
rm, |
||||
checkout, |
||||
createBranch, |
||||
clone, |
||||
repositories, |
||||
remoteBranches, |
||||
getCommitChanges, |
||||
getBranchCommits, |
||||
getBranchDifferences, |
||||
diff, |
||||
resolveRef, |
||||
setUpstreamRemote, |
||||
loadGitHubUserFromToken: loadGitHubUserFromToken, |
||||
getBranches, |
||||
getRemotes, |
||||
fetch, |
||||
pull, |
||||
push, |
||||
setDefaultRemote, |
||||
addRemote, |
||||
removeRemote, |
||||
sendToGitLog, |
||||
clearGitLog, |
||||
getFileStatusMatrix, |
||||
init |
||||
} |
||||
|
||||
const pluginActionsProviderValue = { |
||||
statusChanged, |
||||
loadFiles, |
||||
openFile, |
||||
openDiff, |
||||
saveToken, |
||||
saveGitHubCredentials, |
||||
getGitHubCredentialsFromLocalStorage |
||||
} |
||||
|
||||
return ( |
||||
<div className="m-1"> |
||||
<gitPluginContext.Provider value={gitState}> |
||||
<loaderContext.Provider value={loaderState}> |
||||
<gitActionsContext.Provider value={gitActionsProviderValue}> |
||||
<BranchHeader /> |
||||
<pluginActionsContext.Provider value={pluginActionsProviderValue}> |
||||
{setup && !needsInit ? <Setup></Setup> : null} |
||||
{needsInit ? <Init></Init> : null} |
||||
{!setup && !needsInit ? |
||||
<Accordion activeKey={activePanel} defaultActiveKey="0"> |
||||
<SourceControlNavigation eventKey="0" activePanel={activePanel} callback={setActivePanel} /> |
||||
|
||||
<Accordion.Collapse className='bg-light' eventKey="0"> |
||||
<> |
||||
<SourceControlBase><CommitMessage /></SourceControlBase> |
||||
<SourceControl /> |
||||
</> |
||||
</Accordion.Collapse> |
||||
<hr></hr> |
||||
<CommandsNavigation eventKey="1" activePanel={activePanel} callback={setActivePanel} /> |
||||
<Accordion.Collapse className='bg-light' eventKey="1"> |
||||
<> |
||||
<Commands></Commands> |
||||
</> |
||||
</Accordion.Collapse> |
||||
<hr></hr> |
||||
<CommitsNavigation title={`COMMITS`} eventKey="3" activePanel={activePanel} callback={setActivePanel} showButtons={true} /> |
||||
<Accordion.Collapse className='bg-light' eventKey="3"> |
||||
<> |
||||
<Commits /> |
||||
</> |
||||
</Accordion.Collapse> |
||||
<hr></hr> |
||||
<BranchesNavigation eventKey="2" activePanel={activePanel} callback={setActivePanel} /> |
||||
<Accordion.Collapse className='bg-light' eventKey="2"> |
||||
<> |
||||
<Branches /></> |
||||
</Accordion.Collapse> |
||||
<hr></hr> |
||||
<RemotesNavigation eventKey="5" activePanel={activePanel} callback={setActivePanel} /> |
||||
<Accordion.Collapse className='bg-light' eventKey="5"> |
||||
<> |
||||
<Remotes></Remotes> |
||||
</> |
||||
</Accordion.Collapse> |
||||
<hr></hr> |
||||
<CloneNavigation eventKey="4" activePanel={activePanel} callback={setActivePanel} /> |
||||
<Accordion.Collapse className='bg-light' eventKey="4"> |
||||
<> |
||||
<Clone /></> |
||||
</Accordion.Collapse> |
||||
<hr></hr> |
||||
<GitHubNavigation eventKey="7" activePanel={activePanel} callback={setActivePanel} /> |
||||
<Accordion.Collapse className='bg-light' eventKey="7"> |
||||
<> |
||||
<GetDeviceCode></GetDeviceCode> |
||||
<hr></hr> |
||||
<GitHubCredentials></GitHubCredentials> |
||||
</> |
||||
</Accordion.Collapse> |
||||
<hr></hr> |
||||
<LogNavigation eventKey="6" activePanel={activePanel} callback={setActivePanel} /> |
||||
<Accordion.Collapse className='bg-light' eventKey="6"> |
||||
<> |
||||
<LogViewer /> |
||||
</> |
||||
</Accordion.Collapse> |
||||
|
||||
</Accordion> |
||||
: null} |
||||
</pluginActionsContext.Provider> |
||||
</gitActionsContext.Provider> |
||||
</loaderContext.Provider> |
||||
</gitPluginContext.Provider> |
||||
</div> |
||||
) |
||||
} |
@ -0,0 +1,97 @@ |
||||
import { faCaretUp, faCaretDown, faCaretRight, faArrowUp, faArrowDown, faArrowRotateRight, faArrowsUpDown, faGlobe, faCheckCircle, faToggleOff, faToggleOn, faSync } from "@fortawesome/free-solid-svg-icons"; |
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; |
||||
import React, { useContext, useEffect } from "react"; |
||||
import { gitActionsContext } from "../../state/context"; |
||||
import { branch } from "../../types"; |
||||
import GitUIButton from "../buttons/gituibutton"; |
||||
import { gitPluginContext } from "../gitui"; |
||||
|
||||
interface BrancheDetailsNavigationProps { |
||||
eventKey: string; |
||||
activePanel: string; |
||||
callback: (eventKey: string) => void; |
||||
branch: branch; |
||||
checkout: (branch: branch) => void; |
||||
} |
||||
|
||||
export const BrancheDetailsNavigation = (props: BrancheDetailsNavigationProps) => { |
||||
const { eventKey, activePanel, callback, branch, checkout } = props; |
||||
const context = React.useContext(gitPluginContext) |
||||
const actions = React.useContext(gitActionsContext) |
||||
const handleClick = () => { |
||||
if (!callback) return |
||||
if (activePanel === eventKey) { |
||||
callback('') |
||||
} else { |
||||
callback(eventKey) |
||||
} |
||||
} |
||||
|
||||
const getRemote = () => { |
||||
return context.upstream ? context.upstream : context.defaultRemote ? context.defaultRemote : null |
||||
} |
||||
|
||||
const openRemote = () => { |
||||
const remote = branch.remote || getRemote() |
||||
window.open(`${remote.url}/tree/${branch.name}`, '_blank'); |
||||
} |
||||
|
||||
const reloadBranch = () => { |
||||
actions.getBranchCommits(branch, 1) |
||||
} |
||||
|
||||
const canFetch = () => { |
||||
if (getRemote()) |
||||
return context.branches.find((b) => b.name === branch.name && b.remote && b.remote.url === getRemote().url) ? true : false |
||||
} |
||||
|
||||
const fetchBranch = async () => { |
||||
await actions.fetch({ |
||||
remote: null, |
||||
ref: branch, |
||||
singleBranch: true, |
||||
relative: true |
||||
}) |
||||
//actions.fetch(null, branch.name, null, null, false, true)
|
||||
} |
||||
|
||||
return ( |
||||
<> |
||||
<div className="d-flex flex-row w-100 mb-2 mt-2"> |
||||
<div onClick={() => handleClick()} role={'button'} className='pointer d-flex flex-row w-100 commit-navigation'> |
||||
{ |
||||
activePanel === eventKey ? <FontAwesomeIcon className='' icon={faCaretDown}></FontAwesomeIcon> : <FontAwesomeIcon className='' icon={faCaretRight}></FontAwesomeIcon> |
||||
} |
||||
<i className="fa fa-code-branch ml-1"></i> |
||||
<div className={`ml-1 ${context.currentBranch.name === branch.name ? 'text-success' : ''}`}>{branch.name} {branch.remote ? `on ${branch.remote.name}` : ''}</div> |
||||
|
||||
</div> |
||||
{context.currentBranch && context.currentBranch.name === branch.name ? |
||||
<GitUIButton className="btn btn-sm p-0 mr-1" onClick={() => { }}> |
||||
<FontAwesomeIcon className='pointer text-success' icon={faToggleOff} ></FontAwesomeIcon> |
||||
</GitUIButton> |
||||
: |
||||
<GitUIButton className="btn btn-sm p-0 mr-1" onClick={() => checkout(branch)}> |
||||
<FontAwesomeIcon icon={faToggleOn}></FontAwesomeIcon> |
||||
</GitUIButton> |
||||
} |
||||
{!branch.remote && canFetch() && <> |
||||
<GitUIButton className="btn btn-sm p-0 mr-1 text-muted" onClick={() => fetchBranch()}><FontAwesomeIcon icon={faSync} ></FontAwesomeIcon></GitUIButton> |
||||
<GitUIButton className="btn btn-sm p-0 mr-1 text-muted" onClick={() => openRemote()}><FontAwesomeIcon icon={faGlobe} ></FontAwesomeIcon></GitUIButton> |
||||
</>} |
||||
{branch.remote?.url && <> |
||||
<GitUIButton className="btn btn-sm p-0 mr-1 text-muted" onClick={() => reloadBranch()}> |
||||
<FontAwesomeIcon icon={faSync} ></FontAwesomeIcon> |
||||
</GitUIButton> |
||||
</>} |
||||
|
||||
{branch.remote?.url && <> |
||||
<GitUIButton className="btn btn-sm p-0 mr-1 text-muted" onClick={() => openRemote()}> |
||||
<FontAwesomeIcon icon={faGlobe} ></FontAwesomeIcon> |
||||
</GitUIButton> |
||||
</>} |
||||
|
||||
</div> |
||||
</> |
||||
); |
||||
} |
@ -0,0 +1,32 @@ |
||||
import { faCaretDown, faCaretRight } from "@fortawesome/free-solid-svg-icons"; |
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; |
||||
import React, { } from "react"; |
||||
import { gitActionsContext, pluginActionsContext } from "../../state/context"; |
||||
import LoaderIndicator from "./loaderindicator"; |
||||
|
||||
export const BranchesNavigation = ({ eventKey, activePanel, callback }) => { |
||||
const pluginactions = React.useContext(pluginActionsContext) |
||||
const context = React.useContext(gitActionsContext) |
||||
|
||||
const handleClick = () => { |
||||
if (!callback) return |
||||
if (activePanel === eventKey) { |
||||
callback('') |
||||
} else { |
||||
callback(eventKey) |
||||
} |
||||
} |
||||
return ( |
||||
<> |
||||
<div className={'d-flex justify-content-between pt-1 ' + (activePanel === eventKey? 'bg-light': '')}> |
||||
<span onClick={()=>handleClick()} role={'button'} className='nav d-flex justify-content-start align-items-center w-75'> |
||||
{ |
||||
activePanel === eventKey ? <FontAwesomeIcon className='' icon={faCaretDown}></FontAwesomeIcon> : <FontAwesomeIcon className='' icon={faCaretRight}></FontAwesomeIcon> |
||||
} |
||||
<label className="pl-1 nav form-check-label">BRANCHES</label> |
||||
<LoaderIndicator></LoaderIndicator> |
||||
</span> |
||||
</div> |
||||
</> |
||||
); |
||||
} |
@ -0,0 +1,29 @@ |
||||
import { faCaretUp, faCaretDown, faArrowUp, faArrowDown, faArrowRotateRight, faCaretRight } from "@fortawesome/free-solid-svg-icons"; |
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; |
||||
import React, { useContext, useEffect } from "react"; |
||||
import LoaderIndicator from "./loaderindicator"; |
||||
|
||||
export const CloneNavigation = ({ eventKey, activePanel, callback }) => { |
||||
|
||||
const handleClick = () => { |
||||
if (!callback) return |
||||
if (activePanel === eventKey) { |
||||
callback('') |
||||
} else { |
||||
callback(eventKey) |
||||
} |
||||
} |
||||
return ( |
||||
<> |
||||
<div className={'d-flex justify-content-between pb-1 pt-1 ' + (activePanel === eventKey? 'bg-light': '')}> |
||||
<span onClick={()=>handleClick()} role={'button'} className='nav d-flex justify-content-start align-items-center w-75'> |
||||
{ |
||||
activePanel === eventKey ? <FontAwesomeIcon className='' icon={faCaretDown}></FontAwesomeIcon> : <FontAwesomeIcon className='' icon={faCaretRight}></FontAwesomeIcon> |
||||
} |
||||
<label className="pl-1 nav form-check-label">CLONE</label> |
||||
<LoaderIndicator></LoaderIndicator> |
||||
</span> |
||||
</div> |
||||
</> |
||||
); |
||||
} |
@ -0,0 +1,35 @@ |
||||
import { faCaretUp, faCaretDown, faArrowUp, faArrowDown, faArrowRotateRight, faCaretRight, faCircleCheck, faArrowsUpDown, faSpinner } from "@fortawesome/free-solid-svg-icons"; |
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; |
||||
import React, { useContext, useEffect } from "react"; |
||||
import { pluginActionsContext } from "../../state/context"; |
||||
import GitUIButton from "../buttons/gituibutton"; |
||||
import { SourceControlButtons } from "../buttons/sourcecontrolbuttons"; |
||||
import LoaderIndicator from "./loaderindicator"; |
||||
|
||||
export const CommandsNavigation = ({ eventKey, activePanel, callback }) => { |
||||
const pluginactions = React.useContext(pluginActionsContext) |
||||
|
||||
const handleClick = () => { |
||||
if (!callback) return |
||||
if (activePanel === eventKey) { |
||||
callback('') |
||||
} else { |
||||
callback(eventKey) |
||||
} |
||||
} |
||||
|
||||
return ( |
||||
<> |
||||
<div className={'d-flex justify-content-between ' + (activePanel === eventKey ? 'bg-light' : '')}> |
||||
<span onClick={() => handleClick()} role={'button'} className='nav d-flex justify-content-start align-items-center w-75'> |
||||
{ |
||||
activePanel === eventKey ? <FontAwesomeIcon className='' icon={faCaretDown}></FontAwesomeIcon> : <FontAwesomeIcon className='' icon={faCaretRight}></FontAwesomeIcon> |
||||
} |
||||
<label className="pl-1 nav form-check-label">COMMANDS</label> |
||||
<LoaderIndicator></LoaderIndicator> |
||||
|
||||
</span> |
||||
</div> |
||||
</> |
||||
); |
||||
} |
@ -0,0 +1,37 @@ |
||||
import { faCaretUp, faCaretDown, faCaretRight, faArrowUp, faArrowDown, faArrowRotateRight } from "@fortawesome/free-solid-svg-icons"; |
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; |
||||
import React, { useContext, useEffect } from "react"; |
||||
import { CommitSummary } from "../panels/commits/commitsummary"; |
||||
import { ReadCommitResult } from "isomorphic-git" |
||||
|
||||
interface CommitDetailsNavigationProps { |
||||
commit: ReadCommitResult, |
||||
checkout: (oid: string) => void |
||||
eventKey: string |
||||
activePanel: string |
||||
callback: (eventKey: string) => void |
||||
isAheadOfRepo: boolean |
||||
} |
||||
|
||||
export const CommitDetailsNavigation = (props: CommitDetailsNavigationProps) => { |
||||
const { commit, checkout, eventKey, activePanel, callback, isAheadOfRepo } = props; |
||||
const handleClick = () => { |
||||
if (!callback) return |
||||
if (activePanel === eventKey) { |
||||
callback('') |
||||
} else { |
||||
callback(eventKey) |
||||
} |
||||
} |
||||
return ( |
||||
<> |
||||
<div onClick={() => handleClick()} role={'button'} className={`pointer mb-2 mt-2 w-100 d-flex flex-row commit-navigation ${isAheadOfRepo ? 'text-success' : ''}`}> |
||||
{ |
||||
activePanel === eventKey ? <FontAwesomeIcon className='' icon={faCaretDown}></FontAwesomeIcon> : <FontAwesomeIcon className='' icon={faCaretRight}></FontAwesomeIcon> |
||||
} |
||||
|
||||
<CommitSummary isAheadOfRepo={isAheadOfRepo} commit={commit} checkout={checkout}></CommitSummary> |
||||
</div> |
||||
</> |
||||
); |
||||
} |
@ -0,0 +1,63 @@ |
||||
import { faCaretDown, faArrowUp, faArrowDown, faArrowRotateRight, faCaretRight, faArrowsUpDown, faCloudArrowUp, faCloudArrowDown } from "@fortawesome/free-solid-svg-icons"; |
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; |
||||
import { CustomTooltip } from "@remix-ui/helper"; |
||||
import React, { useEffect } from "react"; |
||||
import { FormattedMessage } from "react-intl"; |
||||
import { pluginActionsContext } from "../../state/context"; |
||||
import { branch, remote } from "../../types"; |
||||
import { SourceControlBase } from "../buttons/sourceControlBase"; |
||||
import { SourceControlButtons } from "../buttons/sourcecontrolbuttons"; |
||||
import { gitPluginContext } from "../gitui"; |
||||
import LoaderIndicator from "./loaderindicator"; |
||||
|
||||
export interface CommitsNavigationProps { |
||||
title: string, |
||||
eventKey: string, |
||||
activePanel: string, |
||||
callback: (eventKey: string) => void |
||||
branch?: branch, |
||||
remote?: remote |
||||
showButtons?: boolean |
||||
ahead?: boolean, |
||||
behind?: boolean, |
||||
} |
||||
|
||||
export const CommitsNavigation = ({ eventKey, activePanel, callback, title, branch, remote, showButtons, ahead, behind }: CommitsNavigationProps) => { |
||||
const pluginactions = React.useContext(pluginActionsContext) |
||||
const [pullEnabled, setPullEnabled] = React.useState(true) |
||||
const [pushEnabled, setPushEnabled] = React.useState(true) |
||||
const [syncEnabled, setSyncEnabled] = React.useState(false) |
||||
const [fetchEnabled, setFetchEnabled] = React.useState(true) |
||||
const context = React.useContext(gitPluginContext) |
||||
|
||||
const handleClick = () => { |
||||
if (!callback) return |
||||
if (activePanel === eventKey) { |
||||
callback('') |
||||
} else { |
||||
callback(eventKey) |
||||
} |
||||
} |
||||
|
||||
return ( |
||||
<> |
||||
<div className={`d-flex justify-content-between ${activePanel === eventKey ? 'bg-light' : ''} ${ahead || behind? 'text-success':''}`}> |
||||
<span onClick={() => handleClick()} role={'button'} className='nav d-flex justify-content-start align-items-center w-100'> |
||||
{ |
||||
activePanel === eventKey ? <FontAwesomeIcon className='' icon={faCaretDown}></FontAwesomeIcon> : <FontAwesomeIcon className='' icon={faCaretRight}></FontAwesomeIcon> |
||||
} |
||||
{ahead? <FontAwesomeIcon className='ml-1' icon={faCloudArrowUp}></FontAwesomeIcon> : null} |
||||
{behind? <FontAwesomeIcon className='ml-1' icon={faCloudArrowDown}></FontAwesomeIcon> : null} |
||||
<label className={`pl-1 nav form-check-label ${ahead || behind? 'text-success':''}`}>{title}</label> |
||||
<LoaderIndicator></LoaderIndicator> |
||||
|
||||
</span> |
||||
{showButtons ? |
||||
<SourceControlBase branch={branch} remote={remote}> |
||||
<SourceControlButtons /> |
||||
</SourceControlBase> : null} |
||||
|
||||
</div> |
||||
</> |
||||
); |
||||
} |
@ -0,0 +1,29 @@ |
||||
import { faCaretDown, faCaretRight } from "@fortawesome/free-solid-svg-icons"; |
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; |
||||
import React, { } from "react"; |
||||
import { pluginActionsContext } from "../../state/context"; |
||||
|
||||
export const GitHubNavigation = ({ eventKey, activePanel, callback }) => { |
||||
const pluginactions = React.useContext(pluginActionsContext) |
||||
|
||||
const handleClick = () => { |
||||
if (!callback) return |
||||
if (activePanel === eventKey) { |
||||
callback('') |
||||
} else { |
||||
callback(eventKey) |
||||
} |
||||
} |
||||
return ( |
||||
<> |
||||
<div className={'d-flex justify-content-between pt-1 pb-1 ' + (activePanel === eventKey? 'bg-light': '')}> |
||||
<span onClick={()=>handleClick()} role={'button'} className='nav d-flex justify-content-start align-items-center w-75'> |
||||
{ |
||||
activePanel === eventKey ? <FontAwesomeIcon className='' icon={faCaretDown}></FontAwesomeIcon> : <FontAwesomeIcon className='' icon={faCaretRight}></FontAwesomeIcon> |
||||
} |
||||
<label className="pl-1 nav form-check-label">GITHUB SETUP</label> |
||||
</span> |
||||
</div> |
||||
</> |
||||
); |
||||
} |
@ -0,0 +1,20 @@ |
||||
import React, { useContext } from 'react' |
||||
import { gitPluginContext } from '../gitui' |
||||
|
||||
interface LoaderIndicatorProps { |
||||
type?: string; |
||||
isLoadingCondition?: boolean; // Optional additional disabling condition
|
||||
} |
||||
|
||||
// This component extends a button, disabling it when loading is true
|
||||
const LoaderIndicator = ({ type, isLoadingCondition }: LoaderIndicatorProps) => { |
||||
const { loading } = React.useContext(gitPluginContext) |
||||
|
||||
const isLoading = loading || isLoadingCondition |
||||
if (!isLoading) return null |
||||
return ( |
||||
<i style={{ fontSize: 'x-small' }} className="ml-1 fas fa-spinner fa-spin fa-4x"></i> |
||||
); |
||||
}; |
||||
|
||||
export default LoaderIndicator; |
@ -0,0 +1,87 @@ |
||||
import { faBan, faCaretDown, faCaretRight, faCircleCheck, faCircleInfo, faInfo, faTrash, faTriangleExclamation, faWarning } from "@fortawesome/free-solid-svg-icons"; |
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; |
||||
import React, { useContext, useEffect, useState } from "react"; |
||||
import { gitActionsContext, pluginActionsContext } from "../../state/context"; |
||||
import { gitPluginContext } from "../gitui"; |
||||
|
||||
export const LogNavigation = ({ eventKey, activePanel, callback }) => { |
||||
const context = useContext(gitPluginContext) |
||||
const actions = React.useContext(gitActionsContext) |
||||
|
||||
const [logState, setLogState] = useState({ |
||||
errorCount: 0, |
||||
warningCount: 0, |
||||
infoCount: 0, |
||||
successCount: 0 |
||||
}); |
||||
const handleClick = () => { |
||||
if (!callback) return |
||||
if (activePanel === eventKey) { |
||||
callback('') |
||||
} else { |
||||
callback(eventKey) |
||||
} |
||||
} |
||||
|
||||
useEffect(() => { |
||||
if (!context.log) return |
||||
// count different types of logs
|
||||
const errorCount = context.log.filter(log => log.type === 'error').length |
||||
const warningCount = context.log.filter(log => log.type === 'warning').length |
||||
const infoCount = context.log.filter(log => log.type === 'info').length |
||||
const successCount = context.log.filter(log => log.type === 'success').length |
||||
// update the state
|
||||
setLogState({ |
||||
errorCount, |
||||
warningCount, |
||||
infoCount, |
||||
successCount |
||||
}) |
||||
}, [context.log]) |
||||
|
||||
const clearLogs = () => { |
||||
actions.clearGitLog() |
||||
} |
||||
|
||||
return ( |
||||
<> |
||||
<div className={'d-flex justify-content-between pt-1 pb-1 ' + (activePanel === eventKey ? 'bg-light' : '')}> |
||||
<span onClick={() => handleClick()} role={'button'} className='nav d-flex justify-content-start align-items-center w-75'> |
||||
{ |
||||
activePanel === eventKey ? <FontAwesomeIcon className='' icon={faCaretDown}></FontAwesomeIcon> : <FontAwesomeIcon className='' icon={faCaretRight}></FontAwesomeIcon> |
||||
} |
||||
<label className="pl-1 nav form-check-label mr-2">LOG</label> |
||||
{logState.errorCount > 0 && ( |
||||
<div className="text-danger mr-1"> |
||||
{logState.errorCount} |
||||
<FontAwesomeIcon className="ml-1" icon={faTriangleExclamation} /> |
||||
</div> |
||||
)} |
||||
|
||||
{logState.warningCount > 0 && ( |
||||
<div className="text-warning mr-1"> |
||||
{logState.warningCount} |
||||
<FontAwesomeIcon className="ml-1" icon={faWarning} /> |
||||
</div> |
||||
)} |
||||
|
||||
{logState.infoCount > 0 && ( |
||||
<div className="text-info mr-1"> |
||||
{logState.infoCount} |
||||
<FontAwesomeIcon className="ml-1" icon={faCircleInfo} /> |
||||
</div> |
||||
)} |
||||
|
||||
{logState.successCount > 0 && ( |
||||
<div className="text-success"> |
||||
{logState.successCount} |
||||
<FontAwesomeIcon className="ml-1" icon={faCircleCheck} /> |
||||
</div> |
||||
)} |
||||
</span> |
||||
{context.log && context.log.length > 0 && ( |
||||
<FontAwesomeIcon onClick={clearLogs} className='btn btn-sm' icon={faBan}></FontAwesomeIcon>)} |
||||
</div> |
||||
</> |
||||
); |
||||
} |
@ -0,0 +1,49 @@ |
||||
import { count } from "console" |
||||
import { CustomIconsToggle, CustomMenu, CustomTooltip } from "@remix-ui/helper" |
||||
import React, { useState } from "react" |
||||
import { Dropdown } from "react-bootstrap" |
||||
import { FormattedMessage } from "react-intl" |
||||
|
||||
export const SourceControlMenu = () => { |
||||
const [showIconsMenu, hideIconsMenu] = useState<boolean>(false) |
||||
return ( |
||||
<Dropdown id="workspacesMenuDropdown" data-id="sourceControlMenuDropdown" onToggle={() => hideIconsMenu(!showIconsMenu)} show={showIconsMenu}> |
||||
<Dropdown.Toggle |
||||
onClick={() => { |
||||
hideIconsMenu(!showIconsMenu) |
||||
}} |
||||
as={CustomIconsToggle} |
||||
icon={'fas fa-bars'} |
||||
></Dropdown.Toggle> |
||||
<Dropdown.Menu as={CustomMenu} data-id="wsdropdownMenu" className='custom-dropdown-items remixui_menuwidth' rootCloseEvent="click"> |
||||
<Dropdown.Item key={0}> |
||||
<CustomTooltip |
||||
placement="right-start" |
||||
tooltipId="cloneWorkspaceTooltip" |
||||
tooltipClasses="text-nowrap" |
||||
tooltipText={<FormattedMessage id='filePanel.workspace.clone' defaultMessage='Clone Git Repository' />} |
||||
> |
||||
<div |
||||
data-id='cloneGitRepository' |
||||
onClick={() => { |
||||
hideIconsMenu(!showIconsMenu) |
||||
}} |
||||
key={`cloneGitRepository-fe-ws`} |
||||
> |
||||
<span |
||||
id='cloneGitRepository' |
||||
data-id='cloneGitRepository' |
||||
onClick={() => { |
||||
hideIconsMenu(!showIconsMenu) |
||||
}} |
||||
className='fab fa-github pl-2' |
||||
> |
||||
</span> |
||||
<span className="pl-3"><FormattedMessage id='filePanel.clone' defaultMessage='Clone' /></span> |
||||
</div> |
||||
</CustomTooltip> |
||||
</Dropdown.Item> |
||||
</Dropdown.Menu> |
||||
</Dropdown> |
||||
) |
||||
} |
@ -0,0 +1,32 @@ |
||||
import { faCaretDown, faCaretRight } from "@fortawesome/free-solid-svg-icons"; |
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; |
||||
import React, { } from "react"; |
||||
import { gitActionsContext, pluginActionsContext } from "../../state/context"; |
||||
import LoaderIndicator from "./loaderindicator"; |
||||
|
||||
export const RemotesNavigation = ({ eventKey, activePanel, callback }) => { |
||||
const pluginactions = React.useContext(pluginActionsContext) |
||||
const context = React.useContext(gitActionsContext) |
||||
|
||||
const handleClick = () => { |
||||
if (!callback) return |
||||
if (activePanel === eventKey) { |
||||
callback('') |
||||
} else { |
||||
callback(eventKey) |
||||
} |
||||
} |
||||
return ( |
||||
<> |
||||
<div className={'d-flex justify-content-between pt-1 pb-1 ' + (activePanel === eventKey? 'bg-light': '')}> |
||||
<span onClick={()=>handleClick()} role={'button'} className='nav d-flex justify-content-start align-items-center w-75'> |
||||
{ |
||||
activePanel === eventKey ? <FontAwesomeIcon className='' icon={faCaretDown}></FontAwesomeIcon> : <FontAwesomeIcon className='' icon={faCaretRight}></FontAwesomeIcon> |
||||
} |
||||
<label className="pl-1 nav form-check-label">REMOTES</label> |
||||
<LoaderIndicator></LoaderIndicator> |
||||
</span> |
||||
</div> |
||||
</> |
||||
); |
||||
} |
@ -0,0 +1,68 @@ |
||||
import { faCaretDown, faCaretRight, faArrowRightArrowLeft, faGlobe, faToggleOff, faToggleOn, faTrash, faCheck, faSync } from "@fortawesome/free-solid-svg-icons"; |
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; |
||||
import { CustomTooltip } from "@remix-ui/helper"; |
||||
import React, { useContext, useEffect } from "react"; |
||||
import { gitActionsContext } from "../../state/context"; |
||||
import { branch, remote } from "../../types"; |
||||
import GitUIButton from "../buttons/gituibutton"; |
||||
import { gitPluginContext } from "../gitui"; |
||||
|
||||
interface RemotesDetailsNavigationProps { |
||||
eventKey: string; |
||||
activePanel: string; |
||||
callback: (eventKey: string) => void; |
||||
remote: remote; |
||||
} |
||||
|
||||
export const RemotesDetailsNavigation = (props: RemotesDetailsNavigationProps) => { |
||||
const { eventKey, activePanel, callback, remote } = props; |
||||
const context = React.useContext(gitPluginContext) |
||||
const actions = React.useContext(gitActionsContext) |
||||
|
||||
const handleClick = () => { |
||||
if (!callback) return |
||||
if (activePanel === eventKey) { |
||||
callback('') |
||||
} else { |
||||
callback(eventKey) |
||||
} |
||||
} |
||||
|
||||
const openRemote = () => { |
||||
window.open(`${remote.url}`, '_blank'); |
||||
} |
||||
|
||||
const setAsDefault = () => { |
||||
actions.setDefaultRemote(remote) |
||||
} |
||||
|
||||
return ( |
||||
<> |
||||
<div className="d-flex flex-row w-100 mb-2 mt-2"> |
||||
<div onClick={() => handleClick()} role={'button'} className='pointer long-and-truncated d-flex flex-row commit-navigation'> |
||||
{ |
||||
activePanel === eventKey ? <FontAwesomeIcon className='' icon={faCaretDown}></FontAwesomeIcon> : <FontAwesomeIcon className='' icon={faCaretRight}></FontAwesomeIcon> |
||||
} |
||||
<CustomTooltip tooltipText={remote.url} placement="top"> |
||||
<div className={`long-and-truncated ml-1 ${context.defaultRemote && context.defaultRemote?.url === remote.url ? 'text-success' : ''}`}> |
||||
{remote.name} <FontAwesomeIcon className='' icon={faArrowRightArrowLeft}></FontAwesomeIcon> {remote.url} |
||||
</div> |
||||
</CustomTooltip> |
||||
|
||||
</div> |
||||
{context.defaultRemote && context.defaultRemote?.url === remote.url ? |
||||
<GitUIButton className="btn btn-sm" onClick={() => { }} disabledCondition={true}><FontAwesomeIcon className='text-success' icon={faCheck} ></FontAwesomeIcon></GitUIButton> |
||||
: |
||||
<GitUIButton className="btn btn-sm" onClick={setAsDefault}><FontAwesomeIcon icon={faToggleOn}></FontAwesomeIcon></GitUIButton> |
||||
} |
||||
<GitUIButton className="btn btn-sm" onClick={async () => { |
||||
await actions.fetch({ |
||||
remote |
||||
}) |
||||
}}><FontAwesomeIcon icon={faSync} ></FontAwesomeIcon></GitUIButton> |
||||
<GitUIButton className="btn btn-sm" onClick={() => actions.removeRemote(remote)}><FontAwesomeIcon className='text-danger' icon={faTrash} ></FontAwesomeIcon></GitUIButton> |
||||
{remote?.url && <GitUIButton className="btn btn-sm pr-0" onClick={() => openRemote()}><FontAwesomeIcon icon={faGlobe} ></FontAwesomeIcon></GitUIButton>} |
||||
</div> |
||||
</> |
||||
); |
||||
} |
@ -0,0 +1,41 @@ |
||||
import { faCaretUp, faCaretDown, faArrowUp, faArrowDown, faArrowRotateRight, faCaretRight, faArrowsUpDown, faTriangleExclamation } from "@fortawesome/free-solid-svg-icons"; |
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; |
||||
import { CustomTooltip } from "@remix-ui/helper"; |
||||
import React, { useContext, useEffect } from "react"; |
||||
import { FormattedMessage } from "react-intl"; |
||||
import { pluginActionsContext } from "../../state/context"; |
||||
|
||||
export const SettingsNavigation = ({ eventKey, activePanel, callback }) => { |
||||
const pluginactions = React.useContext(pluginActionsContext) |
||||
|
||||
const handleClick = () => { |
||||
if (!callback) return |
||||
if (activePanel === eventKey) { |
||||
callback('') |
||||
} else { |
||||
callback(eventKey) |
||||
} |
||||
} |
||||
|
||||
return ( |
||||
<> |
||||
<div className={'d-flex justify-content-between ' + (activePanel === eventKey ? 'bg-light' : '')}> |
||||
<span onClick={() => handleClick()} role={'button'} className='nav d-flex justify-content-start align-items-center w-75'> |
||||
{ |
||||
activePanel === eventKey ? <FontAwesomeIcon className='' icon={faCaretDown}></FontAwesomeIcon> : <FontAwesomeIcon className='' icon={faCaretRight}></FontAwesomeIcon> |
||||
} |
||||
<label className="nav pl-1 form-check-label">SETTINGS</label> |
||||
|
||||
</span> |
||||
|
||||
<span className='d-flex justify-content-end align-items-center w-25'> |
||||
<CustomTooltip tooltipText={<FormattedMessage id="Missing values" />}> |
||||
<button onClick={async () => { await pluginactions.loadFiles() }} className='btn btn-sm text-warning'><FontAwesomeIcon icon={faTriangleExclamation} className="" /></button> |
||||
</CustomTooltip> |
||||
|
||||
</span> |
||||
|
||||
</div> |
||||
</> |
||||
); |
||||
} |
@ -0,0 +1,42 @@ |
||||
import { faCaretUp, faCaretDown, faArrowUp, faArrowDown, faArrowRotateRight, faCaretRight, faArrowsUpDown } from "@fortawesome/free-solid-svg-icons"; |
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; |
||||
import { CustomTooltip } from "@remix-ui/helper"; |
||||
import React, { useContext, useEffect } from "react"; |
||||
import { FormattedMessage } from "react-intl"; |
||||
import { pluginActionsContext } from "../../state/context"; |
||||
import { SourceControlBase } from "../buttons/sourceControlBase"; |
||||
import { SourceControlButtons } from "../buttons/sourcecontrolbuttons"; |
||||
import { gitPluginContext } from "../gitui"; |
||||
import LoaderIndicator from "./loaderindicator"; |
||||
import { SourceControlMenu } from "./menu/sourcecontrolmenu"; |
||||
|
||||
export const SourceControlNavigation = ({ eventKey, activePanel, callback }) => { |
||||
const pluginactions = React.useContext(pluginActionsContext) |
||||
const context = React.useContext(gitPluginContext) |
||||
const handleClick = () => { |
||||
if (!callback) return |
||||
if (activePanel === eventKey) { |
||||
callback('') |
||||
} else { |
||||
callback(eventKey) |
||||
} |
||||
} |
||||
|
||||
return ( |
||||
<> |
||||
<div className={'d-flex justify-content-between ' + (activePanel === eventKey ? 'bg-light' : '')}> |
||||
<span onClick={() => handleClick()} role={'button'} className='nav d-flex justify-content-start align-items-center w-75'> |
||||
{ |
||||
activePanel === eventKey ? <FontAwesomeIcon className='' icon={faCaretDown}></FontAwesomeIcon> : <FontAwesomeIcon className='' icon={faCaretRight}></FontAwesomeIcon> |
||||
} |
||||
<label className="nav pl-1 form-check-label">SOURCE CONTROL</label> |
||||
<LoaderIndicator></LoaderIndicator> |
||||
|
||||
</span> |
||||
|
||||
<SourceControlBase><SourceControlButtons/></SourceControlBase> |
||||
|
||||
</div> |
||||
</> |
||||
); |
||||
} |
@ -0,0 +1,52 @@ |
||||
import { faCaretUp, faCaretDown, faArrowUp, faArrowDown, faArrowRotateRight, faCaretRight, faArrowsUpDown, faPlus, faMinus } from "@fortawesome/free-solid-svg-icons"; |
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; |
||||
import { CustomTooltip } from "@remix-ui/helper"; |
||||
import React, { useContext, useEffect } from "react"; |
||||
import { FormattedMessage } from "react-intl"; |
||||
import { gitActionsContext, pluginActionsContext } from "../../state/context"; |
||||
import { sourceControlGroup } from "../../types"; |
||||
import { gitPluginContext } from "../gitui"; |
||||
|
||||
interface SourceControlGroupNavigationProps { |
||||
eventKey: string; |
||||
activePanel: string; |
||||
callback: (eventKey: string) => void; |
||||
group: sourceControlGroup |
||||
} |
||||
|
||||
export const SourceControlGroupNavigation = (props: SourceControlGroupNavigationProps) => { |
||||
const { eventKey, activePanel, callback, group } = props; |
||||
const actions = React.useContext(gitActionsContext) |
||||
const pluginActions = React.useContext(pluginActionsContext) |
||||
const context = React.useContext(gitPluginContext) |
||||
const handleClick = () => { |
||||
if (!callback) return |
||||
if (activePanel === eventKey) { |
||||
callback('') |
||||
} else { |
||||
callback(eventKey) |
||||
} |
||||
} |
||||
return ( |
||||
<> |
||||
<div className={'d-flex justify-content-between pt-1 ' + (activePanel === eventKey? 'bg-light': '')}> |
||||
<span onClick={()=>handleClick()} role={'button'} className='nav d-flex justify-content-start align-items-center w-75'> |
||||
{ |
||||
activePanel === eventKey ? <FontAwesomeIcon className='' icon={faCaretDown}></FontAwesomeIcon> : <FontAwesomeIcon className='' icon={faCaretRight}></FontAwesomeIcon> |
||||
} |
||||
<label className="pl-1 nav form-check-label">{group.name}</label> |
||||
</span> |
||||
{ |
||||
activePanel === eventKey ? |
||||
<span className='d-flex justify-content-end align-items-center w-25'> |
||||
{group.name === 'Changes' ? |
||||
<CustomTooltip tooltipText={<FormattedMessage id="git.stageall" />}> |
||||
<button onClick={async () => { await actions.addall(context.allchangesnotstaged) }} className='btn btn-sm'><FontAwesomeIcon icon={faPlus} className="" /></button> |
||||
</CustomTooltip>: null} |
||||
|
||||
</span> : null |
||||
} |
||||
</div> |
||||
</> |
||||
); |
||||
} |
@ -0,0 +1,64 @@ |
||||
import React, { useEffect, useState } from "react"; |
||||
import { Alert } from "react-bootstrap"; |
||||
import { gitActionsContext } from "../../state/context"; |
||||
import { remote } from "../../types"; |
||||
import GitUIButton from "../buttons/gituibutton"; |
||||
import { gitPluginContext } from "../gitui"; |
||||
import { LocalBranchDetails } from "./branches/localbranchdetails"; |
||||
import { RemoteBranchDetails } from "./branches/remotebranchedetails"; |
||||
|
||||
export const Branches = () => { |
||||
const context = React.useContext(gitPluginContext) |
||||
const actions = React.useContext(gitActionsContext) |
||||
const [newBranch, setNewBranch] = useState({ value: "" }); |
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { |
||||
setNewBranch({ value: e.currentTarget.value }); |
||||
}; |
||||
|
||||
const checkout = async (oid: string, remote: remote) => { |
||||
try { |
||||
actions.checkout({ ref: oid, remote: remote.name }); |
||||
} catch (e) { |
||||
// do nothing
|
||||
} |
||||
}; |
||||
|
||||
useEffect(() => { |
||||
console.log("branches", context.branches) |
||||
}, [context.branches]) |
||||
|
||||
return ( |
||||
<> |
||||
<div className="pt-1"> |
||||
{context.branches && context.branches.length ? |
||||
<div> |
||||
{context.branches && context.branches.filter((branch, index) => !branch.remote).map((branch, index) => { |
||||
return ( |
||||
<LocalBranchDetails key={index} branch={branch}></LocalBranchDetails> |
||||
); |
||||
})} |
||||
<hr /> |
||||
<label>create branch</label> |
||||
<div className="form-group"> |
||||
|
||||
<input |
||||
placeholder="branch name" |
||||
onChange={handleChange} |
||||
className="form-control w-md-25 w-100" |
||||
type="text" |
||||
id="newbranchname" |
||||
/> |
||||
</div> |
||||
<GitUIButton |
||||
onClick={async () => actions.createBranch(newBranch.value)} |
||||
className="btn w-md-25 w-100 btn-primary" |
||||
id="createbranch-btn" |
||||
> |
||||
create new branch |
||||
</GitUIButton> |
||||
</div> : <div className="text-muted">No branches</div>} |
||||
</div> |
||||
</> |
||||
); |
||||
} |
@ -0,0 +1,48 @@ |
||||
import { ReadCommitResult } from "isomorphic-git"; |
||||
import { Accordion } from "react-bootstrap"; |
||||
import React, { useEffect, useState } from "react"; |
||||
import { CommitDetails } from "../commits/commitdetails"; |
||||
import { CommitsNavigation } from "../../navigation/commits"; |
||||
import { branch, remote } from "../../../types"; |
||||
import { gitActionsContext } from "../../../state/context"; |
||||
import { gitPluginContext } from "../../gitui"; |
||||
|
||||
export interface BrancheDifferenceProps { |
||||
commits: ReadCommitResult[]; |
||||
title: string, |
||||
remote?: remote, |
||||
branch?: branch |
||||
ahead?: boolean, |
||||
behind?: boolean |
||||
} |
||||
|
||||
export const BranchDifferenceDetails = (props: BrancheDifferenceProps) => { |
||||
const { commits, title, branch, remote, ahead, behind } = props; |
||||
const [activePanel, setActivePanel] = useState<string>(""); |
||||
const context = React.useContext(gitPluginContext) |
||||
const actions = React.useContext(gitActionsContext) |
||||
|
||||
if (commits.length === 0) return null |
||||
|
||||
const getRemote = () => { |
||||
return remote ? remote : context.upstream ? context.upstream : context.defaultRemote ? context.defaultRemote : null |
||||
} |
||||
|
||||
const getCommitChanges = async (commit: ReadCommitResult) => { |
||||
await actions.getCommitChanges(commit.oid, commit.commit.parent[0], null, getRemote()) |
||||
} |
||||
|
||||
return ( |
||||
<Accordion activeKey={activePanel} defaultActiveKey=""> |
||||
<CommitsNavigation ahead={ahead} behind={behind} branch={branch} remote={remote} title={title} eventKey="0" activePanel={activePanel} callback={setActivePanel} /> |
||||
<Accordion.Collapse className="pl-2 border-left ml-1" eventKey="0"> |
||||
<div className="ml-1"> |
||||
{commits && commits.map((commit, index) => { |
||||
return ( |
||||
<CommitDetails branch={branch} getCommitChanges={getCommitChanges} key={index} checkout={()=>{}} commit={commit}></CommitDetails> |
||||
); |
||||
})} |
||||
</div> |
||||
</Accordion.Collapse> |
||||
</Accordion>) |
||||
} |
@ -0,0 +1,47 @@ |
||||
import { branch, remote } from "../../../types"; |
||||
import React, { useEffect, useState } from "react"; |
||||
import { gitPluginContext } from "../../gitui"; |
||||
import { CommitDetails } from "../commits/commitdetails"; |
||||
import { BranchDifferenceDetails } from "./branchdifferencedetails"; |
||||
|
||||
export interface BrancheDetailsProps { |
||||
branch: branch; |
||||
showSummary?: boolean; |
||||
} |
||||
|
||||
export const BranchDifferences = (props: BrancheDetailsProps) => { |
||||
const { branch, showSummary } = props; |
||||
const context = React.useContext(gitPluginContext) |
||||
|
||||
useEffect(() => { |
||||
console.log('GET BRANCH DIFF', branch) |
||||
}, []) |
||||
|
||||
const getRemote = (): remote | null => { |
||||
return context.upstream ? context.upstream : context.defaultRemote ? context.defaultRemote : null |
||||
} |
||||
|
||||
useEffect(() => { |
||||
console.log('BRANCH DIFF', context.branchDifferences) |
||||
}, [context.branchDifferences]) |
||||
|
||||
const commitsAhead = (remote: remote) => { |
||||
if (!remote) return []; |
||||
return context.branchDifferences[`${remote.name}/${branch.name}`]?.uniqueHeadCommits || []; |
||||
} |
||||
|
||||
const commitsBehind = (remote: remote) => { |
||||
if (!remote) return []; |
||||
return context.branchDifferences[`${remote.name}/${branch.name}`]?.uniqueRemoteCommits || []; |
||||
} |
||||
|
||||
if (!getRemote()) return null; |
||||
|
||||
return ( |
||||
|
||||
<div> |
||||
<BranchDifferenceDetails ahead={true} branch={branch} remote={getRemote()} title={`ahead of ${getRemote().name} by ${commitsAhead(getRemote()).length} commit(s)`} commits={commitsAhead(getRemote())}></BranchDifferenceDetails> |
||||
<BranchDifferenceDetails behind={true} branch={branch} remote={getRemote()} title={`behind ${getRemote().name} by ${commitsBehind(getRemote()).length} commit(s)`} commits={commitsBehind(getRemote())}></BranchDifferenceDetails> |
||||
{commitsAhead(getRemote()).length === 0 && commitsBehind(getRemote()).length === 0 ? null : <hr></hr>} |
||||
</div>) |
||||
} |
@ -0,0 +1,84 @@ |
||||
import { ReadCommitResult } from "isomorphic-git" |
||||
import React, { useEffect, useState } from "react"; |
||||
import { Accordion } from "react-bootstrap"; |
||||
import { CommitDetailsNavigation } from "../../navigation/commitdetails"; |
||||
import { gitActionsContext } from "../../../state/context"; |
||||
import { gitPluginContext } from "../../gitui"; |
||||
import { branch } from "../../../types"; |
||||
import { BrancheDetailsNavigation } from "../../navigation/branchedetails"; |
||||
import { CommitDetailsItems } from "../commits/commitdetailsitem"; |
||||
import { CommitDetails } from "../commits/commitdetails"; |
||||
import { BranchDifferences } from "./branchdifferences"; |
||||
import GitUIButton from "../../buttons/gituibutton"; |
||||
|
||||
export interface BrancheDetailsProps { |
||||
branch: branch; |
||||
} |
||||
|
||||
export const LocalBranchDetails = (props: BrancheDetailsProps) => { |
||||
const { branch } = props; |
||||
const actions = React.useContext(gitActionsContext) |
||||
const context = React.useContext(gitPluginContext) |
||||
const [activePanel, setActivePanel] = useState<string>(""); |
||||
const [hasNextPage, setHasNextPage] = useState<boolean>(false) |
||||
const [lastPageNumber, setLastPageNumber] = useState<number>(0) |
||||
|
||||
useEffect(() => { |
||||
if (activePanel === "0") { |
||||
console.log('GET BRANCH COMMITS', branch) |
||||
if (lastPageNumber === 0) |
||||
actions.getBranchCommits(branch, 1) |
||||
actions.getBranchDifferences(branch, null, context) |
||||
} |
||||
}, [activePanel]) |
||||
|
||||
const checkout = (branch: branch) => { |
||||
actions.checkout({ |
||||
ref: branch.name, |
||||
remote: branch.remote && branch.remote.name || null |
||||
}); |
||||
} |
||||
|
||||
const loadNextPage = () => { |
||||
console.log('LOAD NEXT PAGE', lastPageNumber + 1) |
||||
actions.getBranchCommits(branch, lastPageNumber + 1) |
||||
} |
||||
|
||||
const checkoutCommit = async (oid: string) => { |
||||
try { |
||||
//await ModalRef.current?.show();
|
||||
actions.checkout({ ref: oid }) |
||||
//Utils.log("yes");
|
||||
} catch (e) { |
||||
//Utils.log("no");
|
||||
} |
||||
}; |
||||
|
||||
const getRemote = () => { |
||||
return context.upstream ? context.upstream : context.defaultRemote ? context.defaultRemote : null |
||||
} |
||||
|
||||
const getCommitChanges = async (commit: ReadCommitResult) => { |
||||
await actions.getCommitChanges(commit.oid, commit.commit.parent[0], null, getRemote()) |
||||
} |
||||
|
||||
return (<Accordion activeKey={activePanel} defaultActiveKey=""> |
||||
<BrancheDetailsNavigation checkout={checkout} branch={branch} eventKey="0" activePanel={activePanel} callback={setActivePanel} /> |
||||
<Accordion.Collapse className="pl-2 border-left ml-1" eventKey="0"> |
||||
<> |
||||
<div className="ml-1"> |
||||
<BranchDifferences branch={branch}></BranchDifferences> |
||||
{context.localBranchCommits && Object.entries(context.localBranchCommits).map(([key, value]) => { |
||||
if (key == branch.name) { |
||||
return value.map((commit, index) => { |
||||
return (<CommitDetails branch={branch} key={index} getCommitChanges={getCommitChanges} checkout={checkoutCommit} commit={commit}></CommitDetails>) |
||||
}) |
||||
} |
||||
})} |
||||
|
||||
</div> |
||||
{hasNextPage && <GitUIButton className="mb-1 ml-2 btn btn-sm" onClick={loadNextPage}>Load more</GitUIButton>} |
||||
</> |
||||
</Accordion.Collapse> |
||||
</Accordion>) |
||||
} |
@ -0,0 +1,109 @@ |
||||
import { ReadCommitResult } from "isomorphic-git" |
||||
import React, { useEffect, useState } from "react"; |
||||
import { Accordion } from "react-bootstrap"; |
||||
import { CommitDetailsNavigation } from "../../navigation/commitdetails"; |
||||
import { gitActionsContext } from "../../../state/context"; |
||||
import { gitPluginContext } from "../../gitui"; |
||||
import { branch } from "../../../types"; |
||||
import { BrancheDetailsNavigation } from "../../navigation/branchedetails"; |
||||
import { CommitDetailsItems } from "../commits/commitdetailsitem"; |
||||
import { CommitDetails } from "../commits/commitdetails"; |
||||
import GitUIButton from "../../buttons/gituibutton"; |
||||
|
||||
export interface BrancheDetailsProps { |
||||
branch: branch; |
||||
} |
||||
|
||||
export const RemoteBranchDetails = (props: BrancheDetailsProps) => { |
||||
const { branch } = props; |
||||
const actions = React.useContext(gitActionsContext) |
||||
const context = React.useContext(gitPluginContext) |
||||
const [activePanel, setActivePanel] = useState<string>(""); |
||||
const [hasNextPage, setHasNextPage] = useState<boolean>(false) |
||||
const [lastPageNumber, setLastPageNumber] = useState<number>(0) |
||||
|
||||
useEffect(() => { |
||||
if (activePanel === "0") { |
||||
console.log('GET BRANCH COMMITS', branch) |
||||
if (lastPageNumber === 0) |
||||
actions.getBranchCommits(branch, 1) |
||||
} |
||||
}, [activePanel]) |
||||
|
||||
useEffect(() => { |
||||
let hasNextPage = false |
||||
let lastPageNumber = 0 |
||||
console.log('BRANCH COMMITS', context.remoteBranchCommits) |
||||
context.remoteBranchCommits && Object.entries(context.remoteBranchCommits).map(([key, value]) => { |
||||
if (key == branch.name) { |
||||
value.map((page, index) => { |
||||
hasNextPage = page.hasNextPage |
||||
lastPageNumber = page.page |
||||
}) |
||||
} |
||||
}) |
||||
setHasNextPage(hasNextPage) |
||||
setLastPageNumber(lastPageNumber) |
||||
}, [context.remoteBranchCommits]) |
||||
|
||||
const checkout = async (branch: branch) => { |
||||
await actions.checkout({ |
||||
ref: branch.name, |
||||
remote: branch.remote && branch.remote.name || null |
||||
}); |
||||
await actions.getBranches() |
||||
} |
||||
|
||||
const loadNextPage = () => { |
||||
console.log('LOAD NEXT PAGE', lastPageNumber + 1) |
||||
actions.getBranchCommits(branch, lastPageNumber + 1) |
||||
} |
||||
|
||||
const checkoutCommit = async (oid: string) => { |
||||
try { |
||||
//await ModalRef.current?.show();
|
||||
actions.checkout({ ref: oid }) |
||||
//Utils.log("yes");
|
||||
} catch (e) { |
||||
//Utils.log("no");
|
||||
} |
||||
}; |
||||
|
||||
const getCommitChanges = async (commit: ReadCommitResult) => { |
||||
const changes = await actions.getCommitChanges(commit.oid, commit.commit.parent[0], branch, branch.remote) |
||||
console.log('CHANGES', changes) |
||||
if (!changes) { |
||||
// try to fetch the data
|
||||
//await actions.fetch(branch.remote.name, branch.name,null,20, true, false, true)
|
||||
await actions.fetch({ |
||||
remote: branch.remote, |
||||
ref: branch, |
||||
depth: 20, |
||||
singleBranch: true, |
||||
relative: false, |
||||
quiet: true |
||||
}) |
||||
} |
||||
} |
||||
|
||||
return (<Accordion activeKey={activePanel} defaultActiveKey=""> |
||||
<BrancheDetailsNavigation checkout={checkout} branch={branch} eventKey="0" activePanel={activePanel} callback={setActivePanel} /> |
||||
<Accordion.Collapse className="pl-2 border-left ml-1" eventKey="0"> |
||||
<> |
||||
<div className="ml-1"> |
||||
{context.remoteBranchCommits && Object.entries(context.remoteBranchCommits).map(([key, value]) => { |
||||
if (key == branch.name) { |
||||
return value.map((page, index) => { |
||||
return page.commits.map((commit, index) => { |
||||
return (<CommitDetails branch={branch} getCommitChanges={getCommitChanges} key={index} checkout={checkoutCommit} commit={commit}></CommitDetails>) |
||||
}) |
||||
}) |
||||
} |
||||
})} |
||||
|
||||
</div> |
||||
{hasNextPage && <GitUIButton className="mb-1 ml-2 btn btn-sm" onClick={loadNextPage}>Load more</GitUIButton>} |
||||
</> |
||||
</Accordion.Collapse> |
||||
</Accordion>) |
||||
} |
@ -0,0 +1,100 @@ |
||||
|
||||
import React, { useState } from "react"; |
||||
import { Alert, Form, FormControl, InputGroup } from "react-bootstrap"; |
||||
import { useLocalStorage } from "../../hooks/useLocalStorage"; |
||||
import { gitActionsContext } from "../../state/context"; |
||||
import { gitPluginContext } from "../gitui"; |
||||
import { SelectAndCloneRepositories } from "../github/selectandclonerepositories"; |
||||
import { RemixUiCheckbox } from "@remix-ui/checkbox"; |
||||
import GitUIButton from "../buttons/gituibutton"; |
||||
|
||||
export const Clone = () => { |
||||
const context = React.useContext(gitPluginContext) |
||||
const actions = React.useContext(gitActionsContext) |
||||
const [cloneUrl, setCloneUrl] = useLocalStorage( |
||||
"CLONE_URL", |
||||
'' |
||||
); |
||||
|
||||
const [cloneDepth, setCloneDepth] = useLocalStorage( |
||||
"CLONE_DEPTH", |
||||
1 |
||||
); |
||||
|
||||
const [cloneBranch, setCloneBranch] = useLocalStorage( |
||||
"CLONE_BRANCH", |
||||
'' |
||||
); |
||||
|
||||
const [url, setUrl] = useLocalStorage( |
||||
"GITHUB_URL", |
||||
'' |
||||
); |
||||
|
||||
const clone = async () => { |
||||
await actions.clone({ |
||||
url: cloneUrl, |
||||
branch: cloneBranch, |
||||
depth: cloneDepth, |
||||
singleBranch: !cloneAllBranches |
||||
}) |
||||
//await actions.clone(cloneUrl, cloneBranch, cloneDepth, !cloneAllBranches)
|
||||
} |
||||
|
||||
const onCloneBranchChange = (value: string) => { |
||||
setCloneBranch(value) |
||||
} |
||||
|
||||
const onGitHubCloneUrlChange = (value: string) => { |
||||
setCloneUrl(value) |
||||
} |
||||
|
||||
const onDepthChange = (value: number) => { |
||||
setCloneDepth(value) |
||||
} |
||||
|
||||
const [cloneAllBranches, setcloneAllBranches] = useLocalStorage( |
||||
"GITHUB_CLONE_ALL_BRANCES", |
||||
false |
||||
); |
||||
|
||||
const onAllBranchChange = () => { |
||||
setcloneAllBranches((e: any) => !e) |
||||
} |
||||
|
||||
return ( |
||||
<> |
||||
<InputGroup className="mb-1"> |
||||
<FormControl id="cloneulr" placeholder="url" name='cloneurl' value={cloneUrl} onChange={e => onGitHubCloneUrlChange(e.target.value)} aria-describedby="urlprepend" /> |
||||
</InputGroup> |
||||
|
||||
<input name='clonebranch' onChange={e => onCloneBranchChange(e.target.value)} value={cloneBranch} className="form-control mb-1 mt-2" placeholder="branch" type="text" id="clonebranch" /> |
||||
<GitUIButton disabledCondition={!cloneUrl || !cloneBranch} data-id='clonebtn' className='btn btn-primary mt-1 w-100' onClick={async () => { |
||||
clone() |
||||
}}>clone</GitUIButton> |
||||
<hr /> |
||||
<SelectAndCloneRepositories cloneAllBranches={cloneAllBranches} cloneDepth={cloneDepth} /> |
||||
<hr /> |
||||
<label>options</label> |
||||
<InputGroup className="mt-1 mb-1"> |
||||
<InputGroup.Prepend> |
||||
<InputGroup.Text id="clonedepthprepend"> |
||||
--depth |
||||
</InputGroup.Text> |
||||
</InputGroup.Prepend> |
||||
<FormControl id="clonedepth" type="number" value={cloneDepth} onChange={e => onDepthChange(parseInt(e.target.value))} aria-describedby="clonedepthprepend" /> |
||||
</InputGroup> |
||||
|
||||
<RemixUiCheckbox |
||||
id={`cloneAllBranches`} |
||||
inputType="checkbox" |
||||
name="cloneAllBranches" |
||||
label={`Clone all branches`} |
||||
onClick={() => onAllBranchChange()} |
||||
checked={cloneAllBranches} |
||||
onChange={() => { }} |
||||
/> |
||||
|
||||
<hr></hr> |
||||
</>) |
||||
} |
@ -0,0 +1,16 @@ |
||||
import React, { useEffect, useState } from "react"; |
||||
import { PushPull } from "./commands/pushpull"; |
||||
import { Fetch } from "./commands/fetch"; |
||||
import { Merge } from "./commands/merge"; |
||||
|
||||
export const Commands = () => { |
||||
|
||||
return ( |
||||
<> |
||||
<PushPull></PushPull> |
||||
<hr></hr> |
||||
<Fetch></Fetch> |
||||
<hr></hr> |
||||
<Merge></Merge> |
||||
</>) |
||||
} |
@ -0,0 +1,25 @@ |
||||
import React, { useEffect, useState } from "react"; |
||||
import { gitActionsContext } from "../../../state/context"; |
||||
import GitUIButton from "../../buttons/gituibutton"; |
||||
import { gitPluginContext } from "../../gitui"; |
||||
|
||||
export const Fetch = () => { |
||||
const actions = React.useContext(gitActionsContext) |
||||
const context = React.useContext(gitPluginContext) |
||||
|
||||
const fetchIsDisabled = () => { |
||||
return (!context.upstream) || context.remotes.length === 0 |
||||
} |
||||
return ( |
||||
<> |
||||
<div className="btn-group w-100" role="group"> |
||||
<GitUIButton disabledCondition={fetchIsDisabled()} type="button" onClick={async () => actions.fetch({ |
||||
remote: context.upstream, |
||||
})} className="btn btn-primary mr-1 w-50"><div>Fetch {context.upstream && context.upstream.name}</div></GitUIButton> |
||||
<GitUIButton disabledCondition={fetchIsDisabled()} type="button" onClick={async () => actions.fetch({ |
||||
remote: context.upstream, |
||||
ref: context.currentBranch |
||||
})} className="btn btn-primary w-50 long-and-truncated">Fetch {context.currentBranch.name}</GitUIButton> |
||||
</div> |
||||
</>) |
||||
} |
@ -0,0 +1,58 @@ |
||||
import React, { useEffect, useState } from "react"; |
||||
import { gitActionsContext } from "../../../state/context"; |
||||
import { gitPluginContext } from "../../gitui"; |
||||
import { selectStyles, selectTheme } from "../../../types/styles"; |
||||
import Select from 'react-select' |
||||
import GitUIButton from "../../buttons/gituibutton"; |
||||
|
||||
export const Merge = () => { |
||||
const context = React.useContext(gitPluginContext) |
||||
const actions = React.useContext(gitActionsContext) |
||||
const [localBranch, setLocalBranch] = useState('') |
||||
const [localBranchOptions, setLocalBranchOptions] = useState<any>([]); |
||||
|
||||
useEffect(() => { |
||||
setLocalBranch(context.currentBranch.name) |
||||
}, [context.currentBranch]) |
||||
|
||||
const onLocalBranchChange = (value: any) => { |
||||
console.log('onLocalBranchChange', value) |
||||
setLocalBranch(value) |
||||
} |
||||
|
||||
const merge = async () => { |
||||
//gitservice.push(currentRemote, branch || '', remoteBranch, force)
|
||||
} |
||||
|
||||
useEffect(() => { |
||||
// map context.repositories to options
|
||||
const localBranches = context.branches && context.branches.length > 0 && context.branches |
||||
.filter(branch => !branch.remote) |
||||
.map(repo => { |
||||
return { value: repo.name, label: repo.name } |
||||
}) |
||||
setLocalBranchOptions(localBranches) |
||||
|
||||
}, [context.branches]) |
||||
|
||||
return ( |
||||
<> |
||||
|
||||
<div className="btn-group w-100" role="group" aria-label="Basic example"> |
||||
<GitUIButton type="button" onClick={async () => merge()} className="btn btn-primary mr-1">Merge</GitUIButton> |
||||
</div> |
||||
|
||||
<label>Merge from Branch</label> |
||||
<Select |
||||
options={localBranchOptions} |
||||
isDisabled={context.branches.length === 0} |
||||
onChange={(e: any) => e && onLocalBranchChange(e.value)} |
||||
theme={selectTheme} |
||||
styles={selectStyles} |
||||
isClearable={true} |
||||
value={{ value: localBranch, label: localBranch }} |
||||
placeholder="Type to search for a branch..." |
||||
/> |
||||
|
||||
</>) |
||||
} |
@ -0,0 +1,180 @@ |
||||
import React, { useEffect, useState } from "react"; |
||||
import { gitActionsContext } from "../../../state/context"; |
||||
import { gitPluginContext } from "../../gitui"; |
||||
import { selectStyles, selectTheme } from "../../../types/styles"; |
||||
import Select, { Options, OptionsOrGroups } from 'react-select' |
||||
import GitUIButton from "../../buttons/gituibutton"; |
||||
import { remote } from "../../../types"; |
||||
import { relative } from "path"; |
||||
|
||||
export const PushPull = () => { |
||||
const context = React.useContext(gitPluginContext) |
||||
const actions = React.useContext(gitActionsContext) |
||||
const [remoteBranch, setRemoteBranch] = useState('') |
||||
const [localBranch, setLocalBranch] = useState('') |
||||
const [localBranchOptions, setLocalBranchOptions] = useState<any>([]); |
||||
const [remoteBranchOptions, setRemoteBranchOptions] = useState<any>([]); |
||||
const [localRemotesOptions, setLocalRemotesOptions] = useState<any>([]); |
||||
const [force, setForce] = useState(false) |
||||
|
||||
useEffect(() => { |
||||
setRemoteBranch(context.currentBranch.name) |
||||
setLocalBranch(context.currentBranch.name) |
||||
if ((!context.upstream) && context.currentBranch && context.currentBranch.remote && context.currentBranch.remote.name) { |
||||
actions.setUpstreamRemote(context.currentBranch.remote) |
||||
} |
||||
}, [context.currentBranch]) |
||||
|
||||
const onRemoteBranchChange = (value: string) => { |
||||
setRemoteBranch(value) |
||||
} |
||||
|
||||
const onLocalBranchChange = (value: any) => { |
||||
setLocalBranch(value) |
||||
} |
||||
|
||||
const onRemoteChange = (value: string) => { |
||||
const remote: remote = context.remotes.find(r => r.name === value) |
||||
if (remote) { |
||||
actions.setUpstreamRemote(remote) |
||||
} |
||||
} |
||||
|
||||
const onForceChange = (event: any) => { |
||||
const target = event.target; |
||||
const value = target.checked; |
||||
setForce(value) |
||||
} |
||||
|
||||
const push = async () => { |
||||
console.log('PUSH', context.upstream, localBranch, remoteBranch, force) |
||||
await actions.push({ |
||||
remote: context.upstream, |
||||
ref: { |
||||
name: localBranch, |
||||
remote: null |
||||
}, |
||||
remoteRef: { |
||||
name: remoteBranch, |
||||
remote: null |
||||
}, |
||||
force: force |
||||
}) |
||||
await actions.fetch({ |
||||
remote: context.upstream, |
||||
ref: { |
||||
name: localBranch, |
||||
remote: null |
||||
}, |
||||
remoteRef: { |
||||
name: remoteBranch, |
||||
remote: null |
||||
}, |
||||
depth: 1, |
||||
relative: true, |
||||
singleBranch: true |
||||
}) |
||||
} |
||||
|
||||
const pull = async () => { |
||||
await actions.pull({ |
||||
remote: context.upstream, |
||||
ref: { |
||||
name: localBranch, |
||||
remote: null |
||||
}, |
||||
remoteRef: { |
||||
name: remoteBranch, |
||||
remote: null |
||||
}, |
||||
}) |
||||
} |
||||
|
||||
useEffect(() => { |
||||
console.log('context branches', context.branches) |
||||
// map context.repositories to options
|
||||
const localBranches = context.branches && context.branches.length > 0 && context.branches |
||||
.filter(branch => !branch.remote) |
||||
.map(repo => { |
||||
return { value: repo.name, label: repo.name } |
||||
}) |
||||
setLocalBranchOptions(localBranches) |
||||
|
||||
const remoteBranches = context.branches && context.branches.length > 0 && context.branches |
||||
.filter(branch => branch.remote) |
||||
.map(repo => { |
||||
return { value: repo.name, label: repo.name } |
||||
} |
||||
) |
||||
setRemoteBranchOptions(remoteBranches) |
||||
|
||||
}, [context.branches]) |
||||
|
||||
useEffect(() => { |
||||
console.log('context', context.remotes) |
||||
// map context.repositories to options
|
||||
const options = context.remotes && context.remotes.length > 0 && context.remotes |
||||
.map(repo => { |
||||
return { value: repo.name, label: repo.name } |
||||
}) |
||||
|
||||
setLocalRemotesOptions(options) |
||||
|
||||
}, [context.remotes]) |
||||
|
||||
const pushPullIsDisabled = () => { |
||||
return localBranch === '' || remoteBranch === '' || !context.upstream || context.remotes.length === 0 |
||||
} |
||||
|
||||
return ( |
||||
<> |
||||
|
||||
<div className="btn-group w-100" role="group"> |
||||
|
||||
<GitUIButton disabledCondition={pushPullIsDisabled()} type="button" onClick={async () => pull()} className="btn btn-primary mr-1">Pull</GitUIButton> |
||||
<GitUIButton disabledCondition={pushPullIsDisabled()} type="button" onClick={async () => push()} className="btn btn-primary">Push</GitUIButton> |
||||
</div> |
||||
|
||||
<label>Local Branch</label> |
||||
<Select |
||||
options={localBranchOptions} |
||||
isDisabled={context.branches.length === 0} |
||||
onChange={(e: any) => e && onLocalBranchChange(e.value)} |
||||
theme={selectTheme} |
||||
styles={selectStyles} |
||||
isClearable={true} |
||||
value={{ value: localBranch, label: localBranch }} |
||||
placeholder="Type to search for a branch..." |
||||
/> |
||||
|
||||
<label>Remote Branch</label> |
||||
<Select |
||||
options={remoteBranchOptions} |
||||
isDisabled={context.branches.length === 0} |
||||
onChange={(e: any) => e && onRemoteBranchChange(e.value)} |
||||
theme={selectTheme} |
||||
styles={selectStyles} |
||||
isClearable={true} |
||||
value={{ value: remoteBranch, label: remoteBranch }} |
||||
placeholder="Type to search for a branch..." |
||||
/> |
||||
|
||||
<label>Remote</label> |
||||
<Select |
||||
options={localRemotesOptions} |
||||
isDisabled={context.remotes.length === 0} |
||||
onChange={(e: any) => e && onRemoteChange(e.value)} |
||||
theme={selectTheme} |
||||
styles={selectStyles} |
||||
isClearable={true} |
||||
value={{ value: context.upstream && context.upstream.name, label: context.upstream && context.upstream.name }} |
||||
placeholder="Type to search for a branch..." |
||||
/> |
||||
|
||||
<div className="mt-2 remixui_compilerConfig custom-control custom-checkbox"> |
||||
<input checked={force} onChange={e => onForceChange(e)} className="remixui_autocompile custom-control-input" type="checkbox" data-id="compilerContainerAutoCompile" id="forcepush" title="Force Push" /> |
||||
<label className="form-check-label custom-control-label" htmlFor="forcepush">Force push</label> |
||||
</div> |
||||
|
||||
</>) |
||||
} |
@ -0,0 +1,68 @@ |
||||
import { checkout, ReadCommitResult } from "isomorphic-git"; |
||||
import React from "react"; |
||||
import { gitActionsContext } from "../../state/context"; |
||||
import GitUIButton from "../buttons/gituibutton"; |
||||
import { gitPluginContext } from "../gitui"; |
||||
import LoaderIndicator from "../navigation/loaderindicator"; |
||||
import { BranchDifferences } from "./branches/branchdifferences"; |
||||
import { CommitDetails } from "./commits/commitdetails"; |
||||
import { CommitSummary } from "./commits/commitsummary"; |
||||
|
||||
export const Commits = () => { |
||||
const [hasNextPage, setHasNextPage] = React.useState(true) |
||||
const context = React.useContext(gitPluginContext) |
||||
const actions = React.useContext(gitActionsContext) |
||||
|
||||
const checkout = async (oid: string) => { |
||||
try { |
||||
//await ModalRef.current?.show();
|
||||
actions.checkout({ ref: oid }) |
||||
//Utils.log("yes");
|
||||
} catch (e) { |
||||
//Utils.log("no");
|
||||
} |
||||
}; |
||||
|
||||
const loadNextPage = () => { |
||||
console.log('LOAD NEXT PAGE', context.commits.length) |
||||
//actions.fetch(null, context.currentBranch.name, null, 5, true, true)
|
||||
actions.fetch({ |
||||
remote: null, |
||||
ref: context.currentBranch, |
||||
relative: true, |
||||
depth: 5, |
||||
singleBranch: true |
||||
}) |
||||
//actions.getBranchCommits(branch, lastPageNumber+1)
|
||||
} |
||||
|
||||
const getRemote = () => { |
||||
return context.upstream ? context.upstream : context.defaultRemote ? context.defaultRemote : null |
||||
} |
||||
|
||||
const getCommitChanges = async (commit: ReadCommitResult) => { |
||||
await actions.getCommitChanges(commit.oid, commit.commit.parent[0],null, getRemote()) |
||||
} |
||||
|
||||
const fetchIsDisabled = () => { |
||||
return (!context.upstream)|| context.remotes.length === 0 |
||||
} |
||||
|
||||
return ( |
||||
<> |
||||
{context.commits && context.commits.length ? |
||||
<><BranchDifferences branch={context.currentBranch}></BranchDifferences><div> |
||||
<div className="pt-1"> |
||||
{context.commits && context.commits.map((commit, index) => { |
||||
return ( |
||||
<CommitDetails branch={context.currentBranch} getCommitChanges={getCommitChanges} key={index} checkout={checkout} commit={commit}></CommitDetails> |
||||
); |
||||
})} |
||||
</div> |
||||
</div> |
||||
{hasNextPage && <GitUIButton disabledCondition={fetchIsDisabled()} className="mb-1 ml-2 btn btn-sm" onClick={loadNextPage}>Load more</GitUIButton>} |
||||
</> |
||||
: <div className="text-muted">No commits</div>} |
||||
</> |
||||
) |
||||
} |
@ -0,0 +1,62 @@ |
||||
import { ReadCommitResult } from "isomorphic-git" |
||||
import React, { useEffect, useState } from "react"; |
||||
import { Accordion } from "react-bootstrap"; |
||||
import { CommitDetailsNavigation } from "../../navigation/commitdetails"; |
||||
import { gitActionsContext } from "../../../state/context"; |
||||
import { gitPluginContext } from "../../gitui"; |
||||
import { CommitDetailsItems } from "./commitdetailsitem"; |
||||
import { branch, remote } from "@remix-ui/git"; |
||||
|
||||
export interface CommitDetailsProps { |
||||
commit: ReadCommitResult; |
||||
checkout: (oid: string) => void; |
||||
getCommitChanges: (commit: ReadCommitResult) => void; |
||||
branch: branch |
||||
} |
||||
|
||||
export const CommitDetails = (props: CommitDetailsProps) => { |
||||
const { commit, checkout, getCommitChanges, branch } = props; |
||||
const actions = React.useContext(gitActionsContext) |
||||
const context = React.useContext(gitPluginContext) |
||||
const [activePanel, setActivePanel] = useState<string>(""); |
||||
|
||||
useEffect(() => { |
||||
if (activePanel === "0") { |
||||
console.log(context) |
||||
getCommitChanges(commit) |
||||
} |
||||
}, [activePanel]) |
||||
|
||||
const getRemote = (): remote | null => { |
||||
return context.upstream ? context.upstream : context.defaultRemote ? context.defaultRemote : null |
||||
} |
||||
|
||||
const commitsAhead = (remote: remote) => { |
||||
if (!remote) return []; |
||||
return context.branchDifferences[`${remote.name}/${branch.name}`]?.uniqueHeadCommits || []; |
||||
} |
||||
|
||||
const isAheadOfRepo = () => { |
||||
return commitsAhead(getRemote()).findIndex((c) => c.oid === commit.oid) > -1 |
||||
} |
||||
|
||||
const openFileOnRemote = (file: string, hash: string) => { |
||||
console.log("open file on remote", file, hash, getRemote() ? `${getRemote().name}/${branch.name}/commit/${hash}/${file}` : "") |
||||
if (!getRemote()) return |
||||
window.open(`${getRemote() ? `${getRemote().url}/blob/${hash}/${file}` : ""}`, "_blank") |
||||
} |
||||
|
||||
return (<Accordion activeKey={activePanel} defaultActiveKey=""> |
||||
<CommitDetailsNavigation isAheadOfRepo={isAheadOfRepo()} commit={commit} checkout={checkout} eventKey="0" activePanel={activePanel} callback={setActivePanel} /> |
||||
<Accordion.Collapse className="pl-2 border-left ml-1" eventKey="0"> |
||||
<> |
||||
{context.commitChanges && context.commitChanges.filter( |
||||
(change) => change.hashModified === commit.oid && change.hashOriginal === commit.commit.parent[0] |
||||
).map((change, index) => { |
||||
return (<CommitDetailsItems openFileOnRemote={openFileOnRemote} isAheadOfRepo={isAheadOfRepo()} key={index} commitChange={change}></CommitDetailsItems>) |
||||
})} |
||||
|
||||
</> |
||||
</Accordion.Collapse> |
||||
</Accordion>) |
||||
} |
@ -0,0 +1,53 @@ |
||||
import { branch, commitChange } from "../../../types"; |
||||
import React from "react"; |
||||
import path from "path"; |
||||
import { gitActionsContext, pluginActionsContext } from "../../../state/context"; |
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; |
||||
import { faGlobe } from "@fortawesome/free-solid-svg-icons"; |
||||
|
||||
export interface CCommitDetailsItemsProps { |
||||
commitChange: commitChange; |
||||
isAheadOfRepo: boolean; |
||||
openFileOnRemote: (file: string, hash: string) => void; |
||||
} |
||||
|
||||
export const CommitDetailsItems = (props: CCommitDetailsItemsProps) => { |
||||
const { commitChange, isAheadOfRepo, openFileOnRemote } = props; |
||||
const actions = React.useContext(gitActionsContext) |
||||
const pluginActions = React.useContext(pluginActionsContext) |
||||
|
||||
const openChanges = async (change: commitChange) => { |
||||
console.log("open changes", change); |
||||
await actions.diff(change) |
||||
console.log("open changes", change); |
||||
await pluginActions.openDiff(change) |
||||
} |
||||
|
||||
const openRemote = () => { |
||||
openFileOnRemote(commitChange.path, commitChange.hashModified) |
||||
} |
||||
|
||||
function FunctionStatusIcons() { |
||||
const status = commitChange.type |
||||
return (<> |
||||
|
||||
{status && status.indexOf("modified") === -1 ? <></> : <span>M</span>} |
||||
{status && status.indexOf("deleted") === -1 ? <></> : <span>D</span>} |
||||
{status && status.indexOf("added") === -1 ? <></> : <span>A</span>} |
||||
|
||||
</>) |
||||
} |
||||
return (<> |
||||
<div className={`d-flex w-100 d-flex flex-row commitdetailsitem ${isAheadOfRepo ? 'text-success' : ''}`}> |
||||
<div className='pointer gitfile long-and-truncated' onClick={async () => await openChanges(commitChange)}> |
||||
<span className='font-weight-bold long-and-truncated'>{path.basename(commitChange.path)}</span> |
||||
<div className='text-secondary long-and-truncated'> {commitChange.path}</div> |
||||
</div> |
||||
<div className="d-flex align-items-end"> |
||||
{!isAheadOfRepo ? |
||||
<FontAwesomeIcon role={'button'} icon={faGlobe} onClick={() => openRemote()} className="pointer mr-1 align-self-center" /> : <></>} |
||||
<FunctionStatusIcons></FunctionStatusIcons> |
||||
</div> |
||||
</div> |
||||
</>) |
||||
} |
@ -0,0 +1,67 @@ |
||||
import { ReadCommitResult } from "isomorphic-git" |
||||
import { default as dateFormat } from "dateformat"; |
||||
import React from "react"; |
||||
import { faGlobe } from "@fortawesome/free-solid-svg-icons"; |
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; |
||||
import { remote } from "@remix-ui/git"; |
||||
import GitUIButton from "../../buttons/gituibutton"; |
||||
import { gitPluginContext } from "../../gitui"; |
||||
|
||||
export interface CommitSummaryProps { |
||||
commit: ReadCommitResult; |
||||
checkout: (oid: string) => void; |
||||
isAheadOfRepo: boolean |
||||
} |
||||
|
||||
export const CommitSummary = (props: CommitSummaryProps) => { |
||||
const { commit, checkout, isAheadOfRepo } = props; |
||||
const context = React.useContext(gitPluginContext) |
||||
|
||||
const getDate = (commit: ReadCommitResult) => { |
||||
const timestamp = commit.commit.author.timestamp; |
||||
|
||||
if (timestamp) { |
||||
// calculate the difference between the current time and the commit time in days or hours or minutes
|
||||
const diff = Math.floor((Date.now() - timestamp * 1000) / 1000 / 60 / 60 / 24); |
||||
|
||||
if (diff == 0) { |
||||
return "today at " + dateFormat(timestamp * 1000, "HH:MM"); |
||||
} else |
||||
|
||||
if (diff < 1) { |
||||
// return how many hours ago
|
||||
return `${Math.floor(diff * 24)} hour(s) ago`; |
||||
} |
||||
|
||||
if (diff < 7) { |
||||
// return how many days ago
|
||||
return `${diff} day(s) ago`; |
||||
} |
||||
if (diff < 365) { |
||||
return dateFormat(timestamp * 1000, "mmm dd"); |
||||
} |
||||
return dateFormat(timestamp * 1000, "mmm dd yyyy"); |
||||
} |
||||
return ""; |
||||
}; |
||||
|
||||
const getRemote = (): remote | null => { |
||||
return context.upstream ? context.upstream : context.defaultRemote ? context.defaultRemote : null |
||||
} |
||||
|
||||
const openRemote = () => { |
||||
if (getRemote()) |
||||
window.open(`${getRemote().url}/commit/${commit.oid}`, '_blank'); |
||||
} |
||||
|
||||
return ( |
||||
<> |
||||
<div className="long-and-truncated ml-2"> |
||||
{commit.commit.message} |
||||
</div> |
||||
{commit.commit.author.name || ""} |
||||
<span className="ml-1">{getDate(commit)}</span> |
||||
{getRemote() && getRemote()?.url && !isAheadOfRepo && <GitUIButton className="btn btn-sm p-0 text-muted ml-1" onClick={() => openRemote()}><FontAwesomeIcon icon={faGlobe} ></FontAwesomeIcon></GitUIButton>} |
||||
</> |
||||
) |
||||
} |
@ -0,0 +1,83 @@ |
||||
import React, { useEffect } from "react"; |
||||
import { gitActionsContext, pluginActionsContext } from "../../state/context"; |
||||
import { gitPluginContext, loaderContext } from "../gitui"; |
||||
import { CustomTooltip } from "@remix-ui/helper"; |
||||
|
||||
import { useIntl, FormattedMessage } from "react-intl"; |
||||
import { CopyToClipboard } from "@remix-ui/clipboard"; |
||||
|
||||
export const GitHubCredentials = () => { |
||||
const context = React.useContext(gitPluginContext) |
||||
const pluginactions = React.useContext(pluginActionsContext) |
||||
const loader = React.useContext(loaderContext) |
||||
const actions = React.useContext(gitActionsContext) |
||||
const [githubToken, setGithubToken] = React.useState('') |
||||
const [githubUsername, setGithubUsername] = React.useState('') |
||||
const [githubEmail, setGithubEmail] = React.useState('') |
||||
const intl = useIntl() |
||||
|
||||
useEffect(() => { |
||||
refresh() |
||||
}, [loader.plugin, context.gitHubAccessToken, context.userEmails, context.gitHubUser]) |
||||
|
||||
function handleChangeTokenState(e: string): void { |
||||
setGithubToken(e) |
||||
} |
||||
|
||||
function handleChangeUserNameState(e: string): void { |
||||
setGithubUsername(e) |
||||
} |
||||
|
||||
function handleChangeEmailState(e: string): void { |
||||
setGithubEmail(e) |
||||
} |
||||
|
||||
async function saveGithubToken() { |
||||
await pluginactions.saveGitHubCredentials({ |
||||
username: githubUsername, |
||||
email: githubEmail, |
||||
token: githubToken |
||||
}) |
||||
} |
||||
|
||||
async function refresh() { |
||||
const credentials = await pluginactions.getGitHubCredentialsFromLocalStorage() |
||||
if (!credentials) return |
||||
console.log('credentials', credentials) |
||||
setGithubToken(credentials.token || '') |
||||
setGithubUsername(credentials.username || '') |
||||
setGithubEmail(credentials.email || '') |
||||
} |
||||
|
||||
function removeToken(): void { |
||||
setGithubToken('') |
||||
setGithubUsername('') |
||||
setGithubEmail('') |
||||
pluginactions.saveGitHubCredentials({ |
||||
username: '', |
||||
email: '', |
||||
token: '' |
||||
}) |
||||
} |
||||
|
||||
return ( |
||||
<> |
||||
<div className="input-group text-secondary mb-1 h6"> |
||||
<input type="password" value={githubToken} placeholder="GitHub token" className="form-control" name='githubToken' onChange={e => handleChangeTokenState(e.target.value)} /> |
||||
<div className="input-group-append"> |
||||
<CopyToClipboard content={githubToken} data-id='copyToClipboardCopyIcon' className='far fa-copy ml-1 p-2 mt-1' direction={"top"} /> |
||||
</div> |
||||
</div> |
||||
<input name='githubUsername' onChange={e => handleChangeUserNameState(e.target.value)} value={githubUsername} className="form-control mb-1" placeholder="Git username" type="text" id="githubUsername" /> |
||||
<input name='githubEmail' onChange={e => handleChangeEmailState(e.target.value)} value={githubEmail} className="form-control mb-1" placeholder="Git email" type="text" id="githubEmail" /> |
||||
<div className="d-flex justify-content-between"> |
||||
<button className="btn btn-primary w-100" onClick={saveGithubToken}> |
||||
<FormattedMessage id="save" defaultMessage="Save" /> |
||||
</button> |
||||
<button className="btn btn-danger far fa-trash-alt" onClick={removeToken}> |
||||
</button> |
||||
</div> |
||||
<hr /> |
||||
</> |
||||
); |
||||
} |
@ -0,0 +1,29 @@ |
||||
import { CustomTooltip } from '@remix-ui/helper'; |
||||
import { pull } from 'lodash'; |
||||
import React, { useContext } from 'react'; |
||||
import { FormattedMessage } from 'react-intl'; |
||||
import { gitActionsContext } from '../../state/context'; |
||||
import GitUIButton from '../buttons/gituibutton'; |
||||
|
||||
export const Init = () => { |
||||
|
||||
const actions = React.useContext(gitActionsContext) |
||||
|
||||
const init = async () => { |
||||
actions.init() |
||||
} |
||||
|
||||
return ( |
||||
<> |
||||
<div> |
||||
<div className='mt-1 mb-2'> |
||||
<GitUIButton |
||||
onClick={init} |
||||
className="btn w-md-25 w-100 btn-primary" |
||||
id="initgit-btn" |
||||
><FormattedMessage id='git.init'/></GitUIButton> |
||||
</div> |
||||
</div> |
||||
</> |
||||
) |
||||
} |
@ -0,0 +1,39 @@ |
||||
// src/LogViewer.tsx
|
||||
import React, { useContext } from 'react'; |
||||
import { gitPluginContext } from '../gitui'; |
||||
|
||||
const LogViewer = () => { |
||||
const context = useContext(gitPluginContext); |
||||
|
||||
const typeToCssClass = (type: string) => { |
||||
switch (type) { |
||||
case 'error': |
||||
return 'text-danger'; |
||||
case 'warning': |
||||
return 'text-warning'; |
||||
case 'info': |
||||
return 'text-info'; |
||||
case 'debug': |
||||
return 'text-secondary'; |
||||
default: |
||||
return 'text-success'; |
||||
} |
||||
}; |
||||
|
||||
if (context.log && context.log.length > 0) { |
||||
|
||||
return ( |
||||
<div className="p-1"> |
||||
{context.log && context.log.map((log, index) => ( |
||||
<div key={index} className={`log-entry ${typeToCssClass(log.type)}`}> |
||||
[{log.type.toUpperCase()}] {log.message} |
||||
</div> |
||||
))} |
||||
</div> |
||||
); |
||||
} else { |
||||
return <div className="p-1">No logs</div> |
||||
} |
||||
}; |
||||
|
||||
export default LogViewer; |
@ -0,0 +1,57 @@ |
||||
import React, { useEffect } from "react"; |
||||
import { gitActionsContext } from "../../state/context"; |
||||
import { gitPluginContext } from "../gitui"; |
||||
import { Remoteselect } from "./remoteselect"; |
||||
import { RemotesImport } from "./remotesimport"; |
||||
|
||||
export const Remotes = () => { |
||||
const context = React.useContext(gitPluginContext) |
||||
const actions = React.useContext(gitActionsContext) |
||||
const [remoteName, setRemoteName] = React.useState<string>('') |
||||
const [url, setUrl] = React.useState<string>('') |
||||
|
||||
const onRemoteNameChange = (value: string) => { |
||||
setRemoteName(value) |
||||
} |
||||
const onUrlChange = (value: string) => { |
||||
setUrl(value) |
||||
} |
||||
|
||||
const addRemote = async () => { |
||||
actions.addRemote({ |
||||
name: remoteName, |
||||
url: url |
||||
}) |
||||
} |
||||
|
||||
useEffect(() => { |
||||
console.log('SHOW REMOTES', context.remotes) |
||||
}, [context.remotes]) |
||||
|
||||
return ( |
||||
<> |
||||
|
||||
{context.remotes && context.remotes.length ? |
||||
<> |
||||
|
||||
{context.remotes && context.remotes.map((remote, index) => { |
||||
|
||||
return ( |
||||
<Remoteselect key={index} remote={remote}></Remoteselect> |
||||
); |
||||
})} |
||||
</> : <>No remotes</>} |
||||
<hr></hr> |
||||
|
||||
<input placeholder="remote name" name='remotename' onChange={e => onRemoteNameChange(e.target.value)} value={remoteName} className="form-control mb-2" type="text" id="remotename" /> |
||||
<input placeholder="remote url" name='remoteurl' onChange={e => onUrlChange(e.target.value)} value={url} className="form-control" type="text" id="remoteurl" /> |
||||
|
||||
<button disabled={(remoteName && url)?false:true} className='btn btn-primary mt-1 w-100' onClick={async () => { |
||||
addRemote(); |
||||
}}>add remote</button> |
||||
<hr /> |
||||
<RemotesImport /> |
||||
<hr /> |
||||
|
||||
</>) |
||||
} |
@ -0,0 +1,47 @@ |
||||
import { branch, checkout, ReadCommitResult } from "isomorphic-git"; |
||||
import React, { useEffect, useState } from "react"; |
||||
import { gitActionsContext } from "../../state/context"; |
||||
import { gitPluginContext } from "../gitui"; |
||||
import { default as dateFormat } from "dateformat"; |
||||
import { RemotesDetailsNavigation } from "../navigation/remotesdetails"; |
||||
import { Accordion } from "react-bootstrap"; |
||||
import { remote } from "../../types"; |
||||
import { RemoteBranchDetails } from "./branches/remotebranchedetails"; |
||||
|
||||
export interface RemoteSelectProps { |
||||
remote: remote |
||||
} |
||||
|
||||
export const Remoteselect = (props: RemoteSelectProps) => { |
||||
const { remote } = props; |
||||
const context = React.useContext(gitPluginContext) |
||||
const actions = React.useContext(gitActionsContext) |
||||
const [activePanel, setActivePanel] = useState<string>(""); |
||||
|
||||
useEffect(() => { |
||||
if (activePanel === "0") { |
||||
console.log('fetching', remote) |
||||
} |
||||
}, [activePanel]) |
||||
|
||||
useEffect(() => { |
||||
console.log('remote branches', context.branches) |
||||
}, [context.branches]) |
||||
|
||||
return ( |
||||
<> |
||||
<Accordion activeKey={activePanel} defaultActiveKey=""> |
||||
<RemotesDetailsNavigation callback={setActivePanel} eventKey="0" activePanel={activePanel} remote={remote} /> |
||||
<Accordion.Collapse className="pl-2 border-left ml-1" eventKey="0"> |
||||
<> |
||||
{context.branches && context.branches.filter((branch, index) => branch.remote && branch.remote.name === remote.name ).map((branch, index) => { |
||||
return ( |
||||
<RemoteBranchDetails key={index} branch={branch}></RemoteBranchDetails> |
||||
); |
||||
})}</> |
||||
|
||||
</Accordion.Collapse> |
||||
</Accordion> |
||||
</> |
||||
) |
||||
} |
@ -0,0 +1,86 @@ |
||||
import React, { useEffect, useState } from "react"; |
||||
import { Alert, Button } from "react-bootstrap"; |
||||
import { gitActionsContext } from "../../state/context"; |
||||
import { repository } from "../../types"; |
||||
import { gitPluginContext } from "../gitui"; |
||||
import Select from 'react-select' |
||||
import { selectStyles, selectTheme } from "../../types/styles"; |
||||
import { TokenWarning } from "./tokenWarning"; |
||||
import RepositorySelect from "../github/repositoryselect"; |
||||
|
||||
export const RemotesImport = () => { |
||||
const context = React.useContext(gitPluginContext) |
||||
const actions = React.useContext(gitActionsContext) |
||||
const [repo, setRepo] = useState<repository>(null); |
||||
const [repoOtions, setRepoOptions] = useState<any>([]); |
||||
const [loading, setLoading] = useState(false) |
||||
const [show, setShow] = useState(false) |
||||
const [remoteName, setRemoteName] = useState('') |
||||
|
||||
useEffect(() => { |
||||
if (context.repositories && context.repositories.length > 0) { |
||||
// map context.repositories to options
|
||||
const options = context.repositories && context.repositories.length > 0 && context.repositories.map(repo => { |
||||
return { value: repo.id, label: repo.full_name } |
||||
}) |
||||
setRepoOptions(options) |
||||
} else { |
||||
setRepoOptions(null) |
||||
setShow(false) |
||||
} |
||||
setLoading(false) |
||||
|
||||
}, [context.repositories]) |
||||
|
||||
const fetchRepositories = async () => { |
||||
try { |
||||
setShow(true) |
||||
setLoading(true) |
||||
setRepoOptions([]) |
||||
console.log(await actions.repositories()) |
||||
} catch (e) { |
||||
// do nothing
|
||||
} |
||||
}; |
||||
|
||||
const selectRepo = async (repo: repository) => { |
||||
setRepo(repo) |
||||
} |
||||
|
||||
useEffect(() => { |
||||
console.log('OTIONS', repoOtions) |
||||
},[repoOtions]) |
||||
|
||||
const addRemote = async () => { |
||||
try { |
||||
actions.addRemote({ |
||||
name: remoteName, |
||||
url: repo.html_url |
||||
}) |
||||
} catch (e) { |
||||
// do nothing
|
||||
} |
||||
|
||||
}; |
||||
const onRemoteNameChange = (value: string) => { |
||||
setRemoteName(value) |
||||
} |
||||
|
||||
return ( |
||||
<> |
||||
<TokenWarning /> |
||||
<RepositorySelect select={selectRepo} /> |
||||
|
||||
{repo ? |
||||
<input placeholder="remote name" name='remotename' onChange={e => onRemoteNameChange(e.target.value)} value={remoteName} className="form-control mb-2" type="text" id="remotename" /> |
||||
: null} |
||||
|
||||
{repo && remoteName ? |
||||
<button data-id='clonebtn' className='btn btn-primary mt-1 w-100' onClick={async () => { |
||||
await addRemote() |
||||
}}>add {remoteName}:{repo.full_name}</button> : null} |
||||
|
||||
</> |
||||
) |
||||
} |
||||
|
@ -0,0 +1,48 @@ |
||||
import { checkout, clone, ReadCommitResult } from "isomorphic-git"; |
||||
import React from "react"; |
||||
import { gitActionsContext } from "../../state/context"; |
||||
import { gitPluginContext } from "../gitui"; |
||||
import { CustomTooltip } from "@remix-ui/helper"; |
||||
|
||||
import { useIntl, FormattedMessage } from "react-intl"; |
||||
import { CopyToClipboard } from "@remix-ui/clipboard"; |
||||
import { FormControl, InputGroup } from "react-bootstrap"; |
||||
|
||||
export const Settings = () => { |
||||
|
||||
const [githubToken, setGithubToken] = React.useState('') |
||||
const [githubUsername, setGithubUsername] = React.useState('') |
||||
const [githubEmail, setGithubEmail] = React.useState('') |
||||
const intl = useIntl() |
||||
|
||||
const gitAccessTokenLink = 'https://github.com/settings/tokens/new?scopes=gist,repo&description=Remix%20IDE%20Token' |
||||
|
||||
function handleChangeTokenState(e: string): void { |
||||
throw new Error("Function not implemented."); |
||||
} |
||||
|
||||
function handleChangeUserNameState(e: string): void { |
||||
throw new Error("Function not implemented."); |
||||
} |
||||
|
||||
function handleChangeEmailState(e: string): void { |
||||
throw new Error("Function not implemented."); |
||||
} |
||||
|
||||
function saveGithubToken(): void { |
||||
throw new Error("Function not implemented."); |
||||
} |
||||
|
||||
function removeToken(): void { |
||||
throw new Error("Function not implemented."); |
||||
} |
||||
|
||||
return ( |
||||
<> |
||||
<input name='githubToken' onChange={e => handleChangeUserNameState(e.target.value)} value={githubToken} className="form-control mb-2" placeholder="GitHub token" type="text" id="githubToken" /> |
||||
<input name='githubUsername' onChange={e => handleChangeUserNameState(e.target.value)} value={githubUsername} className="form-control mb-2" placeholder="GitHub username" type="text" id="githubUsername" /> |
||||
<input name='githubEmail' onChange={e => handleChangeEmailState(e.target.value)} value={githubEmail} className="form-control mb-1" placeholder="GitHub email" type="text" id="githubEmail" /> |
||||
<hr /> |
||||
</> |
||||
); |
||||
} |
@ -0,0 +1,36 @@ |
||||
import React, { useEffect, useState } from 'react' |
||||
import { GetDeviceCode } from '../github/devicecode' |
||||
import { GitHubCredentials } from './githubcredentials' |
||||
|
||||
export const Setup = () => { |
||||
|
||||
const [screen, setScreen] = useState(0) |
||||
|
||||
if (screen === 0) { |
||||
return ( |
||||
<> |
||||
<h5>SETUP</h5> |
||||
<div> |
||||
<div className='mt-1 mb-2'> |
||||
To ensure that your commits are properly attributed in Git, you need to configure a username and email address. |
||||
These will be used to identify the author of the commit. |
||||
</div> |
||||
<GetDeviceCode></GetDeviceCode> |
||||
<hr></hr> |
||||
<GitHubCredentials></GitHubCredentials> |
||||
</div> |
||||
</> |
||||
) |
||||
} else if (screen === 1) { |
||||
return ( |
||||
<> |
||||
<h5>SETUP</h5> |
||||
<h6>Step 2</h6> |
||||
<div> |
||||
To ensure that your commits are properly attributed in Git, you need to configure your username and email address. |
||||
<GitHubCredentials></GitHubCredentials> |
||||
</div> |
||||
</> |
||||
) |
||||
} |
||||
} |
@ -0,0 +1,38 @@ |
||||
import { ReadCommitResult } from "isomorphic-git" |
||||
import React, { useEffect, useState } from "react"; |
||||
import { Accordion } from "react-bootstrap"; |
||||
import { gitActionsContext } from "../../../state/context"; |
||||
import { gitPluginContext } from "../../gitui"; |
||||
import { sourceControlGroup } from "../../../types"; |
||||
import { SourceControlGroupNavigation } from "../../navigation/sourcecontrolgroup"; |
||||
import { SourceControlItem } from "./sourcecontrolitem"; |
||||
|
||||
export interface SourceControGroupProps { |
||||
group: sourceControlGroup |
||||
} |
||||
|
||||
export const SourceControGroup = (props: SourceControGroupProps) => { |
||||
const { group } = props; |
||||
const actions = React.useContext(gitActionsContext) |
||||
const context = React.useContext(gitPluginContext) |
||||
const [activePanel, setActivePanel] = useState<string>("0"); |
||||
|
||||
useEffect(() => { |
||||
if (activePanel === "0") { |
||||
} |
||||
}, [activePanel]) |
||||
|
||||
return (<> |
||||
{group.group.length > 0 ? |
||||
<Accordion activeKey={activePanel} defaultActiveKey=""> |
||||
<SourceControlGroupNavigation group={group} eventKey="0" activePanel={activePanel} callback={setActivePanel} /> |
||||
<Accordion.Collapse className="pl-2 border-left ml-1" eventKey="0"> |
||||
<> |
||||
{group.group.map((file, index) => { |
||||
return (<SourceControlItem key={index} group={group} file={file}></SourceControlItem>) |
||||
})} |
||||
</> |
||||
</Accordion.Collapse> |
||||
</Accordion> : <></>} |
||||
</>) |
||||
} |
@ -0,0 +1,67 @@ |
||||
import { commitChange, fileStatusResult, sourceControlGroup } from "../../../types"; |
||||
import React from "react"; |
||||
import path from "path"; |
||||
import { gitActionsContext, pluginActionsContext } from "../../../state/context"; |
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; |
||||
import { faGlobe } from "@fortawesome/free-solid-svg-icons"; |
||||
import { SourceControlItemButtons } from "./sourcontrolitembuttons"; |
||||
import { removeSlash } from "../../../utils"; |
||||
|
||||
export interface SourceControlItemProps { |
||||
file: fileStatusResult; |
||||
group: sourceControlGroup; |
||||
} |
||||
|
||||
export const SourceControlItem = (props: SourceControlItemProps) => { |
||||
const { file, group } = props; |
||||
const actions = React.useContext(gitActionsContext) |
||||
const pluginActions = React.useContext(pluginActionsContext) |
||||
|
||||
async function fileClick(file: fileStatusResult) { |
||||
console.log(file) |
||||
//let status = fileservice.getFileStatusForFile(file.filename || "");
|
||||
if (file.statusNames && file.statusNames.indexOf("modified") !== -1) { |
||||
const headHash = await actions.resolveRef("HEAD") |
||||
const change: commitChange = { |
||||
path: removeSlash(file.filename), |
||||
type: "modified", |
||||
hashOriginal: headHash, |
||||
hashModified: '', |
||||
readonly: false, |
||||
} |
||||
await actions.diff(change) |
||||
await pluginActions.openDiff(change) |
||||
console.log("diff", change) |
||||
|
||||
} else { |
||||
await pluginActions.openFile(file.filename) |
||||
//await client.call('fileManager', 'open', file.filename)
|
||||
} |
||||
} |
||||
|
||||
function FunctionStatusIcons() { |
||||
|
||||
const status = file.statusNames |
||||
|
||||
return (<> |
||||
|
||||
{status && status.indexOf("modified") === -1 ? <></> : <div>M</div>} |
||||
{status && status.indexOf("deleted") === -1 ? <></> : <span>D</span>} |
||||
{status && status.indexOf("added") === -1 ? <></> : <span>A</span>} |
||||
{status && status.indexOf("untracked") === -1 ? <></> : <span>U</span>} |
||||
</>) |
||||
|
||||
} |
||||
return (<> |
||||
<div className="d-flex w-100 d-flex flex-row align-items-center"> |
||||
<div className='pointer gitfile long-and-truncated' onClick={async () => await fileClick(file)}> |
||||
<span className='font-weight-bold long-and-truncated'>{path.basename(file.filename)}</span> |
||||
<div className='text-secondary long-and-truncated'> {file.filename}</div> |
||||
</div> |
||||
<div className="d-flex align-items-center ml-1"> |
||||
<SourceControlItemButtons group={group} file={file}></SourceControlItemButtons> |
||||
<FunctionStatusIcons></FunctionStatusIcons> |
||||
</div> |
||||
</div> |
||||
</>) |
||||
} |
@ -0,0 +1,44 @@ |
||||
import { commitChange, fileStatusResult, sourceControlGroup } from "../../../types"; |
||||
import React from "react"; |
||||
import path from "path"; |
||||
import { gitActionsContext, pluginActionsContext } from "../../../state/context"; |
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; |
||||
import { faGlobe, faMinus, faPlus, faUndo } from "@fortawesome/free-solid-svg-icons"; |
||||
|
||||
export interface SourceControlItemButtonsProps { |
||||
file: fileStatusResult, |
||||
group: sourceControlGroup |
||||
} |
||||
|
||||
export const SourceControlItemButtons = (props: SourceControlItemButtonsProps) => { |
||||
const { file, group } = props; |
||||
const actions = React.useContext(gitActionsContext) |
||||
const pluginActions = React.useContext(pluginActionsContext) |
||||
|
||||
function RenderButtons() { |
||||
const status = file.statusNames |
||||
|
||||
if (group.name === 'Staged') { |
||||
return <> |
||||
|
||||
{status && status.indexOf("modified") === -1 ? <></> : <button onClick={async () => await actions.checkoutfile(file.filename)} className='btn btn-sm btn-secondary mr-1 '><FontAwesomeIcon icon={faUndo} className="" /></button>} |
||||
{status && status.indexOf("deleted") === -1 ? <></> : <button onClick={async () => await actions.checkoutfile(file.filename)} className='btn btn-sm btn-secondary mr-1 '><FontAwesomeIcon icon={faUndo} className="" /></button>} |
||||
{status && status.indexOf("deleted") !== -1 ? <></> : <button data-id={`unStage${group.name}${path.basename(file.filename)}`} onClick={async () => await actions.rm({ filepath:file.filename })} className='btn btn-sm btn-secondary mr-1 '><FontAwesomeIcon icon={faMinus} className="" /></button>} |
||||
|
||||
</> |
||||
} |
||||
if (group.name === 'Changes') { |
||||
return <> |
||||
|
||||
{status && status.indexOf("deleted") === -1 ? <></> : <><button onClick={async () => await actions.checkoutfile(file.filename)} data-id={`undo${group.name}${path.basename(file.filename)}`} className='btn btn-sm btn-secondary mr-1 '><FontAwesomeIcon icon={faUndo} className="" /></button><button data-id={`addToGit${group.name}${path.basename(file.filename)}`} onClick={async () => await actions.rm({ filepath:file.filename })} className='btn btn-sm btn-secondary mr-1 '><FontAwesomeIcon icon={faPlus} className="" /></button></>} |
||||
{status && status.indexOf("modified") === -1 ? <></> : <button onClick={async () => await actions.checkoutfile(file.filename)} data-id={`undo${group.name}${path.basename(file.filename)}`} className='btn btn-sm btn-secondary mr-1 '><FontAwesomeIcon icon={faUndo} className="" /></button>} |
||||
{(status && status.indexOf("unstaged") !== -1 || status && status.indexOf("deleted") !== -1) ? <></> : <button data-id={`addToGit${group.name}${path.basename(file.filename)}`} onClick={async () => await actions.add({ filepath:file.filename })} className='btn btn-sm btn-secondary mr-1 '><FontAwesomeIcon icon={faPlus} className="" /></button>} |
||||
{(status && status.indexOf("unstaged") !== -1 && status && status.indexOf("modified") !== -1) ? <button data-id={`addToGit${group.name}${path.basename(file.filename)}`} onClick={async () => await actions.add({ filepath:file.filename })} className='btn btn-sm btn-secondary mr-1 '><FontAwesomeIcon icon={faPlus} className="" /></button> : <></>} |
||||
</> |
||||
} |
||||
return <></> |
||||
} |
||||
|
||||
return <RenderButtons /> |
||||
|
||||
} |
@ -0,0 +1,57 @@ |
||||
import React, { useEffect, useState } from 'react' |
||||
import { gitActionsContext, pluginActionsContext } from '../../state/context' |
||||
import { gitPluginContext } from '../gitui' |
||||
import { sourceControlGroup } from '../../types' |
||||
import { SourceControGroup } from './sourcecontrol/sourcecontrolgroup' |
||||
|
||||
export const SourceControl = () => { |
||||
const context = React.useContext(gitPluginContext) |
||||
const actions = React.useContext(gitActionsContext) |
||||
const pluginactions = React.useContext(pluginActionsContext) |
||||
const [show, setShow] = useState(false) |
||||
|
||||
useEffect(() => { |
||||
if (context.fileStatusResult) { |
||||
console.log(context) |
||||
const total = context.allchangesnotstaged.length |
||||
const badges = total + context.staged.length |
||||
pluginactions.statusChanged(badges) |
||||
//console.log("allchangesnotstaged", context.allchangesnotstaged, context.fileStatusResult)
|
||||
setShow((context.deleted.length > 0 || context.staged.length > 0 || context.untracked.length > 0 || context.modified.length > 0)) |
||||
} |
||||
}, [context.fileStatusResult, context.modified, context.allchangesnotstaged, context.untracked, context.deleted]) |
||||
|
||||
useEffect(() => { |
||||
if (context.commits) { |
||||
console.log("SC commits", context.localCommitCount, context.currentBranch) |
||||
} |
||||
}, [context.localCommitCount, context.currentBranch]) |
||||
|
||||
function RenderGroups() { |
||||
const groups: sourceControlGroup[] = [{ name: 'Staged', group: context.staged }, { name: 'Changes', group: context.allchangesnotstaged }] |
||||
return (<> |
||||
{ |
||||
groups.map((ob: sourceControlGroup, index: number) => { |
||||
return ( |
||||
<SourceControGroup key={index} group={ob}></SourceControGroup> |
||||
) |
||||
}) |
||||
} |
||||
|
||||
</>) |
||||
} |
||||
|
||||
return ( |
||||
<> |
||||
{show ? |
||||
<> |
||||
<div> |
||||
<RenderGroups></RenderGroups> |
||||
</div></> |
||||
: <> |
||||
|
||||
</>} |
||||
</> |
||||
); |
||||
|
||||
} |
@ -0,0 +1,12 @@ |
||||
import { gitPluginContext } from "../gitui" |
||||
import React, { useEffect, useState } from "react"; |
||||
export const TokenWarning = () => { |
||||
const context = React.useContext(gitPluginContext) |
||||
return (<> |
||||
{(context.gitHubUser && context.gitHubUser.login) ? null : |
||||
<li className="text-warning list-group-item d-flex justify-content-between align-items-center"> |
||||
To use add a GitHub token to the settings.</li> |
||||
} |
||||
</> |
||||
) |
||||
} |
@ -0,0 +1,37 @@ |
||||
import { useState } from "react" |
||||
|
||||
export function useLocalStorage(key: string, initialValue: any) { |
||||
// State to store our value
|
||||
// Pass initial state function to useState so logic is only executed once
|
||||
const [storedValue, setStoredValue] = useState(() => { |
||||
try { |
||||
// Get from local storage by key
|
||||
const item = window.localStorage.getItem(key) |
||||
// Parse stored json or if none return initialValue
|
||||
return item ? JSON.parse(item) : initialValue |
||||
} catch (error) { |
||||
// If error also return initialValue
|
||||
console.error(error) |
||||
return initialValue |
||||
} |
||||
}) |
||||
|
||||
// Return a wrapped version of useState's setter function that ...
|
||||
// ... persists the new value to localStorage.
|
||||
const setValue = (value: any) => { |
||||
try { |
||||
// Allow value to be a function so we have same API as useState
|
||||
const valueToStore = |
||||
value instanceof Function ? value(storedValue) : value |
||||
// Save state
|
||||
setStoredValue(valueToStore) |
||||
// Save to local storage
|
||||
window.localStorage.setItem(key, JSON.stringify(valueToStore)) |
||||
} catch (error) { |
||||
// A more advanced implementation would handle the error case
|
||||
console.error(error) |
||||
} |
||||
} |
||||
|
||||
return [storedValue, setValue] |
||||
} |
@ -0,0 +1,4 @@ |
||||
export * from './types' |
||||
export { GitUI } from './components/gitui' |
||||
export { commitChange, commitChangeType, remote, branch } from './types' |
||||
export * from './types/styles' |
@ -0,0 +1,56 @@ |
||||
import { fileStatusResult, gitState } from "../types"; |
||||
|
||||
export const getFilesCountByStatus = (status: string, files: fileStatusResult[]) => { |
||||
let count = 0; |
||||
|
||||
files.map((m) => { |
||||
if (m.statusNames !== undefined) { |
||||
if (m.statusNames && m.statusNames.indexOf(status) > -1) { |
||||
count++; |
||||
} |
||||
} |
||||
}); |
||||
return count; |
||||
} |
||||
|
||||
export const getFilesByStatus = (status: string, files: fileStatusResult[]): fileStatusResult[] => { |
||||
const result: fileStatusResult[] = [] |
||||
files.map((m) => { |
||||
if (m.statusNames !== undefined) { |
||||
if (m.statusNames && m.statusNames.indexOf(status) > -1) { |
||||
result.push(m) |
||||
} |
||||
} |
||||
}); |
||||
return result; |
||||
} |
||||
|
||||
export const getFilesWithNotModifiedStatus = (files: fileStatusResult[]): fileStatusResult[] => { |
||||
const result: fileStatusResult[] = [] |
||||
|
||||
files.map((m) => { |
||||
|
||||
if (m.statusNames !== undefined) { |
||||
if (m.statusNames && m.statusNames.indexOf("unmodified") === -1) { |
||||
result.push(m) |
||||
} |
||||
} |
||||
}); |
||||
return result; |
||||
} |
||||
|
||||
export const allChangedButNotStagedFiles = (files: fileStatusResult[]): fileStatusResult[] => { |
||||
let allfiles = getFilesWithNotModifiedStatus(files) |
||||
const staged = getFilesByStatus("staged", files) |
||||
allfiles = allfiles.filter((trackedFile) => { |
||||
return staged.findIndex((stagedFile) => stagedFile.filename === trackedFile.filename) === -1 |
||||
}) |
||||
return allfiles; |
||||
} |
||||
|
||||
export const getFileStatusForFile = (filename: string, files: fileStatusResult[]) => { |
||||
for (let i: number = 0; i < files.length; i++) { |
||||
if (files[i].filename === filename) |
||||
return files[i].statusNames; |
||||
} |
||||
} |
@ -0,0 +1,802 @@ |
||||
import { ViewPlugin } from "@remixproject/engine-web"; |
||||
import { ReadBlobResult, ReadCommitResult } from "isomorphic-git"; |
||||
import React from "react"; |
||||
import { fileStatus, fileStatusMerge, setRemoteBranchCommits, resetRemoteBranchCommits, setBranches, setCanCommit, setCommitChanges, setCommits, setCurrentBranch, setGitHubUser, setLoading, setRateLimit, setRemoteBranches, setRemotes, setRepos, setUpstream, setLocalBranchCommits, setBranchDifferences, setRemoteAsDefault, setScopes, setLog, clearLog, setUserEmails } from "../state/gitpayload"; |
||||
import { GitHubUser, RateLimit, branch, commitChange, gitActionDispatch, statusMatrixType, gitState, branchDifference, remote, gitLog, fileStatusResult, customGitApi, IGitApi, cloneInputType, fetchInputType, pullInputType, pushInputType, checkoutInput, rmInput, addInput, repository, userEmails } from '../types'; |
||||
import { removeSlash } from "../utils"; |
||||
import { disableCallBacks, enableCallBacks } from "./listeners"; |
||||
import { AlertModal, ModalTypes } from "@remix-ui/app"; |
||||
import { gitActions, gitActionsContext } from "../state/context"; |
||||
import { gitPluginContext } from "../components/gitui"; |
||||
import { setFileDecorators } from "./pluginActions"; |
||||
import { IDgitSystem, IRemixApi, RemixApi } from "@remixproject/plugin-api"; |
||||
import { Plugin } from "@remixproject/engine"; |
||||
import { AnyMxRecord } from "dns"; |
||||
import { StatusEvents } from "@remixproject/plugin-utils"; |
||||
import { CustomRemixApi } from "@remix-api"; |
||||
|
||||
export const fileStatuses = [ |
||||
["new,untracked", 0, 2, 0], // new, untracked
|
||||
["added,staged", 0, 2, 2], //
|
||||
["added,staged,with unstaged changes", 0, 2, 3], // added, staged, with unstaged changes
|
||||
["unmodified", 1, 1, 1], // unmodified
|
||||
["modified,unstaged", 1, 2, 1], // modified, unstaged
|
||||
["modified,staged", 1, 2, 2], // modified, staged
|
||||
["modified,staged,with unstaged changes", 1, 2, 3], // modified, staged, with unstaged changes
|
||||
["deleted,unstaged", 1, 0, 1], // deleted, unstaged
|
||||
["deleted,staged", 1, 0, 0], |
||||
["unmodified", 1, 1, 3], |
||||
["deleted,not in git", 0, 0, 3], |
||||
["unstaged,modified", 1, 2, 0] |
||||
]; |
||||
|
||||
const statusmatrix: statusMatrixType[] = fileStatuses.map((x: any) => { |
||||
return { |
||||
matrix: x.shift().split(","), |
||||
status: x, |
||||
}; |
||||
}); |
||||
|
||||
let plugin: Plugin<any, CustomRemixApi>, dispatch: React.Dispatch<gitActionDispatch> |
||||
|
||||
export const setPlugin = (p: Plugin, dispatcher: React.Dispatch<gitActionDispatch>) => { |
||||
plugin = p |
||||
dispatch = dispatcher |
||||
console.log('setPlugin') |
||||
} |
||||
|
||||
export const init = async () => { |
||||
console.log('gitInit') |
||||
await plugin.call('dgitApi', "init"); |
||||
await gitlog(); |
||||
await getBranches(); |
||||
} |
||||
|
||||
export const getBranches = async () => { |
||||
console.log('getBranches') |
||||
const branches = await plugin.call('dgitApi', "branches") |
||||
console.log('branches :>>', branches) |
||||
dispatch(setBranches(branches)); |
||||
} |
||||
export const getRemotes = async () => { |
||||
console.log('getRemotes') |
||||
const remotes: remote[] = await plugin.call('dgitApi', "remotes"); |
||||
console.log('remotes :>>', remotes) |
||||
dispatch(setRemotes(remotes)); |
||||
} |
||||
|
||||
export const setUpstreamRemote = async (remote: remote) => { |
||||
dispatch(setUpstream(remote)); |
||||
} |
||||
|
||||
export const getFileStatusMatrix = async (filepaths: string[]) => { |
||||
const fileStatusResult = await statusMatrix(filepaths); |
||||
fileStatusResult.map((m) => { |
||||
statusmatrix.map((sm) => { |
||||
if (JSON.stringify(sm.status) === JSON.stringify(m.status)) { |
||||
//Utils.log(m, sm);
|
||||
m.statusNames = sm.matrix; |
||||
} |
||||
}); |
||||
}); |
||||
//console.log(fileStatusResult);
|
||||
if (!filepaths) { |
||||
dispatch(fileStatus(fileStatusResult)) |
||||
} else { |
||||
dispatch(fileStatusMerge(fileStatusResult)) |
||||
setFileDecorators(fileStatusResult) |
||||
} |
||||
} |
||||
|
||||
export const getCommits = async () => { |
||||
//Utils.log("get commits");
|
||||
console.log('getCommits') |
||||
try { |
||||
const commits: ReadCommitResult[] = await plugin.call( |
||||
'dgitApi', |
||||
"log", |
||||
{ ref: "HEAD" } |
||||
); |
||||
console.log('commits :>>', commits) |
||||
return commits; |
||||
} catch (e) { |
||||
return []; |
||||
} |
||||
} |
||||
|
||||
export const gitlog = async () => { |
||||
let commits = [] |
||||
try { |
||||
commits = await getCommits() |
||||
} catch (e) { |
||||
} |
||||
dispatch(setCommits(commits)) |
||||
await showCurrentBranch() |
||||
} |
||||
|
||||
export const showCurrentBranch = async () => { |
||||
try { |
||||
const branch = await currentBranch(); |
||||
console.log('branch :>>', branch) |
||||
|
||||
dispatch(setCanCommit((branch && branch.name !== ""))); |
||||
dispatch(setCurrentBranch(branch)); |
||||
|
||||
} catch (e) { |
||||
// show empty branch
|
||||
} |
||||
} |
||||
|
||||
export const currentBranch = async () => { |
||||
|
||||
const branch: branch = |
||||
(await plugin.call('dgitApi', "currentbranch")) || { |
||||
name: "", |
||||
remote: { |
||||
name: "", |
||||
url: "", |
||||
}, |
||||
}; |
||||
return branch; |
||||
|
||||
} |
||||
|
||||
export const createBranch = async (name: string = "") => { |
||||
dispatch(setLoading(true)) |
||||
if (name) { |
||||
await plugin.call('dgitApi', 'branch', { ref: name }); |
||||
await plugin.call('dgitApi', 'checkout', { ref: name }); |
||||
} |
||||
dispatch(setLoading(false)) |
||||
} |
||||
|
||||
export const getCommitFromRef = async (ref: string) => { |
||||
const commitOid = await plugin.call('dgitApi', "resolveref", { |
||||
ref: ref, |
||||
}); |
||||
return commitOid; |
||||
} |
||||
|
||||
const settingsWarning = async () => { |
||||
const username = await plugin.call('config', 'getAppParameter', 'settings/github-user-name') |
||||
const email = await plugin.call('config', 'getAppParameter', 'settings/github-email') |
||||
if (!username || !email) { |
||||
plugin.call('notification', 'toast', 'Please set your github username and email in the settings') |
||||
return false; |
||||
} else { |
||||
return { |
||||
username, |
||||
email |
||||
}; |
||||
} |
||||
} |
||||
|
||||
export const commit = async (message: string = "") => { |
||||
|
||||
try { |
||||
const credentials = await settingsWarning() |
||||
if (!credentials) { |
||||
dispatch(setLoading(false)) |
||||
return |
||||
} |
||||
|
||||
const sha = await plugin.call('dgitApi', 'commit', { |
||||
author: { |
||||
name: credentials.username, |
||||
email: credentials.email, |
||||
}, |
||||
message: message, |
||||
}); |
||||
|
||||
sendToGitLog({ |
||||
type: 'success', |
||||
message: `Commited: ${sha}` |
||||
}) |
||||
|
||||
} catch (err) { |
||||
plugin.call('notification', 'toast', `${err}`) |
||||
} |
||||
|
||||
} |
||||
|
||||
export const addall = async (files: fileStatusResult[]) => { |
||||
try { |
||||
console.log('addall', files.map(f => removeSlash(f.filename))) |
||||
const filesToAdd = files.map(f => removeSlash(f.filename)) |
||||
try { |
||||
add({ filepath: filesToAdd }) |
||||
} catch (e) { } |
||||
sendToGitLog({ |
||||
type: 'success', |
||||
message: `Added all files to git` |
||||
}) |
||||
|
||||
} catch (e) { |
||||
plugin.call('notification', 'toast', `${e}`) |
||||
} |
||||
} |
||||
|
||||
export const add = async (filepath: addInput) => { |
||||
try { |
||||
await plugin.call('dgitApi', 'add', filepath); |
||||
sendToGitLog({ |
||||
type: 'success', |
||||
message: `Added to git` |
||||
}) |
||||
} catch (e) { |
||||
plugin.call('notification', 'toast', `${e}`) |
||||
} |
||||
|
||||
} |
||||
|
||||
const getLastCommmit = async () => { |
||||
try { |
||||
let currentcommitoid = ""; |
||||
currentcommitoid = await getCommitFromRef("HEAD"); |
||||
return currentcommitoid; |
||||
} catch (e) { |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
export const rm = async (args: rmInput) => { |
||||
await plugin.call('dgitApi', 'rm', { |
||||
filepath: removeSlash(args.filepath), |
||||
}); |
||||
sendToGitLog({ |
||||
type: 'success', |
||||
message: `Removed from git` |
||||
}) |
||||
} |
||||
|
||||
export const checkoutfile = async (filename: string) => { |
||||
const oid = await getLastCommmit(); |
||||
if (oid) |
||||
try { |
||||
const commitOid = await plugin.call('dgitApi', 'resolveref', { |
||||
ref: oid, |
||||
}); |
||||
const { blob } = await plugin.call('dgitApi', 'readblob', { |
||||
oid: commitOid, |
||||
filepath: removeSlash(filename), |
||||
}); |
||||
const original = Buffer.from(blob).toString("utf8"); |
||||
//(original, filename);
|
||||
await disableCallBacks(); |
||||
await plugin.call( |
||||
"fileManager", |
||||
"setFile", |
||||
removeSlash(filename), |
||||
original |
||||
); |
||||
await enableCallBacks(); |
||||
} catch (e) { |
||||
plugin.call('notification', 'toast', `No such file`) |
||||
} |
||||
} |
||||
|
||||
export const checkout = async (cmd: checkoutInput) => { |
||||
console.log(cmd) |
||||
await disableCallBacks(); |
||||
await plugin.call('fileManager', 'closeAllFiles') |
||||
try { |
||||
await plugin.call('dgitApi', 'checkout', cmd) |
||||
gitlog(); |
||||
} catch (e) { |
||||
plugin.call('notification', 'toast', `${e}`) |
||||
} |
||||
await enableCallBacks(); |
||||
} |
||||
|
||||
export const clone = async (input: cloneInputType) => { |
||||
|
||||
dispatch(setLoading(true)) |
||||
try { |
||||
await disableCallBacks() |
||||
// get last part of url
|
||||
const urlParts = input.url.split("/"); |
||||
const lastPart = urlParts[urlParts.length - 1]; |
||||
const repoName = lastPart.split(".")[0]; |
||||
// add timestamp to repo name
|
||||
const timestamp = new Date().getTime(); |
||||
const repoNameWithTimestamp = `${repoName}-${timestamp}`; |
||||
//const token = await tokenWarning();
|
||||
const token = await plugin.call('config' as any, 'getAppParameter' as any, 'settings/gist-access-token') |
||||
|
||||
await plugin.call('dgitApi', 'clone', { ...input, workspaceName: repoNameWithTimestamp }); |
||||
await enableCallBacks() |
||||
|
||||
sendToGitLog({ |
||||
type: 'success', |
||||
message: `Cloned ${input.url} to ${repoNameWithTimestamp}` |
||||
}) |
||||
//}
|
||||
} catch (e: any) { |
||||
await parseError(e) |
||||
} |
||||
dispatch(setLoading(false)) |
||||
} |
||||
|
||||
export const fetch = async (input: fetchInputType) => { |
||||
dispatch(setLoading(true)) |
||||
await disableCallBacks() |
||||
try { |
||||
await plugin.call('dgitApi', 'fetch', input); |
||||
if (!input.quiet) { |
||||
await gitlog() |
||||
await getBranches() |
||||
} |
||||
} catch (e: any) { |
||||
console.log(e) |
||||
await parseError(e) |
||||
} |
||||
dispatch(setLoading(false)) |
||||
await enableCallBacks() |
||||
} |
||||
|
||||
export const pull = async (input: pullInputType) => { |
||||
dispatch(setLoading(true)) |
||||
await disableCallBacks() |
||||
try { |
||||
await plugin.call('dgitApi', 'pull', input) |
||||
await gitlog() |
||||
} catch (e: any) { |
||||
await parseError(e) |
||||
} |
||||
dispatch(setLoading(false)) |
||||
await enableCallBacks() |
||||
} |
||||
|
||||
export const push = async (input: pushInputType) => { |
||||
dispatch(setLoading(true)) |
||||
await disableCallBacks() |
||||
try { |
||||
await plugin.call('dgitApi', 'push', input) |
||||
} catch (e: any) { |
||||
await parseError(e) |
||||
} |
||||
dispatch(setLoading(false)) |
||||
await enableCallBacks() |
||||
} |
||||
|
||||
const tokenWarning = async () => { |
||||
const token = await plugin.call('config' as any, 'getAppParameter' as any, 'settings/gist-access-token') |
||||
if (!token) { |
||||
return false; |
||||
} else { |
||||
return token; |
||||
} |
||||
} |
||||
|
||||
const parseError = async (e: any) => { |
||||
console.trace(e) |
||||
// if message conttains 401 Unauthorized, show token warning
|
||||
if (e.message.includes('401')) { |
||||
const result = await plugin.call('notification', 'modal' as any, { |
||||
title: 'The GitHub token may be missing or invalid', |
||||
message: 'Please check the GitHub token and try again. Error: 401 Unauthorized', |
||||
okLabel: 'Go to settings', |
||||
cancelLabel: 'Close', |
||||
type: ModalTypes.confirm |
||||
}) |
||||
console.log(result) |
||||
} |
||||
// if message contains 404 Not Found, show repo not found
|
||||
else if (e.message.includes('404')) { |
||||
await plugin.call('notification', 'modal' as any, { |
||||
title: 'Repository not found', |
||||
message: 'Please check the URL and try again.', |
||||
okLabel: 'Go to settings', |
||||
cancelLabel: 'Close', |
||||
type: ModalTypes.confirm |
||||
}) |
||||
} |
||||
// if message contains 403 Forbidden
|
||||
else if (e.message.includes('403')) { |
||||
await plugin.call('notification', 'modal' as any, { |
||||
title: 'The GitHub token may be missing or invalid', |
||||
message: 'Please check the GitHub token and try again. Error: 403 Forbidden', |
||||
okLabel: 'Go to settings', |
||||
cancelLabel: 'Close', |
||||
type: ModalTypes.confirm |
||||
}) |
||||
} else if (e.toString().includes('NotFoundError') && !e.toString().includes('fetch')) { |
||||
await plugin.call('notification', 'modal', { |
||||
title: 'Remote branch not found', |
||||
message: 'The branch you are trying to fetch does not exist on the remote. If you have forked this branch from another branch, you may need to fetch the original branch first or publish this branch on the remote.', |
||||
okLabel: 'OK', |
||||
type: ModalTypes.alert |
||||
}) |
||||
} else { |
||||
await plugin.call('notification', 'alert' as any, { |
||||
title: 'Error', |
||||
message: e.message |
||||
}) |
||||
} |
||||
} |
||||
|
||||
export const repositories = async () => { |
||||
try { |
||||
const token = await tokenWarning(); |
||||
if (token) { |
||||
let repos = await plugin.call('dgitApi', 'repositories', { token, per_page: 100 }) |
||||
dispatch(setRepos(repos)) |
||||
let page = 2 |
||||
let hasMoreData = true |
||||
const per_page = 100 |
||||
while (hasMoreData) { |
||||
const pagedResponse = await plugin.call('dgitApi', 'repositories', { token, page: page, per_page: per_page }) |
||||
if (pagedResponse.length < per_page) { |
||||
hasMoreData = false |
||||
} |
||||
repos = [...repos, ...pagedResponse] |
||||
dispatch(setRepos(repos)) |
||||
page++ |
||||
} |
||||
|
||||
} else { |
||||
plugin.call('notification', 'alert', { |
||||
title: 'Error getting repositories', |
||||
message: `Please check your GitHub token in the GitHub settings... cannot connect to GitHub` |
||||
}) |
||||
dispatch(setRepos([])) |
||||
} |
||||
} catch (e) { |
||||
console.log(e) |
||||
plugin.call('notification', 'alert', { |
||||
title: 'Error getting repositories', |
||||
message: `${e.message}: Please check your GitHub token in the GitHub settings.` |
||||
}) |
||||
dispatch(setRepos([])) |
||||
} |
||||
} |
||||
|
||||
export const remoteBranches = async (owner: string, repo: string) => { |
||||
try { |
||||
const token = await tokenWarning(); |
||||
if (token) { |
||||
let branches = await plugin.call('dgitApi' as any, 'remotebranches', { token, owner, repo, per_page: 100 }); |
||||
dispatch(setRemoteBranches(branches)) |
||||
let page = 2 |
||||
let hasMoreData = true |
||||
const per_page = 100 |
||||
while (hasMoreData) { |
||||
const pagedResponse = await plugin.call('dgitApi' as any, 'remotebranches', { token, owner, repo, page: page, per_page: per_page }) |
||||
if (pagedResponse.length < per_page) { |
||||
hasMoreData = false |
||||
} |
||||
branches = [...branches, ...pagedResponse] |
||||
dispatch(setRemoteBranches(branches)) |
||||
page++ |
||||
} |
||||
} else { |
||||
plugin.call('notification', 'alert', { |
||||
title: 'Error getting branches', |
||||
message: `Please check your GitHub token in the GitHub settings. It needs to have access to the branches.` |
||||
}) |
||||
dispatch(setRemoteBranches([])) |
||||
} |
||||
} catch (e) { |
||||
console.log(e) |
||||
plugin.call('notification', 'alert', { |
||||
title: 'Error', |
||||
message: e.message |
||||
}) |
||||
dispatch(setRemoteBranches([])) |
||||
} |
||||
} |
||||
|
||||
export const remoteCommits = async (url: string, branch: string, length: number) => { |
||||
const urlParts = url.split("/"); |
||||
|
||||
console.log(urlParts, 'urlParts') |
||||
// check if it's github
|
||||
if (!urlParts[urlParts.length - 3].includes('github')) { |
||||
return |
||||
} |
||||
|
||||
const owner = urlParts[urlParts.length - 2]; |
||||
const repo = urlParts[urlParts.length - 1].split(".")[0]; |
||||
|
||||
try { |
||||
const token = await tokenWarning(); |
||||
if (token) { |
||||
console.log(token, owner, repo, branch, length) |
||||
const commits = await plugin.call('dgitApi' as any, 'remotecommits', { token, owner, repo, branch, length }); |
||||
console.log(commits, 'remote commits') |
||||
} else { |
||||
sendToGitLog({ |
||||
type: 'error', |
||||
message: `Please check your GitHub token in the GitHub settings.` |
||||
}) |
||||
} |
||||
} catch (e) { |
||||
console.log(e) |
||||
plugin.call('notification', 'alert', { |
||||
title: 'Error', |
||||
message: e.message |
||||
}) |
||||
} |
||||
} |
||||
|
||||
export const saveGitHubCredentials = async (credentials: { username: string, email: string, token: string }) => { |
||||
console.log('saveGitHubCredentials', credentials) |
||||
try { |
||||
const storedEmail = await plugin.call('config', 'getAppParameter', 'settings/github-email') |
||||
const storedUsername = await plugin.call('config', 'getAppParameter', 'settings/github-user-name') |
||||
const storedToken = await plugin.call('config', 'getAppParameter', 'settings/gist-access-token') |
||||
|
||||
if (storedUsername !== credentials.username) await plugin.call('config', 'setAppParameter', 'settings/github-user-name', credentials.username) |
||||
if (storedEmail !== credentials.email) await plugin.call('config', 'setAppParameter', 'settings/github-email', credentials.email) |
||||
if (storedToken !== credentials.token) await plugin.call('config', 'setAppParameter', 'settings/gist-access-token', credentials.token) |
||||
|
||||
} catch (e) { |
||||
console.log(e) |
||||
} |
||||
} |
||||
|
||||
export const getGitHubCredentialsFromLocalStorage = async () => { |
||||
if (!plugin) return |
||||
try { |
||||
const username = await plugin.call('config', 'getAppParameter', 'settings/github-user-name') |
||||
const email = await plugin.call('config', 'getAppParameter', 'settings/github-email') |
||||
const token = await plugin.call('config', 'getAppParameter', 'settings/gist-access-token') |
||||
return { |
||||
username, |
||||
email, |
||||
token |
||||
} |
||||
} catch (e) { |
||||
console.log(e) |
||||
} |
||||
} |
||||
|
||||
export const loadGitHubUserFromToken = async () => { |
||||
if (!plugin) return |
||||
try { |
||||
const token = await tokenWarning(); |
||||
if (token) { |
||||
const data: { |
||||
user: GitHubUser, |
||||
ratelimit: RateLimit |
||||
scopes: string[] |
||||
emails: userEmails |
||||
} = await plugin.call('dgitApi' as any, 'getGitHubUser', { token }); |
||||
|
||||
console.log('GET USER"', data) |
||||
if (data && data.emails && data.user && data.user.login) { |
||||
console.log('SET USER"', data) |
||||
const primaryEmail = data.emails.find(email => email.primary) |
||||
|
||||
const storedEmail = await plugin.call('config', 'getAppParameter', 'settings/github-email') |
||||
if (primaryEmail && storedEmail !== primaryEmail.email) await plugin.call('config', 'setAppParameter', 'settings/github-email', primaryEmail.email) |
||||
const storedUsername = await plugin.call('config', 'getAppParameter', 'settings/github-user-name') |
||||
if (data.user && data.user.login && (storedUsername !== data.user.login)) await plugin.call('config', 'setAppParameter', 'settings/github-user-name', data.user.login) |
||||
|
||||
dispatch(setGitHubUser(data.user)) |
||||
dispatch(setRateLimit(data.ratelimit)) |
||||
dispatch(setScopes(data.scopes)) |
||||
dispatch(setUserEmails(data.emails)) |
||||
|
||||
} |
||||
} else { |
||||
const credentials = await getGitHubCredentialsFromLocalStorage() |
||||
if (credentials) { |
||||
//dispatch(setGitHubUser({ login: credentials.username }))
|
||||
//dispatch(setUserEmails([{ email: credentials.email, primary: true, visibility: 'public', verified: true }]))
|
||||
} |
||||
dispatch(setGitHubUser(null)) |
||||
} |
||||
} catch (e) { |
||||
console.log(e) |
||||
} |
||||
} |
||||
|
||||
export const statusMatrix = async (filepaths: string[]) => { |
||||
const matrix = await plugin.call('dgitApi', 'status', { ref: "HEAD", filepaths: filepaths || ['.']}); |
||||
const result = (matrix || []).map((x) => { |
||||
return { |
||||
filename: `/${x.shift()}`, |
||||
status: x, |
||||
statusNames: [] |
||||
}; |
||||
}); |
||||
|
||||
return result; |
||||
} |
||||
|
||||
export const resolveRef = async (ref: string) => { |
||||
const oid = await plugin.call('dgitApi', "resolveref", { |
||||
ref, |
||||
}); |
||||
return oid; |
||||
} |
||||
|
||||
export const diff = async (commitChange: commitChange) => { |
||||
|
||||
if (!commitChange.hashModified) { |
||||
const newcontent = await plugin.call( |
||||
"fileManager", |
||||
"readFile",//
|
||||
removeSlash(commitChange.path) |
||||
); |
||||
commitChange.modified = newcontent; |
||||
commitChange.readonly = false; |
||||
|
||||
} else { |
||||
|
||||
try { |
||||
const modifiedContentReadBlobResult: ReadBlobResult = await plugin.call('dgitApi', "readblob", { |
||||
oid: commitChange.hashModified, |
||||
filepath: removeSlash(commitChange.path), |
||||
}); |
||||
|
||||
const modifiedContent = Buffer.from(modifiedContentReadBlobResult.blob).toString("utf8"); |
||||
console.log(modifiedContent) |
||||
commitChange.modified = modifiedContent; |
||||
commitChange.readonly = true; |
||||
} catch (e) { |
||||
commitChange.modified = ""; |
||||
} |
||||
} |
||||
|
||||
try { |
||||
const originalContentReadBlobResult: ReadBlobResult = await plugin.call('dgitApi', "readblob", { |
||||
oid: commitChange.hashOriginal, |
||||
filepath: removeSlash(commitChange.path), |
||||
}); |
||||
|
||||
const originalContent = Buffer.from(originalContentReadBlobResult.blob).toString("utf8"); |
||||
console.log(originalContent) |
||||
commitChange.original = originalContent; |
||||
} catch (e) { |
||||
commitChange.original = ""; |
||||
} |
||||
|
||||
} |
||||
|
||||
export const getCommitChanges = async (oid1: string, oid2: string, branch?: branch, remote?: remote) => { |
||||
console.log(oid1, oid2, branch, remote) |
||||
|
||||
try { |
||||
let log |
||||
try { |
||||
// check if oid2 exists
|
||||
log = await plugin.call('dgitApi', 'log', { |
||||
ref: branch ? branch.name : 'HEAD', |
||||
}) |
||||
console.log(log, 'log') |
||||
} catch (e) { |
||||
console.log(e, 'log error') |
||||
} |
||||
if (log) { |
||||
const foundCommit = log.find((commit: ReadCommitResult) => commit.oid === oid2) |
||||
if (!foundCommit && remote) { |
||||
console.log('getCommitChanges fetching remote') |
||||
//await fetch(remote ? remote.name : null, branch ? branch.name : null, null, 5, true, true)
|
||||
await fetch({ |
||||
remote: remote, |
||||
singleBranch: true, |
||||
quiet: true, |
||||
relative: true, |
||||
depth: 5, |
||||
ref: branch, |
||||
remoteRef: null |
||||
}) |
||||
} |
||||
} |
||||
const result: commitChange[] = await plugin.call('dgitApi', 'getCommitChanges', oid1, oid2) |
||||
dispatch(setCommitChanges(result)) |
||||
return result |
||||
} catch (e) { |
||||
console.log(e) |
||||
return false |
||||
} |
||||
} |
||||
|
||||
async function getRepoDetails(url: string) { |
||||
// Extract the owner and repo name from the URL
|
||||
const pathParts = new URL(url).pathname.split('/').filter(part => part); |
||||
if (pathParts.length < 2) { |
||||
throw new Error("URL must be in the format 'https://github.com/[owner]/[repository]'"); |
||||
} |
||||
const owner = pathParts[0]; |
||||
const repo = pathParts[1]; |
||||
return { owner, repo }; |
||||
} |
||||
|
||||
export const fetchBranch = async (branch: branch, page: number) => { |
||||
if (!branch.remote || !branch.remote.url) return |
||||
const token = await tokenWarning(); |
||||
console.log('fetch', branch) |
||||
if (page == 1) { |
||||
dispatch(resetRemoteBranchCommits({ branch })) |
||||
} |
||||
const { owner, repo } = await getRepoDetails(branch.remote.url); |
||||
const rc = await plugin.call('dgitApi', 'remotecommits', { token, owner: owner, repo: repo, branch: branch.name, length, page }); |
||||
console.log(rc, 'remote commits from octokit') |
||||
dispatch(setRemoteBranchCommits({ branch, commits: rc })) |
||||
return |
||||
} |
||||
|
||||
export const getBranchDifferences = async (branch: branch, remote: remote, state: gitState) => { |
||||
if (!remote && state) { |
||||
if (state.defaultRemote) { |
||||
remote = state.defaultRemote |
||||
} else { |
||||
remote = state.remotes.find((remote: remote) => remote.name === 'origin') |
||||
} |
||||
if (!remote && state.remotes[0]) { |
||||
remote = state.remotes[0] |
||||
} |
||||
} |
||||
if (!remote) return |
||||
try { |
||||
console.log('compare', branch, remote) |
||||
const branchDifference: branchDifference = await plugin.call('dgitApi', 'compareBranches', { |
||||
branch, |
||||
remote |
||||
}) |
||||
|
||||
dispatch(setBranchDifferences( |
||||
{ |
||||
branch, |
||||
remote, |
||||
branchDifference: branchDifference |
||||
})) |
||||
} catch (e) { |
||||
console.log(e) |
||||
} |
||||
} |
||||
|
||||
export const getBranchCommits = async (branch: branch, page: number) => { |
||||
dispatch(setLoading(true)) |
||||
await disableCallBacks() |
||||
try { |
||||
console.log(branch) |
||||
if (!branch.remote) { |
||||
const commits: ReadCommitResult[] = await plugin.call('dgitApi', 'log', { |
||||
ref: branch.name, |
||||
}) |
||||
console.log(commits) |
||||
dispatch(setLocalBranchCommits({ branch, commits })) |
||||
} else { |
||||
await fetchBranch(branch, page) |
||||
} |
||||
} catch (e) { |
||||
console.trace(e) |
||||
await fetchBranch(branch, page) |
||||
} |
||||
dispatch(setLoading(false)) |
||||
await enableCallBacks() |
||||
} |
||||
|
||||
export const setDefaultRemote = async (remote: remote) => { |
||||
dispatch(setRemoteAsDefault(remote)) |
||||
} |
||||
|
||||
export const addRemote = async (remote: remote) => { |
||||
try { |
||||
await plugin.call('dgitApi', 'addremote', remote) |
||||
await getRemotes() |
||||
} catch (e) { |
||||
console.log(e) |
||||
} |
||||
} |
||||
|
||||
export const removeRemote = async (remote: remote) => { |
||||
try { |
||||
await plugin.call('dgitApi', 'delremote', remote) |
||||
await getRemotes() |
||||
} catch (e) { |
||||
console.log(e) |
||||
} |
||||
} |
||||
|
||||
export const sendToGitLog = async (message: gitLog) => { |
||||
dispatch(setLog(message)) |
||||
} |
||||
|
||||
export const clearGitLog = async () => { |
||||
dispatch(clearLog()) |
||||
} |
@ -0,0 +1,195 @@ |
||||
|
||||
import React from "react"; |
||||
import { setCanUseApp, setLoading, setRepoName, setGItHubToken, setLog, setGitHubUser, setUserEmails } from "../state/gitpayload"; |
||||
import { gitActionDispatch } from "../types"; |
||||
import { Plugin } from "@remixproject/engine"; |
||||
import { getBranches, getFileStatusMatrix, loadGitHubUserFromToken, getRemotes, gitlog, setPlugin } from "./gitactions"; |
||||
import { Profile } from "@remixproject/plugin-utils"; |
||||
import { CustomRemixApi } from "@remix-api"; |
||||
import { statusChanged } from "./pluginActions"; |
||||
|
||||
let plugin: Plugin<any, CustomRemixApi>, gitDispatch: React.Dispatch<gitActionDispatch>, loaderDispatch: React.Dispatch<any>, loadFileQueue: AsyncDebouncedQueue |
||||
let callBackEnabled: boolean = false |
||||
|
||||
type AsyncCallback = () => Promise<void>; |
||||
|
||||
class AsyncDebouncedQueue { |
||||
private queues: Map<AsyncCallback, { timer: any, lastCall: number }>; |
||||
|
||||
constructor(private delay: number = 300) { |
||||
this.queues = new Map(); |
||||
} |
||||
|
||||
enqueue(callback: AsyncCallback, customDelay?:number): void { |
||||
if (this.queues.has(callback)) { |
||||
clearTimeout(this.queues.get(callback)!.timer); |
||||
} |
||||
|
||||
const timer = setTimeout(async () => { |
||||
await callback(); |
||||
this.queues.delete(callback); |
||||
}, customDelay || this.delay); |
||||
|
||||
this.queues.set(callback, { timer, lastCall: Date.now() }); |
||||
} |
||||
} |
||||
|
||||
export const setCallBacks = (viewPlugin: Plugin, gitDispatcher: React.Dispatch<gitActionDispatch>, loaderDispatcher: React.Dispatch<any>) => { |
||||
plugin = viewPlugin |
||||
gitDispatch = gitDispatcher |
||||
loaderDispatch = loaderDispatcher |
||||
loadFileQueue = new AsyncDebouncedQueue() |
||||
|
||||
setPlugin(viewPlugin, gitDispatcher) |
||||
|
||||
plugin.on("fileManager", "fileSaved", async (file: string) => { |
||||
console.log(file) |
||||
loadFileQueue.enqueue(async () => { |
||||
loadFiles() |
||||
}) |
||||
}); |
||||
|
||||
plugin.on('dgitApi', 'checkout', async () => { |
||||
//await synTimerStart();
|
||||
}) |
||||
plugin.on('dgitApi', 'branch', async () => { |
||||
//await synTimerStart();
|
||||
}) |
||||
|
||||
plugin.on("fileManager", "fileAdded", async (e) => { |
||||
loadFileQueue.enqueue(async () => { |
||||
loadFiles() |
||||
}) |
||||
}); |
||||
|
||||
plugin.on("fileManager", "fileRemoved", async (e) => { |
||||
//await synTimerStart();
|
||||
}); |
||||
|
||||
plugin.on("fileManager", "currentFileChanged", async (e) => { |
||||
console.log('current file change', e) |
||||
//await synTimerStart();
|
||||
}); |
||||
|
||||
plugin.on("fileManager", "fileRenamed", async (oldfile, newfile) => { |
||||
//await synTimerStart();
|
||||
}); |
||||
|
||||
plugin.on("filePanel", "setWorkspace", async (x: any) => { |
||||
loadFileQueue.enqueue(async () => { |
||||
loadFiles() |
||||
}) |
||||
loadFileQueue.enqueue(async () => { |
||||
gitlog() |
||||
}) |
||||
loadFileQueue.enqueue(async () => { |
||||
getBranches() |
||||
}) |
||||
loadFileQueue.enqueue(async () => { |
||||
getRemotes() |
||||
}) |
||||
}); |
||||
|
||||
plugin.on("filePanel", "deleteWorkspace" as any, async (x: any) => { |
||||
//await synTimerStart();
|
||||
}); |
||||
|
||||
plugin.on("filePanel", "renameWorkspace" as any, async (x: any) => { |
||||
//await synTimerStart();
|
||||
}); |
||||
|
||||
plugin.on('dgitApi', 'checkout', async () => { |
||||
|
||||
}) |
||||
plugin.on('dgitApi', 'init', async () => { |
||||
loadFileQueue.enqueue(async () => { |
||||
loadFiles() |
||||
}, 10) |
||||
}) |
||||
plugin.on('dgitApi', 'add', async () => { |
||||
loadFileQueue.enqueue(async () => { |
||||
loadFiles() |
||||
}, 10) |
||||
}) |
||||
plugin.on('dgitApi', 'rm', async () => { |
||||
loadFileQueue.enqueue(async () => { |
||||
loadFiles() |
||||
}, 10) |
||||
}) |
||||
plugin.on('dgitApi', 'commit', async () => { |
||||
loadFileQueue.enqueue(async () => { |
||||
gitlog() |
||||
}, 10) |
||||
gitDispatch(setLog({ |
||||
message: 'Committed changes...', |
||||
type: 'success' |
||||
})) |
||||
}) |
||||
plugin.on('dgitApi', 'branch', async () => { |
||||
gitDispatch(setLog({ |
||||
message: "Created Branch", |
||||
type: "success" |
||||
})) |
||||
}) |
||||
plugin.on('dgitApi', 'clone', async () => { |
||||
gitDispatch(setLog({ |
||||
message: "Cloned Repository", |
||||
type: "success" |
||||
})) |
||||
loadFileQueue.enqueue(async () => { |
||||
loadFiles() |
||||
}) |
||||
}) |
||||
plugin.on('manager', 'pluginActivated', async (p: Profile<any>) => { |
||||
if (p.name === 'dgitApi') { |
||||
loadGitHubUserFromToken(); |
||||
plugin.off('manager', 'pluginActivated'); |
||||
} |
||||
}) |
||||
|
||||
plugin.on('config', 'configChanged', async () => { |
||||
console.log('config changed') |
||||
await getGitConfig() |
||||
}) |
||||
plugin.on('settings', 'configChanged', async () => { |
||||
console.log('settings changed') |
||||
await getGitConfig() |
||||
}) |
||||
|
||||
callBackEnabled = true; |
||||
} |
||||
|
||||
export const getGitConfig = async () => { |
||||
const username = await plugin.call('settings', 'get', 'settings/github-user-name') |
||||
const email = await plugin.call('settings', 'get', 'settings/github-email') |
||||
const token = await plugin.call('settings', 'get', 'settings/gist-access-token') |
||||
const config = { username, email, token } |
||||
|
||||
loadGitHubUserFromToken() |
||||
return |
||||
|
||||
} |
||||
|
||||
export const loadFiles = async (filepaths: string[] = null) => { |
||||
try { |
||||
const branch = await plugin.call('dgitApi', "currentbranch") |
||||
console.log('load files', branch) |
||||
if (branch) { |
||||
await getFileStatusMatrix(filepaths); |
||||
} else { |
||||
await plugin.call('fileDecorator', 'clearFileDecorators') |
||||
statusChanged(0) |
||||
} |
||||
} catch (e) { |
||||
// TODO: handle error
|
||||
console.error(e); |
||||
} |
||||
} |
||||
|
||||
export const disableCallBacks = async () => { |
||||
callBackEnabled = false; |
||||
} |
||||
export const enableCallBacks = async () => { |
||||
callBackEnabled = true; |
||||
} |
||||
|
@ -0,0 +1,101 @@ |
||||
|
||||
import { commitChange, fileStatusResult, gitActionDispatch, gitState } from "../types" |
||||
import { fileDecoration, fileDecorationType } from "@remix-ui/file-decorators" |
||||
import { removeSlash } from "../utils" |
||||
import { getFilesByStatus } from "./fileHelpers" |
||||
import { CustomRemixApi } from "@remix-api"; |
||||
import { Plugin } from "@remixproject/engine"; |
||||
|
||||
let plugin: Plugin<any, CustomRemixApi>, gitDispatch: React.Dispatch<gitActionDispatch>, loaderDispatch: React.Dispatch<any> |
||||
|
||||
export const setPlugin = (p: Plugin<any, CustomRemixApi>, gitDispatcher: React.Dispatch<gitActionDispatch>, loaderDispatcher: React.Dispatch<any>) => { |
||||
plugin = p |
||||
gitDispatch = gitDispatcher |
||||
loaderDispatch = loaderDispatcher |
||||
} |
||||
|
||||
export const statusChanged = (badges: number) => { |
||||
if (!plugin) return |
||||
plugin.emit('statusChanged', { |
||||
key: badges === 0 ? 'none' : badges, |
||||
type: badges === 0 ? '' : 'success', |
||||
title: 'Git changes' |
||||
}) |
||||
} |
||||
|
||||
export const openFile = async (path: string) => { |
||||
if (!plugin) return |
||||
await plugin.call('fileManager', 'open', path) |
||||
} |
||||
|
||||
export const openDiff = async (change: commitChange) => { |
||||
console.log('openDiff', change) |
||||
if (!plugin) return |
||||
plugin.call('fileManager', 'diff', change) |
||||
} |
||||
|
||||
export const saveToken = async (token: string) => { |
||||
if (!plugin) return |
||||
await plugin.call('config', 'setAppParameter', 'settings/gist-access-token', token) |
||||
} |
||||
|
||||
export const setFileDecorators = async (files: fileStatusResult[]) => { |
||||
|
||||
if (!plugin) return |
||||
const modified = getFilesByStatus('modified', files) |
||||
const untracked = getFilesByStatus('untracked', files) |
||||
const unmodified = getFilesByStatus('unmodified', files) |
||||
|
||||
await setModifiedDecorator(modified) |
||||
await setUntrackedDecorator(untracked) |
||||
unmodified.forEach((file) => { |
||||
clearFileDecorator(removeSlash(file.filename)) |
||||
}) |
||||
} |
||||
|
||||
export const setModifiedDecorator = async (files: fileStatusResult[]) => { |
||||
const decorators: fileDecoration[] = [] |
||||
for (const file of files) { |
||||
const decorator: fileDecoration = { |
||||
path: removeSlash(file.filename), |
||||
isDirectory: false, |
||||
fileStateType: fileDecorationType.Custom, |
||||
fileStateLabelClass: 'text-warning', |
||||
fileStateIconClass: 'text-warning', |
||||
fileStateIcon: '', |
||||
text: 'M', |
||||
owner: 'git', |
||||
bubble: true, |
||||
comment: 'Modified' |
||||
} |
||||
decorators.push(decorator) |
||||
} |
||||
|
||||
await plugin.call('fileDecorator', 'setFileDecorators', decorators) |
||||
} |
||||
|
||||
export const setUntrackedDecorator = async (files: fileStatusResult[]) => { |
||||
const decorators: fileDecoration[] = [] |
||||
for (const file of files) { |
||||
const decorator: fileDecoration = { |
||||
path: removeSlash(file.filename), |
||||
isDirectory: false, |
||||
fileStateType: fileDecorationType.Custom, |
||||
fileStateLabelClass: 'text-success', |
||||
fileStateIconClass: 'text-success', |
||||
fileStateIcon: '', |
||||
text: 'U', |
||||
owner: 'git', |
||||
bubble: true, |
||||
comment: 'Untracked' |
||||
} |
||||
decorators.push(decorator) |
||||
} |
||||
|
||||
await plugin.call('fileDecorator', 'setFileDecorators', decorators) |
||||
} |
||||
|
||||
export const clearFileDecorator = async(path: string) => { |
||||
await plugin.call('fileDecorator', 'clearFileDecorators', path) |
||||
} |
||||
|
@ -0,0 +1,51 @@ |
||||
import { ReadCommitResult } from "isomorphic-git" |
||||
import { branch, branchDifference, commitChange, fileStatusResult, GitHubUser, gitLog, pagedCommits, remote, remoteBranch, repository, userEmails } from "../types" |
||||
|
||||
export interface ActionPayloadTypes { |
||||
FILE_STATUS: fileStatusResult[], |
||||
FILE_STATUS_MERGE: fileStatusResult[] |
||||
SET_COMMITS: ReadCommitResult[] |
||||
SET_BRANCHES: branch[] |
||||
SET_CURRENT_BRANCH: branch |
||||
SET_CAN_USE_APP: boolean |
||||
SET_REPO_NAME: string |
||||
SET_LOADING: boolean |
||||
SET_REPOS: repository[] |
||||
SET_REMOTE_BRANCHES: remoteBranch[] |
||||
SET_CAN_COMMIT: boolean |
||||
SET_REMOTES: remote[] |
||||
SET_UPSTREAM: remote |
||||
SET_COMMIT_CHANGES: commitChange[] |
||||
RESET_REMOTE_BRANCH_COMMITS: { |
||||
branch: branch |
||||
pagedCommits: pagedCommits[] |
||||
} |
||||
SET_REMOTE_BRANCH_COMMITS: { |
||||
branch: branch |
||||
commits: pagedCommits[] |
||||
} |
||||
SET_LOCAL_BRANCH_COMMITS: { |
||||
branch: branch |
||||
commits: ReadCommitResult[] |
||||
} |
||||
SET_BRANCH_DIFFERENCES: { |
||||
branch: branch |
||||
remote: remote |
||||
branchDifference: branchDifference |
||||
} |
||||
SET_GITHUB_USER: GitHubUser |
||||
SET_RATE_LIMIT: any |
||||
SET_GITHUB_ACCESS_TOKEN: string |
||||
SET_SCOPES: string[] |
||||
SET_DEFAULT_REMOTE: remote |
||||
SET_LOG: gitLog |
||||
CLEAR_LOG: void |
||||
SET_USER_EMAILS: userEmails |
||||
} |
||||
|
||||
export interface Action<T extends keyof ActionPayloadTypes> { |
||||
type: T, |
||||
payload: ActionPayloadTypes[T] |
||||
} |
||||
|
||||
export type Actions = {[A in keyof ActionPayloadTypes]: Action<A>}[keyof ActionPayloadTypes] |
@ -0,0 +1,57 @@ |
||||
import { ReadCommitResult } from "isomorphic-git" |
||||
import React from "react" |
||||
import { addInput, branch, checkoutInput, cloneInputType, commitChange, fetchInputType, fileStatusResult, gitLog, gitState, pullInputType, pushInputType, remote, rmInput } from "../types" |
||||
|
||||
export interface gitActions { |
||||
removeRemote(remote: remote): void |
||||
clone(input: cloneInputType): Promise<void> |
||||
add(input: addInput): Promise<void> |
||||
rm(input: rmInput): Promise<void> |
||||
commit(message: string): Promise<any> |
||||
addall(files: fileStatusResult[]): Promise<void> |
||||
push(input: pushInputType): Promise<void> |
||||
pull(input: pullInputType): Promise<void> |
||||
fetch(input: fetchInputType): Promise<void> |
||||
repositories(): Promise<any> |
||||
checkoutfile(file: string): Promise<void> |
||||
checkout(input: checkoutInput): Promise<void> |
||||
createBranch(branch: string): Promise<void> |
||||
remoteBranches(owner: string, repo: string): Promise<any> |
||||
getCommitChanges(oid1: string, oid2: string, branch?: branch, remote?: remote): Promise<commitChange[] | boolean> |
||||
getBranchCommits(branch: branch, page: number): Promise<void> |
||||
getBranchDifferences(branch: branch, remote?: remote, state?: gitState): Promise<void> |
||||
loadGitHubUserFromToken(): Promise<any> |
||||
diff(commitChange: commitChange): Promise<void> |
||||
resolveRef(ref: string): Promise<string> |
||||
setUpstreamRemote(upstream: remote): Promise<void> |
||||
getBranches: () => Promise<void> |
||||
getRemotes: () => Promise<void> |
||||
setDefaultRemote: (remote: remote) => Promise<void> |
||||
addRemote: (remote: remote) => Promise<void> |
||||
sendToGitLog: (message: gitLog) => Promise<void> |
||||
clearGitLog: () => Promise<void> |
||||
getFileStatusMatrix(filespaths:[]): Promise<void> |
||||
init(): Promise<void> |
||||
} |
||||
|
||||
export const gitActionsContext = React.createContext<gitActions>(null) |
||||
|
||||
export interface pluginActions { |
||||
statusChanged(data: any): void |
||||
loadFiles(): void |
||||
openFile(path: string): Promise<void> |
||||
openDiff(change: commitChange): Promise<void> |
||||
saveToken(token: string): Promise<void> |
||||
saveGitHubCredentials({ |
||||
username, |
||||
email, |
||||
token |
||||
}): Promise<void> |
||||
getGitHubCredentialsFromLocalStorage(): Promise<{ |
||||
username: string |
||||
email: string |
||||
token: string |
||||
}> |
||||
} |
||||
|
||||
export const pluginActionsContext = React.createContext<pluginActions>(null) |
@ -0,0 +1,220 @@ |
||||
import { ReadCommitResult } from "isomorphic-git" |
||||
import { GitHubUser, branch, commitChange, fileStatusResult, remote, pagedCommits, branchDifference, gitLog, repository, userEmails } from "../types" |
||||
import { Endpoints } from "@octokit/types" |
||||
|
||||
export const fileStatus = (files: fileStatusResult[]) => { |
||||
return { |
||||
type: 'FILE_STATUS', |
||||
payload: files |
||||
} |
||||
} |
||||
|
||||
export const fileStatusMerge = (files: fileStatusResult[]) => { |
||||
return { |
||||
type: 'FILE_STATUS_MERGE', |
||||
payload: files |
||||
} |
||||
} |
||||
|
||||
export const setCommits = (commits: ReadCommitResult[]) => { |
||||
return { |
||||
type: 'SET_COMMITS', |
||||
payload: commits |
||||
} |
||||
} |
||||
|
||||
export const setBranches = (branches: any[]) => { |
||||
return { |
||||
type: 'SET_BRANCHES', |
||||
payload: branches |
||||
} |
||||
} |
||||
|
||||
export const setRepos = (repos: repository[]) => { |
||||
return { |
||||
type: 'SET_REPOS', |
||||
payload: repos |
||||
} |
||||
} |
||||
|
||||
export const setRemoteBranches = (branches: any[]) => { |
||||
return { |
||||
type: 'SET_REMOTE_BRANCHES', |
||||
payload: branches |
||||
} |
||||
} |
||||
|
||||
export const setGitHubUser = (user: GitHubUser) => { |
||||
console.log('set user', user) |
||||
return { |
||||
type: 'SET_GITHUB_USER', |
||||
payload: user |
||||
} |
||||
} |
||||
|
||||
export const setUserEmails = (emails: userEmails) => { |
||||
return { |
||||
type: 'SET_USER_EMAILS', |
||||
payload: emails |
||||
} |
||||
} |
||||
|
||||
export const setRateLimit = (rateLimit: any) => { |
||||
return { |
||||
type: 'SET_RATE_LIMIT', |
||||
payload: rateLimit |
||||
} |
||||
} |
||||
|
||||
export const setScopes = (scopes: string[]) => { |
||||
return { |
||||
type: 'SET_SCOPES', |
||||
payload: scopes |
||||
} |
||||
} |
||||
|
||||
export const setGitHubAccessToken = (token: string) => { |
||||
return { |
||||
type: 'SET_GITHUB_ACCESS_TOKEN', |
||||
payload: token |
||||
} |
||||
} |
||||
|
||||
export const setLoading = (loading: boolean) => { |
||||
return { |
||||
type: 'SET_LOADING', |
||||
payload: loading |
||||
} |
||||
} |
||||
|
||||
export const setCanUseApp = (canUseApp: boolean) => { |
||||
return { |
||||
type: 'SET_CAN_USE_APP', |
||||
payload: canUseApp |
||||
} |
||||
} |
||||
|
||||
export const setRepoName = (reponame: string) => { |
||||
return { |
||||
type: 'SET_REPO_NAME', |
||||
payload: reponame |
||||
} |
||||
} |
||||
|
||||
export const setCurrentBranch = (currentBranch: branch) => { |
||||
return { |
||||
type: 'SET_CURRENT_BRANCH', |
||||
payload: currentBranch |
||||
} |
||||
} |
||||
|
||||
export const setCanCommit = (canCommit: boolean) => { |
||||
return { |
||||
type: 'SET_CAN_COMMIT', |
||||
payload: canCommit |
||||
} |
||||
} |
||||
|
||||
export const setRemotes = (remotes: remote[]) => { |
||||
return { |
||||
type: 'SET_REMOTES', |
||||
payload: remotes |
||||
} |
||||
} |
||||
|
||||
export const setUpstream = (upstream: remote) => { |
||||
return { |
||||
type: 'SET_UPSTREAM', |
||||
payload: upstream |
||||
} |
||||
} |
||||
|
||||
export const setCommitChanges = (commitChanges: commitChange[]) => { |
||||
return { |
||||
type: 'SET_COMMIT_CHANGES', |
||||
payload: commitChanges |
||||
} |
||||
} |
||||
|
||||
export const setRemoteBranchCommits = ({ branch, commits }:{ |
||||
branch: branch, |
||||
commits: pagedCommits[] |
||||
}):{ |
||||
type: string; |
||||
payload: { branch: branch; commits: pagedCommits[] }; |
||||
} => { |
||||
return { |
||||
type: 'SET_REMOTE_BRANCH_COMMITS', |
||||
payload: { branch, commits } |
||||
} |
||||
} |
||||
|
||||
export const resetRemoteBranchCommits = ({ branch }:{ |
||||
branch: branch, |
||||
}):{ |
||||
type: string; |
||||
payload: { branch: branch }; |
||||
} => { |
||||
return { |
||||
type: 'RESET_REMOTE_BRANCH_COMMITS', |
||||
payload: { branch } |
||||
} |
||||
} |
||||
|
||||
export const setLocalBranchCommits = ({ |
||||
branch, |
||||
commits |
||||
}: { |
||||
branch: branch; |
||||
commits: ReadCommitResult[]; |
||||
}): { |
||||
type: string; |
||||
payload: { branch: branch; commits: ReadCommitResult[] }; |
||||
} => { |
||||
return { |
||||
type: 'SET_LOCAL_BRANCH_COMMITS', |
||||
payload: { branch, commits } |
||||
}; |
||||
}; |
||||
|
||||
export const setBranchDifferences = ({ |
||||
branch, |
||||
remote, |
||||
branchDifference |
||||
}:{ |
||||
branch: branch; |
||||
remote: remote; |
||||
branchDifference: branchDifference; |
||||
}) => { |
||||
return { |
||||
type: 'SET_BRANCH_DIFFERENCES', |
||||
payload: { branch, remote, branchDifference } |
||||
} |
||||
} |
||||
|
||||
export const setGItHubToken = (token: string) => { |
||||
return { |
||||
type: 'SET_GITHUB_ACCESS_TOKEN', |
||||
payload: token |
||||
} |
||||
} |
||||
|
||||
export const setRemoteAsDefault = (remote: remote) => { |
||||
return { |
||||
type: 'SET_DEFAULT_REMOTE', |
||||
payload: remote |
||||
} |
||||
} |
||||
|
||||
export const setLog = (message: gitLog) => { |
||||
return { |
||||
type: 'SET_LOG', |
||||
payload: message |
||||
} |
||||
} |
||||
|
||||
export const clearLog = () => { |
||||
return { |
||||
type: 'CLEAR_LOG' |
||||
} |
||||
} |
@ -0,0 +1,210 @@ |
||||
import { ReadCommitResult } from "isomorphic-git" |
||||
import { allChangedButNotStagedFiles, getFilesByStatus, getFilesWithNotModifiedStatus } from "../lib/fileHelpers" |
||||
import { branch, commitChange, defaultGitState, fileStatusResult, gitState, setRemoteBranchCommitsAction, setLocalBranchCommitsAction, setBranchDifferencesAction, setDefaultRemoteAction, setRemotesAction, setUpstreamAction } from "../types" |
||||
import { Actions } from "./actions" |
||||
|
||||
export const gitReducer = (state: gitState = defaultGitState, action: Actions): gitState => { |
||||
///console.log(action, state)
|
||||
switch (action.type) { |
||||
|
||||
case 'FILE_STATUS': |
||||
return { |
||||
...state, |
||||
fileStatusResult: action.payload, |
||||
staged: getFilesByStatus("staged", action.payload), |
||||
modified: getFilesByStatus("modified", action.payload), |
||||
untracked: getFilesByStatus("untracked", action.payload), |
||||
deleted: getFilesByStatus("deleted", action.payload), |
||||
allchangesnotstaged: allChangedButNotStagedFiles(action.payload) |
||||
} |
||||
|
||||
case 'FILE_STATUS_MERGE': |
||||
action.payload.map((fileStatusResult: fileStatusResult) => { |
||||
const file = state.fileStatusResult.find(stateFile => { |
||||
return stateFile.filename === fileStatusResult.filename |
||||
}) |
||||
if (file) { |
||||
file.status = fileStatusResult.status |
||||
file.statusNames = fileStatusResult.statusNames |
||||
} |
||||
}) |
||||
|
||||
return { |
||||
...state, |
||||
staged: getFilesByStatus("staged", state.fileStatusResult), |
||||
modified: getFilesByStatus("modified", state.fileStatusResult), |
||||
untracked: getFilesByStatus("untracked", state.fileStatusResult), |
||||
deleted: getFilesByStatus("deleted", state.fileStatusResult), |
||||
allchangesnotstaged: allChangedButNotStagedFiles(state.fileStatusResult) |
||||
} |
||||
|
||||
case 'SET_COMMITS': |
||||
return { |
||||
...state, |
||||
commits: action.payload, |
||||
localCommitCount: action.payload.length |
||||
} |
||||
|
||||
case 'SET_BRANCHES': |
||||
return { |
||||
...state, |
||||
branches: action.payload |
||||
} |
||||
|
||||
case 'SET_CURRENT_BRANCH': |
||||
return { |
||||
...state, |
||||
currentBranch: action.payload |
||||
} |
||||
|
||||
case 'SET_CAN_USE_APP': |
||||
return { |
||||
...state, |
||||
canUseApp: action.payload |
||||
} |
||||
case 'SET_REPO_NAME': |
||||
return { |
||||
...state, |
||||
reponame: action.payload |
||||
} |
||||
case 'SET_LOADING': |
||||
return { |
||||
...state, |
||||
loading: action.payload |
||||
} |
||||
|
||||
case 'SET_REPOS': |
||||
return { |
||||
...state, |
||||
repositories: action.payload |
||||
} |
||||
|
||||
case 'SET_REMOTE_BRANCHES': |
||||
return { |
||||
...state, |
||||
remoteBranches: action.payload |
||||
} |
||||
|
||||
case 'SET_CAN_COMMIT': |
||||
return { |
||||
...state, |
||||
canCommit: action.payload |
||||
} |
||||
|
||||
case 'SET_REMOTES': |
||||
return { |
||||
...state, |
||||
remotes: action.payload |
||||
} |
||||
|
||||
case 'SET_UPSTREAM': |
||||
return { |
||||
...state, |
||||
upstream: action.payload |
||||
} |
||||
|
||||
case 'SET_COMMIT_CHANGES': |
||||
|
||||
action.payload.forEach((change: commitChange) => { |
||||
state.commitChanges.find((c) => c.hashModified === change.hashModified && c.hashOriginal === change.hashOriginal && c.path === change.path) ? null : state.commitChanges.push(change) |
||||
}) |
||||
|
||||
return { |
||||
...state, |
||||
commitChanges: [...state.commitChanges] |
||||
} |
||||
|
||||
case 'RESET_REMOTE_BRANCH_COMMITS': |
||||
if (state.remoteBranchCommits[action.payload.branch.name]) { |
||||
delete state.remoteBranchCommits[action.payload.branch.name] |
||||
} |
||||
return { |
||||
...state, |
||||
remoteBranchCommits: { ...state.remoteBranchCommits } |
||||
} |
||||
|
||||
case 'SET_REMOTE_BRANCH_COMMITS': |
||||
if (state.remoteBranchCommits[action.payload.branch.name]) { |
||||
state.remoteBranchCommits[action.payload.branch.name].push(...action.payload.commits) |
||||
} else { |
||||
state.remoteBranchCommits[action.payload.branch.name] = action.payload.commits |
||||
} |
||||
return { |
||||
...state, |
||||
remoteBranchCommits: { ...state.remoteBranchCommits } |
||||
} |
||||
|
||||
case 'SET_LOCAL_BRANCH_COMMITS': |
||||
|
||||
state.localBranchCommits[action.payload.branch.name] = action.payload.commits |
||||
return { |
||||
...state, |
||||
localBranchCommits: { ...state.localBranchCommits } |
||||
} |
||||
|
||||
case 'SET_BRANCH_DIFFERENCES': |
||||
|
||||
state.branchDifferences[`${action.payload.remote.name}/${action.payload.branch.name}`] = action.payload.branchDifference |
||||
|
||||
return { |
||||
...state, |
||||
branchDifferences: { ...state.branchDifferences } |
||||
} |
||||
|
||||
case 'SET_GITHUB_USER': |
||||
return { |
||||
...state, |
||||
gitHubUser: action.payload |
||||
} |
||||
|
||||
case 'SET_RATE_LIMIT': |
||||
console.log("rate limit", action.payload) |
||||
return { |
||||
...state, |
||||
rateLimit: action.payload |
||||
} |
||||
|
||||
case 'SET_GITHUB_ACCESS_TOKEN': |
||||
return { |
||||
...state, |
||||
gitHubAccessToken: action.payload |
||||
} |
||||
|
||||
case 'SET_SCOPES': |
||||
return { |
||||
...state, |
||||
gitHubScopes: action.payload |
||||
} |
||||
|
||||
case 'SET_USER_EMAILS': |
||||
return { |
||||
...state, |
||||
userEmails: action.payload |
||||
} |
||||
|
||||
case 'SET_DEFAULT_REMOTE': |
||||
return { |
||||
...state, |
||||
defaultRemote: action.payload |
||||
} |
||||
|
||||
case 'SET_LOG': |
||||
if (state.log.length > 0 && state.log[[...state.log].length - 1].message === action.payload.message) { |
||||
return { |
||||
...state, |
||||
log: [...state.log] |
||||
} |
||||
} |
||||
return { |
||||
...state, |
||||
log: [...state.log, action.payload] |
||||
} |
||||
|
||||
case 'CLEAR_LOG': |
||||
return { |
||||
...state, |
||||
log: [] |
||||
} |
||||
|
||||
} |
||||
} |
@ -0,0 +1,11 @@ |
||||
import { defaultLoaderState, loaderState } from "../types"; |
||||
|
||||
interface Action { |
||||
type: string |
||||
payload: any |
||||
} |
||||
|
||||
export const loaderReducer = (state: loaderState = defaultLoaderState, action: Action): loaderState => { |
||||
state[action.type] = action.payload |
||||
return state |
||||
} |
@ -0,0 +1,36 @@ |
||||
.nav { |
||||
cursor: pointer; |
||||
} |
||||
|
||||
.pointer { |
||||
cursor: pointer; |
||||
} |
||||
|
||||
.long-and-truncated { |
||||
flex: 1; |
||||
white-space: nowrap; |
||||
overflow: hidden; |
||||
text-overflow: ellipsis; |
||||
} |
||||
|
||||
.commit-navigation{ |
||||
align-items: center; |
||||
} |
||||
|
||||
.commit-navigation:hover { |
||||
background-color: var(--custom-select); |
||||
} |
||||
|
||||
.commitdetailsitem { |
||||
align-items: baseline; |
||||
} |
||||
|
||||
.gitfile:hover { |
||||
background-color : var(--custom-select); |
||||
} |
||||
|
||||
hr { |
||||
background-color: var(--custom-select); |
||||
} |
||||
|
||||
|
@ -0,0 +1,430 @@ |
||||
import { Endpoints } from "@octokit/types" |
||||
import { IRemixApi } from "@remixproject/plugin-api" |
||||
import { LibraryProfile, StatusEvents } from "@remixproject/plugin-utils" |
||||
import { CommitObject, ReadBlobResult, ReadCommitResult, StatusRow } from "isomorphic-git" |
||||
export type GitHubUser = Partial<Endpoints["GET /user"]["response"]['data']> |
||||
export type RateLimit = Endpoints["GET /rate_limit"]["response"]["data"] |
||||
export type userEmails = Endpoints["GET /user/emails"]["response"]["data"] |
||||
|
||||
export interface IGitApi { |
||||
events: { |
||||
"checkout": () => void |
||||
"clone": () => void |
||||
"add": () => void |
||||
"rm": () => void |
||||
"commit": () => void |
||||
"branch": () => void |
||||
"init": () => void |
||||
} & StatusEvents, |
||||
methods: { |
||||
getCommitChanges(oid1: string, oid2: string): Promise<commitChange[]> |
||||
repositories(input: repositoriesInput): Promise<repository[]> |
||||
clone(input: cloneInputType): Promise<any> |
||||
branches(input?: branchesInput): Promise<branch[]>, |
||||
remotes(): Promise<remote[]>, |
||||
log(cmd: { ref: string }): Promise<ReadCommitResult[]>, |
||||
remotecommits(input: remoteCommitsInputType): Promise<pagedCommits[]> |
||||
fetch(input: fetchInputType): Promise<any> |
||||
pull(input: pullInputType): Promise<any> |
||||
push(input: pushInputType): Promise<any> |
||||
currentbranch(input?: currentBranchInput): Promise<branch> |
||||
branch(input: branchInputType): Promise<void> |
||||
checkout(input: checkoutInput): Promise<void> |
||||
add(input: addInput): Promise<void> |
||||
rm(input: rmInput): Promise<void> |
||||
resolveref(input: resolveRefInput): Promise<string> |
||||
readblob(input: readBlobInput): Promise<ReadBlobResult> |
||||
commit(input: commitInput): Promise<string> |
||||
addremote(input: remote): Promise<void> |
||||
delremote(input: remote): Promise<void> |
||||
status(input?: statusInput): Promise<Array<StatusRow>> |
||||
compareBranches(input: compareBranchesInput): Promise<branchDifference> |
||||
init(input?: initInput): Promise<void> |
||||
updateSubmodules: (input: updateSubmodulesInput) => Promise<void> |
||||
} |
||||
} |
||||
|
||||
export type initInput = { |
||||
defaultBranch: string |
||||
} |
||||
|
||||
export type updateSubmodulesInput = { |
||||
dir?: string |
||||
token?: string |
||||
} |
||||
|
||||
export type remoteCommitsInputType = { |
||||
owner: string, repo: string, token: string, branch: string, length: number, page: number |
||||
} |
||||
|
||||
export type compareBranchesInput = { |
||||
branch: branch, remote: remote |
||||
} |
||||
|
||||
export type fetchInputType = { |
||||
remote: remote, |
||||
ref?: branch, |
||||
remoteRef?: branch, |
||||
depth?: number, |
||||
singleBranch?: boolean, |
||||
relative?: boolean, |
||||
quiet?: boolean |
||||
} |
||||
|
||||
export type pullInputType = { |
||||
remote: remote, ref: branch, remoteRef?: branch |
||||
} |
||||
|
||||
export type pushInputType = { |
||||
remote: remote, ref: branch, remoteRef?: branch, force?: boolean |
||||
} |
||||
|
||||
export type branchInputType = { |
||||
ref: string, |
||||
checkout?: boolean |
||||
refresh?: boolean |
||||
} |
||||
|
||||
export type currentBranchInput = { |
||||
fs: any, |
||||
dir: string |
||||
} |
||||
|
||||
export type checkoutInput = { |
||||
ref: string, |
||||
force?: boolean, |
||||
remote?: string |
||||
refresh?: boolean |
||||
} |
||||
|
||||
export type addInput = { |
||||
filepath: string | string[] |
||||
} |
||||
|
||||
export type rmInput = { |
||||
filepath: string |
||||
} |
||||
|
||||
export type resolveRefInput = { |
||||
ref: string |
||||
} |
||||
|
||||
export type readBlobInput = { |
||||
oid: string, |
||||
filepath: string |
||||
} |
||||
|
||||
export type commitInput = { |
||||
author: { |
||||
name: string, |
||||
email: string, |
||||
}, |
||||
message: string, |
||||
} |
||||
|
||||
export type branchesInput = { |
||||
fs?: any |
||||
dir?: string |
||||
} |
||||
|
||||
export interface cloneInputType { |
||||
url: string, |
||||
branch?: string, |
||||
depth?: number, |
||||
singleBranch?: boolean |
||||
workspaceName?: string |
||||
workspaceExists?: boolean |
||||
token?: string |
||||
} |
||||
|
||||
export interface repositoriesInput { token: string, page?: number, per_page?: number } |
||||
|
||||
export interface statusInput { ref: string, filepaths?: string[] } |
||||
|
||||
export const dGitProfile: LibraryProfile<IGitApi> = { |
||||
name: 'dgitApi', |
||||
methods: ['clone', 'branches', 'remotes', 'getCommitChanges', 'log', 'remotecommits'], |
||||
} |
||||
|
||||
export interface customGitApi extends IRemixApi { |
||||
dgit: IGitApi |
||||
} |
||||
|
||||
export type gitState = { |
||||
currentBranch: branch |
||||
commits: ReadCommitResult[] |
||||
branch: string |
||||
canCommit: boolean |
||||
branches: branch[] |
||||
remotes: remote[] |
||||
defaultRemote: remote |
||||
fileStatusResult: fileStatusResult[] |
||||
canUseApp: boolean |
||||
loading: boolean |
||||
storageUsed: any |
||||
reponame: string |
||||
staged: fileStatusResult[] |
||||
untracked: fileStatusResult[] |
||||
deleted: fileStatusResult[] |
||||
modified: fileStatusResult[] |
||||
allchangesnotstaged: fileStatusResult[], |
||||
repositories: repository[] |
||||
remoteBranches: remoteBranch[] |
||||
commitChanges: commitChange[] |
||||
remoteBranchCommits: Record<string, pagedCommits[]> |
||||
localBranchCommits: Record<string, ReadCommitResult[]> |
||||
branchDifferences: Record<remoteBranchIdentifier, branchDifference> |
||||
syncStatus: syncStatus, |
||||
localCommitCount: number |
||||
remoteCommitCount: number |
||||
upstream: remote |
||||
gitHubUser: GitHubUser |
||||
rateLimit: RateLimit |
||||
userEmails: userEmails |
||||
gitHubScopes: string[] |
||||
gitHubAccessToken: string |
||||
log: gitLog[] |
||||
} |
||||
export type gitLog = { |
||||
type: 'error' | 'warning' | 'info' | 'success', |
||||
message: string |
||||
} |
||||
|
||||
export type remoteBranchIdentifier = `${string}/${string}` |
||||
|
||||
export type branchDifference = { |
||||
uniqueHeadCommits: ReadCommitResult[], |
||||
uniqueRemoteCommits: ReadCommitResult[], |
||||
} |
||||
|
||||
export type pagedCommits = { |
||||
page: number, |
||||
perPage: number, |
||||
total: number, |
||||
hasNextPage: boolean, |
||||
commits: ReadCommitResult[] |
||||
} |
||||
|
||||
export type loaderState = { |
||||
branches: boolean |
||||
remotes: boolean |
||||
commits: boolean |
||||
sourcecontrol: boolean |
||||
plugin: boolean |
||||
} |
||||
|
||||
export type commitChangeTypes = { |
||||
"deleted": "D" |
||||
"modified": "M" |
||||
"added": "A", |
||||
"unknown": "?" |
||||
} |
||||
|
||||
export enum syncStatus { |
||||
"sync" = "sync", |
||||
"publishBranch" = "publishBranch", |
||||
"none" = "none", |
||||
} |
||||
|
||||
export type commitChangeType = keyof commitChangeTypes |
||||
|
||||
export type commitChange = { |
||||
type: commitChangeType |
||||
path: string, |
||||
hashModified: string, |
||||
hashOriginal: string, |
||||
original?: string, |
||||
modified?: string, |
||||
readonly?: boolean |
||||
} |
||||
|
||||
export type repository = { |
||||
name: string |
||||
html_url: string |
||||
owner: { |
||||
login: string |
||||
}, |
||||
full_name: string |
||||
default_branch: string |
||||
id: number |
||||
url: string |
||||
} |
||||
|
||||
export type branch = { |
||||
name: string |
||||
remote: remote |
||||
} |
||||
|
||||
export type remote = { |
||||
name: string |
||||
url: string |
||||
} |
||||
|
||||
export type remoteBranch = { |
||||
name: string |
||||
} |
||||
|
||||
export const defaultGitState: gitState = { |
||||
currentBranch: { name: "", remote: { name: "", url: "" } }, |
||||
commits: [], |
||||
branch: "", |
||||
canCommit: true, |
||||
branches: [], |
||||
remotes: [], |
||||
defaultRemote: null, |
||||
fileStatusResult: [], |
||||
staged: [], |
||||
untracked: [], |
||||
deleted: [], |
||||
modified: [], |
||||
allchangesnotstaged: [], |
||||
canUseApp: false, |
||||
loading: false, |
||||
storageUsed: {}, |
||||
reponame: "", |
||||
repositories: [], |
||||
remoteBranches: [], |
||||
commitChanges: [], |
||||
remoteBranchCommits: {}, |
||||
localBranchCommits: {}, |
||||
branchDifferences: {}, |
||||
syncStatus: syncStatus.none, |
||||
localCommitCount: 0, |
||||
remoteCommitCount: 0, |
||||
upstream: null, |
||||
gitHubUser: {} as GitHubUser, |
||||
rateLimit: {} as RateLimit, |
||||
userEmails: [] as userEmails, |
||||
gitHubScopes: [], |
||||
gitHubAccessToken: "", |
||||
log: [] |
||||
} |
||||
|
||||
export const defaultLoaderState: loaderState = { |
||||
branches: false, |
||||
commits: false, |
||||
sourcecontrol: false, |
||||
remotes: false, |
||||
plugin: false |
||||
} |
||||
|
||||
export type fileStatusResult = { |
||||
filename: string, |
||||
status?: fileStatus |
||||
statusNames?: string[] |
||||
} |
||||
|
||||
export type fileStatus = [string, 0 | 1, 0 | 1 | 2, 0 | 1 | 2 | 3] |
||||
|
||||
export type statusMatrixType = { matrix: string[] | undefined; status: string[] } |
||||
|
||||
export type sourceControlGroup = { |
||||
group: fileStatusResult[], |
||||
name: string |
||||
} |
||||
|
||||
export interface fileStatusAction { |
||||
type: string, |
||||
payload: fileStatusResult[] |
||||
} |
||||
|
||||
export interface setCommitsAction { |
||||
type: string, |
||||
payload: ReadCommitResult[] |
||||
} |
||||
|
||||
export interface setBranchesAction { |
||||
type: string, |
||||
payload: any[] |
||||
} |
||||
|
||||
export interface setReposAction { |
||||
type: string, |
||||
payload: any[] |
||||
} |
||||
|
||||
export interface setRemoteBranchesAction { |
||||
type: string, |
||||
payload: any[] |
||||
} |
||||
|
||||
export interface setGitHubUserAction { |
||||
type: string, |
||||
payload: any |
||||
} |
||||
|
||||
export interface setLoadingAction { |
||||
type: string, |
||||
payload: boolean |
||||
} |
||||
|
||||
export interface setCanUseAppAction { |
||||
type: string, |
||||
payload: boolean |
||||
} |
||||
|
||||
export interface setRepoNameAction { |
||||
type: string, |
||||
payload: string |
||||
} |
||||
|
||||
export interface setCurrentBranchAction { |
||||
type: string, |
||||
payload: branch |
||||
} |
||||
|
||||
export interface setRemotesAction { |
||||
type: string, |
||||
payload: remote[] |
||||
} |
||||
|
||||
export interface setUpstreamAction { |
||||
type: string, |
||||
payload: remote |
||||
} |
||||
|
||||
export interface setRemoteBranchCommitsAction { |
||||
type: string, |
||||
payload: { |
||||
branch: branch, |
||||
commits: pagedCommits[] |
||||
} |
||||
} |
||||
|
||||
export interface setLocalBranchCommitsAction { |
||||
type: string, |
||||
payload: { |
||||
branch: branch, |
||||
commits: ReadCommitResult[] |
||||
} |
||||
} |
||||
|
||||
export interface setBranchDifferencesAction { |
||||
type: string, |
||||
payload: { |
||||
branch: branch, |
||||
remote: remote, |
||||
branchDifference: branchDifference |
||||
} |
||||
} |
||||
|
||||
export interface setTokenAction { |
||||
type: string, |
||||
payload: string |
||||
} |
||||
|
||||
export interface setDefaultRemoteAction { |
||||
type: string, |
||||
payload: remote |
||||
} |
||||
|
||||
export interface setLogAction { |
||||
type: string, |
||||
payload: gitLog |
||||
} |
||||
|
||||
export interface clearLogAction { |
||||
type: string |
||||
} |
||||
|
||||
export type gitActionDispatch = clearLogAction | setLogAction | setDefaultRemoteAction | setTokenAction | setUpstreamAction | setRemoteBranchCommitsAction | setLocalBranchCommitsAction | setBranchDifferencesAction | setRemotesAction | setCurrentBranchAction | fileStatusAction | setLoadingAction | setCanUseAppAction | setRepoNameAction | setCommitsAction | setBranchesAction | setReposAction | setRemoteBranchesAction |
@ -0,0 +1,53 @@ |
||||
import { StylesConfig } from 'react-select' |
||||
export const selectStyles: StylesConfig = { |
||||
option: (baseStyles, state) => { |
||||
return { |
||||
...baseStyles, |
||||
color: 'var(--text)', |
||||
} |
||||
}, |
||||
input(base, props) { |
||||
return { |
||||
...base, |
||||
color: 'var(--text)', |
||||
} |
||||
}, |
||||
singleValue: (baseStyles, state) => { |
||||
return { |
||||
...baseStyles, |
||||
color: 'var(--text)', |
||||
} |
||||
}, |
||||
control: (baseStyles, state) => ({ |
||||
...baseStyles, |
||||
color: 'var(--text)', |
||||
backgroundColor: 'var(--custom-select)', |
||||
border: 'none', |
||||
}), |
||||
menu: (baseStyles, state) => { |
||||
return { |
||||
...baseStyles, |
||||
backgroundColor: 'var(--custom-select)', |
||||
color: 'var(--text)', |
||||
} |
||||
}, |
||||
menuList: (baseStyles, props) => { |
||||
return { |
||||
...baseStyles, |
||||
backgroundColor: 'var(--custom-select)', |
||||
color: 'var(--text)', |
||||
} |
||||
}, |
||||
} |
||||
|
||||
export const selectTheme = (theme) => ({ |
||||
...theme, |
||||
borderRadius: 0, |
||||
colors: { |
||||
...theme.colors, |
||||
primary25: 'var(--primary)', |
||||
primary: 'var(--primary)', |
||||
primary50: 'var(--primary)', |
||||
primary75: 'var(--primary)', |
||||
}, |
||||
}) |
@ -0,0 +1,3 @@ |
||||
export const removeSlash = (s: string) => { |
||||
return s.replace(/^\/+/, ""); |
||||
}; |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue