Merge branch 'master' into 4990-improve-multi-select-drag-and-drop-implementation

pull/4999/head
Joseph Izang 4 months ago committed by GitHub
commit f9bb4f8e6c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      apps/etherscan/src/app/utils/networks.ts
  2. 8
      apps/etherscan/src/app/views/CaptureKeyView.tsx
  3. 8
      apps/learneth/src/components/BackButton/index.tsx
  4. 4
      apps/learneth/src/pages/StepDetail/index.tsx
  5. 34
      apps/remix-ide-e2e/src/commands/hideMetaMaskPopup.ts
  6. 12
      apps/remix-ide-e2e/src/commands/setupMetamask.ts
  7. 55
      apps/remix-ide-e2e/src/tests/runAndDeploy_injected.test.ts
  8. 13
      apps/remix-ide-e2e/src/tests/url.test.ts
  9. 1
      apps/remix-ide-e2e/src/types/index.d.ts
  10. 4
      apps/remix-ide/src/app/plugins/git.tsx
  11. 10
      libs/remix-core-plugin/src/lib/compiler-content-imports.ts
  12. 4
      libs/remix-ui/editor/src/lib/providers/inlineCompletionProvider.ts
  13. 77
      libs/remix-ui/git/src/components/branchHeader.tsx
  14. 4
      libs/remix-ui/git/src/components/buttons/commitmessage.tsx
  15. 6
      libs/remix-ui/git/src/components/github/devicecode.tsx
  16. 4
      libs/remix-ui/git/src/components/github/repositoryselect.tsx
  17. 8
      libs/remix-ui/git/src/components/github/selectandclonerepositories.tsx
  18. 43
      libs/remix-ui/git/src/components/gitui.tsx
  19. 4
      libs/remix-ui/git/src/components/navigation/branches.tsx
  20. 4
      libs/remix-ui/git/src/components/navigation/clone.tsx
  21. 9
      libs/remix-ui/git/src/components/navigation/commands.tsx
  22. 5
      libs/remix-ui/git/src/components/navigation/commits.tsx
  23. 14
      libs/remix-ui/git/src/components/navigation/github.tsx
  24. 4
      libs/remix-ui/git/src/components/navigation/log.tsx
  25. 4
      libs/remix-ui/git/src/components/navigation/remotes.tsx
  26. 4
      libs/remix-ui/git/src/components/navigation/settings.tsx
  27. 9
      libs/remix-ui/git/src/components/navigation/sourcecontrol.tsx
  28. 6
      libs/remix-ui/git/src/components/navigation/sourcecontrolgroup.tsx
  29. 10
      libs/remix-ui/git/src/components/panels/branches.tsx
  30. 32
      libs/remix-ui/git/src/components/panels/clone.tsx
  31. 6
      libs/remix-ui/git/src/components/panels/commands.tsx
  32. 6
      libs/remix-ui/git/src/components/panels/commands/fetch.tsx
  33. 14
      libs/remix-ui/git/src/components/panels/commands/pushpull.tsx
  34. 8
      libs/remix-ui/git/src/components/panels/githubcredentials.tsx
  35. 31
      libs/remix-ui/git/src/components/panels/remotes.tsx
  36. 19
      libs/remix-ui/git/src/components/panels/remotesimport.tsx
  37. 2
      libs/remix-ui/git/src/components/panels/sourcecontrol/sourcecontrolgroup.tsx
  38. 12
      libs/remix-ui/git/src/components/panels/sourcecontrol/sourcecontrolitem.tsx
  39. 4
      libs/remix-ui/git/src/components/panels/sourcontrol.tsx
  40. 11
      libs/remix-ui/git/src/components/panels/tokenWarning.tsx
  41. 11
      libs/remix-ui/git/src/lib/gitactions.ts
  42. 53
      libs/remix-ui/git/src/lib/listeners.ts
  43. 3
      libs/remix-ui/git/src/state/actions.ts
  44. 9
      libs/remix-ui/git/src/state/gitpayload.ts
  45. 8
      libs/remix-ui/git/src/state/gitreducer.tsx
  46. 10
      libs/remix-ui/git/src/style/index.css
  47. 18
      libs/remix-ui/git/src/types/index.ts
  48. 3
      libs/remix-ui/home-tab/src/lib/components/homeTabGetStarted.tsx
  49. 1
      libs/remix-ui/plugin-manager/src/lib/reducers/pluginManagerReducer.ts
  50. 13
      libs/remix-ui/solidity-compiler/src/lib/contract-selection.tsx
  51. 2
      libs/remix-ui/solidity-compiler/src/lib/solScanTable.tsx
  52. 2
      libs/remix-ui/solidity-compiler/src/lib/types/index.ts
  53. 3
      libs/remix-ui/workspace/src/lib/actions/index.ts
  54. 20
      libs/remix-ui/workspace/src/lib/actions/workspace.ts
  55. 100
      libs/remix-ui/workspace/src/lib/components/file-explorer.tsx
  56. 8
      libs/remix-ui/workspace/src/lib/components/flat-tree.tsx
  57. 2
      libs/remix-ui/workspace/src/lib/types/index.ts
  58. 49
      libs/remix-url-resolver/src/github-folder-resolver.ts
  59. 1
      libs/remix-url-resolver/src/index.ts

