Merge branch 'master' into intl

pull/2581/head
drafish 2 years ago
commit d95d8ae9a3
  1. 38
      apps/remix-ide/src/app/files/dgitProvider.js
  2. 4
      apps/remix-ide/src/app/files/fileManager.ts
  3. 34
      libs/remix-core-plugin/src/lib/compiler-metadata.ts
  4. 222
      libs/remix-lib/src/helpers/hhconsoleSigs.ts
  5. 1
      libs/remix-ui/helper/src/index.ts
  6. 23
      libs/remix-ui/helper/src/lib/components/custom-tooltip.tsx
  7. 5
      libs/remix-ui/helper/src/lib/remix-ui-helper.ts
  8. 10
      libs/remix-ui/helper/src/types/customtooltip.ts
  9. 23
      libs/remix-ui/panel/src/lib/plugins/panel-header.tsx
  10. 1
      libs/remix-ui/workspace/src/lib/actions/index.ts
  11. 16
      libs/remix-ui/workspace/src/lib/actions/payload.ts
  12. 203
      libs/remix-ui/workspace/src/lib/actions/workspace.ts
  13. 6
      libs/remix-ui/workspace/src/lib/contexts/index.ts
  14. 5
      libs/remix-ui/workspace/src/lib/css/remix-ui-workspace.css
  15. 26
      libs/remix-ui/workspace/src/lib/providers/FileSystemProvider.tsx
  16. 48
      libs/remix-ui/workspace/src/lib/reducers/workspace.ts
  17. 104
      libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx
  18. 8
      libs/remix-ui/workspace/src/lib/utils/gitStatusFilter.ts