@ -18,6 +18,7 @@ export const scanAPIurls = {
59144: 'https://api.lineascan.build/api',
8453: 'https://api.basescan.org/api',
534352: 'https://api.scrollscan.com/api',
1116: 'https://openapi.coredao.org/api',
// all testnet
17000: 'https://api-holesky.etherscan.io/api',
@ -39,4 +40,5 @@ export const scanAPIurls = {
1442: 'https://api-testnet-zkevm.polygonscan.com/api',
59140: 'https://api-testnet.lineascan.build/api',
534351: 'https://api-sepolia.scrollscan.com/api',
1115: 'https://api.test.btcs.network/api',
}

@ -13,7 +13,7 @@ export const CaptureKeyView = () => {
const context = React.useContext(AppContext)
useEffect(() => {
if (!context.apiKey) setMsg('Please provide a 34-character API key to continue')
if (!context.apiKey) setMsg('Please provide a 34 or 32 character API key to continue')
}, [context.apiKey])
return (
@ -24,14 +24,14 @@ export const CaptureKeyView = () => {
const errors = {} as any
if (!values.apiKey) {
errors.apiKey = 'Required'
} else if (values.apiKey.length !== 34) {
errors.apiKey = 'API key should be 34 characters long'
} else if (values.apiKey.length !== 34 && values.apiKey.length !== 32) {
errors.apiKey = 'API key should be 34 or 32 characters long'
}
return errors
}}
onSubmit={(values) => {
const apiKey = values.apiKey
if (apiKey.length === 34) {
if (apiKey.length === 34 || apiKey.length === 32) {
context.setAPIKey(values.apiKey)
navigate(location && location.state ? location.state : '/')
}

@ -31,9 +31,11 @@ function BackButton({entity}: any) {
</li>
{isDetailPage && (
<li className="nav-item">
<Link className="btn" to={`/list?id=${entity.id}`} title="Tutorial menu" onClick={() => (window as any)._paq.push(['trackEvent', 'learneth', 'back_to_menu_step', entity && entity.name])}>
<i className="fas fa-bars" />
</Link>
<OverlayTrigger placement="right" overlay={<Tooltip id="tooltip-rightTutorialMenu">Tutorial menu</Tooltip>}>
<Link className="btn" to={`/list?id=${entity.id}`} onClick={() => (window as any)._paq.push(['trackEvent', 'learneth', 'back_to_menu_step', entity && entity.name])}>
<i className="fas fa-bars" />
</Link>
</OverlayTrigger>
</li>
)}
</ul>

@ -41,7 +41,7 @@ function StepDetailPage() {
}, [errors, success])
return (
<>
<div className='pb-4'>
<div className="fixed-top">
<div className="bg-light">
<BackButton entity={entity} />
@ -223,7 +223,7 @@ function StepDetailPage() {
)}
</>
)}
</>
</div>
)
}

@ -0,0 +1,34 @@
import { NightwatchBrowser } from 'nightwatch'
import EventEmitter from 'events'
class HideMetaMaskPopup extends EventEmitter {
command(this: NightwatchBrowser) {
browser
.pause(5000)
.isVisible({
selector: 'button[data-testid="popover-close"]',
locateStrategy: 'css selector',
suppressNotFoundErrors: true,
timeout: 2000
}, (okVisible) => {
console.log('okVisible', okVisible)
if (!okVisible.value) {
console.log('popover not found')
} else {
console.log('popover found... closing')
browser.click('button[data-testid="popover-close"]')
}
})
.waitForElementNotPresent({
selector: 'button[data-testid="popover-close"]',
locateStrategy: 'css selector',
timeout: 2000
})
.perform((done) => {
done()
this.emit('complete')
})
}
}
module.exports = HideMetaMaskPopup

@ -16,6 +16,7 @@ class MetaMask extends EventEmitter {
function setupMetaMask(browser: NightwatchBrowser, passphrase: string, password: string, done: VoidFunction) {
const words = passphrase.split(' ')
console.log('setup metamask')
browser
.switchBrowserTab(1)
.waitForElementVisible('input[data-testid="onboarding-terms-checkbox"]')
@ -49,6 +50,7 @@ function setupMetaMask(browser: NightwatchBrowser, passphrase: string, password:
.click('button[data-testid="pin-extension-next"]')
.waitForElementVisible('button[data-testid="pin-extension-done"]')
.click('button[data-testid="pin-extension-done"]')
.pause(5000)
.isVisible({
selector: 'button[data-testid="popover-close"]',
locateStrategy: 'css selector',
@ -58,14 +60,22 @@ function setupMetaMask(browser: NightwatchBrowser, passphrase: string, password:
console.log('okVisible', okVisible)
if (!okVisible.value) {
console.log('popover not found')
}else{
} else {
console.log('popover found... closing')
browser.click('button[data-testid="popover-close"]')
}
})
.waitForElementNotPresent({
selector: 'button[data-testid="popover-close"]',
locateStrategy: 'css selector',
timeout: 3000
})
.saveScreenshot('./reports/screenshots/metamask.png')
.click('[data-testid="network-display"]')
.click('.mm-modal-content label.toggle-button--off') // show test networks
.click('div[data-testid="Sepolia"]') // switch to sepolia
.perform(() => {
console.log('MetaMask setup complete')
done()
})
}

@ -11,7 +11,7 @@ const checkBrowserIsChrome = function (browser: NightwatchBrowser) {
return browser.browserName.indexOf('chrome') > -1
}
const checkAlerts = function (browser: NightwatchBrowser){
const checkAlerts = function (browser: NightwatchBrowser) {
browser.isVisible({
selector: '//*[contains(.,"not have enough")]',
locateStrategy: 'xpath',
@ -38,7 +38,7 @@ const tests = {
'Should connect to Sepolia Test Network using MetaMask #group1': function (browser: NightwatchBrowser) {
if (!checkBrowserIsChrome(browser)) return
browser.waitForElementPresent('*[data-id="remixIdeSidePanel"]')
.setupMetamask(passphrase, password)
.setupMetamask(passphrase, password)
.useCss().switchBrowserTab(0)
.refreshPage()
.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000)
@ -50,13 +50,14 @@ const tests = {
.pause(5000)
.switchBrowserWindow(extension_url, 'MetaMask', (browser) => {
browser
.hideMetaMaskPopup()
.waitForElementVisible('*[data-testid="page-container-footer-next"]', 60000)
.click('*[data-testid="page-container-footer-next"]') // this connects the metamask account to remix
.pause(2000)
.waitForElementVisible('*[data-testid="page-container-footer-next"]', 60000)
.click('*[data-testid="page-container-footer-next"]')
// .waitForElementVisible('*[data-testid="popover-close"]')
// .click('*[data-testid="popover-close"]')
// .waitForElementVisible('*[data-testid="popover-close"]')
// .click('*[data-testid="popover-close"]')
})
.switchBrowserTab(0) // back to remix
},
@ -83,6 +84,7 @@ const tests = {
browser.switchBrowserWindow(extension_url, 'MetaMask', (browser) => {
checkAlerts(browser)
browser
.hideMetaMaskPopup()
.waitForElementPresent('[data-testid="page-container-footer-next"]')
.click('[data-testid="page-container-footer-next"]') // approve the tx
.switchBrowserTab(0) // back to remix
@ -90,7 +92,7 @@ const tests = {
.waitForElementContainsText('*[data-id="terminalJournal"]', 'from: 0x76a...2708f', 60000)
.perform(() => done())
})
})
})
},
'Should run low level interaction (fallback function) on Sepolia Test Network using MetaMask #group1': function (browser: NightwatchBrowser) {
@ -102,14 +104,15 @@ const tests = {
.perform((done) => {
browser.switchBrowserWindow(extension_url, 'MetaMask', (browser) => {
browser
.hideMetaMaskPopup()
.waitForElementPresent('[data-testid="page-container-footer-next"]')
.click('[data-testid="page-container-footer-next"]') // approve the tx
.switchBrowserTab(0) // back to remix
.waitForElementContainsText('*[data-id="terminalJournal"]', 'view on etherscan', 60000)
.waitForElementContainsText('*[data-id="terminalJournal"]', 'from: 0x76a...2708f', 60000)
.perform(() => done())
})
})
})
})
},
'Should connect to Ethereum Main Network using MetaMask #group1': function (browser: NightwatchBrowser) {
@ -162,6 +165,8 @@ const tests = {
.perform((done) => {
browser.switchBrowserWindow(extension_url, 'MetaMask', (browser) => {
browser
.hideMetaMaskPopup()
.saveScreenshot('./reports/screenshots/metamask_4.png')
.waitForElementPresent('[data-testid="page-container-footer-next"]', 60000)
.click('[data-testid="page-container-footer-next"]') // approve the tx
.switchBrowserTab(0) // back to remix
@ -169,7 +174,7 @@ const tests = {
.waitForElementContainsText('*[data-id="terminalJournal"]', 'from: 0x76a...2708f', 60000)
.perform(() => done())
})
})
})
.waitForElementPresent('*[data-id="universalDappUiContractActionWrapper"]', 60000)
.clearConsole()
.clickInstance(0)
@ -177,6 +182,8 @@ const tests = {
.perform((done) => { // call delegate
browser.switchBrowserWindow(extension_url, 'MetaMask', (browser) => {
browser
.hideMetaMaskPopup()
.saveScreenshot('./reports/screenshots/metamask_5.png')
.waitForElementPresent('[data-testid="page-container-footer-next"]', 60000)
.click('[data-testid="page-container-footer-next"]') // approve the tx
.switchBrowserTab(0) // back to remix
@ -199,11 +206,11 @@ const tests = {
*/
'Should debug Sepolia transaction with source highlighting MetaMask #group1': function (browser: NightwatchBrowser) {
if (!checkBrowserIsChrome(browser)) return
let txhash
browser.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000)
let txhash
browser.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000)
.clickLaunchIcon('pluginManager') // load debugger and source verification
// .scrollAndClick('#pluginManager article[id="remixPluginManagerListItem_sourcify"] button')
// debugger already activated .scrollAndClick('#pluginManager article[id="remixPluginManagerListItem_debugger"] button')
// .scrollAndClick('#pluginManager article[id="remixPluginManagerListItem_sourcify"] button')
// debugger already activated .scrollAndClick('#pluginManager article[id="remixPluginManagerListItem_debugger"] button')
.clickLaunchIcon('udapp')
.perform((done) => {
browser.getLastTransactionHash((hash) => {
@ -213,15 +220,17 @@ const tests = {
})
.perform((done) => {
browser
.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000)
.clickLaunchIcon('debugger')
.setValue('*[data-id="debuggerTransactionInput"]', txhash) // debug tx
.click('*[data-id="debuggerTransactionStartButton"]')
.waitForElementVisible('*[data-id="treeViewDivto"]', 30000)
.checkVariableDebug('soliditylocals', localsCheck)
.perform(() => done())
.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000)
.clickLaunchIcon('debugger')
.setValue('*[data-id="debuggerTransactionInput"]', txhash) // debug tx
.saveScreenshot('./reports/screenshots/metamask_2.png')
.click('*[data-id="debuggerTransactionStartButton"]')
.saveScreenshot('./reports/screenshots/metamask_3.png')
.waitForElementVisible('*[data-id="treeViewDivto"]', 30000)
.checkVariableDebug('soliditylocals', localsCheck)
.perform(() => done())
})
},
'Call web3.eth.getAccounts() using Injected Provider (Metamask) #group1': function (browser: NightwatchBrowser) {
@ -229,14 +238,14 @@ const tests = {
browser
.executeScriptInTerminal('web3.eth.getAccounts()')
.journalLastChildIncludes('["0x76a3ABb5a12dcd603B52Ed22195dED17ee82708f"]')
}
}
}
const branch = process.env.CIRCLE_BRANCH;
const isMasterBranch = branch === 'master';
module.exports = {
...(branch ? (isMasterBranch ? tests : {}) : tests),
...{} //(branch ? (isMasterBranch ? tests : {}) : tests),
};
const localsCheck = {
@ -250,7 +259,7 @@ const sources = [
{
'Greet.sol': {
content:
`
`
pragma solidity ^0.8.0;
contract HelloWorld {
string public message;

@ -336,5 +336,18 @@ module.exports = {
.waitForElementVisible('*[data-shared="tooltipPopup"]')
.waitForElementContainsText('*[data-shared="tooltipPopup"]', 'initiating fileManager and calling "open" ...')
.waitForElementContainsText('*[data-shared="tooltipPopup"]', 'initiating terminal and calling "log" ...')
},
'Import Github folder from URL params #group4': function (browser: NightwatchBrowser) {
browser
.url('http://127.0.0.1:8080/#ghfolder=https://github.com/ethereum/remix-project/tree/master/apps/remix-ide/contracts/hardhat')
.refreshPage()
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]', 40000)
.currentWorkspaceIs('code-sample')
.openFile('contracts')
.openFile('contracts/Lock.sol')
.getEditorValue((content) => {
browser.assert.ok(content.indexOf('contract Lock {') !== -1, 'content does contain "contract Lock {"')
})
}
}

@ -48,6 +48,7 @@ declare module 'nightwatch' {
removeFile(path: string, workspace: string): NightwatchBrowser
switchBrowserWindow(url: string, windowName: string, cb: (browser: NightwatchBrowser, window?: NightwatchCallbackResult<Window>) => void): NightwatchBrowser
setupMetamask(passphrase: string, password: string): NightwatchBrowser
hideMetaMaskPopup(): NightwatchBrowser
signMessage(msg: string, callback: (hash: {value: string}, signature: {value: string}) => void): NightwatchBrowser
setSolidityCompilerVersion(version: string): NightwatchBrowser
clickElementAtPosition(cssSelector: string, index: number, opt?: {forceSelectIfUnselected: boolean}): NightwatchBrowser

@ -1,7 +1,7 @@
'use strict'
import { ViewPlugin } from '@remixproject/engine-web';
import { ViewPlugin } from '@remixproject/engine-web'
import React from 'react' // eslint-disable-line
import { gitState, GitUI } from '@remix-ui/git';
import { gitState, GitUI } from '@remix-ui/git'
import * as packageJson from '../../../../../package.json'
const profile = {

@ -1,12 +1,12 @@
'use strict'
import { Plugin } from '@remixproject/engine'
import { RemixURLResolver } from '@remix-project/remix-url-resolver'
import { RemixURLResolver, githubFolderResolver } from '@remix-project/remix-url-resolver'
const profile = {
name: 'contentImport',
displayName: 'content import',
version: '0.0.1',
methods: ['resolve', 'resolveAndSave', 'isExternalUrl']
methods: ['resolve', 'resolveAndSave', 'isExternalUrl', 'resolveGithubFolder']
}
export type ResolvedImport = {
@ -212,4 +212,10 @@ export class CompilerImports extends Plugin {
throw new Error(e)
}
}
async resolveGithubFolder (url) {
const ghFolder = {}
await githubFolderResolver(url, ghFolder, 3)
return ghFolder
}
}

@ -117,7 +117,7 @@ export class RemixInLineCompletionProvider implements monacoTypes.languages.Inli
};
this.completionEnabled = false
const handleCompletionTimer = new CompletionTimer(1000, () => { this.completionEnabled = true });
const handleCompletionTimer = new CompletionTimer(100, () => { this.completionEnabled = true });
handleCompletionTimer.start()
return {
@ -150,7 +150,7 @@ export class RemixInLineCompletionProvider implements monacoTypes.languages.Inli
// handle the completion timer by locking suggestions request for 2 seconds
this.completionEnabled = false
const handleCompletionTimer = new CompletionTimer(2000, () => { this.completionEnabled = true });
const handleCompletionTimer = new CompletionTimer(100, () => { this.completionEnabled = true });
handleCompletionTimer.start()
return {

@ -43,6 +43,14 @@ export const BranchHeader = () => {
}
}, [context.fileStatusResult, context.modified, context.allchangesnotstaged, context.untracked, context.deleted])
const getName = () => {
const url = context.currentBranch?.remote?.url
if (!url) return
const regex = /https:\/\/github\.com\/[^/]+\/([^/]+)\.git/
const match = url.match(regex)
return match ? match[1] : 'Couldn\'t get repo name!'
}
const showDetachedWarningText = async () => {
await pluginActions.showAlert({
message: `You are in 'detached HEAD' state. This means you are not on a branch because you checkout a tag or a specific commit. If you want to commit changes, you will need to create a new branch.`,
@ -50,21 +58,64 @@ export const BranchHeader = () => {
})
}
const Heading = () => {
return (
<div className="container-fluid px-3">
<div className="d-flex flex-column pt-1 mb-1">
<div className="d-flex flex-column justify-content-start align-items-start">
{getName() !== "Couldn't get repo name!" ? (
<div className="pr-1 m-0">
<span className="col-4 px-0">Repository Name:</span>
<span className="" style={{ width: '15rem' }}>
<span className={`${ changed ? 'text-danger pl-2 text-truncate overflow-hidden whitespace-nowrap ml-4' : "text-secondary pl-2 text-truncate overflow-hidden whitespace-nowrap ml-4" }`}>
{getName() ?? ''}
</span>
</span>
</div>
) : null
}
<div className="pr-1 m-0">
<span className="col-4 px-0">Branch Name:</span>
<span className="pl-2 text-secondary text-truncate overflow-hidden whitespace-nowrap ml-4">
<span className={`${changed ? 'text-danger pl-2' : "pl-2"}`}>
<i className="fa fa-code-branch mr-1 pl-2"></i>
{context.currentBranch && context.currentBranch.name}
</span>
</span>
</div>
{context.storage.enabled ?
<div className="d-flex">
<span className="d-flex justify-between align-items-center" style={{ width: '15rem' }}>
<span className="col-4 px-0">Storage :</span>
<span className="text-secondary text-sm text-truncate overflow-hidden whitespace-nowrap ml-4">
{context.storage.used} MB used
({context.storage.percentUsed} %)
</span>
</span>
</div> : null}
<div className="d-flex flex-row">
<span className="d-flex justify-between align-items-center" style={{ width: '15rem' }}>
<span className="col-4 px-0">Messages :</span>
<span className="text-truncate overflow-hidden" >
<span className="text-secondary text-truncate overflow-hidden whitespace-nowrap ml-4">
{latestCommit ?
latestCommit.commit && latestCommit.commit.message ? latestCommit.commit.message : '' : null}
{isDetached ?
<>You are in a detached state<i onClick={showDetachedWarningText} className="btn fa fa-info-circle mr-1 pl-2"></i></>: null}
</span>
</span>
</span>
</div>
</div>
</div>
</div>
)
}
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>
{latestCommit ?
<div className='text-secondary long-and-truncated'>
{latestCommit.commit && latestCommit.commit.message ? latestCommit.commit.message : ''}
</div> : null}
{isDetached ?
<div className='text-warning long-and-truncated'>
You are in a detached state<i onClick={showDetachedWarningText} className="btn fa fa-info-circle mr-1 pl-2"></i>
</div> : null}
<Heading />
</div>
<hr></hr>
</>)
}
}

@ -126,7 +126,7 @@ export const CommitMessage = () => {
return (
<>
<div className="form-group">
<div className="form-group pt-3">
<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()} >
@ -144,4 +144,4 @@ export const CommitMessage = () => {
<hr></hr>
</>
);
}
}

@ -72,8 +72,8 @@ export const GetDeviceCode = () => {
return (
<>
{(context.gitHubUser && context.gitHubUser.login) ? null :
<button className='btn btn-primary mt-1 w-100' onClick={async () => {
getDeviceCodeFromGitHub();
<button className='btn btn-secondary mt-1 w-100' onClick={async () => {
await getDeviceCodeFromGitHub()
}}><i className="fab fa-github mr-1"></i>Login in with github</button>
}
{gitHubResponse && !authorized &&
@ -124,4 +124,4 @@ export const GetDeviceCode = () => {
}
</>)
}
}

@ -64,9 +64,9 @@ const RepositorySelect = (props: RepositorySelectProps) => {
};
return (
<><Button data-id='fetch-repositories' onClick={fetchRepositories} className="w-100 mt-1">
<><button data-id='fetch-repositories' onClick={fetchRepositories} className="w-100 mt-1 btn btn-secondary mb-2">
<i className="fab fa-github mr-1"></i>Fetch Repositories from GitHub
</Button>
</button>
{
show ?
<Select

@ -43,15 +43,15 @@ export const SelectAndCloneRepositories = (props: RepositoriesProps) => {
return (
<>
<TokenWarning />
<RepositorySelect select={selectRepo} />
<TokenWarning />
{repo &&<BranchSelect select={selectRemoteBranch} />}
{ repo && <BranchSelect select={selectRemoteBranch} /> }
{repo && branch && branch.name && branch.name !== '0' ?
{ repo && branch && branch.name && branch.name !== '0' ?
<button data-id={`clonebtn-${repo.full_name}-${branch.name}`} className='btn btn-primary mt-1 w-100' onClick={async () => {
await clone()
}}>clone {repo.full_name}:{branch.name}</button> : null}
}}>clone {repo.full_name}:{branch.name}</button> : null }
</>
)

@ -31,8 +31,8 @@ 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";
import { CustomRemixApi } from "@remix-api"
import { Plugin } from "@remixproject/engine"
import { Disabled } from './disabled'
export const gitPluginContext = React.createContext<gitState>(defaultGitState)
@ -174,63 +174,64 @@ export const GitUI = (props: IGitUi) => {
{setup && !needsInit ? <Setup></Setup> : null}
{needsInit ? <Init></Init> : null}
{!setup && !needsInit ?
<Accordion activeKey={activePanel} defaultActiveKey="0">
<Accordion activeKey={activePanel} defaultActiveKey="0" className="">
<SourceControlNavigation eventKey="0" activePanel={activePanel} callback={setActivePanel} />
<Accordion.Collapse className='bg-light' eventKey="0">
<>
<div className="px-2 py-2">
<SourceControlBase><CommitMessage /></SourceControlBase>
<SourceControl />
</>
</div>
</Accordion.Collapse>
<hr></hr>
<CommandsNavigation eventKey="1" activePanel={activePanel} callback={setActivePanel} />
<Accordion.Collapse className='bg-light' eventKey="1">
<>
<Accordion.Collapse className="bg-light" eventKey="1">
<div className="px-2 py-2">
<Commands></Commands>
</>
</div>
</Accordion.Collapse>
<hr></hr>
<CommitsNavigation title={`COMMITS`} eventKey="3" activePanel={activePanel} callback={setActivePanel} showButtons={true} />
<Accordion.Collapse className='bg-light' eventKey="3">
<>
<div className="px-2 py-2">
<Commits />
</>
</div>
</Accordion.Collapse>
<hr></hr>
<BranchesNavigation eventKey="2" activePanel={activePanel} callback={setActivePanel} />
<Accordion.Collapse className='bg-light' eventKey="2">
<>
<Branches /></>
<div className="px-2 py-2">
<Branches />
</div>
</Accordion.Collapse>
<hr></hr>
<RemotesNavigation eventKey="5" activePanel={activePanel} callback={setActivePanel} />
<Accordion.Collapse className='bg-light' eventKey="5">
<>
<div className="px-2 py-2">
<Remotes></Remotes>
</>
</div>
</Accordion.Collapse>
<hr></hr>
<CloneNavigation eventKey="4" activePanel={activePanel} callback={setActivePanel} />
<Accordion.Collapse className='bg-light' eventKey="4">
<>
<Clone /></>
<div className="px-2 py-2">
<Clone /></div>
</Accordion.Collapse>
<hr></hr>
<GitHubNavigation eventKey="7" activePanel={activePanel} callback={setActivePanel} />
<Accordion.Collapse className='bg-light' eventKey="7">
<>
<div className="px-2 py-2">
<GetDeviceCode></GetDeviceCode>
<hr></hr>
<GitHubCredentials></GitHubCredentials>
</>
</div>
</Accordion.Collapse>
<hr></hr>
<LogNavigation eventKey="6" activePanel={activePanel} callback={setActivePanel} />
<Accordion.Collapse className='bg-light' eventKey="6">
<>
<div className="px-2 py-2">
<LogViewer />
</>
</div>
</Accordion.Collapse>
</Accordion>
@ -242,4 +243,4 @@ export const GitUI = (props: IGitUi) => {
</div>}
</>
)
}
}

@ -23,10 +23,10 @@ export const BranchesNavigation = ({ eventKey, activePanel, callback }) => {
{
activePanel === eventKey ? <FontAwesomeIcon className='' icon={faCaretDown}></FontAwesomeIcon> : <FontAwesomeIcon className='' icon={faCaretRight}></FontAwesomeIcon>
}
<label className="pl-1 nav form-check-label">BRANCHES</label>
<label className="pl-2 nav form-check-label">BRANCHES</label>
<LoaderIndicator></LoaderIndicator>
</span>
</div>
</>
);
}
}

@ -20,10 +20,10 @@ export const CloneNavigation = ({ eventKey, activePanel, callback }) => {
{
activePanel === eventKey ? <FontAwesomeIcon className='' icon={faCaretDown}></FontAwesomeIcon> : <FontAwesomeIcon className='' icon={faCaretRight}></FontAwesomeIcon>
}
<label className="pl-1 nav form-check-label">CLONE</label>
<label className="pl-2 nav form-check-label ">CLONE</label>
<LoaderIndicator></LoaderIndicator>
</span>
</div>
</>
);
}
}

@ -21,15 +21,14 @@ export const CommandsNavigation = ({ eventKey, activePanel, callback }) => {
return (
<>
<div className={'d-flex justify-content-between ' + (activePanel === eventKey ? 'bg-light' : '')}>
<span data-id='commands-panel' onClick={() => handleClick()} role={'button'} className='nav d-flex justify-content-start align-items-center w-75'>
<span data-id='commands-panel' 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>
<label className="pl-2 nav form-check-label">COMMANDS</label>
</span>
<LoaderIndicator></LoaderIndicator>
</div>
</>
);
}
}

@ -48,9 +48,8 @@ export const CommitsNavigation = ({ eventKey, activePanel, callback, title, bran
}
{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>
<label className={`pl-2 nav form-check-label ${ahead || behind? 'text-success':''}`}>{title}</label>
<LoaderIndicator></LoaderIndicator>
</span>
{showButtons ?
<SourceControlBase branch={branch} remote={remote}>
@ -60,4 +59,4 @@ export const CommitsNavigation = ({ eventKey, activePanel, callback, title, bran
</div>
</>
);
}
}

@ -1,7 +1,7 @@
import { faCaretDown, faCaretRight } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import React, { } from "react";
import { pluginActionsContext } from "../../state/context";
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)
@ -21,9 +21,9 @@ export const GitHubNavigation = ({ eventKey, activePanel, callback }) => {
{
activePanel === eventKey ? <FontAwesomeIcon className='' icon={faCaretDown}></FontAwesomeIcon> : <FontAwesomeIcon className='' icon={faCaretRight}></FontAwesomeIcon>
}
<label className="pl-1 nav form-check-label">GITHUB SETUP</label>
<label className="pl-2 nav form-check-label">GITHUB SETUP</label>
</span>
</div>
</>
);
}
)
}

@ -50,7 +50,7 @@ export const LogNavigation = ({ eventKey, activePanel, callback }) => {
{
activePanel === eventKey ? <FontAwesomeIcon className='' icon={faCaretDown}></FontAwesomeIcon> : <FontAwesomeIcon className='' icon={faCaretRight}></FontAwesomeIcon>
}
<label className="pl-1 nav form-check-label mr-2">LOG</label>
<label className="pl-2 nav form-check-label mr-2">LOG</label>
{logState.errorCount > 0 && (
<div className="text-danger mr-1">
{logState.errorCount}
@ -84,4 +84,4 @@ export const LogNavigation = ({ eventKey, activePanel, callback }) => {
</div>
</>
);
}
}

@ -23,10 +23,10 @@ export const RemotesNavigation = ({ eventKey, activePanel, callback }) => {
{
activePanel === eventKey ? <FontAwesomeIcon className='' icon={faCaretDown}></FontAwesomeIcon> : <FontAwesomeIcon className='' icon={faCaretRight}></FontAwesomeIcon>
}
<label className="pl-1 nav form-check-label">REMOTES</label>
<label className="pl-2 nav form-check-label">REMOTES</label>
<LoaderIndicator></LoaderIndicator>
</span>
</div>
</>
);
}
}

@ -24,7 +24,7 @@ export const SettingsNavigation = ({ eventKey, activePanel, callback }) => {
{
activePanel === eventKey ? <FontAwesomeIcon className='' icon={faCaretDown}></FontAwesomeIcon> : <FontAwesomeIcon className='' icon={faCaretRight}></FontAwesomeIcon>
}
<label className="nav pl-1 form-check-label">SETTINGS</label>
<label className="nav pl-2 form-check-label">SETTINGS</label>
</span>
@ -38,4 +38,4 @@ export const SettingsNavigation = ({ eventKey, activePanel, callback }) => {
</div>
</>
);
}
}

@ -24,12 +24,13 @@ export const SourceControlNavigation = ({ eventKey, activePanel, callback }) =>
return (
<>
<div className={'d-flex justify-content-between ' + (activePanel === eventKey ? 'bg-light' : '')}>
<span data-id='sourcecontrol-panel' onClick={() => handleClick()} role={'button'} className='nav d-flex justify-content-start align-items-center w-75'>
<div className={'d-flex align-items-center justify-content-between ' + (activePanel === eventKey ? 'bg-light' : '')}>
<span data-id='sourcecontrol-panel' 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>
<label className="nav pl-2 form-check-label">SOURCE CONTROL</label>
<LoaderIndicator></LoaderIndicator>
</span>
@ -39,4 +40,4 @@ export const SourceControlNavigation = ({ eventKey, activePanel, callback }) =>
</div>
</>
);
}
}

@ -38,10 +38,10 @@ export const SourceControlGroupNavigation = (props: SourceControlGroupNavigation
</span>
{
activePanel === eventKey ?
<span className='d-flex justify-content-end align-items-center w-25'>
<span className='d-flex justify-content-end align-items-center w-25 py-2'>
{group.name === 'Changes' ?
<CustomTooltip tooltipText={<FormattedMessage id="git.stageall" />}>
<button data-id='sourcecontrol-add-all' onClick={async () => { await actions.addall(context.allchangesnotstaged) }} className='btn btn-sm'><FontAwesomeIcon icon={faPlus} className="" /></button>
<button data-id='sourcecontrol-add-all' onClick={async () => { await actions.addall(context.allchangesnotstaged) }} className='btn btn-sm' style={{ marginLeft: '1rem', marginRight: '1.3rem' }}><FontAwesomeIcon icon={faPlus} className="" /></button>
</CustomTooltip>: null}
</span> : null
@ -49,4 +49,4 @@ export const SourceControlGroupNavigation = (props: SourceControlGroupNavigation
</div>
</>
);
}
}

@ -18,7 +18,7 @@ export const Branches = () => {
return (
<>
<div data-id='branches-panel-content' className="pt-1">
<div data-id='branches-panel-content' className="pt-2">
{context.branches && context.branches.length ?
<div>
{context.branches && context.branches.filter((branch, index) => !branch.remote).map((branch, index) => {
@ -32,9 +32,9 @@ export const Branches = () => {
{context.currentBranch
&& context.currentBranch.name !== ''
&& (!context.branches || context.branches.length === 0) ?
<div className="text-muted">Current branch is `{context.currentBranch.name}` but you have no commits.<hr /></div>
<div className="text-muted">Current branch is <strong className="text-dark">{`${context.currentBranch.name}`}</strong> but you have no commits.</div>
: null}
<label>create branch</label>
<label className="text-uppercase pt-2 pb-1">Create branch</label>
<div className="form-group">
<input
@ -49,7 +49,7 @@ export const Branches = () => {
<GitUIButton
data-id="sourcecontrol-create-branch"
onClick={async () => actions.createBranch(newBranch.value)}
className="btn w-md-25 w-100 btn-primary"
className="btn w-md-25 w-100 btn-primary mb-3"
id="createbranch-btn"
>
create new branch
@ -57,4 +57,4 @@ export const Branches = () => {
</div>
</>
);
}
}

@ -1,12 +1,12 @@
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";
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)
@ -64,21 +64,21 @@ export const Clone = () => {
return (
<>
<div data-id="clone-panel-content">
<InputGroup className="mb-1">
<SelectAndCloneRepositories cloneAllBranches={cloneAllBranches} cloneDepth={cloneDepth} />
<hr />
<InputGroup className="mb-2 pb-1">
<FormControl data-id="clone-url" 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" />
<input name='clonebranch' onChange={e => onCloneBranchChange(e.target.value)} value={cloneBranch} className="form-control mb-2 mt-2" placeholder="branch" type="text" id="clonebranch" />
<GitUIButton disabledCondition={!cloneUrl} data-id='clone-btn' className='btn btn-primary mt-1 w-100' onClick={async () => {
clone()
}}>clone</GitUIButton>
<hr />
<SelectAndCloneRepositories cloneAllBranches={cloneAllBranches} cloneDepth={cloneDepth} />
<hr />
<label>options</label>
<label className="text-uppercase">Options</label>
<InputGroup className="mt-1 mb-1">
<InputGroup.Prepend>
<InputGroup.Text id="clonedepthprepend">
<InputGroup.Prepend className="bg-secondary">
<InputGroup.Text id="clonedepthprepend" className="text-dark">
--depth
</InputGroup.Text>
</InputGroup.Prepend>
@ -98,4 +98,4 @@ export const Clone = () => {
<hr></hr>
</div>
</>)
}
}

@ -6,9 +6,9 @@ import { Merge } from "./commands/merge";
export const Commands = () => {
return (
<>
<div>
<PushPull></PushPull>
<hr></hr>
<Fetch></Fetch>
</>)
}
</div>)
}

@ -15,11 +15,11 @@ export const Fetch = () => {
<div className="btn-group w-100" role="group">
<GitUIButton data-id='sourcecontrol-fetch-remote' 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>
})} className="btn btn-secondary mr-1 w-50"><div>Fetch {context.upstream && context.upstream.name}</div></GitUIButton>
<GitUIButton data-id='sourcecontrol-fetch-branch' 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>
})} className="btn btn-secondary w-50 long-and-truncated">Fetch {context.currentBranch.name}</GitUIButton>
</div>
</>)
}
}