@ -50,6 +50,8 @@ class DGitProvider extends Plugin {
async getGitConfig () {
const workspace = await this.call('filePanel', 'getCurrentWorkspace')
if (!workspace) return
return {
fs: window.remixFileSystemCallback,
dir: addSlash(workspace.absolutePath)
@ -106,15 +108,18 @@ class DGitProvider extends Plugin {
}, 1000)
}
async checkout (cmd) {
async checkout (cmd, refresh = true) {
await git.checkout({
...await this.getGitConfig(),
...cmd
})
if (refresh) {
setTimeout(async () => {
await this.call('fileManager', 'refresh')
}, 1000)
}
this.emit('checkout')
}
async log (cmd) {
const status = await git.log({
@ -124,39 +129,42 @@ class DGitProvider extends Plugin {
return status
}
async remotes () {
async remotes (config) {
let remotes = []
try {
remotes = await git.listRemotes({ ...await this.getGitConfig() })
remotes = await git.listRemotes({ ...config ? config : await this.getGitConfig() })
} catch (e) {
// do nothing
}
return remotes
}
async branch (cmd) {
async branch (cmd, refresh = true) {
const status = await git.branch({
...await this.getGitConfig(),
...cmd
})
if (refresh) {
setTimeout(async () => {
await this.call('fileManager', 'refresh')
}, 1000)
}
this.emit('branch')
return status
}
async currentbranch () {
const name = await git.currentBranch({
...await this.getGitConfig()
})
async currentbranch (config) {
const defaultConfig = await this.getGitConfig()
const cmd = config ? defaultConfig ? { ...defaultConfig, ...config } : config : defaultConfig
const name = await git.currentBranch(cmd)
return name
}
async branches () {
const cmd = {
...await this.getGitConfig()
}
const remotes = await this.remotes()
async branches (config) {
const defaultConfig = await this.getGitConfig()
const cmd = config ? defaultConfig ? { ...defaultConfig, ...config } : config : defaultConfig
const remotes = await this.remotes(config)
let branches = []
branches = (await git.listBranches(cmd)).map((branch) => { return { remote: undefined, name: branch } })
for (const remote of remotes) {
@ -387,6 +395,8 @@ class DGitProvider extends Plugin {
pinata_api_key: pinataApiKey,
pinata_secret_api_key: pinataSecretApiKey
}
}).catch((e) => {
console.log(e)
})
// also commit to remix IPFS for availability after pinning to Pinata
return await this.export(this.remixIPFS) || result.data.IpfsHash
@ -405,6 +415,8 @@ class DGitProvider extends Plugin {
pinata_api_key: pinataApiKey,
pinata_secret_api_key: pinataSecretApiKey
}
}).catch((e) => {
console.log('Pinata unreachable')
})
return result.data
} catch (error) {

@ -818,8 +818,8 @@ class FileManager extends Plugin {
}
}
async isGitRepo (directory: string): Promise<boolean> {
const path = directory + '/.git'
async isGitRepo (): Promise<boolean> {
const path = '.git'
const exists = await this.exists(path)
return exists

@ -13,10 +13,12 @@ const profile = {
export class CompilerMetadata extends Plugin {
networks: string[]
innerPath: string
buildInfoNames: Record<string, string>
constructor () {
super(profile)
this.networks = ['VM:-', 'main:1', 'ropsten:3', 'rinkeby:4', 'kovan:42', 'goerli:5', 'Custom']
this.innerPath = 'artifacts'
this.buildInfoNames = {}
}
_JSONFileName (path, contractName) {
@ -33,7 +35,7 @@ export class CompilerMetadata extends Plugin {
if (!await this.call('settings', 'get', 'settings/generate-contract-metadata')) return
const compiler = new CompilerAbstract(languageVersion, data, source, input)
const path = self._extractPathOf(source.target)
await this.setBuildInfo(version, input, data, path)
await this.setBuildInfo(version, input, data, path, file)
compiler.visitContracts((contract) => {
if (contract.file !== source.target) return
(async () => {
@ -45,7 +47,24 @@ export class CompilerMetadata extends Plugin {
})
}
async setBuildInfo (version, input, output, path) {
// Access each file in build-info, check the input sources
// If they are all same as in current compiled file and sources includes the path of compiled file, remove old build file
async removeStoredBuildInfo (currentInput, path, filePath) {
const buildDir = this.joinPath(path, this.innerPath, 'build-info/')
if (await this.call('fileManager', 'exists', buildDir)) {
const allBuildFiles = await this.call('fileManager', 'fileList', buildDir)
const currentInputFileNames = Object.keys(currentInput.sources)
for (const fileName of allBuildFiles) {
let fileContent = await this.call('fileManager', 'readFile', fileName)
fileContent = JSON.parse(fileContent)
const inputFiles = Object.keys(fileContent.input.sources)
const inputIntersection = currentInputFileNames.filter(element => !inputFiles.includes(element))
if (inputIntersection.length === 0 && inputFiles.includes(filePath)) await this.call('fileManager', 'remove', fileName)
}
}
}
async setBuildInfo (version, input, output, path, filePath) {
input = JSON.parse(input)
const solcLongVersion = version.replace('.Emscripten.clang', '')
const solcVersion = solcLongVersion.substring(0, solcLongVersion.indexOf('+commit'))
@ -58,8 +77,19 @@ export class CompilerMetadata extends Plugin {
})
const id = createHash('md5').update(Buffer.from(json)).digest().toString('hex')
const buildFilename = this.joinPath(path, this.innerPath, 'build-info/' + id + '.json')
// If there are no file in buildInfoNames,it means compilation is running first time after loading Remix
if (!this.buildInfoNames[filePath]) {
// Check the existing build-info and delete all the previous build files for compiled file
await this.removeStoredBuildInfo(input, path, filePath)
this.buildInfoNames[filePath] = buildFilename
const buildData = {id, _format: format, solcVersion, solcLongVersion, input, output}
await this.call('fileManager', 'writeFile', buildFilename, JSON.stringify(buildData, null, '\t'))
} else if (this.buildInfoNames[filePath] && this.buildInfoNames[filePath] !== buildFilename) {
await this.call('fileManager', 'remove', this.buildInfoNames[filePath])
this.buildInfoNames[filePath] = buildFilename
const buildData = {id, _format: format, solcVersion, solcLongVersion, input, output}
await this.call('fileManager', 'writeFile', buildFilename, JSON.stringify(buildData, null, '\t'))
}
}
_extractPathOf (file) {

@ -1,6 +1,7 @@
// Fetched from https://github.com/nomiclabs/hardhat/blob/ee4969a0a8f746f4775d4018326056d161066869/packages/hardhat-core/src/internal/hardhat-network/stack-traces/logger.ts#L47
export const ConsoleLogs = {
// Legacy method signatures before this PR: https://github.com/NomicFoundation/hardhat/pull/2964
1368866505: '()',
1309416733: '(int)',
4122065833: '(uint)',
@ -377,5 +378,224 @@ export const ConsoleLogs = {
238520724: '(address,address,address,bool)',
1717301556: '(address,address,address,address)',
4133908826: '(uint,uint)',
3054400204: '(string,uint)'
3054400204: '(string,uint)',
// Latest method signatures after updating uint to uint256 and int to int256
760966329: '(int256)',
4163653873: '(uint256)',
1681903839: '(uint256, string)',
480083635: '(uint256, bool)',
1764191366: '(uint256, address)',
965833939: '(bool, uint256)',
2198464680: '(address, uint256)',
3522001468: '(uint256, uint256, uint256)',
1909476082: '(uint256, uint256, string)',
1197922930: '(uint256, uint256, bool)',
1553380145: '(uint256, uint256, address)',
933920076: '(uint256, string, uint256)',
2970968351: '(uint256, string, string)',
1290643290: '(uint256, string, bool)',
2063255897: '(uint256, string, address)',
537493524: '(uint256, bool, uint256)',
2239189025: '(uint256, bool, string)',
544310864: '(uint256, bool, bool)',
889741179: '(uint256, bool, address)',
1520131797: '(uint256, address, uint256)',
1674265081: '(uint256, address, string)',
2607726658: '(uint256, address, bool)',
3170737120: '(uint256, address, address)',
3393701099: '(string, uint256, uint256)',
1500569737: '(string, uint256, string)',
3396809649: '(string, uint256, bool)',
478069832: '(string, uint256, address)',
1478619041: '(string, string, uint256)',
3378075862: '(string, bool, uint256)',
220641573: '(string, address, uint256)',
923808615: '(bool, uint256, uint256)',
3288086896: '(bool, uint256, string)',
3906927529: '(bool, uint256, bool)',
143587794: '(bool, uint256, address)',
278130193: '(bool, string, uint256)',
317855234: '(bool, bool, uint256)',
1601936123: '(bool, address, uint256)',
3063663350: '(address, uint256, uint256)',
2717051050: '(address, uint256, string)',
1736575400: '(address, uint256, bool)',
2076235848: '(address, uint256, address)',
1742565361: '(address, string, uint256)',
2622462459: '(address, bool, uint256)',
402547077: '(address, address, uint256)',
423606272: '(uint256, uint256, uint256, uint256)',
1506790371: '(uint256, uint256, uint256, string)',
4202792367: '(uint256, uint256, uint256, address)',
1570936811: '(uint256, uint256, string, uint256)',
668512210: '(uint256, uint256, string, string)',
2062986021: '(uint256, uint256, string, bool)',
1121066423: '(uint256, uint256, string, address)',
3950997458: '(uint256, uint256, bool, uint256)',
2780101785: '(uint256, uint256, bool, string)',
2869451494: '(uint256, uint256, bool, bool)',
2592172675: '(uint256, uint256, bool, address)',
2297881778: '(uint256, uint256, address, uint256)',
1826504888: '(uint256, uint256, address, string)',
365610102: '(uint256, uint256, address, bool)',
1453707697: '(uint256, uint256, address, address)',
2193775476: '(uint256, string, uint256, uint256)',
3082360010: '(uint256, string, uint256, string)',
1763348340: '(uint256, string, uint256, bool)',
992115124: '(uint256, string, uint256, address)',
2955463101: '(uint256, string, string, uint256)',
564987523: '(uint256, string, string, string)',
3014047421: '(uint256, string, string, bool)',
3582182914: '(uint256, string, string, address)',
3472922752: '(uint256, string, bool, uint256)',
3537118157: '(uint256, string, bool, string)',
3126025628: '(uint256, string, bool, bool)',
2922300801: '(uint256, string, bool, address)',
3906142605: '(uint256, string, address, uint256)',
2621104033: '(uint256, string, address, string)',
2428701270: '(uint256, string, address, bool)',
1634266465: '(uint256, string, address, address)',
3333212072: '(uint256, bool, uint256, uint256)',
3724797812: '(uint256, bool, uint256, string)',
2443193898: '(uint256, bool, uint256, bool)',
2295029825: '(uint256, bool, uint256, address)',
740099910: '(uint256, bool, string, uint256)',
1757984957: '(uint256, bool, string, string)',
3952250239: '(uint256, bool, string, bool)',
4015165464: '(uint256, bool, string, address)',
1952763427: '(uint256, bool, bool, uint256)',
3722155361: '(uint256, bool, bool, string)',
3069540257: '(uint256, bool, bool, bool)',
1768164185: '(uint256, bool, bool, address)',
125994997: '(uint256, bool, address, uint256)',
2917159623: '(uint256, bool, address, string)',
1162695845: '(uint256, bool, address, bool)',
2716814523: '(uint256, bool, address, address)',
211605953: '(uint256, address, uint256, uint256)',
3719324961: '(uint256, address, uint256, string)',
1601452668: '(uint256, address, uint256, bool)',
364980149: '(uint256, address, uint256, address)',
1182952285: '(uint256, address, string, uint256)',
1041403043: '(uint256, address, string, string)',
3425872647: '(uint256, address, string, bool)',
2629472255: '(uint256, address, string, address)',
1522374954: '(uint256, address, bool, uint256)',
2432370346: '(uint256, address, bool, string)',
3813741583: '(uint256, address, bool, bool)',
4017276179: '(uint256, address, bool, address)',
1936653238: '(uint256, address, address, uint256)',
52195187: '(uint256, address, address, string)',
153090805: '(uint256, address, address, bool)',
612938772: '(uint256, address, address, address)',
2812835923: '(string, uint256, uint256, uint256)',
2236298390: '(string, uint256, uint256, string)',
1982258066: '(string, uint256, uint256, bool)',
3793609336: '(string, uint256, uint256, address)',
3330189777: '(string, uint256, string, uint256)',
1522028063: '(string, uint256, string, string)',
2099530013: '(string, uint256, string, bool)',
2084975268: '(string, uint256, string, address)',
3827003247: '(string, uint256, bool, uint256)',
2885106328: '(string, uint256, bool, string)',
894187222: '(string, uint256, bool, bool)',
3773389720: '(string, uint256, bool, address)',
1325727174: '(string, uint256, address, uint256)',
2684039059: '(string, uint256, address, string)',
2182163010: '(string, uint256, address, bool)',
1587722158: '(string, uint256, address, address)',
4099767596: '(string, string, uint256, uint256)',
1562023706: '(string, string, uint256, string)',
3282609748: '(string, string, uint256, bool)',
270792626: '(string, string, uint256, address)',
2393878571: '(string, string, string, uint256)',
3601791698: '(string, string, bool, uint256)',
2093204999: '(string, string, address, uint256)',
1689631591: '(string, bool, uint256, uint256)',
1949134567: '(string, bool, uint256, string)',
2331496330: '(string, bool, uint256, bool)',
2472413631: '(string, bool, uint256, address)',
620303461: '(string, bool, string, uint256)',
2386524329: '(string, bool, bool, uint256)',
1560853253: '(string, bool, address, uint256)',
4176812830: '(string, address, uint256, uint256)',
1514632754: '(string, address, uint256, string)',
4232594928: '(string, address, uint256, bool)',
1677429701: '(string, address, uint256, address)',
2446397742: '(string, address, string, uint256)',
1050642026: '(string, address, bool, uint256)',
2398352281: '(string, address, address, uint256)',
927708338: '(bool, uint256, uint256, uint256)',
2389310301: '(bool, uint256, uint256, string)',
3197649747: '(bool, uint256, uint256, bool)',
14518201: '(bool, uint256, uint256, address)',
1779538402: '(bool, uint256, string, uint256)',
4122747465: '(bool, uint256, string, string)',
3857124139: '(bool, uint256, string, bool)',
4275904511: '(bool, uint256, string, address)',
2437143473: '(bool, uint256, bool, string)',
3468031191: '(bool, uint256, bool, bool)',
2597139990: '(bool, uint256, bool, address)',
355982471: '(bool, uint256, address, uint256)',
464760986: '(bool, uint256, address, string)',
3032683775: '(bool, uint256, address, bool)',
653615272: '(bool, uint256, address, address)',
679886795: '(bool, string, uint256, uint256)',
450457062: '(bool, string, uint256, string)',
1796103507: '(bool, string, uint256, bool)',
362193358: '(bool, string, uint256, address)',
2078327787: '(bool, string, string, uint256)',
369533843: '(bool, string, bool, uint256)',
196087467: '(bool, bool, uint256, uint256)',
2111099104: '(bool, bool, uint256, string)',
1637764366: '(bool, bool, uint256, bool)',
1420274080: '(bool, bool, uint256, address)',
3819555375: '(bool, bool, string, uint256)',
1836074433: '(bool, bool, bool, uint256)',
1276263767: '(bool, bool, address, uint256)',
2079424929: '(bool, address, uint256, uint256)',
1374724088: '(bool, address, uint256, string)',
3590430492: '(bool, address, uint256, bool)',
325780957: '(bool, address, uint256, address)',
3256837319: '(bool, address, string, uint256)',
126031106: '(bool, address, bool, uint256)',
208064958: '(bool, address, address, uint256)',
888202806: '(address, uint256, uint256, uint256)',
1244184599: '(address, uint256, uint256, string)',
1727118439: '(address, uint256, uint256, bool)',
551786573: '(address, uint256, uint256, address)',
3204577425: '(address, uint256, string, uint256)',
2292761606: '(address, uint256, string, string)',
3474460764: '(address, uint256, string, bool)',
1547898183: '(address, uint256, string, address)',
586594713: '(address, uint256, bool, uint256)',
3316483577: '(address, uint256, bool, string)',
1005970743: '(address, uint256, bool, bool)',
2736520652: '(address, uint256, bool, address)',
269444366: '(address, uint256, address, uint256)',
497649386: '(address, uint256, address, string)',
2713504179: '(address, uint256, address, bool)',
1200430178: '(address, uint256, address, address)',
499704248: '(address, string, uint256, uint256)',
1149776040: '(address, string, uint256, string)',
251125840: '(address, string, uint256, bool)',
1662531192: '(address, string, uint256, address)',
362776871: '(address, string, string, uint256)',
1365129398: '(address, string, bool, uint256)',
1166009295: '(address, string, address, uint256)',
946861556: '(address, bool, uint256, uint256)',
178704301: '(address, bool, uint256, string)',
3294903840: '(address, bool, uint256, bool)',
3438776481: '(address, bool, uint256, address)',
2162598411: '(address, bool, string, uint256)',
2353946086: '(address, bool, bool, uint256)',
2807847390: '(address, bool, address, uint256)',
3193255041: '(address, address, uint256, uint256)',
4256496016: '(address, address, uint256, string)',
2604815586: '(address, address, uint256, bool)',
2376523509: '(address, address, uint256, address)',
4011651047: '(address, address, string, uint256)',
963766156: '(address, address, bool, uint256)',
2485456247: '(address, address, address, uint256)',
}

@ -3,3 +3,4 @@ export * from './lib/bleach'
export * from './lib/helper-components'
export * from './lib/components/PluginViewWrapper'
export * from './lib/components/custom-dropdown'
export * from './lib/components/custom-tooltip'

@ -0,0 +1,23 @@
import React from 'react';
import { Fragment } from 'react';
import { OverlayTrigger, Tooltip } from 'react-bootstrap';
import { CustomTooltipType } from '../../types/customtooltip'
export function CustomTooltip({ children, placement, tooltipId, tooltipClasses, tooltipText }: CustomTooltipType) {
return (
<Fragment>
<OverlayTrigger
placement={placement}
overlay={
<Tooltip id={!tooltipId ? `${tooltipText}Tooltip` : tooltipId} className={tooltipClasses}>
<span>{tooltipText}</span>
</Tooltip>
}
>
{children}
</OverlayTrigger>
</Fragment>
)
}

@ -123,3 +123,8 @@ export const shortenHexData = (data) => {
const len = data.length
return data.slice(0, 5) + '...' + data.slice(len - 5, len)
}
export const addSlash = (file: string) => {
if (!file.startsWith('/'))file = '/' + file
return file
}

@ -0,0 +1,10 @@
import { Placement } from 'react-bootstrap/esm/Overlay'
import { OverlayTriggerRenderProps } from 'react-bootstrap/esm/OverlayTrigger'
export type CustomTooltipType = {
children: React.ReactElement<any, string | React.JSXElementConstructor<any>> | ((props: OverlayTriggerRenderProps) => React.ReactNode),
placement?: Placement,
tooltipId?: string,
tooltipClasses?:string,
tooltipText: string
}

@ -4,6 +4,7 @@ import { FormattedMessage } from 'react-intl'
import { PluginRecord } from '../types'
import './panel.css'
import { OverlayTrigger, Tooltip } from 'react-bootstrap'
import { CustomTooltip } from '@remix-ui/helper'
export interface RemixPanelProps {
plugins: Record<string, PluginRecord>;
@ -26,10 +27,14 @@ const RemixUIPanelHeader = (props: RemixPanelProps) => {
setToggleExpander(!toggleExpander)
}
const tooltipChild = (
<i className={`px-1 ml-2 pt-1 pb-2 ${!toggleExpander ? 'fas fa-angle-right' : 'fas fa-angle-down bg-light'}`} aria-hidden="true"></i>
)
return (
<header className='d-flex flex-column'>
<div className="swapitHeader px-3 pt-2 pb-0 d-flex flex-row">
<h6 className="mb-3" data-id='sidePanelSwapitTitle'>
<h6 className="pt-0 mb-1" data-id='sidePanelSwapitTitle'>
<FormattedMessage id={plugin?.profile.name + '.displayName'} defaultMessage={plugin?.profile.displayName || plugin?.profile.name} />
</h6>
<div className="d-flex flex-row">
@ -47,17 +52,19 @@ const RemixUIPanelHeader = (props: RemixPanelProps) => {
</OverlayTrigger>
)}
</div>
<OverlayTrigger overlay={
<Tooltip className="text-nowrap" id="pluginInfoTooltip">
<span>Plugin info</span>
</Tooltip>
} placement={'right-end'}>
<div className="swapitHeaderInfoSection d-flex justify-content-between" data-id='swapitHeaderInfoSectionId' onClick={toggleClass}>
<i className={`px-2 ml-2 pt-1 pb-4 ${!toggleExpander ? 'fas fa-angle-right' : 'fas fa-angle-down bg-light'}`} aria-hidden="true"></i>
<CustomTooltip
placement="right-end"
tooltipText="Plugin info"
tooltipId="pluginInfoTooltip"
tooltipClasses="text-nowrap"
>
{tooltipChild}
</CustomTooltip>
</div>
</OverlayTrigger>
</div>
</div>
<div className="d-flex w-100 flex-row py-2"></div>
<div className={`bg-light mx-3 mb-2 p-3 pt-1 border-bottom flex-column ${toggleExpander ? "d-flex" : "d-none"}`}>
{plugin?.profile?.author && <span className="d-flex flex-row align-items-center">
<label className="mb-0 pr-2"><FormattedMessage id='panel.author' defaultMessage='Author' />:</label>

@ -53,6 +53,7 @@ export const initWorkspace = (filePanelPlugin) => async (reducerDispatch: React.
const params = queryParams.get() as UrlParametersType
const workspaces = await getWorkspaces() || []
dispatch(setWorkspaces(workspaces))
// console.log('workspaces: ', workspaces)
if (params.gist) {
await createWorkspaceTemplate('gist-sample', 'gist-template')
plugin.setWorkspace({ name: 'gist-sample', isLocalhost: false })

@ -126,7 +126,7 @@ export const createWorkspaceRequest = (promise: Promise<any>) => {
}
}
export const createWorkspaceSuccess = (workspaceName: { name: string; isGitRepo: boolean; }) => {
export const createWorkspaceSuccess = (workspaceName: { name: string; isGitRepo: boolean; branches?: { remote: any; name: string; }[], currentBranch?: string }) => {
return {
type: 'CREATE_WORKSPACE_SUCCESS',
payload: workspaceName
@ -264,3 +264,17 @@ export const cloneRepositoryFailed = () => {
type: 'CLONE_REPOSITORY_FAILED'
}
}
export const setCurrentWorkspaceBranches = (branches?: { remote: any, name: string }[]) => {
return {
type: 'SET_CURRENT_WORKSPACE_BRANCHES',
payload: branches
}
}
export const setCurrentWorkspaceCurrentBranch = (currentBranch?: string) => {
return {
type: 'SET_CURRENT_WORKSPACE_CURRENT_BRANCH',
payload: currentBranch
}
}

@ -1,13 +1,20 @@
import React from 'react'
import { bufferToHex, keccakFromString } from 'ethereumjs-util'
import axios, { AxiosResponse } from 'axios'
import { addInputFieldSuccess, cloneRepositoryFailed, cloneRepositoryRequest, cloneRepositorySuccess, createWorkspaceError, createWorkspaceRequest, createWorkspaceSuccess, displayNotification, displayPopUp, fetchWorkspaceDirectoryError, fetchWorkspaceDirectoryRequest, fetchWorkspaceDirectorySuccess, hideNotification, setCurrentWorkspace, setDeleteWorkspace, setMode, setReadOnlyMode, setRenameWorkspace } from './payload'
import { checkSlash, checkSpecialChars } from '@remix-ui/helper'
import { addInputFieldSuccess, cloneRepositoryFailed, cloneRepositoryRequest, cloneRepositorySuccess, createWorkspaceError, createWorkspaceRequest, createWorkspaceSuccess, displayNotification, displayPopUp, fetchWorkspaceDirectoryError, fetchWorkspaceDirectoryRequest, fetchWorkspaceDirectorySuccess, hideNotification, setCurrentWorkspace, setCurrentWorkspaceBranches, setCurrentWorkspaceCurrentBranch, setDeleteWorkspace, setMode, setReadOnlyMode, setRenameWorkspace } from './payload'
import { addSlash, checkSlash, checkSpecialChars } from '@remix-ui/helper'
import { JSONStandardInput, WorkspaceTemplate } from '../types'
import { QueryParams } from '@remix-project/remix-lib'
import * as templateWithContent from '@remix-project/remix-ws-templates'
import { ROOT_PATH } from '../utils/constants'
// eslint-disable-next-line @nrwl/nx/enforce-module-boundaries
import { IndexedDBStorage } from '../../../../../../apps/remix-ide/src/app/files/filesystems/indexedDB'
import { getUncommittedFiles } from '../utils/gitStatusFilter'
declare global {
interface Window { remixFileSystemCallback: IndexedDBStorage; }
}
const LOCALHOST = ' - connect to localhost - '
@ -19,6 +26,13 @@ let plugin, dispatch: React.Dispatch<any>
export const setPlugin = (filePanelPlugin, reducerDispatch) => {
plugin = filePanelPlugin
dispatch = reducerDispatch
plugin.on('dGitProvider', 'checkout', async () => {
const currentBranch = await plugin.call('dGitProvider', 'currentbranch')
dispatch(setCurrentWorkspaceCurrentBranch(currentBranch))
})
plugin.on('dGitProvider', 'branch', async () => {
await refreshBranches()
})
}
export const addInputField = async (type: 'file' | 'folder', path: string, cb?: (err: Error, result?: string | number | boolean | Record<string, any>) => void) => {
@ -54,13 +68,13 @@ export const createWorkspace = async (workspaceName: string, workspaceTemplateNa
await plugin.workspaceCreated(workspaceName)
if (isGitRepo) {
await plugin.call('dGitProvider', 'init')
await plugin.call('dGitProvider', 'init', { branch: 'main' })
dispatch(setCurrentWorkspaceCurrentBranch('main'))
const isActive = await plugin.call('manager', 'isActive', 'dgit')
if (!isActive) await plugin.call('manager', 'activatePlugin', 'dgit')
}
if (!isEmpty) await loadWorkspacePreset(workspaceTemplateName, opts)
cb && cb(null, workspaceName)
}).catch((error) => {
dispatch(createWorkspaceError({ error }))
@ -265,6 +279,11 @@ export const switchToWorkspace = async (name: string) => {
await plugin.setWorkspace({ name, isLocalhost: false })
const isGitRepo = await plugin.fileManager.isGitRepo()
if (isGitRepo) {
const isActive = await plugin.call('manager', 'isActive', 'dgit')
if (!isActive) await plugin.call('manager', 'activatePlugin', 'dgit')
}
dispatch(setMode('browser'))
dispatch(setCurrentWorkspace({ name, isGitRepo }))
dispatch(setReadOnlyMode(false))
@ -313,9 +332,9 @@ export const uploadFile = async (target, targetFolder: string, cb?: (err: Error,
})
}
export const getWorkspaces = async (): Promise<{name: string, isGitRepo: boolean}[]> | undefined => {
export const getWorkspaces = async (): Promise<{name: string, isGitRepo: boolean, branches?: { remote: any; name: string; }[], currentBranch?: string }[]> | undefined => {
try {
const workspaces: {name: string, isGitRepo: boolean}[] = await new Promise((resolve, reject) => {
const workspaces: {name: string, isGitRepo: boolean, branches?: { remote: any; name: string; }[], currentBranch?: string}[] = await new Promise((resolve, reject) => {
const workspacesPath = plugin.fileProviders.workspace.workspacesPath
plugin.fileProviders.browser.resolveDirectory('/' + workspacesPath, (error, items) => {
@ -326,10 +345,25 @@ export const getWorkspaces = async (): Promise<{name: string, isGitRepo: boolean
.filter((item) => items[item].isDirectory)
.map(async (folder) => {
const isGitRepo: boolean = await plugin.fileProviders.browser.exists('/' + folder + '/.git')
if (isGitRepo) {
let branches = []
let currentBranch = null
branches = await getGitRepoBranches(folder)
currentBranch = await getGitRepoCurrentBranch(folder)
return {
name: folder.replace(workspacesPath + '/', ''),
isGitRepo,
branches,
currentBranch
}
} else {
return {
name: folder.replace(workspacesPath + '/', ''),
isGitRepo
}
}
})).then(workspacesList => resolve(workspacesList))
})
})
@ -355,6 +389,13 @@ export const cloneRepository = async (url: string) => {
if (!isActive) await plugin.call('manager', 'activatePlugin', 'dgit')
await fetchWorkspaceDirectory(ROOT_PATH)
const workspacesPath = plugin.fileProviders.workspace.workspacesPath
const branches = await getGitRepoBranches(workspacesPath + '/' + repoName)
dispatch(setCurrentWorkspaceBranches(branches))
const currentBranch = await getGitRepoCurrentBranch(workspacesPath + '/' + repoName)
dispatch(setCurrentWorkspaceCurrentBranch(currentBranch))
dispatch(cloneRepositorySuccess())
}).catch(() => {
const cloneModal = {
@ -397,3 +438,153 @@ export const getRepositoryTitle = async (url: string) => {
return name + counter
}
export const getGitRepoBranches = async (workspacePath: string) => {
const gitConfig: { fs: IndexedDBStorage, dir: string } = {
fs: window.remixFileSystemCallback,
dir: addSlash(workspacePath)
}
const branches: { remote: any; name: string; }[] = await plugin.call('dGitProvider', 'branches', { ...gitConfig })
return branches
}
export const getGitRepoCurrentBranch = async (workspaceName: string) => {
const gitConfig: { fs: IndexedDBStorage, dir: string } = {
fs: window.remixFileSystemCallback,
dir: addSlash(workspaceName)
}
const currentBranch: string = await plugin.call('dGitProvider', 'currentbranch', { ...gitConfig })
return currentBranch
}
export const showAllBranches = async () => {
const isActive = await plugin.call('manager', 'isActive', 'dgit')
if (!isActive) await plugin.call('manager', 'activatePlugin', 'dgit')
plugin.call('menuicons', 'select', 'dgit')
}
const refreshBranches = async () => {
const workspacesPath = plugin.fileProviders.workspace.workspacesPath
const workspaceName = plugin.fileProviders.workspace.workspace
const branches = await getGitRepoBranches(workspacesPath + '/' + workspaceName)
dispatch(setCurrentWorkspaceBranches(branches))
}
export const switchBranch = async (branch: string) => {
await plugin.call('fileManager', 'closeAllFiles')
const localChanges = await hasLocalChanges()
if (Array.isArray(localChanges) && localChanges.length > 0) {
const cloneModal = {
id: 'switchBranch',
title: 'Switch Git Branch',
message: `Your local changes to the following files would be overwritten by checkout.\n
${localChanges.join('\n')}\n
Do you want to continue?`,
modalType: 'modal',
okLabel: 'Force Checkout',
okFn: async () => {
dispatch(cloneRepositoryRequest())
plugin.call('dGitProvider', 'checkout', { ref: branch, force: true }, false).then(async () => {
await fetchWorkspaceDirectory(ROOT_PATH)
dispatch(setCurrentWorkspaceCurrentBranch(branch))
dispatch(cloneRepositorySuccess())
}).catch(() => {
dispatch(cloneRepositoryFailed())
})
},
cancelLabel: 'Cancel',
cancelFn: () => {},
hideFn: () => {}
}
plugin.call('notification', 'modal', cloneModal)
} else {
dispatch(cloneRepositoryRequest())
plugin.call('dGitProvider', 'checkout', { ref: branch, force: true }, false).then(async () => {
await fetchWorkspaceDirectory(ROOT_PATH)
dispatch(setCurrentWorkspaceCurrentBranch(branch))
dispatch(cloneRepositorySuccess())
}).catch(() => {
dispatch(cloneRepositoryFailed())
})
}
}
export const createNewBranch = async (branch: string) => {
const promise = plugin.call('dGitProvider', 'branch', { ref: branch, checkout: true }, false)
dispatch(cloneRepositoryRequest())
promise.then(async () => {
await fetchWorkspaceDirectory(ROOT_PATH)
dispatch(setCurrentWorkspaceCurrentBranch(branch))
const workspacesPath = plugin.fileProviders.workspace.workspacesPath
const workspaceName = plugin.fileProviders.workspace.workspace
const branches = await getGitRepoBranches(workspacesPath + '/' + workspaceName)
dispatch(setCurrentWorkspaceBranches(branches))
dispatch(cloneRepositorySuccess())
}).catch(() => {
dispatch(cloneRepositoryFailed())
})
return promise
}
export const checkoutRemoteBranch = async (branch: string, remote: string) => {
const localChanges = await hasLocalChanges()
if (Array.isArray(localChanges) && localChanges.length > 0) {
const cloneModal = {
id: 'checkoutRemoteBranch',
title: 'Checkout Remote Branch',
message: `Your local changes to the following files would be overwritten by checkout.\n
${localChanges.join('\n')}\n
Do you want to continue?`,
modalType: 'modal',
okLabel: 'Force Checkout',
okFn: async () => {
dispatch(cloneRepositoryRequest())
plugin.call('dGitProvider', 'checkout', { ref: branch, remote, force: true }, false).then(async () => {
await fetchWorkspaceDirectory(ROOT_PATH)
dispatch(setCurrentWorkspaceCurrentBranch(branch))
const workspacesPath = plugin.fileProviders.workspace.workspacesPath
const workspaceName = plugin.fileProviders.workspace.workspace
const branches = await getGitRepoBranches(workspacesPath + '/' + workspaceName)
dispatch(setCurrentWorkspaceBranches(branches))
dispatch(cloneRepositorySuccess())
}).catch(() => {
dispatch(cloneRepositoryFailed())
})
},
cancelLabel: 'Cancel',
cancelFn: () => {},
hideFn: () => {}
}
plugin.call('notification', 'modal', cloneModal)
} else {
dispatch(cloneRepositoryRequest())
plugin.call('dGitProvider', 'checkout', { ref: branch, remote, force: true }, false).then(async () => {
await fetchWorkspaceDirectory(ROOT_PATH)
dispatch(setCurrentWorkspaceCurrentBranch(branch))
const workspacesPath = plugin.fileProviders.workspace.workspacesPath
const workspaceName = plugin.fileProviders.workspace.workspace
const branches = await getGitRepoBranches(workspacesPath + '/' + workspaceName)
dispatch(setCurrentWorkspaceBranches(branches))
dispatch(cloneRepositorySuccess())
}).catch(() => {
dispatch(cloneRepositoryFailed())
})
}
}
export const hasLocalChanges = async () => {
const filesStatus = await plugin.call('dGitProvider', 'status')
const uncommittedFiles = getUncommittedFiles(filesStatus)
return uncommittedFiles
}

@ -32,6 +32,10 @@ export const FileSystemContext = createContext<{
dispatchHandleRestoreBackup: () => Promise<void>
dispatchCloneRepository: (url: string) => Promise<void>,
dispatchMoveFile: (src: string, dest: string) => Promise<void>,
dispatchMoveFolder: (src: string, dest: string) => Promise<void>
dispatchMoveFolder: (src: string, dest: string) => Promise<void>,
dispatchShowAllBranches: () => Promise<void>,
dispatchSwitchToBranch: (branch: string) => Promise<void>,
dispatchCreateNewBranch: (branch: string) => Promise<void>,
dispatchCheckoutRemoteBranch: (branch: string, remote: string) => Promise<void>
}>(null)

@ -84,6 +84,7 @@
border-radius: .25rem;
background: var(--custom-select);
}
.custom-dropdown-items a {
border-radius: .25rem;
text-transform: none;
@ -133,3 +134,7 @@
color: var(--text)
}
.checkout-input {
font-size: 10px !important;
}

@ -7,7 +7,9 @@ import { FileSystemContext } from '../contexts'
import { browserReducer, browserInitialState } from '../reducers/workspace'
import { initWorkspace, fetchDirectory, removeInputField, deleteWorkspace, clearPopUp, publishToGist, createNewFile, setFocusElement, createNewFolder,
deletePath, renamePath, copyFile, copyFolder, runScript, emitContextMenuEvent, handleClickFile, handleExpandPath, addInputField, createWorkspace,
fetchWorkspaceDirectory, renameWorkspace, switchToWorkspace, uploadFile, handleDownloadFiles, restoreBackupZip, cloneRepository, moveFile, moveFolder } from '../actions'
fetchWorkspaceDirectory, renameWorkspace, switchToWorkspace, uploadFile, handleDownloadFiles, restoreBackupZip, cloneRepository, moveFile, moveFolder,
showAllBranches, switchBranch, createNewBranch, checkoutRemoteBranch
} from '../actions'
import { Modal, WorkspaceProps, WorkspaceTemplate } from '../types'
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import { Workspace } from '../remix-ui-workspace'
@ -137,6 +139,22 @@ export const FileSystemProvider = (props: WorkspaceProps) => {
await moveFolder(src, dest)
}
const dispatchShowAllBranches = async () => {
await showAllBranches()
}
const dispatchSwitchToBranch = async (branch: string) => {
await switchBranch(branch)
}
const dispatchCreateNewBranch = async (branch: string) => {
await createNewBranch(branch)
}
const dispatchCheckoutRemoteBranch = async (branch: string, remote: string) => {
await checkoutRemoteBranch(branch, remote)
}
useEffect(() => {
dispatchInitWorkspace()
}, [])
@ -241,7 +259,11 @@ export const FileSystemProvider = (props: WorkspaceProps) => {
dispatchHandleRestoreBackup,
dispatchCloneRepository,
dispatchMoveFile,
dispatchMoveFolder
dispatchMoveFolder,
dispatchShowAllBranches,
dispatchSwitchToBranch,
dispatchCreateNewBranch,
dispatchCheckoutRemoteBranch
}
return (
<FileSystemContext.Provider value={value}>

@ -13,6 +13,11 @@ export interface BrowserState {
workspaces: {
name: string;
isGitRepo: boolean;
branches?: {
remote: any;
name: string;
}[],
currentBranch?: string
}[],
files: { [x: string]: Record<string, FileType> },
expandPath: string[]
@ -117,7 +122,7 @@ export const browserInitialState: BrowserState = {
export const browserReducer = (state = browserInitialState, action: Action) => {
switch (action.type) {
case 'SET_CURRENT_WORKSPACE': {
const payload = action.payload as { name: string; isGitRepo: boolean; }
const payload = action.payload as { name: string; isGitRepo: boolean; branches?: { remote: any; name: string; }[], currentBranch?: string }
const workspaces = state.browser.workspaces.find(({ name }) => name === payload.name) ? state.browser.workspaces : [...state.browser.workspaces, action.payload]
return {
@ -131,7 +136,8 @@ export const browserReducer = (state = browserInitialState, action: Action) => {
}
case 'SET_WORKSPACES': {
const payload = action.payload as { name: string; isGitRepo: boolean; }[]
console.log('called SET_WORKSPACES')
const payload = action.payload as { name: string; isGitRepo: boolean; branches?: { remote: any; name: string; }[], currentBranch?: string }[]
return {
...state,
@ -429,7 +435,7 @@ export const browserReducer = (state = browserInitialState, action: Action) => {
}
case 'CREATE_WORKSPACE_SUCCESS': {
const payload = action.payload as { name: string; isGitRepo: boolean; }
const payload = action.payload as { name: string; isGitRepo: boolean; branches?: { remote: any; name: string; }[], currentBranch?: string }
const workspaces = state.browser.workspaces.find(({ name }) => name === payload.name) ? state.browser.workspaces : [...state.browser.workspaces, action.payload]
return {
@ -460,13 +466,15 @@ export const browserReducer = (state = browserInitialState, action: Action) => {
case 'RENAME_WORKSPACE': {
const payload = action.payload as { oldName: string, workspaceName: string }
let renamedWorkspace
const workspaces = state.browser.workspaces.filter(({ name, isGitRepo }) => {
const workspaces = state.browser.workspaces.filter(({ name, isGitRepo, branches, currentBranch }) => {
if (name && (name !== payload.oldName)) {
return true
} else {
renamedWorkspace = {
name: payload.workspaceName,
isGitRepo
isGitRepo,
branches,
currentBranch
}
return false
}
@ -666,6 +674,36 @@ export const browserReducer = (state = browserInitialState, action: Action) => {
}
}
case 'SET_CURRENT_WORKSPACE_BRANCHES': {
const payload: { remote: any, name: string }[] = action.payload
return {
...state,
browser: {
...state.browser,
workspaces: state.browser.workspaces.map((workspace) => {
if (workspace.name === state.browser.currentWorkspace) workspace.branches = payload
return workspace
})
}
}
}
case 'SET_CURRENT_WORKSPACE_CURRENT_BRANCH': {
const payload: string = action.payload
return {
...state,
browser: {
...state.browser,
workspaces: state.browser.workspaces.map((workspace) => {
if (workspace.name === state.browser.currentWorkspace) workspace.currentBranch = payload
return workspace
})
}
}
}
default:
throw new Error()
}

@ -1,4 +1,4 @@
import React, { useState, useEffect, useRef, useContext, SyntheticEvent } from 'react' // eslint-disable-line
import React, { useState, useEffect, useRef, useContext, SyntheticEvent, ChangeEvent, KeyboardEvent } from 'react' // eslint-disable-line
import { FormattedMessage, useIntl } from 'react-intl'
import { Dropdown, OverlayTrigger, Tooltip } from 'react-bootstrap'
import { CustomIconsToggle, CustomMenu, CustomToggle } from '@remix-ui/helper'
@ -14,9 +14,11 @@ export function Workspace () {
const LOCALHOST = ' - connect to localhost - '
const NO_WORKSPACE = ' - none - '
const [currentWorkspace, setCurrentWorkspace] = useState<string>(NO_WORKSPACE)
const [selectedWorkspace, setSelectedWorkspace] = useState<{ name: string, isGitRepo: boolean}>(null)
const [selectedWorkspace, setSelectedWorkspace] = useState<{ name: string, isGitRepo: boolean, branches?: { remote: any; name: string; }[], currentBranch?: string }>(null)
const [showDropdown, setShowDropdown] = useState<boolean>(false)
const [showIconsMenu, hideIconsMenu] = useState<boolean>(false)
const [showBranches, setShowBranches] = useState<boolean>(false)
const [branchFilter, setBranchFilter] = useState<string>('')
const displayOzCustomRef = useRef<HTMLDivElement>()
const mintableCheckboxRef = useRef()
const burnableCheckboxRef = useRef()
@ -30,6 +32,8 @@ export function Workspace () {
const intl = useIntl()
const cloneUrlRef = useRef<HTMLInputElement>()
const initGitRepoRef = useRef<HTMLInputElement>()
const filteredBranches = selectedWorkspace ? (selectedWorkspace.branches || []).filter(branch => branch.name.includes(branchFilter) && branch.name !== 'HEAD').slice(0, 20) : []
const currentBranch = selectedWorkspace ? selectedWorkspace.currentBranch : null
useEffect(() => {
let workspaceName = localStorage.getItem('currentWorkspace')
@ -213,6 +217,41 @@ export function Workspace () {
workspaceCreateInput.current.value = `${workspaceCreateTemplateInput.current.value + '_upgradeable'}_${Date.now()}`
}
const toggleBranches = (isOpen: boolean) => {
setShowBranches(isOpen)
}
const handleBranchFilterChange = (e: ChangeEvent<HTMLInputElement>) => {
const branchFilter = e.target.value
setBranchFilter(branchFilter)
}
const showAllBranches = () => {
global.dispatchShowAllBranches()
}
const switchToBranch = async (branch: { remote: string, name: string }) => {
try {
if (branch.remote) {
await global.dispatchCheckoutRemoteBranch(branch.name, branch.remote)
} else {
await global.dispatchSwitchToBranch(branch.name)
}
} catch (e) {
console.error(e)
global.modal('Checkout Git Branch', e.message, 'OK', () => {})
}
}
const switchToNewBranch = async () => {
try {
await global.dispatchCreateNewBranch(branchFilter)
} catch (e) {
global.modal('Checkout Git Branch', e.message, 'OK', () => {})
}
}
const createModalMessage = () => {
return (
<>
@ -515,7 +554,8 @@ export function Workspace () {
]
return (
<div className='remixui_container'>
<div className='d-flex flex-column justify-content-between h-100'>
<div className='remixui_container overflow-auto' style={{ maxHeight: selectedWorkspace && selectedWorkspace.isGitRepo ? '95%' : '100%' }}>
<div className='d-flex flex-column w-100 remixui_fileexplorer' data-id="remixUIWorkspaceExplorer" onClick={resetFocus}>
<div>
<header>
@ -531,7 +571,7 @@ export function Workspace () {
placement="top-end"
overlay={
<Tooltip id="createWorkspaceTooltip" className="text-nowrap">
<span><FormattedMessage id='filePanel.create' defaultMessage='create' /></span>
<span><FormattedMessage id='filePanel.create' defaultMessage='Create' /></span>
</Tooltip>
}
>
@ -607,6 +647,7 @@ export function Workspace () {
</Dropdown.Item>
))
}
<Dropdown.Item onClick={() => { switchWorkspace(LOCALHOST) }}>{currentWorkspace === LOCALHOST ? <span>&#10003; localhost </span> : <span className="pl-3"> { LOCALHOST } </span>}</Dropdown.Item>
{ ((global.fs.browser.workspaces.length <= 0) || currentWorkspace === NO_WORKSPACE) && <Dropdown.Item onClick={() => { switchWorkspace(NO_WORKSPACE) }}>{ <span className="pl-3">NO_WORKSPACE</span> }</Dropdown.Item> }
</Dropdown.Menu>
</Dropdown>
@ -698,6 +739,61 @@ export function Workspace () {
</div>
</div>
</div>
{
selectedWorkspace &&
<div className={`bg-light border-top ${selectedWorkspace.isGitRepo ? 'd-block' : 'd-none'}`}>
<div className='d-flex justify-space-between p-1'>
<div className="mr-auto text-uppercase text-dark pt-2 pl-2">GIT</div>
<div className="pt-1 mr-1">
<Dropdown style={{ height: 30, minWidth: 80 }} onToggle={toggleBranches} show={showBranches} drop={'up'}>
<Dropdown.Toggle as={CustomToggle} id="dropdown-custom-components" className="btn btn-light btn-block w-100 d-inline-block border border-dark form-control h-100 p-0 pl-2 pr-2 text-dark" icon={null}>
{ global.fs.browser.isRequestingCloning ? <i className="fad fa-spinner fa-spin"></i> : currentBranch || '-none-' }
</Dropdown.Toggle>
<Dropdown.Menu as={CustomMenu} className='custom-dropdown-items branches-dropdown' data-id="custom-dropdown-items">
<div className='d-flex text-dark' style={{ fontSize: 14, fontWeight: 'bold' }}>
<span className='mt-2 ml-2 mr-auto'>Switch branches</span>
<div className='pt-2 pr-2' onClick={() => { toggleBranches(false) }}><i className='fa fa-close'></i>
</div>
</div>
<div className='border-top py-2'>
<input
className='form-control border checkout-input bg-light'
placeholder='Find or create a branch.'
style={{ minWidth: 225 }}
onChange={handleBranchFilterChange}
/>
</div>
<div className='border-top' style={{ maxHeight: 120, overflowY: 'scroll' }}>
{
filteredBranches.length > 0 ? filteredBranches.map((branch, index) => {
return (
<Dropdown.Item key={index} onClick={() => { switchToBranch(branch) }} title={branch.remote ? 'Checkout new branch from remote branch' : 'Checkout to local branch'}>
{
(currentBranch === branch.name) && !branch.remote ?
<span>&#10003; <i className='far fa-code-branch'></i><span className='pl-1'>{ branch.name }</span></span> :
<span className='pl-3'><i className={`far ${ branch.remote ? 'fa-cloud' : 'fa-code-branch'}`}></i><span className='pl-1'>{ branch.remote ? `${branch.remote}/${branch.name}` : branch.name }</span></span>
}
</Dropdown.Item>
)
}) :
<Dropdown.Item onClick={switchToNewBranch}>
<div className="pl-1 pr-1">
<i className="fas fa-code-branch pr-2"></i><span>Create branch: { branchFilter } from '{currentBranch}'</span>
</div>
</Dropdown.Item>
}
</div>
{
(selectedWorkspace.branches || []).length > 4 && <div className='text-center border-top pt-2'><a href='#' style={{ fontSize: 12 }} onClick={showAllBranches}>view all branches</a></div>
}
</Dropdown.Menu>
</Dropdown>
</div>
</div>
</div>
}
</div>
)
}

@ -0,0 +1,8 @@
const FILE = 0, HEAD = 1, WORKDIR = 2, STAGE = 3
export const getUncommittedFiles = (statusMatrix: Array<Array<string | number>>) => {
statusMatrix = statusMatrix.filter(row => (row[HEAD] !== row[WORKDIR]) || (row[HEAD] !== row[STAGE]))
const uncommitedFiles = statusMatrix.map(row => row[FILE])
return uncommitedFiles
}
Loading…
Cancel
Save