@ -148,7 +148,7 @@ export const PushPull = () => {
<GitUIButton data-id='sourcecontrol-push' disabledCondition={pushPullIsDisabled()} type="button" onClick={async () => push()} className="btn btn-primary">Push</GitUIButton>
</div>
<label>Local Branch</label>
<label className="pt-3 text-uppercase">Local Branch</label>
<Select
id='commands-local-branch-select'
options={localBranchOptions}
@ -161,7 +161,7 @@ export const PushPull = () => {
placeholder="Type to search for a branch..."
/>
<label>Remote Branch</label>
<label className="pt-3 text-uppercase">Remote Branch</label>
<Select
id='commands-remote-branch-select'
options={remoteBranchOptions}
@ -174,7 +174,7 @@ export const PushPull = () => {
placeholder="Type to search for a branch..."
/>
<label>Remote</label>
<label className="pt-3 text-uppercase">Remote</label>
<Select
id='commands-remote-origin-select'
options={localRemotesOptions}
@ -187,10 +187,10 @@ export const PushPull = () => {
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 className="pt-3 d-flex align-items-center remixui_compilerConfig custom-control custom-checkbox">
<input checked={force} onChange={e => onForceChange(e)} className="remixui_autocompile form-check-input 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>
</>)
}
}

@ -67,14 +67,14 @@ export const GitHubCredentials = () => {
return (
<>
<div className="input-group text-secondary mb-1 h6">
<div className="input-group text-secondary mb-3 h6">
<input data-id='githubToken' 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 data-id='gitubUsername' name='githubUsername' onChange={e => handleChangeUserNameState(e.target.value)} value={githubUsername} className="form-control mb-1" placeholder="Git username" type="text" id="githubUsername" />
<input data-id='githubEmail' name='githubEmail' onChange={e => handleChangeEmailState(e.target.value)} value={githubEmail} className="form-control mb-1" placeholder="Git email" type="text" id="githubEmail" />
<input data-id='gitubUsername' name='githubUsername' onChange={e => handleChangeUserNameState(e.target.value)} value={githubUsername} className="form-control mb-3" placeholder="Git username" type="text" id="githubUsername" />
<input data-id='githubEmail' name='githubEmail' onChange={e => handleChangeEmailState(e.target.value)} value={githubEmail} className="form-control mb-3" placeholder="Git email" type="text" id="githubEmail" />
<div className="d-flex justify-content-between">
<button data-id='saveGitHubCredentials' className="btn btn-primary w-100" onClick={saveGithubToken}>
<FormattedMessage id="save" defaultMessage="Save" />
@ -87,4 +87,4 @@ export const GitHubCredentials = () => {
<hr />
</>
);
}
}

@ -1,8 +1,8 @@
import React, { useEffect } from "react";
import { gitActionsContext } from "../../state/context";
import { gitPluginContext } from "../gitui";
import { Remoteselect } from "./remoteselect";
import { RemotesImport } from "./remotesimport";
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)
@ -26,9 +26,11 @@ export const Remotes = () => {
return (
<>
<div data-id="remotes-panel-content">
<div data-id="remotes-panel-content" className="d-flex flex-column">
<RemotesImport />
<hr className="mt-0 border border-2" />
{context.remotes && context.remotes.length ?
<>
<div>
{context.remotes && context.remotes.map((remote, index) => {
@ -36,18 +38,17 @@ export const Remotes = () => {
<Remoteselect key={index} remote={remote}></Remoteselect>
);
})}
</> : <>No remotes</>}
<hr></hr>
</div> : <div>
<label className="text-uppercase">No remotes</label>
</div>}
<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" />
<input placeholder="remote name" name='remotename' onChange={e => onRemoteNameChange(e.target.value)} value={remoteName} className="form-control mb-3" type="text" id="remotename" />
<input placeholder="remote url" name='remoteurl' onChange={e => onUrlChange(e.target.value)} value={url} className="form-control mb-3" 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 />
<hr className="mt-0 border border-2" />
</div>
</>)
}
}

@ -1,12 +1,12 @@
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 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";
import { selectStyles, selectTheme } from "../../types/styles"
import { TokenWarning } from "./tokenWarning"
import RepositorySelect from "../github/repositoryselect"
export const RemotesImport = () => {
const context = React.useContext(gitPluginContext)
@ -64,9 +64,8 @@ export const RemotesImport = () => {
return (
<>
<TokenWarning />
<RepositorySelect select={selectRepo} />
<TokenWarning />
{repo ?
<input data-id='remote-panel-remotename' placeholder="remote name" name='remotename' onChange={e => onRemoteNameChange(e.target.value)} value={remoteName} className="form-control mb-2" type="text" id="remotename" />
: null}

@ -35,4 +35,4 @@ export const SourceControGroup = (props: SourceControGroupProps) => {
</Accordion.Collapse>
</Accordion> : <></>}
</>)
}
}

@ -41,10 +41,10 @@ export const SourceControlItem = (props: SourceControlItemProps) => {
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>}
{status && status.indexOf("modified") === -1 ? <></> : <span className="pl-2">M</span>}
{status && status.indexOf("deleted") === -1 ? <></> : <span className="pl-2">D</span>}
{status && status.indexOf("added") === -1 ? <></> : <span className="pl-2">A</span>}
{status && status.indexOf("untracked") === -1 ? <></> : <span className="pl-2">U</span>}
</>)
}
@ -56,10 +56,10 @@ export const SourceControlItem = (props: SourceControlItemProps) => {
<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">
<div className="d-flex align-items-center ml-1 px-2">
<SourceControlItemButtons group={group} file={file}></SourceControlItemButtons>
<FunctionStatusIcons></FunctionStatusIcons>
</div>
</div>
</>)
}
}

@ -37,7 +37,7 @@ export const SourceControl = () => {
<>
{show ?
<>
<div>
<div className="mb-2">
<RenderGroups></RenderGroups>
</div></>
: <>
@ -46,4 +46,4 @@ export const SourceControl = () => {
</>
);
}
}

@ -1,12 +1,15 @@
import { gitPluginContext } from "../gitui"
import React, { useEffect, useState } from "react";
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>
<span className="text-warning text-left">
<span>Generate and add a Git token to use this plugin. Tokens are added in </span><span className=" text-decoration-line-through messageTip" onClick={async () => {
}}>settings.</span>
</span>
}
</>
)
}
}

@ -1,7 +1,7 @@
import { ReadBlobResult, ReadCommitResult } from "isomorphic-git";
import React from "react";
import { fileStatus, fileStatusMerge, setRemoteBranchCommits, resetRemoteBranchCommits, setBranches, setCanCommit, setCommitChanges, setCommits, setCurrentBranch, setGitHubUser, setLoading, setRemoteBranches, setRemotes, setRepos, setUpstream, setLocalBranchCommits, setBranchDifferences, setRemoteAsDefault, setScopes, setLog, clearLog, setUserEmails, setCurrenHead } from "../state/gitpayload";
import { GitHubUser, branch, commitChange, gitActionDispatch, statusMatrixType, gitState, branchDifference, remote, gitLog, fileStatusResult, customGitApi, IGitApi, cloneInputType, fetchInputType, pullInputType, pushInputType, checkoutInput, rmInput, addInput, repository, userEmails } from '../types';
import { fileStatus, fileStatusMerge, setRemoteBranchCommits, resetRemoteBranchCommits, setBranches, setCanCommit, setCommitChanges, setCommits, setCurrentBranch, setGitHubUser, setLoading, setRemoteBranches, setRemotes, setRepos, setUpstream, setLocalBranchCommits, setBranchDifferences, setRemoteAsDefault, setScopes, setLog, clearLog, setUserEmails, setCurrenHead, setStoragePayload } from "../state/gitpayload";
import { GitHubUser, branch, commitChange, gitActionDispatch, statusMatrixType, gitState, branchDifference, remote, gitLog, fileStatusResult, customGitApi, IGitApi, cloneInputType, fetchInputType, pullInputType, pushInputType, checkoutInput, rmInput, addInput, repository, userEmails, storage } from '../types';
import { removeSlash } from "../utils";
import { disableCallBacks, enableCallBacks } from "./listeners";
import { ModalTypes } from "@remix-ui/app";
@ -849,4 +849,9 @@ export const sendToGitLog = async (message: gitLog) => {
export const clearGitLog = async () => {
dispatch(clearLog())
}
}
export const setStorage = async (storage: storage) => {
console.log(storage)
dispatch(setStoragePayload(storage))
}

@ -1,9 +1,9 @@
import React from "react";
import { setCanUseApp, setLoading, setRepoName, setGItHubToken, setLog, setGitHubUser, setUserEmails } from "../state/gitpayload";
import { gitActionDispatch } from "../types";
import { gitActionDispatch, storage } from "../types";
import { Plugin } from "@remixproject/engine";
import { getBranches, getFileStatusMatrix, loadGitHubUserFromToken, getRemotes, gitlog, setPlugin } from "./gitactions";
import { getBranches, getFileStatusMatrix, loadGitHubUserFromToken, getRemotes, gitlog, setPlugin, setStorage } from "./gitactions";
import { Profile } from "@remixproject/plugin-utils";
import { CustomRemixApi } from "@remix-api";
import { statusChanged } from "./pluginActions";
@ -20,7 +20,7 @@ class AsyncDebouncedQueue {
this.queues = new Map();
}
enqueue(callback: AsyncCallback, customDelay?:number): void {
enqueue(callback: AsyncCallback, customDelay?: number): void {
if (this.queues.has(callback)) {
clearTimeout(this.queues.get(callback)!.timer);
}
@ -180,6 +180,7 @@ export const getGitConfig = async () => {
export const loadFiles = async (filepaths: string[] = null) => {
try {
await calculateLocalStorage()
const branch = await plugin.call('dgitApi', "currentbranch")
if (branch) {
await getFileStatusMatrix(filepaths);
@ -199,3 +200,49 @@ export const enableCallBacks = async () => {
callBackEnabled = true;
}
const calculateLocalStorage = async () => {
function bytesToMB(bytes) {
return parseFloat((bytes / (1024 * 1024)).toFixed(2));
}
function calculatePercentage(used, quota) {
return parseFloat(((used / quota) * 100).toFixed(2));
}
let storage: storage = {
used: 0,
total: 0,
available: 0,
percentUsed: 0,
enabled: false
}
if ('storage' in navigator && 'estimate' in navigator.storage) {
navigator.storage.estimate().then(estimate => {
const usedMB = bytesToMB(estimate.usage);
const quotaMB = bytesToMB(estimate.quota);
const availableMB = bytesToMB(estimate.quota - estimate.usage);
const percentageUsed = calculatePercentage(estimate.usage, estimate.quota);
console.log(`Used storage: ${usedMB} MB`);
console.log(`Total quota: ${quotaMB} MB`);
console.log(`Available storage: ${availableMB} MB`);
console.log(`Percentage used: ${percentageUsed}%`);
storage = {
used: usedMB,
total: quotaMB,
available: availableMB,
percentUsed: percentageUsed,
enabled: true
}
setStorage(storage);
});
} else {
console.log('Storage API not supported in this browser.');
setStorage(storage);
}
}

@ -1,5 +1,5 @@
import { ReadCommitResult } from "isomorphic-git"
import { branch, branchDifference, commitChange, fileStatusResult, GitHubUser, gitLog, pagedCommits, remote, remoteBranch, repository, userEmails } from "../types"
import { branch, branchDifference, commitChange, fileStatusResult, GitHubUser, gitLog, pagedCommits, remote, remoteBranch, repository, storage, userEmails } from "../types"
export interface ActionPayloadTypes {
FILE_STATUS: fileStatusResult[],
@ -42,6 +42,7 @@ export interface ActionPayloadTypes {
SET_LOG: gitLog
CLEAR_LOG: void
SET_USER_EMAILS: userEmails
SET_STORAGE: storage
}
export interface Action<T extends keyof ActionPayloadTypes> {

@ -1,5 +1,5 @@
import { ReadCommitResult } from "isomorphic-git"
import { GitHubUser, branch, commitChange, fileStatusResult, remote, pagedCommits, branchDifference, gitLog, repository, userEmails } from "../types"
import { GitHubUser, branch, commitChange, fileStatusResult, remote, pagedCommits, branchDifference, gitLog, repository, userEmails, storage } from "../types"
import { Endpoints } from "@octokit/types"
export const fileStatus = (files: fileStatusResult[]) => {
@ -218,3 +218,10 @@ export const clearLog = () => {
type: 'CLEAR_LOG'
}
}
export const setStoragePayload = (storage: storage) => {
return {
type: 'SET_STORAGE',
payload: storage
}
}

@ -204,5 +204,11 @@ export const gitReducer = (state: gitState = defaultGitState, action: Actions):
log: []
}
case 'SET_STORAGE':
return {
...state,
storage: action.payload
}
}
}
}

@ -28,9 +28,17 @@
.gitfile:hover {
background-color : var(--custom-select);
}
hr {
background-color: var(--custom-select);
}
.messageTip {
}
.messageTip:hover {
cursor: pointer;
text-decoration: underline;
}

@ -163,7 +163,7 @@ export type gitState = {
fileStatusResult: fileStatusResult[]
canUseApp: boolean
loading: boolean
storageUsed: any
storage: storage
reponame: string
staged: fileStatusResult[]
untracked: fileStatusResult[]
@ -282,7 +282,13 @@ export const defaultGitState: gitState = {
allchangesnotstaged: [],
canUseApp: true,
loading: false,
storageUsed: {},
storage: {
used: 0,
total: 0,
available: 0,
percentUsed: 0,
enabled: false
},
reponame: "",
repositories: [],
remoteBranches: [],
@ -324,6 +330,14 @@ export type sourceControlGroup = {
name: string
}
export type storage = {
used: number,
total: number
available: number
percentUsed: number
enabled: boolean
}
export interface fileStatusAction {
type: string,
payload: fileStatusResult[]

@ -129,7 +129,8 @@ function HomeTabGetStarted({ plugin }: HomeTabGetStartedProps) {
{
url: metadata.url,
branch: metadata.branch,
workspaceName: templateDisplayName
workspaceName: templateDisplayName,
depth: 10
})
} else if (metadata && metadata.type === 'plugin') {
await plugin.appManager.activatePlugin('filePanel')

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
export type localPluginReducerActionType = {
type: 'show' | 'close',

@ -313,8 +313,19 @@ export const ContractSelection = (props: ContractSelectionProps) => {
const { data: scanData } = await axios.post('https://solidityscan.remixproject.org/downloadResult', { url })
const scanReport: ScanReport = scanData.scan_report
if (scanReport?.multi_file_scan_details?.length) {
for (const template of scanReport.multi_file_scan_details) {
if (template.metric_wise_aggregated_findings?.length) {
const { metric_wise_aggregated_findings } = template
const positions = []
for (const details of metric_wise_aggregated_findings) {
const { findings } = details
for (const f of findings)
positions.push(`${f.line_nos_start[0]}:${f.line_nos_end[0]}`)
}
template.positions = JSON.stringify(positions)
}
}
await plugin.call('terminal', 'logHtml', <SolScanTable scanReport={scanReport} fileName={fileName}/>)
} else {
const modal: AppModal = {

@ -37,7 +37,7 @@ export function SolScanTable(props: SolScanTableProps) {
<td scope="col">{template.template_details.issue_name}</td>
<td scope="col">{template.template_details.issue_severity}</td>
<td scope="col">{template.template_details.issue_confidence}</td>
<td scope="col">{parse(template.template_details.static_issue_description)}</td>
<td scope="col">{parse(template.template_details.static_issue_description)} {template.positions ? `Lines: ${template.positions}`: ''}</td>
<td scope="col">{template.template_details.issue_remediation ? parse(template.template_details.issue_remediation) : 'Not Available' }</td>
</tr>
)

@ -19,6 +19,8 @@ export interface ScanTemplate {
export interface ScanDetails {
issue_id: string
no_of_findings: string
metric_wise_aggregated_findings?: Record<string, any>[]
positions?: string
template_details: ScanTemplate
}

@ -29,6 +29,7 @@ export type UrlParametersType = {
address: string
opendir: string,
blockscout: string,
ghfolder: string
}
const basicWorkspaceInit = async (workspaces: { name: string; isGitRepo: boolean; }[], workspaceProvider) => {
@ -80,7 +81,7 @@ export const initWorkspace = (filePanelPlugin) => async (reducerDispatch: React.
plugin.setWorkspace({ name, isLocalhost: false })
dispatch(setCurrentWorkspace({ name, isGitRepo: false }))
await loadWorkspacePreset('gist-template')
} else if (params.code || params.url || params.shareCode) {
} else if (params.code || params.url || params.shareCode || params.ghfolder) {
await createWorkspaceTemplate('code-sample', 'code-template')
plugin.setWorkspace({ name: 'code-sample', isLocalhost: false })
dispatch(setCurrentWorkspace({ name: 'code-sample', isGitRepo: false }))

@ -224,7 +224,7 @@ export const createWorkspaceTemplate = async (workspaceName: string, template: W
if ((await workspaceExists(workspaceName)) && template === 'remixDefault') throw new Error('workspace already exists')
else if (metadata && metadata.type === 'git') {
dispatch(cloneRepositoryRequest())
await dgitPlugin.call('dgitApi', 'clone', { url: metadata.url, branch: metadata.branch, workspaceName: workspaceName })
await dgitPlugin.call('dgitApi', 'clone', { url: metadata.url, branch: metadata.branch, workspaceName: workspaceName, depth: 10 })
dispatch(cloneRepositorySuccess())
} else {
const workspaceProvider = plugin.fileProviders.workspace
@ -238,6 +238,7 @@ export type UrlParametersType = {
shareCode: string
url: string
language: string
ghfolder: string
}
export const loadWorkspacePreset = async (template: WorkspaceTemplate = 'remixDefault', opts?) => {
@ -285,10 +286,8 @@ export const loadWorkspacePreset = async (template: WorkspaceTemplate = 'remixDe
}
if (params.url) {
const data = await plugin.call('contentImport', 'resolve', params.url)
path = data.cleanUrl
content = data.content
try {
content = JSON.parse(content) as any
if (content.language && content.language === 'Solidity' && content.sources) {
@ -307,6 +306,17 @@ export const loadWorkspacePreset = async (template: WorkspaceTemplate = 'remixDe
await workspaceProvider.set(path, content)
}
}
if (params.ghfolder) {
try {
const files = await plugin.call('contentImport', 'resolveGithubFolder', params.ghfolder)
for (const [path, content] of Object.entries(files)) {
await workspaceProvider.set(path, content)
}
} catch (e) {
console.log(e)
}
}
return path
} catch (e) {
console.error(e)
@ -645,7 +655,7 @@ export const getWorkspaces = async (): Promise<{ name: string; isGitRepo: boolea
export const cloneRepository = async (url: string) => {
const config = plugin.registry.get('config').api
const token = config.get('settings/gist-access-token')
const repoConfig: cloneInputType = { url, token }
const repoConfig: cloneInputType = { url, token, depth: 10 }
if (plugin.registry.get('platform').api.isDesktop()) {
try {
@ -662,7 +672,7 @@ export const cloneRepository = async (url: string) => {
const repoName = await getRepositoryTitle(url)
await createWorkspace(repoName, 'blank', null, true, null, true, false)
const promise = dgitPlugin.call('dgitApi', 'clone', { ...repoConfig, workspaceExists: true, workspaceName: repoName })
const promise = dgitPlugin.call('dgitApi', 'clone', { ...repoConfig, workspaceExists: true, workspaceName: repoName, depth:10 })
dispatch(cloneRepositoryRequest())
promise

@ -36,7 +36,11 @@ export const FileExplorer = (props: FileExplorerProps) => {
const [state, setState] = useState<WorkSpaceState>(workspaceState)
// const [isPending, startTransition] = useTransition();
const treeRef = useRef<HTMLDivElement>(null)
const { plugin } = useContext(FileSystemContext)
const [feTarget, setFeTarget] = useState<{ key: string, type: 'file' | 'folder' }[]>({} as { key: string, type: 'file' | 'folder' }[])
const [filesSelected, setFilesSelected] = useState<string[]>([])
const feWindow = (window as any)
useEffect(() => {
if (contextMenuItems) {
@ -98,6 +102,100 @@ export const FileExplorer = (props: FileExplorerProps) => {
}
}, [treeRef.current])
useEffect(() => {
const performDeletion = async () => {
const path: string[] = []
if (feTarget?.length > 0 && feTarget[0]?.key.length > 0) {
feTarget.forEach((one) => {
path.push(one.key)
})
await deletePath(path)
}
}
if (treeRef.current) {
const deleteKeyPressHandler = async (eve: KeyboardEvent) => {
if (eve.key === 'Delete' ) {
feWindow._paq.push(['trackEvent', 'fileExplorer', 'deleteKey', 'deletePath'])
setState((prevState) => {
return { ...prevState, deleteKey: true }
})
performDeletion()
return
}
if (eve.metaKey) {
if (eve.key === 'Backspace') {
feWindow._paq.push(['trackEvent', 'fileExplorer', 'osxDeleteKey', 'deletePath'])
setState((prevState) => {
return { ...prevState, deleteKey: true }
})
performDeletion()
return
}
}
}
const deleteKeyPressUpHandler = async (eve: KeyboardEvent) => {
if (eve.key === 'Delete' ) {
setState((prevState) => {
return { ...prevState, deleteKey: false }
})
return
}
if (eve.metaKey) {
if (eve.key === 'Backspace') {
setState((prevState) => {
return { ...prevState, deleteKey: false }
})
return
}
}
}
treeRef.current?.addEventListener('keydown', deleteKeyPressHandler)
treeRef.current?.addEventListener('keyup', deleteKeyPressUpHandler)
return () => {
treeRef.current?.removeEventListener('keydown', deleteKeyPressHandler)
treeRef.current?.removeEventListener('keyup', deleteKeyPressUpHandler)
}
}
}, [treeRef.current, feTarget])
useEffect(() => {
const performRename = async () => {
if (feTarget?.length > 1 && feTarget[0]?.key.length > 1) {
await plugin.call('notification', 'alert', { id: 'renameAlert', message: 'You cannot rename multiple files at once!' })
}
props.editModeOn(feTarget[0].key, feTarget[0].type, false)
}
if (treeRef.current) {
const F2KeyPressHandler = async (eve: KeyboardEvent) => {
if (eve.key === 'F2' ) {
feWindow._paq.push(['trackEvent', 'fileExplorer', 'f2ToRename', 'RenamePath'])
await performRename()
setState((prevState) => {
return { ...prevState, F2Key: true }
})
return
}
}
const F2KeyPressUpHandler = async (eve: KeyboardEvent) => {
if (eve.key === 'F2' ) {
setState((prevState) => {
return { ...prevState, F2Key: false }
})
return
}
}
treeRef.current?.addEventListener('keydown', F2KeyPressHandler)
treeRef.current?.addEventListener('keyup', F2KeyPressUpHandler)
return () => {
treeRef.current?.removeEventListener('keydown', F2KeyPressHandler)
treeRef.current?.removeEventListener('keyup', F2KeyPressUpHandler)
}
}
}, [treeRef.current, feTarget])
const hasReservedKeyword = (content: string): boolean => {
if (state.reservedKeywords.findIndex((value) => content.startsWith(value)) !== -1) return true
else return false
@ -434,6 +532,8 @@ export const FileExplorer = (props: FileExplorerProps) => {
createNewFolder={props.createNewFolder}
deletePath={deletePath}
editPath={props.editModeOn}
fileTarget={feTarget}
setTargetFiles={setFeTarget}
/>
</div>
</div>

@ -26,6 +26,8 @@ export default function useOnScreen(ref: RefObject<HTMLElement>) {
return isIntersecting
}
interface FlatTreeProps {
fileTarget: any
setTargetFiles: React.Dispatch<any>
files: { [x: string]: Record<string, FileType> },
flatTree: FileType[],
expandPath: string[],
@ -115,6 +117,12 @@ export const FlatTree = (props: FlatTreeProps) => {
? 'bg-light border-no-shift'
: ''
useEffect(() => {
if (props.focusElement && props.focusElement.length > 0) {
props.setTargetFiles(props.focusElement)
}
}, [props.focusElement, props.focusElement.length])
const getIndentLevelDiv = (path: string) => {
// remove double slash
path = path.replace(/\/\//g, '/')

@ -203,6 +203,8 @@ export interface FileExplorerContextMenuProps {
export interface WorkSpaceState {
ctrlKey: boolean
deleteKey?: boolean
F2Key?: boolean
newFileName: string
actions: {
id: string

@ -0,0 +1,49 @@
// eslint-disable-next-line no-unused-vars
import axios, { AxiosResponse } from 'axios'
export type GithubItem = {
name: string
path: string
sha: string
size: number
url: string
html_url: string
git_url: string
download_url: string | null
type: 'dir' | 'file'
_links: {
self: string
git: string
html: string
}
}
export const githubFolderResolver = async (url, obj = {}, maxDepth, depth?, rootPath?) => {
depth = depth ? depth : 0
const child = await pullFolder(url)
depth = depth++
const urlObj = new URL(url);
const pathname = urlObj.pathname;
const pathParts = pathname.split('/');
const folderPath = pathParts.slice(5).join('/');
rootPath = rootPath || folderPath
if (!Array.isArray(child)) return obj
for (const item of child) {
if (item.type === 'file') {
const response: AxiosResponse = await axios.get(item.download_url)
obj[item.path.replace(rootPath, '')] = response.data
} else if (maxDepth > depth) {
// dir
await githubFolderResolver(item.html_url, obj, maxDepth, depth, rootPath)
}
}
return obj
}
const pullFolder = async (url) => {
const response = await axios.get('https://ghfolderpull.remixproject.org', { params: { ghfolder: url } });
const data: Array<GithubItem> = await response.data;
return data
}

@ -1 +1,2 @@
export { RemixURLResolver } from './resolve'
export { githubFolderResolver } from './github-folder-resolver'

Loading…
Cancel
Save