Merge branch 'master' of https://github.com/ethereum/remix-project into desktop-master
commit
469682896a
@ -0,0 +1,13 @@ |
|||||||
|
{ |
||||||
|
"scripts": { |
||||||
|
"start:server": "npx ts-node server.ts" |
||||||
|
}, |
||||||
|
"dependencies": { |
||||||
|
"body-parser": "^1.20.2", |
||||||
|
"child_process": "^1.0.2", |
||||||
|
"express": "^4.19.2", |
||||||
|
"git-http-backend": "^1.1.2", |
||||||
|
"path": "^0.12.7", |
||||||
|
"zlib": "^1.0.5" |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,48 @@ |
|||||||
|
import * as http from 'http'; |
||||||
|
import { spawn } from 'child_process'; |
||||||
|
import * as path from 'path'; |
||||||
|
let backend = require('git-http-backend'); |
||||||
|
import * as zlib from 'zlib'; |
||||||
|
|
||||||
|
const directory = process.argv[2]; |
||||||
|
|
||||||
|
if (!directory) { |
||||||
|
console.error('Please provide a directory as a command line argument.'); |
||||||
|
process.exit(1); |
||||||
|
} |
||||||
|
|
||||||
|
const server = http.createServer((req, res) => { |
||||||
|
|
||||||
|
// Set CORS headers
|
||||||
|
res.setHeader('Access-Control-Allow-Origin', '*'); |
||||||
|
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE'); |
||||||
|
res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type'); |
||||||
|
res.setHeader('Access-Control-Allow-Credentials', 'true'); |
||||||
|
|
||||||
|
if (req.method === 'OPTIONS') { |
||||||
|
// Handle preflight request
|
||||||
|
res.writeHead(204); |
||||||
|
res.end(); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
const repo = req.url?.split('/')[1]; |
||||||
|
const dir = path.join(directory, 'git', repo || ''); |
||||||
|
console.log(dir); |
||||||
|
const reqStream = req.headers['content-encoding'] === 'gzip' ? req.pipe(zlib.createGunzip()) : req; |
||||||
|
|
||||||
|
reqStream.pipe(backend(req.url || '', (err, service) => { |
||||||
|
if (err) return res.end(err + '\n'); |
||||||
|
|
||||||
|
res.setHeader('content-type', service.type); |
||||||
|
console.log(service.action, repo, service.fields); |
||||||
|
|
||||||
|
const ps = spawn(service.cmd, [...service.args, dir]); |
||||||
|
ps.stdout.pipe(service.createStream()).pipe(ps.stdin); |
||||||
|
|
||||||
|
})).pipe(res); |
||||||
|
}); |
||||||
|
|
||||||
|
server.listen(6868, () => { |
||||||
|
console.log('Server is listening on port 6868'); |
||||||
|
}); |
@ -0,0 +1,7 @@ |
|||||||
|
|
||||||
|
cd /tmp/ |
||||||
|
rm -rf git/bare.git |
||||||
|
rm -rf git |
||||||
|
mkdir -p git |
||||||
|
cd git |
||||||
|
git clone --bare https://github.com/ethereum/awesome-remix bare.git |
@ -0,0 +1,511 @@ |
|||||||
|
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. |
||||||
|
# yarn lockfile v1 |
||||||
|
|
||||||
|
|
||||||
|
accepts@~1.3.8: |
||||||
|
version "1.3.8" |
||||||
|
resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" |
||||||
|
integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== |
||||||
|
dependencies: |
||||||
|
mime-types "~2.1.34" |
||||||
|
negotiator "0.6.3" |
||||||
|
|
||||||
|
array-flatten@1.1.1: |
||||||
|
version "1.1.1" |
||||||
|
resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" |
||||||
|
integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== |
||||||
|
|
||||||
|
body-parser@1.20.2, body-parser@^1.20.2: |
||||||
|
version "1.20.2" |
||||||
|
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.2.tgz#6feb0e21c4724d06de7ff38da36dad4f57a747fd" |
||||||
|
integrity sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA== |
||||||
|
dependencies: |
||||||
|
bytes "3.1.2" |
||||||
|
content-type "~1.0.5" |
||||||
|
debug "2.6.9" |
||||||
|
depd "2.0.0" |
||||||
|
destroy "1.2.0" |
||||||
|
http-errors "2.0.0" |
||||||
|
iconv-lite "0.4.24" |
||||||
|
on-finished "2.4.1" |
||||||
|
qs "6.11.0" |
||||||
|
raw-body "2.5.2" |
||||||
|
type-is "~1.6.18" |
||||||
|
unpipe "1.0.0" |
||||||
|
|
||||||
|
bytes@3.1.2: |
||||||
|
version "3.1.2" |
||||||
|
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" |
||||||
|
integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== |
||||||
|
|
||||||
|
call-bind@^1.0.7: |
||||||
|
version "1.0.7" |
||||||
|
resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9" |
||||||
|
integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w== |
||||||
|
dependencies: |
||||||
|
es-define-property "^1.0.0" |
||||||
|
es-errors "^1.3.0" |
||||||
|
function-bind "^1.1.2" |
||||||
|
get-intrinsic "^1.2.4" |
||||||
|
set-function-length "^1.2.1" |
||||||
|
|
||||||
|
child_process@^1.0.2: |
||||||
|
version "1.0.2" |
||||||
|
resolved "https://registry.yarnpkg.com/child_process/-/child_process-1.0.2.tgz#b1f7e7fc73d25e7fd1d455adc94e143830182b5a" |
||||||
|
integrity sha512-Wmza/JzL0SiWz7kl6MhIKT5ceIlnFPJX+lwUGj7Clhy5MMldsSoJR0+uvRzOS5Kv45Mq7t1PoE8TsOA9bzvb6g== |
||||||
|
|
||||||
|
content-disposition@0.5.4: |
||||||
|
version "0.5.4" |
||||||
|
resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" |
||||||
|
integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== |
||||||
|
dependencies: |
||||||
|
safe-buffer "5.2.1" |
||||||
|
|
||||||
|
content-type@~1.0.4, content-type@~1.0.5: |
||||||
|
version "1.0.5" |
||||||
|
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" |
||||||
|
integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== |
||||||
|
|
||||||
|
cookie-signature@1.0.6: |
||||||
|
version "1.0.6" |
||||||
|
resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" |
||||||
|
integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== |
||||||
|
|
||||||
|
cookie@0.6.0: |
||||||
|
version "0.6.0" |
||||||
|
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.6.0.tgz#2798b04b071b0ecbff0dbb62a505a8efa4e19051" |
||||||
|
integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw== |
||||||
|
|
||||||
|
debug@2.6.9: |
||||||
|
version "2.6.9" |
||||||
|
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" |
||||||
|
integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== |
||||||
|
dependencies: |
||||||
|
ms "2.0.0" |
||||||
|
|
||||||
|
define-data-property@^1.1.4: |
||||||
|
version "1.1.4" |
||||||
|
resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" |
||||||
|
integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== |
||||||
|
dependencies: |
||||||
|
es-define-property "^1.0.0" |
||||||
|
es-errors "^1.3.0" |
||||||
|
gopd "^1.0.1" |
||||||
|
|
||||||
|
depd@2.0.0: |
||||||
|
version "2.0.0" |
||||||
|
resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" |
||||||
|
integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== |
||||||
|
|
||||||
|
destroy@1.2.0: |
||||||
|
version "1.2.0" |
||||||
|
resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" |
||||||
|
integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== |
||||||
|
|
||||||
|
ee-first@1.1.1: |
||||||
|
version "1.1.1" |
||||||
|
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" |
||||||
|
integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== |
||||||
|
|
||||||
|
encodeurl@~1.0.2: |
||||||
|
version "1.0.2" |
||||||
|
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" |
||||||
|
integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== |
||||||
|
|
||||||
|
es-define-property@^1.0.0: |
||||||
|
version "1.0.0" |
||||||
|
resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.0.tgz#c7faefbdff8b2696cf5f46921edfb77cc4ba3845" |
||||||
|
integrity sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ== |
||||||
|
dependencies: |
||||||
|
get-intrinsic "^1.2.4" |
||||||
|
|
||||||
|
es-errors@^1.3.0: |
||||||
|
version "1.3.0" |
||||||
|
resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" |
||||||
|
integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== |
||||||
|
|
||||||
|
escape-html@~1.0.3: |
||||||
|
version "1.0.3" |
||||||
|
resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" |
||||||
|
integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== |
||||||
|
|
||||||
|
etag@~1.8.1: |
||||||
|
version "1.8.1" |
||||||
|
resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" |
||||||
|
integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== |
||||||
|
|
||||||
|
express@^4.19.2: |
||||||
|
version "4.19.2" |
||||||
|
resolved "https://registry.yarnpkg.com/express/-/express-4.19.2.tgz#e25437827a3aa7f2a827bc8171bbbb664a356465" |
||||||
|
integrity sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q== |
||||||
|
dependencies: |
||||||
|
accepts "~1.3.8" |
||||||
|
array-flatten "1.1.1" |
||||||
|
body-parser "1.20.2" |
||||||
|
content-disposition "0.5.4" |
||||||
|
content-type "~1.0.4" |
||||||
|
cookie "0.6.0" |
||||||
|
cookie-signature "1.0.6" |
||||||
|
debug "2.6.9" |
||||||
|
depd "2.0.0" |
||||||
|
encodeurl "~1.0.2" |
||||||
|
escape-html "~1.0.3" |
||||||
|
etag "~1.8.1" |
||||||
|
finalhandler "1.2.0" |
||||||
|
fresh "0.5.2" |
||||||
|
http-errors "2.0.0" |
||||||
|
merge-descriptors "1.0.1" |
||||||
|
methods "~1.1.2" |
||||||
|
on-finished "2.4.1" |
||||||
|
parseurl "~1.3.3" |
||||||
|
path-to-regexp "0.1.7" |
||||||
|
proxy-addr "~2.0.7" |
||||||
|
qs "6.11.0" |
||||||
|
range-parser "~1.2.1" |
||||||
|
safe-buffer "5.2.1" |
||||||
|
send "0.18.0" |
||||||
|
serve-static "1.15.0" |
||||||
|
setprototypeof "1.2.0" |
||||||
|
statuses "2.0.1" |
||||||
|
type-is "~1.6.18" |
||||||
|
utils-merge "1.0.1" |
||||||
|
vary "~1.1.2" |
||||||
|
|
||||||
|
finalhandler@1.2.0: |
||||||
|
version "1.2.0" |
||||||
|
resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.2.0.tgz#7d23fe5731b207b4640e4fcd00aec1f9207a7b32" |
||||||
|
integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg== |
||||||
|
dependencies: |
||||||
|
debug "2.6.9" |
||||||
|
encodeurl "~1.0.2" |
||||||
|
escape-html "~1.0.3" |
||||||
|
on-finished "2.4.1" |
||||||
|
parseurl "~1.3.3" |
||||||
|
statuses "2.0.1" |
||||||
|
unpipe "~1.0.0" |
||||||
|
|
||||||
|
forwarded@0.2.0: |
||||||
|
version "0.2.0" |
||||||
|
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" |
||||||
|
integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== |
||||||
|
|
||||||
|
fresh@0.5.2: |
||||||
|
version "0.5.2" |
||||||
|
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" |
||||||
|
integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== |
||||||
|
|
||||||
|
function-bind@^1.1.2: |
||||||
|
version "1.1.2" |
||||||
|
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" |
||||||
|
integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== |
||||||
|
|
||||||
|
get-intrinsic@^1.1.3, get-intrinsic@^1.2.4: |
||||||
|
version "1.2.4" |
||||||
|
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd" |
||||||
|
integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ== |
||||||
|
dependencies: |
||||||
|
es-errors "^1.3.0" |
||||||
|
function-bind "^1.1.2" |
||||||
|
has-proto "^1.0.1" |
||||||
|
has-symbols "^1.0.3" |
||||||
|
hasown "^2.0.0" |
||||||
|
|
||||||
|
git-http-backend@^1.1.2: |
||||||
|
version "1.1.2" |
||||||
|
resolved "https://registry.yarnpkg.com/git-http-backend/-/git-http-backend-1.1.2.tgz#fb6c7b261251df8c2bdf9dc30876a96ce323f545" |
||||||
|
integrity sha512-Gx7n/kyCEXGFZlCGmbsEsyeyabLs8XWeb+E/6842up7p3PktQS2/8rlNfB6hCagnW0pJ13Tn8E3yhOkKS6ihdg== |
||||||
|
dependencies: |
||||||
|
git-side-band-message "~0.0.3" |
||||||
|
inherits "~2.0.1" |
||||||
|
|
||||||
|
git-side-band-message@~0.0.3: |
||||||
|
version "0.0.3" |
||||||
|
resolved "https://registry.yarnpkg.com/git-side-band-message/-/git-side-band-message-0.0.3.tgz#b8a5348c2dcbf1949fd295c506014e26c3f26a46" |
||||||
|
integrity sha512-4Rq4xm1+zqCkmuHxRbGdA5ActF7F4UfgK8uI0B7ZfSkByZfikRuF7mqHlvqmycvqos7jpXNkgsZK7DThLLHG3w== |
||||||
|
|
||||||
|
gopd@^1.0.1: |
||||||
|
version "1.0.1" |
||||||
|
resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" |
||||||
|
integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== |
||||||
|
dependencies: |
||||||
|
get-intrinsic "^1.1.3" |
||||||
|
|
||||||
|
has-property-descriptors@^1.0.2: |
||||||
|
version "1.0.2" |
||||||
|
resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" |
||||||
|
integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== |
||||||
|
dependencies: |
||||||
|
es-define-property "^1.0.0" |
||||||
|
|
||||||
|
has-proto@^1.0.1: |
||||||
|
version "1.0.3" |
||||||
|
resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.3.tgz#b31ddfe9b0e6e9914536a6ab286426d0214f77fd" |
||||||
|
integrity sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q== |
||||||
|
|
||||||
|
has-symbols@^1.0.3: |
||||||
|
version "1.0.3" |
||||||
|
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" |
||||||
|
integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== |
||||||
|
|
||||||
|
hasown@^2.0.0: |
||||||
|
version "2.0.2" |
||||||
|
resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" |
||||||
|
integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== |
||||||
|
dependencies: |
||||||
|
function-bind "^1.1.2" |
||||||
|
|
||||||
|
http-errors@2.0.0: |
||||||
|
version "2.0.0" |
||||||
|
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" |
||||||
|
integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== |
||||||
|
dependencies: |
||||||
|
depd "2.0.0" |
||||||
|
inherits "2.0.4" |
||||||
|
setprototypeof "1.2.0" |
||||||
|
statuses "2.0.1" |
||||||
|
toidentifier "1.0.1" |
||||||
|
|
||||||
|
iconv-lite@0.4.24: |
||||||
|
version "0.4.24" |
||||||
|
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" |
||||||
|
integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== |
||||||
|
dependencies: |
||||||
|
safer-buffer ">= 2.1.2 < 3" |
||||||
|
|
||||||
|
inherits@2.0.3: |
||||||
|
version "2.0.3" |
||||||
|
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" |
||||||
|
integrity sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw== |
||||||
|
|
||||||
|
inherits@2.0.4, inherits@~2.0.1: |
||||||
|
version "2.0.4" |
||||||
|
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" |
||||||
|
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== |
||||||
|
|
||||||
|
ipaddr.js@1.9.1: |
||||||
|
version "1.9.1" |
||||||
|
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" |
||||||
|
integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== |
||||||
|
|
||||||
|
media-typer@0.3.0: |
||||||
|
version "0.3.0" |
||||||
|
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" |
||||||
|
integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== |
||||||
|
|
||||||
|
merge-descriptors@1.0.1: |
||||||
|
version "1.0.1" |
||||||
|
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" |
||||||
|
integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== |
||||||
|
|
||||||
|
methods@~1.1.2: |
||||||
|
version "1.1.2" |
||||||
|
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" |
||||||
|
integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== |
||||||
|
|
||||||
|
mime-db@1.52.0: |
||||||
|
version "1.52.0" |
||||||
|
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" |
||||||
|
integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== |
||||||
|
|
||||||
|
mime-types@~2.1.24, mime-types@~2.1.34: |
||||||
|
version "2.1.35" |
||||||
|
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" |
||||||
|
integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== |
||||||
|
dependencies: |
||||||
|
mime-db "1.52.0" |
||||||
|
|
||||||
|
mime@1.6.0: |
||||||
|
version "1.6.0" |
||||||
|
resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" |
||||||
|
integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== |
||||||
|
|
||||||
|
ms@2.0.0: |
||||||
|
version "2.0.0" |
||||||
|
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" |
||||||
|
integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== |
||||||
|
|
||||||
|
ms@2.1.3: |
||||||
|
version "2.1.3" |
||||||
|
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" |
||||||
|
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== |
||||||
|
|
||||||
|
negotiator@0.6.3: |
||||||
|
version "0.6.3" |
||||||
|
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" |
||||||
|
integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== |
||||||
|
|
||||||
|
object-inspect@^1.13.1: |
||||||
|
version "1.13.1" |
||||||
|
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.1.tgz#b96c6109324ccfef6b12216a956ca4dc2ff94bc2" |
||||||
|
integrity sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ== |
||||||
|
|
||||||
|
on-finished@2.4.1: |
||||||
|
version "2.4.1" |
||||||
|
resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" |
||||||
|
integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== |
||||||
|
dependencies: |
||||||
|
ee-first "1.1.1" |
||||||
|
|
||||||
|
parseurl@~1.3.3: |
||||||
|
version "1.3.3" |
||||||
|
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" |
||||||
|
integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== |
||||||
|
|
||||||
|
path-to-regexp@0.1.7: |
||||||
|
version "0.1.7" |
||||||
|
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" |
||||||
|
integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== |
||||||
|
|
||||||
|
path@^0.12.7: |
||||||
|
version "0.12.7" |
||||||
|
resolved "https://registry.yarnpkg.com/path/-/path-0.12.7.tgz#d4dc2a506c4ce2197eb481ebfcd5b36c0140b10f" |
||||||
|
integrity sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q== |
||||||
|
dependencies: |
||||||
|
process "^0.11.1" |
||||||
|
util "^0.10.3" |
||||||
|
|
||||||
|
process@^0.11.1: |
||||||
|
version "0.11.10" |
||||||
|
resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" |
||||||
|
integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== |
||||||
|
|
||||||
|
proxy-addr@~2.0.7: |
||||||
|
version "2.0.7" |
||||||
|
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" |
||||||
|
integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== |
||||||
|
dependencies: |
||||||
|
forwarded "0.2.0" |
||||||
|
ipaddr.js "1.9.1" |
||||||
|
|
||||||
|
qs@6.11.0: |
||||||
|
version "6.11.0" |
||||||
|
resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" |
||||||
|
integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== |
||||||
|
dependencies: |
||||||
|
side-channel "^1.0.4" |
||||||
|
|
||||||
|
range-parser@~1.2.1: |
||||||
|
version "1.2.1" |
||||||
|
resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" |
||||||
|
integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== |
||||||
|
|
||||||
|
raw-body@2.5.2: |
||||||
|
version "2.5.2" |
||||||
|
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.2.tgz#99febd83b90e08975087e8f1f9419a149366b68a" |
||||||
|
integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA== |
||||||
|
dependencies: |
||||||
|
bytes "3.1.2" |
||||||
|
http-errors "2.0.0" |
||||||
|
iconv-lite "0.4.24" |
||||||
|
unpipe "1.0.0" |
||||||
|
|
||||||
|
safe-buffer@5.2.1: |
||||||
|
version "5.2.1" |
||||||
|
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" |
||||||
|
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== |
||||||
|
|
||||||
|
"safer-buffer@>= 2.1.2 < 3": |
||||||
|
version "2.1.2" |
||||||
|
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" |
||||||
|
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== |
||||||
|
|
||||||
|
send@0.18.0: |
||||||
|
version "0.18.0" |
||||||
|
resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" |
||||||
|
integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg== |
||||||
|
dependencies: |
||||||
|
debug "2.6.9" |
||||||
|
depd "2.0.0" |
||||||
|
destroy "1.2.0" |
||||||
|
encodeurl "~1.0.2" |
||||||
|
escape-html "~1.0.3" |
||||||
|
etag "~1.8.1" |
||||||
|
fresh "0.5.2" |
||||||
|
http-errors "2.0.0" |
||||||
|
mime "1.6.0" |
||||||
|
ms "2.1.3" |
||||||
|
on-finished "2.4.1" |
||||||
|
range-parser "~1.2.1" |
||||||
|
statuses "2.0.1" |
||||||
|
|
||||||
|
serve-static@1.15.0: |
||||||
|
version "1.15.0" |
||||||
|
resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540" |
||||||
|
integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g== |
||||||
|
dependencies: |
||||||
|
encodeurl "~1.0.2" |
||||||
|
escape-html "~1.0.3" |
||||||
|
parseurl "~1.3.3" |
||||||
|
send "0.18.0" |
||||||
|
|
||||||
|
set-function-length@^1.2.1: |
||||||
|
version "1.2.2" |
||||||
|
resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" |
||||||
|
integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== |
||||||
|
dependencies: |
||||||
|
define-data-property "^1.1.4" |
||||||
|
es-errors "^1.3.0" |
||||||
|
function-bind "^1.1.2" |
||||||
|
get-intrinsic "^1.2.4" |
||||||
|
gopd "^1.0.1" |
||||||
|
has-property-descriptors "^1.0.2" |
||||||
|
|
||||||
|
setprototypeof@1.2.0: |
||||||
|
version "1.2.0" |
||||||
|
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" |
||||||
|
integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== |
||||||
|
|
||||||
|
side-channel@^1.0.4: |
||||||
|
version "1.0.6" |
||||||
|
resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2" |
||||||
|
integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA== |
||||||
|
dependencies: |
||||||
|
call-bind "^1.0.7" |
||||||
|
es-errors "^1.3.0" |
||||||
|
get-intrinsic "^1.2.4" |
||||||
|
object-inspect "^1.13.1" |
||||||
|
|
||||||
|
statuses@2.0.1: |
||||||
|
version "2.0.1" |
||||||
|
resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" |
||||||
|
integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== |
||||||
|
|
||||||
|
toidentifier@1.0.1: |
||||||
|
version "1.0.1" |
||||||
|
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" |
||||||
|
integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== |
||||||
|
|
||||||
|
type-is@~1.6.18: |
||||||
|
version "1.6.18" |
||||||
|
resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" |
||||||
|
integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== |
||||||
|
dependencies: |
||||||
|
media-typer "0.3.0" |
||||||
|
mime-types "~2.1.24" |
||||||
|
|
||||||
|
unpipe@1.0.0, unpipe@~1.0.0: |
||||||
|
version "1.0.0" |
||||||
|
resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" |
||||||
|
integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== |
||||||
|
|
||||||
|
util@^0.10.3: |
||||||
|
version "0.10.4" |
||||||
|
resolved "https://registry.yarnpkg.com/util/-/util-0.10.4.tgz#3aa0125bfe668a4672de58857d3ace27ecb76901" |
||||||
|
integrity sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A== |
||||||
|
dependencies: |
||||||
|
inherits "2.0.3" |
||||||
|
|
||||||
|
utils-merge@1.0.1: |
||||||
|
version "1.0.1" |
||||||
|
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" |
||||||
|
integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== |
||||||
|
|
||||||
|
vary@~1.1.2: |
||||||
|
version "1.1.2" |
||||||
|
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" |
||||||
|
integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== |
||||||
|
|
||||||
|
zlib@^1.0.5: |
||||||
|
version "1.0.5" |
||||||
|
resolved "https://registry.yarnpkg.com/zlib/-/zlib-1.0.5.tgz#6e7c972fc371c645a6afb03ab14769def114fcc0" |
||||||
|
integrity sha512-40fpE2II+Cd3k8HWTWONfeKE2jL+P42iWJ1zzps5W51qcTsOUKM5Q5m2PFb0CLxlmFAaUuUdJGc3OfZy947v0w== |
@ -0,0 +1,312 @@ |
|||||||
|
import { ChildProcess, spawn } from "child_process" |
||||||
|
import init from "../helpers/init" |
||||||
|
import { Nightwatch, NightwatchBrowser } from "nightwatch" |
||||||
|
|
||||||
|
|
||||||
|
module.exports = { |
||||||
|
'@disabled': true, |
||||||
|
before: function (browser, done) { |
||||||
|
init(browser, done) |
||||||
|
}, |
||||||
|
after: function (browser: NightwatchBrowser) { |
||||||
|
browser.perform((done) => { |
||||||
|
done() |
||||||
|
}) |
||||||
|
}, |
||||||
|
'Update settings for git #group1 #group2': function (browser: NightwatchBrowser) { |
||||||
|
browser. |
||||||
|
clickLaunchIcon('dgit') |
||||||
|
.waitForElementVisible('*[data-id="initgit-btn"]') |
||||||
|
.click('*[data-id="initgit-btn"]') |
||||||
|
.setValue('*[data-id="githubToken"]', process.env.dgit_token) |
||||||
|
.setValue('*[data-id="gitubUsername"]', 'git') |
||||||
|
.setValue('*[data-id="githubEmail"]', 'git@example.com') |
||||||
|
.click('*[data-id="saveGitHubCredentials"]') |
||||||
|
}, |
||||||
|
'check if the settings are loaded #group1 #group2': function (browser: NightwatchBrowser) { |
||||||
|
browser. |
||||||
|
click('*[data-id="github-panel"]') |
||||||
|
.waitForElementVisible('*[data-id="connected-as-bunsenstraat"]') |
||||||
|
.waitForElementVisible('*[data-id="connected-img-bunsenstraat"]') |
||||||
|
.waitForElementVisible('*[data-id="connected-link-bunsenstraat"]') |
||||||
|
}, |
||||||
|
'clone a repository #group1': function (browser: NightwatchBrowser) { |
||||||
|
browser |
||||||
|
.click('*[data-id="clone-panel"]') |
||||||
|
.click({ |
||||||
|
selector: '//*[@data-id="clone-panel-content"]//*[@data-id="fetch-repositories"]', |
||||||
|
locateStrategy: 'xpath' |
||||||
|
}) |
||||||
|
.waitForElementVisible({ |
||||||
|
selector: '//*[@data-id="clone-panel-content"]//*[@id="repository-select"]', |
||||||
|
locateStrategy: 'xpath' |
||||||
|
}) |
||||||
|
.click({ |
||||||
|
selector: '//*[@data-id="clone-panel-content"]//*[@id="repository-select"]', |
||||||
|
locateStrategy: 'xpath' |
||||||
|
}) |
||||||
|
.waitForElementVisible({ |
||||||
|
selector: '//*[@data-id="clone-panel-content"]//*[contains(text(), "awesome-remix")]', |
||||||
|
locateStrategy: 'xpath' |
||||||
|
}) |
||||||
|
.click({ |
||||||
|
selector: '//*[@data-id="clone-panel-content"]//*[contains(text(), "awesome-remix")]', |
||||||
|
locateStrategy: 'xpath' |
||||||
|
}) |
||||||
|
.waitForElementVisible({ |
||||||
|
selector: '//*[@data-id="clone-panel-content"]//*[@id="branch-select"]', |
||||||
|
locateStrategy: 'xpath' |
||||||
|
}) |
||||||
|
.click({ |
||||||
|
selector: '//*[@data-id="clone-panel-content"]//*[@id="branch-select"]', |
||||||
|
locateStrategy: 'xpath' |
||||||
|
}) |
||||||
|
.click({ |
||||||
|
selector: '//*[@data-id="clone-panel-content"]//*[contains(text(), "master")]', |
||||||
|
locateStrategy: 'xpath' |
||||||
|
}) |
||||||
|
.waitForElementVisible({ |
||||||
|
selector: '//*[@data-id="clone-panel-content"]//*[@data-id="clonebtn-ethereum/awesome-remix-master"]', |
||||||
|
locateStrategy: 'xpath' |
||||||
|
}) |
||||||
|
.click({ |
||||||
|
selector: '//*[@data-id="clone-panel-content"]//*[@data-id="clonebtn-ethereum/awesome-remix-master"]', |
||||||
|
locateStrategy: 'xpath' |
||||||
|
}) |
||||||
|
}, |
||||||
|
'check if there is a README.md file #group1': function (browser: NightwatchBrowser) { |
||||||
|
browser |
||||||
|
.clickLaunchIcon('filePanel') |
||||||
|
.waitForElementVisible('*[data-id="treeViewLitreeViewItemREADME.md"]') |
||||||
|
}, |
||||||
|
'check the commands panel #group1': function (browser: NightwatchBrowser) { |
||||||
|
browser |
||||||
|
.clickLaunchIcon('dgit') |
||||||
|
.click('*[data-id="commands-panel"]') |
||||||
|
.waitForElementVisible({ |
||||||
|
selector: "//div[@id='commands-remote-branch-select']//div[contains(@class, 'singleValue') and contains(text(), 'master')]", |
||||||
|
locateStrategy: 'xpath' |
||||||
|
}) |
||||||
|
.waitForElementVisible({ |
||||||
|
selector: "//div[@id='commands-remote-origin-select']//div[contains(@class, 'singleValue') and contains(text(), 'origin')]", |
||||||
|
locateStrategy: 'xpath' |
||||||
|
}) |
||||||
|
.waitForElementVisible({ |
||||||
|
selector: "//div[@id='commands-local-branch-select']//div[contains(@class, 'singleValue') and contains(text(), 'master')]", |
||||||
|
locateStrategy: 'xpath' |
||||||
|
}) |
||||||
|
}, |
||||||
|
'check the remotes #group1': function (browser: NightwatchBrowser) { |
||||||
|
browser |
||||||
|
|
||||||
|
.click('*[data-id="remotes-panel"]') |
||||||
|
.waitForElementVisible('*[data-id="remotes-panel-content"]') |
||||||
|
.click({ |
||||||
|
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="remote-detail-origin"]', |
||||||
|
locateStrategy: 'xpath' |
||||||
|
}) |
||||||
|
.waitForElementVisible({ |
||||||
|
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="branches-current-branch-master"]', |
||||||
|
locateStrategy: 'xpath' |
||||||
|
}) |
||||||
|
.click({ |
||||||
|
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="remote-sync-origin"]', |
||||||
|
locateStrategy: 'xpath' |
||||||
|
}) |
||||||
|
.waitForElementVisible({ |
||||||
|
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="branches-branch-links"]', |
||||||
|
locateStrategy: 'xpath' |
||||||
|
}) |
||||||
|
|
||||||
|
}, |
||||||
|
'check the commits of branch links #group1': function (browser: NightwatchBrowser) { |
||||||
|
browser |
||||||
|
.waitForElementVisible({ |
||||||
|
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="branches-branch-links"]', |
||||||
|
locateStrategy: 'xpath' |
||||||
|
}) |
||||||
|
.click({ |
||||||
|
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="branches-branch-links"]', |
||||||
|
locateStrategy: 'xpath' |
||||||
|
}) |
||||||
|
.waitForElementVisible({ |
||||||
|
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="commit-summary-linking fixed-"]', |
||||||
|
locateStrategy: 'xpath' |
||||||
|
}) |
||||||
|
}, |
||||||
|
'switch to branch links #group1': function (browser: NightwatchBrowser) { |
||||||
|
browser |
||||||
|
.waitForElementVisible({ |
||||||
|
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="branches-branch-links"]', |
||||||
|
locateStrategy: 'xpath' |
||||||
|
}) |
||||||
|
.click({ |
||||||
|
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="branches-toggle-branch-links"]', |
||||||
|
locateStrategy: 'xpath' |
||||||
|
}) |
||||||
|
.waitForElementVisible({ |
||||||
|
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="branches-toggle-current-branch-links"]', |
||||||
|
locateStrategy: 'xpath' |
||||||
|
}) |
||||||
|
}, |
||||||
|
'check the local branches #group1': function (browser: NightwatchBrowser) { |
||||||
|
browser |
||||||
|
.click('*[data-id="branches-panel"]') |
||||||
|
.waitForElementVisible({ |
||||||
|
selector: '//*[@data-id="branches-panel-content"]//*[@data-id="branches-toggle-current-branch-links"]', |
||||||
|
locateStrategy: 'xpath' |
||||||
|
}) |
||||||
|
}, |
||||||
|
'check the local commits #group1': function (browser: NightwatchBrowser) { |
||||||
|
browser |
||||||
|
.click('*[data-id="commits-panel"]') |
||||||
|
.pause(1000) |
||||||
|
.waitForElementVisible({ |
||||||
|
selector: '//*[@data-id="commits-current-branch-links"]//*[@data-id="commit-summary-linking fixed-"]', |
||||||
|
locateStrategy: 'xpath' |
||||||
|
}) |
||||||
|
.click({ |
||||||
|
selector: '//*[@data-id="commits-current-branch-links"]//*[@data-id="commit-summary-linking fixed-"]', |
||||||
|
locateStrategy: 'xpath' |
||||||
|
}) |
||||||
|
.waitForElementVisible({ |
||||||
|
selector: '//*[@data-id="commits-current-branch-links"]//*[@data-id="commit-change-modified-README.md"]', |
||||||
|
locateStrategy: 'xpath' |
||||||
|
}) |
||||||
|
}, |
||||||
|
'check the commands panel for links #group1': function (browser: NightwatchBrowser) { |
||||||
|
browser |
||||||
|
.click('*[data-id="commands-panel"]') |
||||||
|
.waitForElementVisible({ |
||||||
|
selector: "//div[@id='commands-remote-branch-select']//div[contains(@class, 'singleValue') and contains(text(), 'links')]", |
||||||
|
locateStrategy: 'xpath' |
||||||
|
}) |
||||||
|
.waitForElementVisible({ |
||||||
|
selector: "//div[@id='commands-remote-origin-select']//div[contains(@class, 'singleValue') and contains(text(), 'origin')]", |
||||||
|
locateStrategy: 'xpath' |
||||||
|
}) |
||||||
|
.waitForElementVisible({ |
||||||
|
selector: "//div[@id='commands-local-branch-select']//div[contains(@class, 'singleValue') and contains(text(), 'links')]", |
||||||
|
locateStrategy: 'xpath' |
||||||
|
}) |
||||||
|
}, |
||||||
|
|
||||||
|
'add a remote #group2': function (browser: NightwatchBrowser) { |
||||||
|
browser |
||||||
|
.pause(1000) |
||||||
|
.click('*[data-id="remotes-panel"]') |
||||||
|
.click({ |
||||||
|
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="fetch-repositories"]', |
||||||
|
locateStrategy: 'xpath' |
||||||
|
}) |
||||||
|
.waitForElementVisible({ |
||||||
|
selector: '//*[@data-id="remotes-panel-content"]//*[@id="repository-select"]', |
||||||
|
locateStrategy: 'xpath' |
||||||
|
}) |
||||||
|
.click({ |
||||||
|
selector: '//*[@data-id="remotes-panel-content"]//*[@id="repository-select"]', |
||||||
|
locateStrategy: 'xpath' |
||||||
|
}) |
||||||
|
.waitForElementVisible({ |
||||||
|
selector: '//*[@data-id="remotes-panel-content"]//*[contains(text(), "awesome-remix")]', |
||||||
|
locateStrategy: 'xpath' |
||||||
|
}) |
||||||
|
.click({ |
||||||
|
selector: '//*[@data-id="remotes-panel-content"]//*[contains(text(), "awesome-remix")]', |
||||||
|
locateStrategy: 'xpath' |
||||||
|
}) |
||||||
|
.waitForElementVisible({ |
||||||
|
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="remote-panel-remotename"]', |
||||||
|
locateStrategy: 'xpath' |
||||||
|
}) |
||||||
|
.setValue({ |
||||||
|
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="remote-panel-remotename"]', |
||||||
|
locateStrategy: 'xpath' |
||||||
|
}, 'newremote') |
||||||
|
.waitForElementVisible({ |
||||||
|
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="remote-panel-addremote"]', |
||||||
|
locateStrategy: 'xpath' |
||||||
|
}) |
||||||
|
.click({ |
||||||
|
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="remote-panel-addremote"]', |
||||||
|
locateStrategy: 'xpath' |
||||||
|
}) |
||||||
|
.waitForElementVisible({ |
||||||
|
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="remote-detail-newremote"]', |
||||||
|
locateStrategy: 'xpath' |
||||||
|
}) |
||||||
|
}, |
||||||
|
'check the commands panel for newremote #group2': function (browser: NightwatchBrowser) { |
||||||
|
browser |
||||||
|
.pause(1000) |
||||||
|
.click('*[data-id="commands-panel"]') |
||||||
|
.waitForElementVisible({ |
||||||
|
selector: "//div[@id='commands-remote-branch-select']//div[contains(@class, 'singleValue') and contains(text(), 'main')]", |
||||||
|
locateStrategy: 'xpath' |
||||||
|
}) |
||||||
|
.waitForElementVisible({ |
||||||
|
selector: "//div[@id='commands-remote-origin-select']//div[contains(@class, 'singleValue') and contains(text(), 'newremote')]", |
||||||
|
locateStrategy: 'xpath' |
||||||
|
}) |
||||||
|
.waitForElementVisible({ |
||||||
|
selector: "//div[@id='commands-local-branch-select']//div[contains(@class, 'singleValue') and contains(text(), 'main')]", |
||||||
|
locateStrategy: 'xpath' |
||||||
|
}) |
||||||
|
.getAttribute({ |
||||||
|
selector: '//*[@data-id="sourcecontrol-pull"]', |
||||||
|
locateStrategy: 'xpath' |
||||||
|
}, 'disabled', (result) => { |
||||||
|
if (result.value) { |
||||||
|
browser.assert.fail('Button is disabled') |
||||||
|
} else { |
||||||
|
browser.assert.ok(true) |
||||||
|
} |
||||||
|
}) |
||||||
|
}, |
||||||
|
'remove the remove #group2': function (browser: NightwatchBrowser) { |
||||||
|
browser |
||||||
|
.pause(1000) |
||||||
|
.click('*[data-id="remotes-panel"]') |
||||||
|
.waitForElementVisible({ |
||||||
|
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="remote-rm-newremote"]', |
||||||
|
locateStrategy: 'xpath' |
||||||
|
}) |
||||||
|
.pause(2000) |
||||||
|
.click({ |
||||||
|
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="remote-rm-newremote"]', |
||||||
|
locateStrategy: 'xpath' |
||||||
|
}) |
||||||
|
.pause(1000) |
||||||
|
.waitForElementNotPresent({ |
||||||
|
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="remote-detail-newremote"]', |
||||||
|
locateStrategy: 'xpath' |
||||||
|
}) |
||||||
|
}, |
||||||
|
'check the commands panel for removed remote #group2': function (browser: NightwatchBrowser) { |
||||||
|
browser |
||||||
|
.pause(1000) |
||||||
|
.click('*[data-id="commands-panel"]') |
||||||
|
.waitForElementVisible({ |
||||||
|
selector: "//div[@id='commands-remote-branch-select']//div[contains(@class, 'singleValue') and contains(text(), 'main')]", |
||||||
|
locateStrategy: 'xpath' |
||||||
|
}) |
||||||
|
.waitForElementNotPresent({ |
||||||
|
selector: "//div[@id='commands-remote-origin-select']//div[contains(@class, 'singleValue') and contains(text(), 'newremote')]", |
||||||
|
locateStrategy: 'xpath' |
||||||
|
}) |
||||||
|
.waitForElementVisible({ |
||||||
|
selector: "//div[@id='commands-local-branch-select']//div[contains(@class, 'singleValue') and contains(text(), 'main')]", |
||||||
|
locateStrategy: 'xpath' |
||||||
|
}) |
||||||
|
.getAttribute({ |
||||||
|
selector: '//*[@data-id="sourcecontrol-pull"]', |
||||||
|
locateStrategy: 'xpath' |
||||||
|
}, 'disabled', (result) => { |
||||||
|
if (result.value) { |
||||||
|
browser.assert.ok(true) |
||||||
|
} else { |
||||||
|
browser.assert.fail('Button is not disabled') |
||||||
|
} |
||||||
|
}) |
||||||
|
}, |
||||||
|
} |
@ -0,0 +1,521 @@ |
|||||||
|
import { ChildProcess, spawn } from "child_process" |
||||||
|
import kill from 'tree-kill' |
||||||
|
import init from "../helpers/init" |
||||||
|
import { Nightwatch, NightwatchBrowser } from "nightwatch" |
||||||
|
let gitserver: ChildProcess |
||||||
|
|
||||||
|
/* |
||||||
|
/ uses the git-http-backend package to create a git server ( if needed kill the server: kill -9 $(sudo lsof -t -i:6868) ) |
||||||
|
/ GROUP 1: file operations PUSH PULL COMMIT SYNC FETCH CLONE ADD |
||||||
|
/ GROUP 2: branch operations CREATE & PUBLISH |
||||||
|
/ GROUP 3: file operations rename delete |
||||||
|
*/ |
||||||
|
|
||||||
|
module.exports = { |
||||||
|
'@disabled': true, |
||||||
|
before: function (browser, done) { |
||||||
|
init(browser, done) |
||||||
|
}, |
||||||
|
after: function (browser: NightwatchBrowser) { |
||||||
|
browser.perform((done) => { |
||||||
|
console.log('kill server', gitserver.pid) |
||||||
|
kill(gitserver.pid) |
||||||
|
done() |
||||||
|
}) |
||||||
|
}, |
||||||
|
|
||||||
|
'run server #group1 #group2 #group3': function (browser: NightwatchBrowser) { |
||||||
|
browser.perform(async (done) => { |
||||||
|
gitserver = await spawnGitServer('/tmp/') |
||||||
|
console.log('working directory', process.cwd()) |
||||||
|
done() |
||||||
|
}) |
||||||
|
}, |
||||||
|
'Update settings for git #group1 #group2 #group3': function (browser: NightwatchBrowser) { |
||||||
|
browser. |
||||||
|
clickLaunchIcon('dgit') |
||||||
|
.waitForElementVisible('*[data-id="initgit-btn"]') |
||||||
|
.click('*[data-id="initgit-btn"]') |
||||||
|
.setValue('*[data-id="gitubUsername"]', 'git') |
||||||
|
.setValue('*[data-id="githubEmail"]', 'git@example.com') |
||||||
|
.click('*[data-id="saveGitHubCredentials"]') |
||||||
|
.modalFooterOKClick('github-credentials-error') |
||||||
|
.pause(2000) |
||||||
|
}, |
||||||
|
'clone a repo #group1 #group2 #group3': function (browser: NightwatchBrowser) { |
||||||
|
browser |
||||||
|
.waitForElementVisible('*[data-id="clone-panel"]') |
||||||
|
.click('*[data-id="clone-panel"]') |
||||||
|
.waitForElementVisible('*[data-id="clone-url"]') |
||||||
|
.setValue('*[data-id="clone-url"]', 'http://localhost:6868/bare.git') |
||||||
|
.waitForElementVisible('*[data-id="clone-btn"]') |
||||||
|
.click('*[data-id="clone-btn"]') |
||||||
|
.clickLaunchIcon('filePanel') |
||||||
|
.waitForElementVisible('*[data-id="treeViewLitreeViewItemREADME.md"]') |
||||||
|
}, |
||||||
|
|
||||||
|
// GROUP 1
|
||||||
|
|
||||||
|
'check file added #group1 #group3': function (browser: NightwatchBrowser) { |
||||||
|
browser. |
||||||
|
addFile('test.txt', { content: 'hello world' }, 'README.md') |
||||||
|
.clickLaunchIcon('dgit') |
||||||
|
.click('*[data-id="sourcecontrol-panel"]') |
||||||
|
.waitForElementVisible({ |
||||||
|
selector: "//*[@data-status='new-untracked' and @data-file='/test.txt']", |
||||||
|
locateStrategy: 'xpath' |
||||||
|
}) |
||||||
|
.waitForElementVisible('*[data-id="addToGitChangestest.txt"]') |
||||||
|
.pause(1000) |
||||||
|
.click('*[data-id="addToGitChangestest.txt"]') |
||||||
|
.waitForElementVisible({ |
||||||
|
selector: "//*[@data-status='added-staged' and @data-file='/test.txt']", |
||||||
|
locateStrategy: 'xpath' |
||||||
|
}) |
||||||
|
.setValue('*[data-id="commitMessage"]', 'testcommit') |
||||||
|
.click('*[data-id="commitButton"]') |
||||||
|
}, |
||||||
|
'look at the commit #group1': function (browser: NightwatchBrowser) { |
||||||
|
browser |
||||||
|
.click('*[data-id="commits-panel"]') |
||||||
|
.waitForElementPresent({ |
||||||
|
selector: '//*[@data-id="commit-summary-testcommit-ahead"]', |
||||||
|
locateStrategy: 'xpath' |
||||||
|
}) |
||||||
|
}, |
||||||
|
'sync the commit #group1': function (browser: NightwatchBrowser) { |
||||||
|
browser |
||||||
|
.pause(1000) |
||||||
|
.waitForElementVisible('*[data-id="sourcecontrol-panel"]') |
||||||
|
.click('*[data-id="sourcecontrol-panel"]') |
||||||
|
.waitForElementVisible('*[data-id="syncButton"]') |
||||||
|
.click('*[data-id="syncButton"]') |
||||||
|
.pause(2000) |
||||||
|
.waitForElementVisible('*[data-id="commitButton"]') |
||||||
|
.click('*[data-id="commits-panel"]') |
||||||
|
.waitForElementPresent({ |
||||||
|
selector: '//*[@data-id="commit-summary-testcommit-"]', |
||||||
|
locateStrategy: 'xpath' |
||||||
|
}) |
||||||
|
}, |
||||||
|
'check the log #group1': async function (browser: NightwatchBrowser) { |
||||||
|
const logs = await getGitLog('/tmp/git/bare.git') |
||||||
|
console.log(logs) |
||||||
|
browser.assert.ok(logs.includes('testcommit')) |
||||||
|
}, |
||||||
|
'change a file #group1': function (browser: NightwatchBrowser) { |
||||||
|
browser. |
||||||
|
openFile('test.txt'). |
||||||
|
pause(1000). |
||||||
|
setEditorValue('changes', null) |
||||||
|
}, |
||||||
|
'stage changed file #group1': function (browser: NightwatchBrowser) { |
||||||
|
browser |
||||||
|
.clickLaunchIcon('dgit') |
||||||
|
.click('*[data-id="sourcecontrol-panel"]') |
||||||
|
.waitForElementVisible({ |
||||||
|
selector: "//*[@data-status='modified-unstaged' and @data-file='/test.txt']", |
||||||
|
locateStrategy: 'xpath' |
||||||
|
}) |
||||||
|
.waitForElementVisible('*[data-id="addToGitChangestest.txt"]') |
||||||
|
.click('*[data-id="addToGitChangestest.txt"]') |
||||||
|
.waitForElementVisible({ |
||||||
|
selector: "//*[@data-status='modified-staged' and @data-file='/test.txt']", |
||||||
|
locateStrategy: 'xpath' |
||||||
|
}) |
||||||
|
.setValue('*[data-id="commitMessage"]', 'testcommit2') |
||||||
|
.click('*[data-id="commitButton"]') |
||||||
|
}, |
||||||
|
'push the commit #group1': function (browser: NightwatchBrowser) { |
||||||
|
browser |
||||||
|
.click('*[data-id="commands-panel"]') |
||||||
|
.waitForElementVisible('*[data-id="sourcecontrol-push"]') |
||||||
|
.click('*[data-id="sourcecontrol-push"]') |
||||||
|
.pause(2000) |
||||||
|
.click('*[data-id="commits-panel"]') |
||||||
|
.waitForElementPresent({ |
||||||
|
selector: '//*[@data-id="commit-summary-testcommit2-"]', |
||||||
|
locateStrategy: 'xpath' |
||||||
|
}).pause(2000) |
||||||
|
}, |
||||||
|
'check the log for testcommit2 #group1': async function (browser: NightwatchBrowser) { |
||||||
|
const logs = await getGitLog('/tmp/git/bare.git') |
||||||
|
console.log(logs) |
||||||
|
browser.assert.ok(logs.includes('testcommit2')) |
||||||
|
}, |
||||||
|
'clone locally and add a file and push #group1': async function (browser: NightwatchBrowser) { |
||||||
|
await cloneOnServer('http://localhost:6868/bare.git', '/tmp/') |
||||||
|
await onLocalGitRepoAddFile('/tmp/bare/', 'test2.txt') |
||||||
|
await createCommitOnLocalServer('/tmp/bare/', 'testlocal') |
||||||
|
await onLocalGitRepoPush('/tmp/bare/', 'master') |
||||||
|
}, |
||||||
|
'run a git fetch #group1': function (browser: NightwatchBrowser) { |
||||||
|
browser |
||||||
|
.pause(2000) |
||||||
|
.click('*[data-id="commands-panel"]') |
||||||
|
.waitForElementVisible('*[data-id="sourcecontrol-fetch-branch"]') |
||||||
|
.click('*[data-id="sourcecontrol-fetch-branch"]') |
||||||
|
.pause(2000) |
||||||
|
.click('*[data-id="commits-panel"]') |
||||||
|
.click('*[data-id="commits-panel-behind"]') |
||||||
|
.waitForElementPresent({ |
||||||
|
selector: '//*[@data-id="commit-summary-testlocal-"]', |
||||||
|
locateStrategy: 'xpath' |
||||||
|
}) |
||||||
|
}, |
||||||
|
'run pull from the header #group1': function (browser: NightwatchBrowser) { |
||||||
|
browser. |
||||||
|
click('*[data-id="sourcecontrol-button-pull"]') |
||||||
|
.waitForElementNotPresent('*[data-id="commits-panel-behind"]') |
||||||
|
}, |
||||||
|
'check if the file is added #group1': function (browser: NightwatchBrowser) { |
||||||
|
browser |
||||||
|
.clickLaunchIcon('filePanel') |
||||||
|
.waitForElementVisible('*[data-id="treeViewLitreeViewItemtest2.txt"]') |
||||||
|
}, |
||||||
|
|
||||||
|
// group 3
|
||||||
|
'rename a file #group3': function (browser: NightwatchBrowser) { |
||||||
|
browser |
||||||
|
.clickLaunchIcon('filePanel') |
||||||
|
.waitForElementVisible('*[data-id="treeViewLitreeViewItemtest.txt"]') |
||||||
|
.click('*[data-id="treeViewLitreeViewItemtest.txt"]') |
||||||
|
.renamePath('test.txt', 'test_rename', 'test_rename.txt') |
||||||
|
.waitForElementVisible('*[data-id="treeViewLitreeViewItemtest_rename.txt"]') |
||||||
|
.pause(1000) |
||||||
|
}, |
||||||
|
'stage renamed file #group3': function (browser: NightwatchBrowser) { |
||||||
|
browser |
||||||
|
.clickLaunchIcon('dgit') |
||||||
|
.waitForElementVisible({ |
||||||
|
selector: "//*[@data-status='deleted-unstaged' and @data-file='/test.txt']", |
||||||
|
locateStrategy: 'xpath' |
||||||
|
}) |
||||||
|
.waitForElementVisible('*[data-id="addToGitChangestest.txt"]') |
||||||
|
.waitForElementVisible({ |
||||||
|
selector: "//*[@data-status='new-untracked' and @data-file='/test_rename.txt']", |
||||||
|
locateStrategy: 'xpath' |
||||||
|
}) |
||||||
|
.click('*[data-id="sourcecontrol-add-all"]') |
||||||
|
.pause(2000) |
||||||
|
.waitForElementVisible({ |
||||||
|
selector: "//*[@data-status='deleted-staged' and @data-file='/test.txt']", |
||||||
|
locateStrategy: 'xpath' |
||||||
|
}) |
||||||
|
.waitForElementVisible({ |
||||||
|
selector: "//*[@data-status='added-staged' and @data-file='/test_rename.txt']", |
||||||
|
locateStrategy: 'xpath' |
||||||
|
}) |
||||||
|
}, |
||||||
|
'undo the rename #group3': function (browser: NightwatchBrowser) { |
||||||
|
browser |
||||||
|
|
||||||
|
.click('*[data-id="unDoStagedtest.txt"]') |
||||||
|
.pause(1000) |
||||||
|
.waitForElementNotPresent({ |
||||||
|
selector: "//*[@data-file='/test.txt']", |
||||||
|
locateStrategy: 'xpath' |
||||||
|
}) |
||||||
|
}, |
||||||
|
'check if file is returned #group3': function (browser: NightwatchBrowser) { |
||||||
|
browser |
||||||
|
.clickLaunchIcon('filePanel') |
||||||
|
.waitForElementVisible('*[data-id="treeViewLitreeViewItemtest.txt"]') |
||||||
|
}, |
||||||
|
|
||||||
|
|
||||||
|
// GROUP 2
|
||||||
|
'create a branch #group2': function (browser: NightwatchBrowser) { |
||||||
|
browser |
||||||
|
.clickLaunchIcon('dgit') |
||||||
|
.click('*[data-id="branches-panel"]') |
||||||
|
.waitForElementVisible('*[data-id="newbranchname"]') |
||||||
|
.setValue('*[data-id="newbranchname"]', 'testbranch') |
||||||
|
.click('*[data-id="sourcecontrol-create-branch"]') |
||||||
|
.waitForElementVisible('*[data-id="branches-current-branch-testbranch"]') |
||||||
|
.pause(1000) |
||||||
|
}, |
||||||
|
'check if the branch is in the filePanel #group2': function (browser: NightwatchBrowser) { |
||||||
|
browser |
||||||
|
.clickLaunchIcon('filePanel') |
||||||
|
.click('[data-id="workspaceGitBranchesDropdown"]') |
||||||
|
.expect.element('[data-id="workspaceGit-testbranch"]').text.to.contain('✓ ') |
||||||
|
}, |
||||||
|
'publish the branch #group2': function (browser: NightwatchBrowser) { |
||||||
|
browser |
||||||
|
.clickLaunchIcon('dgit') |
||||||
|
.waitForElementVisible('*[data-id="sourcecontrol-panel"]') |
||||||
|
.click('*[data-id="sourcecontrol-panel"]') |
||||||
|
.pause(1000) |
||||||
|
.click('*[data-id="publishBranchButton"]') |
||||||
|
.pause(2000) |
||||||
|
.waitForElementNotVisible('*[data-id="publishBranchButton"]') |
||||||
|
}, |
||||||
|
'check if the branch is published #group2': async function (browser: NightwatchBrowser) { |
||||||
|
const branches = await getBranches('/tmp/git/bare.git') |
||||||
|
browser.assert.ok(branches.includes('testbranch')) |
||||||
|
}, |
||||||
|
'add file to new branch #group2': function (browser: NightwatchBrowser) { |
||||||
|
browser |
||||||
|
.pause(1000) |
||||||
|
.addFile('test.txt', { content: 'hello world' }, 'README.md') |
||||||
|
.clickLaunchIcon('dgit') |
||||||
|
.pause(2000) |
||||||
|
.waitForElementVisible({ |
||||||
|
selector: "//*[@data-status='new-untracked' and @data-file='/test.txt']", |
||||||
|
locateStrategy: 'xpath' |
||||||
|
}) |
||||||
|
.waitForElementVisible('*[data-id="addToGitChangestest.txt"]') |
||||||
|
.pause(1000) |
||||||
|
.click('*[data-id="addToGitChangestest.txt"]') |
||||||
|
.waitForElementVisible({ |
||||||
|
selector: "//*[@data-status='added-staged' and @data-file='/test.txt']", |
||||||
|
locateStrategy: 'xpath' |
||||||
|
}) |
||||||
|
.setValue('*[data-id="commitMessage"]', 'testcommit') |
||||||
|
.click('*[data-id="commitButton"]') |
||||||
|
.pause(1000) |
||||||
|
}, |
||||||
|
'check if the commit is ahead in the branches list #group2': function (browser: NightwatchBrowser) { |
||||||
|
browser |
||||||
|
.waitForElementVisible('*[data-id="branches-panel"]') |
||||||
|
.click('*[data-id="branches-panel"]') |
||||||
|
.waitForElementVisible('*[data-id="branches-current-branch-testbranch"]') |
||||||
|
.click({ |
||||||
|
selector: "//*[@data-id='branches-panel-content']//*[@data-id='branches-current-branch-testbranch']", |
||||||
|
locateStrategy: 'xpath', |
||||||
|
suppressNotFoundErrors: true |
||||||
|
}) |
||||||
|
.click({ |
||||||
|
selector: "//*[@data-id='branches-panel-content']//*[@data-id='commits-panel-ahead']", |
||||||
|
locateStrategy: 'xpath', |
||||||
|
suppressNotFoundErrors: true |
||||||
|
}) |
||||||
|
.click({ |
||||||
|
selector: "//*[@data-id='branches-panel-content']//*[@data-id='branchdifference-commits-testbranch-ahead']//*[@data-id='commit-summary-testcommit-ahead']", |
||||||
|
locateStrategy: 'xpath', |
||||||
|
}) |
||||||
|
.click({ |
||||||
|
selector: "//*[@data-id='branches-panel-content']//*[@data-id='branchdifference-commits-testbranch-ahead']//*[@data-id='commit-change-added-test.txt']", |
||||||
|
locateStrategy: 'xpath', |
||||||
|
}) |
||||||
|
.click({ |
||||||
|
selector: "//*[@data-id='branches-panel-content']//*[@data-id='local-branch-commits-testbranch']//*[@data-id='commit-summary-testcommit-ahead']", |
||||||
|
locateStrategy: 'xpath', |
||||||
|
}) |
||||||
|
.waitForElementVisible({ |
||||||
|
selector: "//*[@data-id='branches-panel-content']//*[@data-id='local-branch-commits-testbranch']//*[@data-id='commit-change-added-test.txt']", |
||||||
|
locateStrategy: 'xpath', |
||||||
|
}) |
||||||
|
}, |
||||||
|
'switch back to master #group2': function (browser: NightwatchBrowser) { |
||||||
|
browser |
||||||
|
.click({ |
||||||
|
selector: "//*[@data-id='branches-panel-content']//*[@data-id='branches-toggle-branch-master']", |
||||||
|
locateStrategy: 'xpath', |
||||||
|
}) |
||||||
|
.waitForElementVisible({ |
||||||
|
selector: "//*[@data-id='branches-panel-content']//*[@data-id='branches-toggle-current-branch-master']", |
||||||
|
locateStrategy: 'xpath', |
||||||
|
}) |
||||||
|
}, |
||||||
|
'check if test file is gone #group2': function (browser: NightwatchBrowser) { |
||||||
|
browser |
||||||
|
.clickLaunchIcon('filePanel') |
||||||
|
.waitForElementNotPresent('*[data-id="treeViewLitreeViewItemtest.txt"]') |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
async function getBranches(path: string): Promise<string> { |
||||||
|
return new Promise((resolve, reject) => { |
||||||
|
const git = spawn('git', ['branch'], { cwd: path }) |
||||||
|
let branches = '' |
||||||
|
git.stdout.on('data', function (data) { |
||||||
|
console.log('stdout git branches', data.toString()) |
||||||
|
branches += data.toString() |
||||||
|
}) |
||||||
|
git.stderr.on('data', function (data) { |
||||||
|
console.log('stderr git branches', data.toString()) |
||||||
|
reject(data.toString()) |
||||||
|
}) |
||||||
|
git.on('close', function () { |
||||||
|
resolve(branches) |
||||||
|
}) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
async function getGitLog(path: string): Promise<string> { |
||||||
|
return new Promise((resolve, reject) => { |
||||||
|
const git = spawn('git', ['log'], { cwd: path }) |
||||||
|
let logs = '' |
||||||
|
git.stdout.on('data', function (data) { |
||||||
|
logs += data.toString() |
||||||
|
}) |
||||||
|
git.stderr.on('err', function (data) { |
||||||
|
reject(data.toString()) |
||||||
|
}) |
||||||
|
git.on('close', function () { |
||||||
|
resolve(logs) |
||||||
|
}) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
async function cloneOnServer(repo: string, path: string) { |
||||||
|
console.log('cloning', repo, path) |
||||||
|
return new Promise((resolve, reject) => { |
||||||
|
const git = spawn('rm -rf bare && git', ['clone', repo], { cwd: path, shell: true, detached: true }); |
||||||
|
|
||||||
|
git.stdout.on('data', function (data) { |
||||||
|
console.log('stdout data cloning', data.toString()); |
||||||
|
if (data.toString().includes('done')) { |
||||||
|
resolve(git); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
git.stderr.on('data', function (data) { |
||||||
|
console.log('stderr data cloning', data.toString()); |
||||||
|
if (data.toString().includes('into')) { |
||||||
|
setTimeout(() => { |
||||||
|
resolve(git); |
||||||
|
}, 5000) |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
git.on('error', (error) => { |
||||||
|
reject(`Process error: ${error.message}`); |
||||||
|
}); |
||||||
|
|
||||||
|
git.on('exit', (code, signal) => { |
||||||
|
if (code !== 0) { |
||||||
|
reject(`Process exited with code: ${code} and signal: ${signal}`); |
||||||
|
} |
||||||
|
}); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
async function onLocalGitRepoAddFile(path: string, file: string) { |
||||||
|
console.log('adding file', file) |
||||||
|
return new Promise((resolve, reject) => { |
||||||
|
const git = spawn('touch', [file], { cwd: path }); |
||||||
|
|
||||||
|
git.stdout.on('data', function (data) { |
||||||
|
console.log('stdout data adding file', data.toString()); |
||||||
|
if (data.toString().includes('done')) { |
||||||
|
resolve(git); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
git.stderr.on('data', function (data) { |
||||||
|
console.error('stderr adding file', data.toString()); |
||||||
|
reject(data.toString()); |
||||||
|
}); |
||||||
|
|
||||||
|
git.on('error', (error) => { |
||||||
|
reject(`Process error: ${error.message}`); |
||||||
|
}); |
||||||
|
|
||||||
|
git.on('exit', (code, signal) => { |
||||||
|
if (code !== 0) { |
||||||
|
reject(`Process exited with code: ${code} and signal: ${signal}`); |
||||||
|
} else { |
||||||
|
resolve(git); |
||||||
|
} |
||||||
|
}); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
async function onLocalGitRepoPush(path: string, branch: string = 'master') { |
||||||
|
console.log('pushing', path) |
||||||
|
return new Promise((resolve, reject) => { |
||||||
|
const git = spawn('git', ['push', 'origin', branch], { cwd: path, shell: true, detached: true }); |
||||||
|
|
||||||
|
git.stdout.on('data', function (data) { |
||||||
|
console.log('stdout data pushing', data.toString()); |
||||||
|
if (data.toString().includes('done')) { |
||||||
|
resolve(git); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
git.stderr.on('data', function (data) { |
||||||
|
console.error('stderr data pushing', data.toString()); |
||||||
|
if (data.toString().includes(branch)) { |
||||||
|
resolve(git); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
git.on('error', (error) => { |
||||||
|
reject(`Process error: ${error.message}`); |
||||||
|
}); |
||||||
|
|
||||||
|
git.on('exit', (code, signal) => { |
||||||
|
if (code !== 0) { |
||||||
|
reject(`Process exited with code: ${code} and signal: ${signal}`); |
||||||
|
} else { |
||||||
|
resolve(git); |
||||||
|
} |
||||||
|
}); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
async function createCommitOnLocalServer(path: string, message: string) { |
||||||
|
console.log('committing', message, path) |
||||||
|
return new Promise((resolve, reject) => { |
||||||
|
const git = spawn('git add . && git', ['commit', '-m', message], { cwd: path, shell: true, detached: true }); |
||||||
|
|
||||||
|
git.stdout.on('data', function (data) { |
||||||
|
console.log('data stdout committing', data.toString()); |
||||||
|
if (data.toString().includes(message)) { |
||||||
|
setTimeout(() => { |
||||||
|
resolve(git); |
||||||
|
}, 1000) |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
git.stderr.on('data', function (data) { |
||||||
|
console.error('data commiting', data.toString()); |
||||||
|
reject(data.toString()); |
||||||
|
}); |
||||||
|
|
||||||
|
git.on('error', (error) => { |
||||||
|
console.error('error', error); |
||||||
|
reject(`Process error: ${error.message}`); |
||||||
|
}); |
||||||
|
|
||||||
|
git.on('exit', (code, signal) => { |
||||||
|
if (code !== 0) { |
||||||
|
console.error('exit', code, signal); |
||||||
|
reject(`Process exited with code: ${code} and signal: ${signal}`); |
||||||
|
} else { |
||||||
|
resolve(git); |
||||||
|
} |
||||||
|
}); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
async function spawnGitServer(path: string): Promise<ChildProcess> { |
||||||
|
console.log(process.cwd()) |
||||||
|
try { |
||||||
|
const server = spawn('yarn && sh setup.sh && npx ts-node server.ts', [`${path}`], { cwd: process.cwd() + '/apps/remix-ide-e2e/src/githttpbackend/', shell: true, detached: true }) |
||||||
|
console.log('spawned', server.stdout.closed, server.stderr.closed) |
||||||
|
return new Promise((resolve, reject) => { |
||||||
|
server.stdout.on('data', function (data) { |
||||||
|
console.log(data.toString()) |
||||||
|
if ( |
||||||
|
data.toString().includes('is listening') |
||||||
|
|| data.toString().includes('address already in use') |
||||||
|
) { |
||||||
|
console.log('resolving') |
||||||
|
resolve(server) |
||||||
|
} |
||||||
|
}) |
||||||
|
server.stderr.on('err', function (data) { |
||||||
|
console.log(data.toString()) |
||||||
|
reject(data.toString()) |
||||||
|
}) |
||||||
|
}) |
||||||
|
} catch (e) { |
||||||
|
console.log(e) |
||||||
|
} |
||||||
|
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,40 @@ |
|||||||
|
'use strict' |
||||||
|
import { ViewPlugin } from '@remixproject/engine-web'; |
||||||
|
import React from 'react' // eslint-disable-line
|
||||||
|
import { gitState, GitUI } from '@remix-ui/git'; |
||||||
|
import * as packageJson from '../../../../../package.json' |
||||||
|
|
||||||
|
const profile = { |
||||||
|
name: 'dgit', |
||||||
|
desciption: 'Git plugin for Remix', |
||||||
|
methods: ['pull', 'track', 'diff', 'clone', 'open'], |
||||||
|
events: [''], |
||||||
|
version: packageJson.version, |
||||||
|
maintainedBy: 'Remix', |
||||||
|
permission: true, |
||||||
|
description: 'Use this plugin to interact with your git repositories', |
||||||
|
location: 'sidePanel', |
||||||
|
icon: "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTIxLjAwNyA4LjIyMTY4QzIxLjAxMDUgNy41Mjc5MiAyMC44MjA3IDYuODQ2ODkgMjAuNDU5MSA2LjI1NDg1QzIwLjA5NzQgNS42NjI4MSAxOS41NzggNS4xODMxNSAxOC45NTkyIDQuODY5NTdDMTguMzQwMyA0LjU1NiAxNy42NDYzIDQuNDIwOTEgMTYuOTU1MSA0LjQ3OTQxQzE2LjI2MzcgNC41Mzc5MyAxNS42MDI1IDQuNzg3NzMgMTUuMDQ1IDUuMjAwODVDMTQuNDg3NyA1LjYxMzk3IDE0LjA1NjMgNi4xNzQwOSAxMy43OTkzIDYuODE4NUMxMy41NDI0IDcuNDYyOSAxMy40Njk3IDguMTY2MTMgMTMuNTg5OCA4Ljg0OTQ0QzEzLjcwOTkgOS41MzI3NCAxNC4wMTc3IDEwLjE2OTIgMTQuNDc4OSAxMC42ODc0QzE0Ljk0MDIgMTEuMjA1NiAxNS41MzY3IDExLjU4NTIgMTYuMjAxNSAxMS43ODM2QzE1Ljk1NiAxMi4yODI0IDE1LjU3NjMgMTIuNzAzIDE1LjEwNDkgMTIuOTk3OUMxNC42MzM2IDEzLjI5MjkgMTQuMDg5NCAxMy40NTA1IDEzLjUzMzQgMTMuNDUzMkgxMC41NDRDOS40MzcyNiAxMy40NTcxIDguMzcxNjMgMTMuODcyNyA3LjU1NDUxIDE0LjYxOTFWNy4zOTgwOUM4LjQ2MTg0IDcuMjEyODggOS4yNjgwOCA2LjY5NzM3IDkuODE2OTIgNS45NTE1MUMxMC4zNjU4IDUuMjA1NjUgMTAuNjE4MSA0LjI4MjU2IDEwLjUyNSAzLjM2MTIxQzEwLjQzMTkgMi40Mzk4NyAxMC4wMDAxIDEuNTg1OSA5LjMxMzE2IDAuOTY0ODczQzguNjI2MjQgMC4zNDM4NDUgNy43MzMxOSAwIDYuODA3MTYgMEM1Ljg4MTEyIDAgNC45ODgwNyAwLjM0Mzg0NSA0LjMwMTE0IDAuOTY0ODczQzMuNjE0MjIgMS41ODU5IDMuMTgyMzYgMi40Mzk4NyAzLjA4OTI4IDMuMzYxMjFDMi45OTYyIDQuMjgyNTYgMy4yNDg1NSA1LjIwNTY1IDMuNzk3MzkgNS45NTE1MUM0LjM0NjIzIDYuNjk3MzcgNS4xNTI0NyA3LjIxMjg4IDYuMDU5OCA3LjM5ODA5VjE2LjUxNTlDNS4xNTQxOCAxNi42ODkxIDQuMzQzMjMgMTcuMTg3NyAzLjc3OTkzIDE3LjkxNzZDMy4yMTY2MyAxOC42NDc2IDIuOTM5OTIgMTkuNTU4NSAzLjAwMTk3IDIwLjQ3ODVDMy4wNjQwMyAyMS4zOTg0IDMuNDYwNTcgMjIuMjYzOSA0LjExNjggMjIuOTExNUM0Ljc3MzAzIDIzLjU1OTIgNS42NDM2IDIzLjk0NDQgNi41NjQyNyAyMy45OTQ0QzcuNDg0OTYgMjQuMDQ0NSA4LjM5MjExIDIzLjc1NTggOS4xMTQ2NCAyMy4xODNDOS44MzcxOCAyMi42MTAyIDEwLjMyNTEgMjEuNzkyOCAxMC40ODY1IDIwLjg4NUMxMC42NDc4IDE5Ljk3NzEgMTAuNDcxNCAxOS4wNDE3IDkuOTkwNDggMTguMjU1QzkuNTA5NTcgMTcuNDY4MyA4Ljc1NzQxIDE2Ljg4NDggNy44NzU4OCAxNi42MTQ1QzguMTIxNzYgMTYuMTE2MiA4LjUwMTY3IDE1LjY5NjMgOC45NzI5NiAxNS40MDE5QzkuNDQ0MjYgMTUuMTA3NCA5Ljk4ODI3IDE0Ljk1MDMgMTAuNTQ0IDE0Ljk0NzlIMTMuNTMzNEMxNC40NjYxIDE0Ljk0MzYgMTUuMzc0MiAxNC42NDg2IDE2LjEzMTMgMTQuMTAzOUMxNi44ODg0IDEzLjU1OTIgMTcuNDU2OCAxMi43OTIgMTcuNzU3NSAxMS45MDkxQzE4LjY1MzQgMTEuNzkxNCAxOS40NzYzIDExLjM1MjggMjAuMDczOCAxMC42NzQ4QzIwLjY3MTMgOS45OTY4IDIxLjAwMjggOS4xMjUzMyAyMS4wMDcgOC4yMjE2OFpNNC41NjUwOCAzLjczNzUyQzQuNTY1MDggMy4yOTQwOCA0LjY5NjU3IDIuODYwNiA0Ljk0MjkzIDIuNDkxOUM1LjE4OTMgMi4xMjMxOSA1LjUzOTQ3IDEuODM1ODEgNS45NDkxNSAxLjY2NjExQzYuMzU4ODQgMS40OTY0MiA2LjgwOTY1IDEuNDUyMDIgNy4yNDQ1NiAxLjUzODU0QzcuNjc5NDggMS42MjUwNCA4LjA3ODk4IDEuODM4NTcgOC4zOTI1NCAyLjE1MjE0QzguNzA2MTEgMi40NjU3IDguOTE5NjQgMi44NjUyIDkuMDA2MTUgMy4zMDAxMkM5LjA5MjY2IDMuNzM1MDQgOS4wNDgyNyA0LjE4NTg1IDguODc4NTcgNC41OTU1M0M4LjcwODg3IDUuMDA1MjEgOC40MjE0OSA1LjM1NTM5IDguMDUyNzggNS42MDE3NUM3LjY4NDA4IDUuODQ4MTEgNy4yNTA2IDUuOTc5NiA2LjgwNzE2IDUuOTc5NkM2LjIxMjUyIDUuOTc5NiA1LjY0MjI0IDUuNzQzMzkgNS4yMjE3NyA1LjMyMjkxQzQuODAxMjkgNC45MDI0NSA0LjU2NTA4IDQuMzMyMTYgNC41NjUwOCAzLjczNzUyWk05LjA0OTIzIDIwLjE3OTRDOS4wNDkyMyAyMC42MjI5IDguOTE3NzQgMjEuMDU2MyA4LjY3MTM4IDIxLjQyNUM4LjQyNTAxIDIxLjc5MzcgOC4wNzQ4NSAyMi4wODExIDcuNjY1MTYgMjIuMjUwOEM3LjI1NTQ3IDIyLjQyMDUgNi44MDQ2NiAyMi40NjQ5IDYuMzY5NzUgMjIuMzc4NEM1LjkzNDgzIDIyLjI5MiA1LjUzNTMzIDIyLjA3ODQgNS4yMjE3NyAyMS43NjQ4QzQuOTA4MjEgMjEuNDUxMiA0LjY5NDY3IDIxLjA1MTcgNC42MDgxNiAyMC42MTY5QzQuNTIxNjUgMjAuMTgxOSA0LjU2NjA1IDE5LjczMTEgNC43MzU3NSAxOS4zMjE0QzQuOTA1NDUgMTguOTExNyA1LjE5MjgyIDE4LjU2MTUgNS41NjE1MyAxOC4zMTUyQzUuOTMwMjMgMTguMDY4OSA2LjM2MzcxIDE3LjkzNzMgNi44MDcxNiAxNy45MzczQzcuNDAxNzkgMTcuOTM3MyA3Ljk3MjA3IDE4LjE3MzYgOC4zOTI1NCAxOC41OTRDOC44MTMwMiAxOS4wMTQ1IDkuMDQ5MjMgMTkuNTg0OCA5LjA0OTIzIDIwLjE3OTRaTTE3LjI3MDIgMTAuNDYzOEMxNi44MjY3IDEwLjQ2MzggMTYuMzkzMyAxMC4zMzIyIDE2LjAyNDYgMTAuMDg1OUMxNS42NTU5IDkuODM5NTQgMTUuMzY4NSA5LjQ4OTM3IDE1LjE5ODggOS4wNzk2OUMxNS4wMjkxIDguNjcgMTQuOTg0NyA4LjIxOTIgMTUuMDcxMiA3Ljc4NDI3QzE1LjE1NzYgNy4zNDkzNSAxNS4zNzEyIDYuOTQ5ODUgMTUuNjg0OCA2LjYzNjI5QzE1Ljk5ODQgNi4zMjI3MyAxNi4zOTc5IDYuMTA5MTkgMTYuODMyNyA2LjAyMjY4QzE3LjI2NzcgNS45MzYxNyAxNy43MTg1IDUuOTgwNTggMTguMTI4MSA2LjE1MDI3QzE4LjUzNzkgNi4zMTk5NyAxOC44ODgxIDYuNjA3MzQgMTkuMTM0NCA2Ljk3NjA1QzE5LjM4MDcgNy4zNDQ3NiAxOS41MTIzIDcuNzc4MjMgMTkuNTEyMyA4LjIyMTY4QzE5LjUxMjMgOC44MTYzMiAxOS4yNzYgOS4zODY2IDE4Ljg1NTYgOS44MDcwNkMxOC40MzUxIDEwLjIyNzUgMTcuODY0OCAxMC40NjM4IDE3LjI3MDIgMTAuNDYzOFoiIGZpbGw9IiM0MjQyNDIiLz4KPC9zdmc+Cg==" |
||||||
|
} |
||||||
|
|
||||||
|
export class GitPlugin extends ViewPlugin { |
||||||
|
|
||||||
|
constructor() { |
||||||
|
|
||||||
|
super(profile) |
||||||
|
} |
||||||
|
|
||||||
|
onDeactivation(): void { |
||||||
|
this.call('fileDecorator', 'clearFileDecorators') |
||||||
|
this.call('manager', 'activatePlugin', 'dgitApi') |
||||||
|
} |
||||||
|
|
||||||
|
async open(panel:string) { |
||||||
|
this.emit('openPanel', panel) |
||||||
|
} |
||||||
|
|
||||||
|
render() { |
||||||
|
return <div id='gitTab'><GitUI plugin={this} /></div> |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,26 @@ |
|||||||
|
'use strict' |
||||||
|
import { Plugin } from '@remixproject/engine' |
||||||
|
|
||||||
|
const _paq = window._paq = window._paq || [] |
||||||
|
|
||||||
|
const profile = { |
||||||
|
name: 'matomo', |
||||||
|
description: 'send analytics to Matomo', |
||||||
|
methods: ['track'], |
||||||
|
events: [''], |
||||||
|
version: '1.0.0' |
||||||
|
} |
||||||
|
|
||||||
|
const allowedPlugins = ['LearnEth', 'etherscan', 'vyper', 'circuit-compiler', 'doc-gen', 'doc-viewer', 'solhint', 'walletconnect', 'scriptRunner'] |
||||||
|
|
||||||
|
export class Matomo extends Plugin { |
||||||
|
|
||||||
|
constructor() { |
||||||
|
super(profile) |
||||||
|
} |
||||||
|
|
||||||
|
async track(data: string[]) { |
||||||
|
if (!allowedPlugins.includes(this.currentRequest.from)) return |
||||||
|
_paq.push(data) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,20 @@ |
|||||||
|
{ |
||||||
|
"git.push": "push", |
||||||
|
"git.pull": "pull", |
||||||
|
"git.commit": "commit", |
||||||
|
"git.sync": "sync", |
||||||
|
"git.syncchanges": "sync changes", |
||||||
|
"git.publish": "publish", |
||||||
|
"git.ignore": "ignore", |
||||||
|
"git.createBranch": "create branch", |
||||||
|
"git.deleteBranch": "delete branch", |
||||||
|
"git.mergeBranch": "merge branch", |
||||||
|
"git.rebaseBranch": "rebase branch", |
||||||
|
"git.checkout": "checkout", |
||||||
|
"git.fetch": "fetch", |
||||||
|
"git.refresh": "refresh", |
||||||
|
"git.unstageall": "unstage all", |
||||||
|
"git.stageall": "stage all", |
||||||
|
"git.noremote": "this repo has no remotes", |
||||||
|
"git.init": "Initialize repository" |
||||||
|
} |
@ -0,0 +1,9 @@ |
|||||||
|
export type branch = { |
||||||
|
name: string |
||||||
|
remote: remote |
||||||
|
} |
||||||
|
|
||||||
|
export type remote = { |
||||||
|
name: string |
||||||
|
url: string |
||||||
|
} |
@ -0,0 +1 @@ |
|||||||
|
export * from './lib/remix-api' |
@ -0,0 +1,11 @@ |
|||||||
|
import { StatusEvents } from "@remixproject/plugin-utils" |
||||||
|
|
||||||
|
export interface IConfigApi { |
||||||
|
events: { |
||||||
|
configChanged: () => void |
||||||
|
} & StatusEvents, |
||||||
|
methods: { |
||||||
|
getAppParameter(key: string): Promise<any>, |
||||||
|
setAppParameter(key: string, value: any): Promise<void> |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,12 @@ |
|||||||
|
import { IFilePanel } from '@remixproject/plugin-api' |
||||||
|
import { StatusEvents } from '@remixproject/plugin-utils' |
||||||
|
|
||||||
|
export interface IFilePanelApi { |
||||||
|
events: IFilePanel['events'] & { |
||||||
|
workspaceInitializationCompleted: () => void; |
||||||
|
switchToWorkspace: (workspace: string) => Promise<void>; |
||||||
|
} & StatusEvents |
||||||
|
methods: IFilePanel['methods'] & { |
||||||
|
|
||||||
|
} |
||||||
|
} |
@ -0,0 +1,10 @@ |
|||||||
|
import { commitChange } from "@remix-ui/git"; |
||||||
|
import { IFileSystem } from "@remixproject/plugin-api" |
||||||
|
|
||||||
|
// Extended interface with 'diff' method
|
||||||
|
export interface IExtendedFileSystem extends IFileSystem { |
||||||
|
methods: IFileSystem['methods'] & { |
||||||
|
diff(change: commitChange): Promise<void> |
||||||
|
isGitRepo(): Promise<boolean> |
||||||
|
}; |
||||||
|
} |
@ -0,0 +1,11 @@ |
|||||||
|
import { fileDecoration } from '@remix-ui/file-decorators' |
||||||
|
import { StatusEvents } from '@remixproject/plugin-utils' |
||||||
|
|
||||||
|
export interface IFileDecoratorApi { |
||||||
|
events: { |
||||||
|
} & StatusEvents |
||||||
|
methods: { |
||||||
|
clearFileDecorators(path?: string): void |
||||||
|
setFileDecorators(decorators: fileDecoration[]): void |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,31 @@ |
|||||||
|
import { ModalTypes } from "@remix-ui/app" |
||||||
|
import { StatusEvents } from "@remixproject/plugin-utils" |
||||||
|
|
||||||
|
export interface INotificationApi { |
||||||
|
events: { |
||||||
|
|
||||||
|
} & StatusEvents, |
||||||
|
methods: { |
||||||
|
toast(key: string): Promise<void>, |
||||||
|
alert({ |
||||||
|
title, |
||||||
|
message, |
||||||
|
id |
||||||
|
}:{ |
||||||
|
title: string, |
||||||
|
message: string, |
||||||
|
id: string |
||||||
|
}): Promise<void>, |
||||||
|
modal({ |
||||||
|
title, |
||||||
|
message, |
||||||
|
okLabel, |
||||||
|
type |
||||||
|
}:{ |
||||||
|
title: string, |
||||||
|
message: string, |
||||||
|
okLabel: string, |
||||||
|
type: ModalTypes |
||||||
|
}): Promise<void>, |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,13 @@ |
|||||||
|
import { StatusEvents } from '@remixproject/plugin-utils' |
||||||
|
|
||||||
|
export interface ISettings { |
||||||
|
events: { |
||||||
|
configChanged: () => void, |
||||||
|
copilotChoiceUpdated: (isChecked: boolean) => void, |
||||||
|
copilotChoiceChanged: (isChecked: boolean) => void, |
||||||
|
} & StatusEvents |
||||||
|
methods: { |
||||||
|
getGithubAccessToken(): string |
||||||
|
get(key: string): Promise<any> |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,22 @@ |
|||||||
|
import { IGitApi } from "@remix-ui/git" |
||||||
|
import { IRemixApi } from "@remixproject/plugin-api" |
||||||
|
import { StatusEvents } from "@remixproject/plugin-utils" |
||||||
|
import { IConfigApi } from "./plugins/config-api" |
||||||
|
import { IFileDecoratorApi } from "./plugins/filedecorator-api" |
||||||
|
import { IExtendedFileSystem } from "./plugins/fileSystem-api" |
||||||
|
import { INotificationApi } from "./plugins/notification-api" |
||||||
|
import { ISettings } from "./plugins/settings-api" |
||||||
|
import { IFilePanelApi } from "./plugins/filePanel-api" |
||||||
|
import { Plugin } from "@remixproject/engine" |
||||||
|
|
||||||
|
export interface ICustomRemixApi extends IRemixApi { |
||||||
|
dgitApi: IGitApi |
||||||
|
config: IConfigApi |
||||||
|
notification: INotificationApi |
||||||
|
settings: ISettings |
||||||
|
fileDecorator: IFileDecoratorApi |
||||||
|
fileManager: IExtendedFileSystem |
||||||
|
filePanel: IFilePanelApi |
||||||
|
} |
||||||
|
|
||||||
|
export declare type CustomRemixApi = Readonly<ICustomRemixApi> |
@ -0,0 +1,70 @@ |
|||||||
|
import React, { useEffect, useState } from 'react' |
||||||
|
import { gitActionsContext, pluginActionsContext } from '../state/context' |
||||||
|
import { ReadCommitResult } from "isomorphic-git" |
||||||
|
import { gitPluginContext } from './gitui' |
||||||
|
export const BranchHeader = () => { |
||||||
|
|
||||||
|
const context = React.useContext(gitPluginContext) |
||||||
|
const actions = React.useContext(gitActionsContext) |
||||||
|
const pluginActions = React.useContext(pluginActionsContext) |
||||||
|
const [changed, setChanged] = useState(false) |
||||||
|
const [isDetached, setIsDetached] = useState(false) |
||||||
|
const [latestCommit, setLatestCommit] = useState<ReadCommitResult>(null) |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
if (context.currentBranch) { |
||||||
|
actions.getBranchDifferences(context.currentBranch, null, context) |
||||||
|
} |
||||||
|
if (!context.currentBranch || (context.currentBranch && context.currentBranch.name === '')) { |
||||||
|
if (context.currentHead === '') { |
||||||
|
setIsDetached(false) |
||||||
|
} else { |
||||||
|
setIsDetached(true) |
||||||
|
} |
||||||
|
} else { |
||||||
|
setIsDetached(false) |
||||||
|
} |
||||||
|
setLatestCommit(null) |
||||||
|
if (context.currentHead !== '') { |
||||||
|
if (context.commits && context.commits.length > 0) { |
||||||
|
const commit = context.commits.find(commit => commit.oid === context.currentHead) |
||||||
|
if (commit) { |
||||||
|
setLatestCommit(commit) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
}, [context.currentBranch, context.commits, context.branches, context.remotes, context.currentHead]) |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
if (context.fileStatusResult) { |
||||||
|
const total = context.allchangesnotstaged.length |
||||||
|
const badges = total + context.staged.length |
||||||
|
setChanged((context.deleted.length > 0 || context.staged.length > 0 || context.untracked.length > 0 || context.modified.length > 0)) |
||||||
|
} |
||||||
|
}, [context.fileStatusResult, context.modified, context.allchangesnotstaged, context.untracked, context.deleted]) |
||||||
|
|
||||||
|
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.`, |
||||||
|
title: 'Warning' |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
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} |
||||||
|
</div> |
||||||
|
<hr></hr> |
||||||
|
</>) |
||||||
|
} |
@ -0,0 +1,147 @@ |
|||||||
|
import React, { useEffect } from "react" |
||||||
|
import { useState } from "react" |
||||||
|
import { gitActionsContext } from "../../state/context" |
||||||
|
import { gitPluginContext } from "../gitui" |
||||||
|
import { faArrowDown, faArrowUp, faCheck, faCloudArrowUp, faSync } from "@fortawesome/free-solid-svg-icons"; |
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; |
||||||
|
import { syncStateContext } from "./sourceControlBase"; |
||||||
|
|
||||||
|
enum buttonStateValues { |
||||||
|
Commit, |
||||||
|
Sync = 1, |
||||||
|
PublishBranch = 2 |
||||||
|
} |
||||||
|
|
||||||
|
export const CommitMessage = () => { |
||||||
|
const context = React.useContext(gitPluginContext) |
||||||
|
const actions = React.useContext(gitActionsContext) |
||||||
|
const syncState = React.useContext(syncStateContext) |
||||||
|
const [buttonState, setButtonState] = useState<buttonStateValues>(buttonStateValues.Commit) |
||||||
|
|
||||||
|
const [message, setMessage] = useState({ value: '' }) |
||||||
|
|
||||||
|
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { |
||||||
|
setMessage({ value: e.currentTarget.value }) |
||||||
|
} |
||||||
|
|
||||||
|
const commit = async () => { |
||||||
|
if (context.staged.length === 0 && context.allchangesnotstaged.length == 0) return |
||||||
|
if (context.staged.length === 0) |
||||||
|
await actions.addall(context.allchangesnotstaged) |
||||||
|
await actions.commit(message.value) |
||||||
|
setMessage({ value: '' }) |
||||||
|
} |
||||||
|
|
||||||
|
const getRemote = () => { |
||||||
|
return context.upstream ? context.upstream : context.defaultRemote ? context.defaultRemote : null |
||||||
|
} |
||||||
|
|
||||||
|
const sync = async () => { |
||||||
|
await actions.pull({ |
||||||
|
remote: getRemote(), |
||||||
|
ref: context.currentBranch |
||||||
|
}) |
||||||
|
await actions.push({ |
||||||
|
remote: getRemote(), |
||||||
|
ref: context.currentBranch |
||||||
|
}) |
||||||
|
await actions.pull({ |
||||||
|
remote: getRemote(), |
||||||
|
ref: context.currentBranch |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
const commitNotAllowed = () => { |
||||||
|
return context.canCommit === false || message.value === "" || (context.staged.length === 0 && context.allchangesnotstaged.length == 0) |
||||||
|
} |
||||||
|
|
||||||
|
const commitMessagePlaceholder = () => { |
||||||
|
if (context.currentBranch === undefined || context.currentBranch.name === "") |
||||||
|
return `message` |
||||||
|
return `message ( commit on ${context.currentBranch.name} )` |
||||||
|
} |
||||||
|
|
||||||
|
const syncEnabled = () => { |
||||||
|
return syncState.commitsAhead.length > 0 || syncState.commitsBehind.length > 0 |
||||||
|
} |
||||||
|
|
||||||
|
const upDownArrows = () => { |
||||||
|
return ( |
||||||
|
<> |
||||||
|
{syncState.commitsBehind && syncState.commitsBehind.length ? <>{syncState.commitsBehind.length}<FontAwesomeIcon icon={faArrowDown} className="ml-1" /></> : null} |
||||||
|
{syncState.commitsAhead && syncState.commitsAhead.length ? <>{syncState.commitsAhead.length}<FontAwesomeIcon icon={faArrowUp} className="ml-1" /></> : null} |
||||||
|
</> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
const publishEnabled = () => { |
||||||
|
const remoteEquivalentBranch = context.branches.find((b) => b.name === context.currentBranch.name && b.remote) |
||||||
|
return remoteEquivalentBranch === undefined && getRemote() !== null |
||||||
|
} |
||||||
|
|
||||||
|
const publishBranch = async () => { |
||||||
|
if (context.currentBranch === undefined || context.currentBranch.name === "") |
||||||
|
return |
||||||
|
await actions.push({ |
||||||
|
remote: getRemote(), |
||||||
|
ref: context.currentBranch |
||||||
|
}) |
||||||
|
await actions.fetch({ |
||||||
|
remote: getRemote(), |
||||||
|
ref: context.currentBranch, |
||||||
|
singleBranch: false, |
||||||
|
relative: true |
||||||
|
}) |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
const messageEnabled = () => { |
||||||
|
return context.canCommit && (context.allchangesnotstaged.length > 0 || context.staged.length > 0) |
||||||
|
} |
||||||
|
|
||||||
|
const setButtonStateValues = () => { |
||||||
|
|
||||||
|
if (!commitNotAllowed() || context.allchangesnotstaged.length > 0 || context.staged.length > 0) { |
||||||
|
if (context.allchangesnotstaged.length == 0 && context.staged.length == 0 && message.value === "" && publishEnabled()) { |
||||||
|
setButtonState(buttonStateValues.PublishBranch) |
||||||
|
return |
||||||
|
} |
||||||
|
setButtonState(buttonStateValues.Commit) |
||||||
|
return |
||||||
|
} |
||||||
|
if (syncEnabled()) { |
||||||
|
setButtonState(buttonStateValues.Sync) |
||||||
|
return |
||||||
|
} |
||||||
|
if (publishEnabled()) { |
||||||
|
setButtonState(buttonStateValues.PublishBranch) |
||||||
|
return |
||||||
|
} |
||||||
|
setButtonState(buttonStateValues.Commit) |
||||||
|
} |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
setButtonStateValues() |
||||||
|
}, [context.canCommit, context.staged, context.allchangesnotstaged, context.currentBranch, syncState.commitsAhead, syncState.commitsBehind, message.value]) |
||||||
|
|
||||||
|
return ( |
||||||
|
<> |
||||||
|
<div className="form-group"> |
||||||
|
<input placeholder={commitMessagePlaceholder()} data-id='commitMessage' disabled={!messageEnabled()} className="form-control" type="text" onChange={handleChange} value={message.value} /> |
||||||
|
</div> |
||||||
|
<button data-id='commitButton' className={`btn btn-primary w-100 ${buttonState === buttonStateValues.Commit ? '' : 'd-none'}`} disabled={commitNotAllowed()} onClick={async () => await commit()} > |
||||||
|
<FontAwesomeIcon icon={faCheck} className="mr-1" /> |
||||||
|
Commit |
||||||
|
</button> |
||||||
|
<button data-id='syncButton' className={`btn btn-primary w-100 ${buttonState === buttonStateValues.Sync ? '' : 'd-none'}`} disabled={!syncEnabled()} onClick={async () => await sync()} > |
||||||
|
<FontAwesomeIcon icon={faSync} className="mr-1" aria-hidden="true" /> |
||||||
|
Sync Changes {upDownArrows()} |
||||||
|
</button> |
||||||
|
<button data-id='publishBranchButton' className={`btn btn-primary w-100 ${buttonState === buttonStateValues.PublishBranch ? '' : 'd-none'}`} onClick={async () => await publishBranch()} > |
||||||
|
<FontAwesomeIcon icon={faCloudArrowUp} className="mr-1" aria-hidden="true" /> |
||||||
|
Publish Branch |
||||||
|
</button> |
||||||
|
<hr></hr> |
||||||
|
</> |
||||||
|
); |
||||||
|
} |
@ -0,0 +1,24 @@ |
|||||||
|
import React, { useContext } from 'react' |
||||||
|
import { gitPluginContext } from '../gitui' |
||||||
|
|
||||||
|
interface ButtonWithContextProps { |
||||||
|
onClick: React.MouseEventHandler<HTMLButtonElement>; |
||||||
|
children: React.ReactNode; |
||||||
|
disabledCondition?: boolean; // Optional additional disabling condition
|
||||||
|
// You can add other props if needed, like 'type', 'className', etc.
|
||||||
|
[key: string]: any; // Allow additional props to be passed
|
||||||
|
} |
||||||
|
|
||||||
|
// This component extends a button, disabling it when loading is true
|
||||||
|
const GitUIButton = ({ children, disabledCondition = false, ...rest }:ButtonWithContextProps) => { |
||||||
|
const { loading } = React.useContext(gitPluginContext) |
||||||
|
|
||||||
|
const isDisabled = loading || disabledCondition |
||||||
|
return ( |
||||||
|
<button disabled={isDisabled} {...rest}> |
||||||
|
{children} |
||||||
|
</button> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default GitUIButton; |
@ -0,0 +1,90 @@ |
|||||||
|
import { faArrowDown, faArrowUp, faArrowsUpDown, faArrowRotateRight } from "@fortawesome/free-solid-svg-icons" |
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome" |
||||||
|
import { CustomTooltip } from "@remix-ui/helper" |
||||||
|
import { ReadCommitResult } from "isomorphic-git" |
||||||
|
import React, { createContext, useEffect, useState } from "react" |
||||||
|
import { FormattedMessage } from "react-intl" |
||||||
|
import { gitActionsContext } from "../../state/context" |
||||||
|
import { branch, remote } from "../../types" |
||||||
|
import { gitPluginContext } from "../gitui" |
||||||
|
import GitUIButton from "./gituibutton" |
||||||
|
|
||||||
|
interface SourceControlButtonsProps { |
||||||
|
remote?: remote, |
||||||
|
branch?: branch, |
||||||
|
children: React.ReactNode |
||||||
|
} |
||||||
|
|
||||||
|
export const syncStateContext = createContext<{ |
||||||
|
commitsAhead: ReadCommitResult[], |
||||||
|
commitsBehind: ReadCommitResult[] |
||||||
|
branch: branch, |
||||||
|
remote: remote |
||||||
|
}> |
||||||
|
({ commitsAhead: [], commitsBehind: [], branch: undefined, remote: undefined }) |
||||||
|
|
||||||
|
export const SourceControlBase = (props: SourceControlButtonsProps) => { |
||||||
|
const [branch, setBranch] = useState(props.branch) |
||||||
|
const [remote, setRemote] = useState(props.remote) |
||||||
|
const context = React.useContext(gitPluginContext) |
||||||
|
const actions = React.useContext(gitActionsContext) |
||||||
|
const [commitsAhead, setCommitsAhead] = useState<ReadCommitResult[]>([]) |
||||||
|
const [commitsBehind, setCommitsBehind] = useState<ReadCommitResult[]>([]) |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
|
||||||
|
setDefaultRemote() |
||||||
|
if (remote && branch && context.branchDifferences && context.branchDifferences[`${remote.name}/${branch.name}`]) { |
||||||
|
setCommitsAhead(context.branchDifferences[`${remote.name}/${branch.name}`]?.uniqueHeadCommits) |
||||||
|
setCommitsBehind(context.branchDifferences[`${remote.name}/${branch.name}`]?.uniqueRemoteCommits) |
||||||
|
} else { |
||||||
|
setCommitsAhead([]) |
||||||
|
setCommitsBehind([]) |
||||||
|
} |
||||||
|
}, [context.branchDifferences, context.currentBranch, branch, remote]) |
||||||
|
|
||||||
|
const setDefaultRemote = () => { |
||||||
|
|
||||||
|
if (context.remotes.length > 0) { |
||||||
|
// find remote called origin
|
||||||
|
const origin = context.remotes.find(remote => remote.name === 'origin') |
||||||
|
if (origin) { |
||||||
|
setRemote(origin) |
||||||
|
} else { |
||||||
|
setRemote(context.remotes[0]) |
||||||
|
} |
||||||
|
return origin |
||||||
|
} |
||||||
|
return null |
||||||
|
} |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
if (!props.branch) { |
||||||
|
setBranch(context.currentBranch) |
||||||
|
} |
||||||
|
if (!props.remote) { |
||||||
|
setRemote(context.defaultRemote) |
||||||
|
} else { |
||||||
|
setDefaultRemote() |
||||||
|
} |
||||||
|
}, []) |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
|
||||||
|
if (!props.branch) { |
||||||
|
setBranch(context.currentBranch) |
||||||
|
} |
||||||
|
if (!props.remote) { |
||||||
|
setRemote(context.defaultRemote) |
||||||
|
} else { |
||||||
|
setDefaultRemote() |
||||||
|
} |
||||||
|
}, [context.defaultRemote, context.currentBranch]) |
||||||
|
|
||||||
|
return (<> |
||||||
|
<syncStateContext.Provider value={{ commitsAhead, commitsBehind, branch, remote }}> |
||||||
|
{props.children} |
||||||
|
</syncStateContext.Provider> |
||||||
|
</>) |
||||||
|
|
||||||
|
} |
@ -0,0 +1,98 @@ |
|||||||
|
import { faArrowDown, faArrowUp, faArrowsUpDown, faArrowRotateRight } from "@fortawesome/free-solid-svg-icons" |
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome" |
||||||
|
import { CustomTooltip } from "@remix-ui/helper" |
||||||
|
import React, { useEffect, useState } from "react" |
||||||
|
import { FormattedMessage } from "react-intl" |
||||||
|
import { gitActionsContext } from "../../state/context" |
||||||
|
import { branch, remote } from "../../types" |
||||||
|
import { gitPluginContext } from "../gitui" |
||||||
|
import GitUIButton from "./gituibutton" |
||||||
|
import { syncStateContext } from "./sourceControlBase" |
||||||
|
|
||||||
|
export const SourceControlButtons = () => { |
||||||
|
const context = React.useContext(gitPluginContext) |
||||||
|
const actions = React.useContext(gitActionsContext) |
||||||
|
const syncState = React.useContext(syncStateContext) |
||||||
|
const [branch, setBranch] = useState<branch>(syncState.branch) |
||||||
|
const [remote, setRemote] = useState<remote>(syncState.remote) |
||||||
|
|
||||||
|
const getRemote = () => { |
||||||
|
return remote ? remote : context.upstream ? context.upstream : context.defaultRemote ? context.defaultRemote : null |
||||||
|
} |
||||||
|
|
||||||
|
const getRemoteName = () => { |
||||||
|
return getRemote() ? getRemote().name : '' |
||||||
|
} |
||||||
|
|
||||||
|
const pull = async () => { |
||||||
|
await actions.pull({ |
||||||
|
remote: getRemote(), |
||||||
|
ref: branch ? branch : context.currentBranch |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
const push = async () => { |
||||||
|
await actions.push({ |
||||||
|
remote: getRemote(), |
||||||
|
ref: branch ? branch : context.currentBranch |
||||||
|
}) |
||||||
|
await actions.fetch({ |
||||||
|
remote: getRemote(), |
||||||
|
ref: branch ? branch : context.currentBranch, |
||||||
|
relative: false, |
||||||
|
depth: 1, |
||||||
|
singleBranch: true |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
const sync = async () => { |
||||||
|
await pull() |
||||||
|
await push() |
||||||
|
} |
||||||
|
|
||||||
|
const refresh = async() => { |
||||||
|
await actions.getFileStatusMatrix(null) |
||||||
|
await actions.gitlog() |
||||||
|
} |
||||||
|
|
||||||
|
const buttonsDisabled = () => { |
||||||
|
return (!context.upstream) || context.remotes.length === 0 |
||||||
|
} |
||||||
|
|
||||||
|
const getTooltipText = (id: string) => { |
||||||
|
if (buttonsDisabled()) return <FormattedMessage id="git.noremote" /> |
||||||
|
return <><FormattedMessage id={id} /> {getRemoteName()}</> |
||||||
|
} |
||||||
|
|
||||||
|
return ( |
||||||
|
<span className='d-flex justify-content-end align-items-center'> |
||||||
|
<CustomTooltip tooltipText={getTooltipText('git.pull')}> |
||||||
|
<GitUIButton data-id='sourcecontrol-button-pull' disabledCondition={buttonsDisabled()} onClick={pull} className='btn btn-sm pl-0 pr-2'> |
||||||
|
<div className="d-flex align-items-baseline"> |
||||||
|
{syncState.commitsBehind.length ? <div className="badge badge-pill pl-0"> |
||||||
|
{syncState.commitsBehind.length} |
||||||
|
</div> : null} |
||||||
|
<FontAwesomeIcon icon={faArrowDown} className="" /> |
||||||
|
</div> |
||||||
|
</GitUIButton> |
||||||
|
</CustomTooltip> |
||||||
|
<CustomTooltip tooltipText={getTooltipText('git.push')}> |
||||||
|
<GitUIButton data-id='sourcecontrol-button-push' disabledCondition={buttonsDisabled()} onClick={push} className='btn btn-sm pl-0 pr-2'> |
||||||
|
<div className="d-flex align-items-baseline"> |
||||||
|
{syncState.commitsAhead.length ? <div className="badge badge-pill pl-0"> |
||||||
|
{syncState.commitsAhead.length} |
||||||
|
</div> : null} |
||||||
|
<FontAwesomeIcon icon={faArrowUp} className="" /> |
||||||
|
</div> |
||||||
|
</GitUIButton> |
||||||
|
</CustomTooltip> |
||||||
|
<CustomTooltip tooltipText={getTooltipText('git.sync')}> |
||||||
|
<GitUIButton data-id='sourcecontrol-button-sync' disabledCondition={buttonsDisabled()} onClick={sync} className='btn btn-sm pl-0 pr-2'><FontAwesomeIcon icon={faArrowsUpDown} className="" /></GitUIButton> |
||||||
|
</CustomTooltip> |
||||||
|
<CustomTooltip tooltipText={<FormattedMessage id="git.refresh" />}> |
||||||
|
<GitUIButton onClick={refresh} className='btn btn-sm'><FontAwesomeIcon icon={faArrowRotateRight} className="" /></GitUIButton> |
||||||
|
</CustomTooltip> |
||||||
|
</span> |
||||||
|
|
||||||
|
) |
||||||
|
} |
@ -0,0 +1,12 @@ |
|||||||
|
import React, { useEffect, useState } from 'react' |
||||||
|
|
||||||
|
export const Disabled = () => { |
||||||
|
|
||||||
|
return ( |
||||||
|
<div data-id='disabled' className='text-sm w-100 alert alert-warning'> |
||||||
|
Git is currently disabled.<br></br> |
||||||
|
If you are using RemixD you can use git on the terminal.<br></br> |
||||||
|
</div> |
||||||
|
) |
||||||
|
|
||||||
|
} |
@ -0,0 +1,51 @@ |
|||||||
|
import React, { useState, useCallback, useEffect } from 'react'; |
||||||
|
import Select from 'react-select'; |
||||||
|
import { gitActionsContext } from '../../state/context'; |
||||||
|
import { selectStyles, selectTheme } from '../../types/styles'; |
||||||
|
import { gitPluginContext } from '../gitui'; |
||||||
|
|
||||||
|
interface BranchySelectProps { |
||||||
|
select: (branch: { name: string }) => void; |
||||||
|
} |
||||||
|
|
||||||
|
export const BranchSelect = (props: BranchySelectProps) => { |
||||||
|
const context = React.useContext(gitPluginContext) |
||||||
|
const actions = React.useContext(gitActionsContext) |
||||||
|
const [branchOptions, setBranchOptions] = useState<any>([]); |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
if (context.remoteBranches && context.remoteBranches.length > 0) { |
||||||
|
const options = context.remoteBranches |
||||||
|
&& context.remoteBranches.length > 0 |
||||||
|
&& context.remoteBranches.map(branch => { |
||||||
|
return { value: branch.name, label: branch.name } |
||||||
|
}) |
||||||
|
setBranchOptions(options) |
||||||
|
} else { |
||||||
|
setBranchOptions(null) |
||||||
|
} |
||||||
|
}, [context.remoteBranches]) |
||||||
|
|
||||||
|
const selectRemoteBranch = async (e: any) => { |
||||||
|
if (!e || !e.value) { |
||||||
|
props.select(null) |
||||||
|
return |
||||||
|
} |
||||||
|
const value = e && e.value |
||||||
|
props.select({ name: value.toString() }) |
||||||
|
} |
||||||
|
|
||||||
|
return (<>{branchOptions && branchOptions.length ? |
||||||
|
<Select |
||||||
|
options={branchOptions} |
||||||
|
className="mt-1" |
||||||
|
id="branch-select" |
||||||
|
onChange={(e: any) => selectRemoteBranch(e)} |
||||||
|
theme={selectTheme} |
||||||
|
styles={selectStyles} |
||||||
|
isClearable={true} |
||||||
|
placeholder="Type to search for a branch..." |
||||||
|
/> : null} |
||||||
|
</>) |
||||||
|
|
||||||
|
} |
@ -0,0 +1,127 @@ |
|||||||
|
import React, { useEffect } from "react"; |
||||||
|
import { gitActionsContext, pluginActionsContext } from "../../state/context"; |
||||||
|
import { gitPluginContext } from "../gitui"; |
||||||
|
import axios from "axios"; |
||||||
|
import { CopyToClipboard } from "@remix-ui/clipboard"; |
||||||
|
import { Card } from "react-bootstrap"; |
||||||
|
|
||||||
|
export const GetDeviceCode = () => { |
||||||
|
const context = React.useContext(gitPluginContext) |
||||||
|
const actions = React.useContext(gitActionsContext) |
||||||
|
const pluginActions = React.useContext(pluginActionsContext) |
||||||
|
const [gitHubResponse, setGitHubResponse] = React.useState<any>(null) |
||||||
|
const [authorized, setAuthorized] = React.useState<boolean>(false) |
||||||
|
|
||||||
|
const getDeviceCodeFromGitHub = async () => { |
||||||
|
|
||||||
|
setAuthorized(false) |
||||||
|
// Send a POST request
|
||||||
|
const response = await axios({ |
||||||
|
method: 'post', |
||||||
|
url: 'https://github.remixproject.org/login/device/code', |
||||||
|
data: { |
||||||
|
client_id: '2795b4e41e7197d6ea11', |
||||||
|
scope: 'repo gist user:email read:user' |
||||||
|
}, |
||||||
|
headers: { |
||||||
|
'Content-Type': 'application/json', |
||||||
|
'Accept': 'application/json' |
||||||
|
}, |
||||||
|
}); |
||||||
|
|
||||||
|
// convert response to json
|
||||||
|
const githubrespone = await response.data; |
||||||
|
|
||||||
|
setGitHubResponse(githubrespone) |
||||||
|
} |
||||||
|
|
||||||
|
const connectApp = async () => { |
||||||
|
// poll https://github.com/login/oauth/access_token
|
||||||
|
const accestokenresponse = await axios({ |
||||||
|
method: 'post', |
||||||
|
url: 'https://github.remixproject.org/login/oauth/access_token', |
||||||
|
data: { |
||||||
|
client_id: '2795b4e41e7197d6ea11', |
||||||
|
device_code: gitHubResponse.device_code, |
||||||
|
grant_type: 'urn:ietf:params:oauth:grant-type:device_code' |
||||||
|
}, |
||||||
|
headers: { |
||||||
|
'Content-Type': 'application/json', |
||||||
|
'Accept': 'application/json' |
||||||
|
}, |
||||||
|
}); |
||||||
|
|
||||||
|
// convert response to json
|
||||||
|
const response = await accestokenresponse.data; |
||||||
|
|
||||||
|
if (response.access_token) { |
||||||
|
setAuthorized(true) |
||||||
|
await pluginActions.saveToken(response.access_token) |
||||||
|
await actions.loadGitHubUserFromToken() |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
const disconnect = async () => { |
||||||
|
setAuthorized(false) |
||||||
|
setGitHubResponse(null) |
||||||
|
await pluginActions.saveToken(null) |
||||||
|
await actions.loadGitHubUserFromToken() |
||||||
|
} |
||||||
|
|
||||||
|
return ( |
||||||
|
<> |
||||||
|
{(context.gitHubUser && context.gitHubUser.login) ? null : |
||||||
|
<button className='btn btn-primary mt-1 w-100' onClick={async () => { |
||||||
|
getDeviceCodeFromGitHub(); |
||||||
|
}}><i className="fab fa-github mr-1"></i>Login in with github</button> |
||||||
|
} |
||||||
|
{gitHubResponse && !authorized && |
||||||
|
<div className="pt-2"> |
||||||
|
|
||||||
|
Step 1: Copy this code: |
||||||
|
<div className="input-group text-secondary mb-0 h6"> |
||||||
|
<input disabled type="text" className="form-control" value={gitHubResponse.user_code} /> |
||||||
|
<div className="input-group-append"> |
||||||
|
<CopyToClipboard content={gitHubResponse.user_code} data-id='copyToClipboardCopyIcon' className='far fa-copy ml-1 p-2 mt-1' direction={"top"} /> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<br></br> |
||||||
|
Step 2: Authorize the app here |
||||||
|
<br></br><a target="_blank" href={gitHubResponse.verification_uri}>{gitHubResponse.verification_uri}</a> |
||||||
|
<br /><br></br> |
||||||
|
Step 3: When you are done, click on the button below: |
||||||
|
<button className='btn btn-primary mt-1 w-100' onClick={async () => { |
||||||
|
connectApp() |
||||||
|
}}>Connect</button> |
||||||
|
</div> |
||||||
|
} |
||||||
|
{ |
||||||
|
(context.gitHubUser && context.gitHubUser.login) ? |
||||||
|
<div className="pt-2"> |
||||||
|
<button className='btn btn-primary mt-1 w-100' onClick={async () => { |
||||||
|
disconnect() |
||||||
|
}}>Disconnect</button> |
||||||
|
</div> : null |
||||||
|
} |
||||||
|
{ |
||||||
|
(context.gitHubUser && context.gitHubUser.login) ? |
||||||
|
<div className="pt-2"> |
||||||
|
<Card> |
||||||
|
<Card.Body> |
||||||
|
<Card.Title data-id={`connected-as-${context.gitHubUser.login}`}>Connected as {context.gitHubUser.login}</Card.Title> |
||||||
|
<Card.Text> |
||||||
|
<img data-id={`connected-img-${context.gitHubUser.login}`} src={context.gitHubUser.avatar_url} className="w-100" /> |
||||||
|
<a data-id={`connected-link-${context.gitHubUser.login}`} href={context.gitHubUser.html_url}>{context.gitHubUser.html_url}</a> |
||||||
|
{context.userEmails && context.userEmails.filter((email: any) => email.primary).map((email: any) => { |
||||||
|
return <span key={email.email}><br></br>{email.email}</span> |
||||||
|
})} |
||||||
|
</Card.Text> |
||||||
|
</Card.Body> |
||||||
|
</Card> |
||||||
|
|
||||||
|
</div> : null |
||||||
|
} |
||||||
|
|
||||||
|
</>) |
||||||
|
} |
@ -0,0 +1,87 @@ |
|||||||
|
import React, { useState, useEffect } from 'react'; |
||||||
|
import { Button } from 'react-bootstrap'; |
||||||
|
import Select from 'react-select'; |
||||||
|
import { gitActionsContext } from '../../state/context'; |
||||||
|
import { repository } from '../../types'; |
||||||
|
import { selectStyles, selectTheme } from '../../types/styles'; |
||||||
|
import { gitPluginContext } from '../gitui'; |
||||||
|
import { TokenWarning } from '../panels/tokenWarning'; |
||||||
|
|
||||||
|
interface RepositorySelectProps { |
||||||
|
select: (repo: repository) => void; |
||||||
|
} |
||||||
|
|
||||||
|
const RepositorySelect = (props: RepositorySelectProps) => { |
||||||
|
const [repoOtions, setRepoOptions] = useState<any>([]); |
||||||
|
const context = React.useContext(gitPluginContext) |
||||||
|
const actions = React.useContext(gitActionsContext) |
||||||
|
const [loading, setLoading] = useState(false) |
||||||
|
const [show, setShow] = useState(false) |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
if (context.repositories && context.repositories.length > 0) { |
||||||
|
// map context.repositories to options
|
||||||
|
const options = context.repositories && context.repositories.length > 0 && context.repositories.map(repo => { |
||||||
|
return { value: repo.id, label: repo.full_name } |
||||||
|
}) |
||||||
|
|
||||||
|
setRepoOptions(options) |
||||||
|
setShow(options.length > 0) |
||||||
|
} else { |
||||||
|
setRepoOptions(null) |
||||||
|
setShow(false) |
||||||
|
} |
||||||
|
setLoading(false) |
||||||
|
|
||||||
|
}, [context.repositories]) |
||||||
|
|
||||||
|
const selectRepo = async (e: any) => { |
||||||
|
if (!e || !e.value) { |
||||||
|
props.select(null) |
||||||
|
return |
||||||
|
} |
||||||
|
const value = e && e.value |
||||||
|
|
||||||
|
const repo = context.repositories.find(repo => { |
||||||
|
return repo.id.toString() === value.toString() |
||||||
|
}) |
||||||
|
|
||||||
|
if (repo) { |
||||||
|
props.select(repo) |
||||||
|
await actions.remoteBranches(repo.owner.login, repo.name) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const fetchRepositories = async () => { |
||||||
|
try { |
||||||
|
setShow(true) |
||||||
|
setLoading(true) |
||||||
|
setRepoOptions([]) |
||||||
|
await actions.repositories() |
||||||
|
} catch (e) { |
||||||
|
// do nothing
|
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
return ( |
||||||
|
<><Button data-id='fetch-repositories' onClick={fetchRepositories} className="w-100 mt-1"> |
||||||
|
<i className="fab fa-github mr-1"></i>Fetch Repositories from GitHub |
||||||
|
</Button> |
||||||
|
{ |
||||||
|
show ? |
||||||
|
<Select |
||||||
|
options={repoOtions} |
||||||
|
className="mt-1" |
||||||
|
id="repository-select" |
||||||
|
onChange={(e: any) => selectRepo(e)} |
||||||
|
theme={selectTheme} |
||||||
|
styles={selectStyles} |
||||||
|
isClearable={true} |
||||||
|
placeholder="Type to search for a repository..." |
||||||
|
isLoading={loading} |
||||||
|
/> : null |
||||||
|
}</> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default RepositorySelect; |
@ -0,0 +1,59 @@ |
|||||||
|
import React, { useEffect, useState } from "react"; |
||||||
|
import { gitActionsContext } from "../../state/context"; |
||||||
|
import { repository } from "../../types"; |
||||||
|
import { gitPluginContext } from "../gitui"; |
||||||
|
import RepositorySelect from "./repositoryselect"; |
||||||
|
import { BranchSelect } from "./branchselect"; |
||||||
|
import { TokenWarning } from "../panels/tokenWarning"; |
||||||
|
|
||||||
|
interface RepositoriesProps { |
||||||
|
cloneDepth?: number |
||||||
|
cloneAllBranches?: boolean |
||||||
|
} |
||||||
|
|
||||||
|
export const SelectAndCloneRepositories = (props: RepositoriesProps) => { |
||||||
|
const { cloneDepth, cloneAllBranches } = props |
||||||
|
const context = React.useContext(gitPluginContext) |
||||||
|
const actions = React.useContext(gitActionsContext) |
||||||
|
const [branch, setBranch] = useState({ name: "" }); |
||||||
|
const [repo, setRepo] = useState<repository>(null); |
||||||
|
|
||||||
|
const selectRemoteBranch = async (branch:{ name: string }) => { |
||||||
|
setBranch(branch) |
||||||
|
} |
||||||
|
|
||||||
|
const selectRepo = async (repo: repository) => { |
||||||
|
setBranch(null) |
||||||
|
setRepo(repo) |
||||||
|
} |
||||||
|
|
||||||
|
const clone = async () => { |
||||||
|
try { |
||||||
|
await actions.clone({ |
||||||
|
url: repo.html_url, |
||||||
|
branch: branch.name, |
||||||
|
depth: cloneDepth, |
||||||
|
singleBranch: !cloneAllBranches |
||||||
|
}) |
||||||
|
//actions.clone(repo.html_url, branch.name, cloneDepth, !cloneAllBranches)
|
||||||
|
} catch (e) { |
||||||
|
// do nothing
|
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
return ( |
||||||
|
<> |
||||||
|
<TokenWarning /> |
||||||
|
<RepositorySelect select={selectRepo} /> |
||||||
|
|
||||||
|
{repo &&<BranchSelect select={selectRemoteBranch} />} |
||||||
|
|
||||||
|
{repo && branch && branch.name && branch.name !== '0' ? |
||||||
|
<button data-id={`clonebtn-${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} |
||||||
|
|
||||||
|
</> |
||||||
|
) |
||||||
|
} |
||||||
|
|
@ -0,0 +1,245 @@ |
|||||||
|
import React, { useEffect, useReducer, useState } from 'react' |
||||||
|
import { add, addall, checkout, checkoutfile, clone, commit, createBranch, remoteBranches, repositories, rm, getCommitChanges, diff, resolveRef, getBranchCommits, setUpstreamRemote, loadGitHubUserFromToken, getBranches, getRemotes, remoteCommits, saveGitHubCredentials, getGitHubCredentialsFromLocalStorage, fetch, pull, push, setDefaultRemote, addRemote, removeRemote, sendToGitLog, clearGitLog, getBranchDifferences, getFileStatusMatrix, init, showAlert, gitlog } from '../lib/gitactions' |
||||||
|
import { loadFiles, setCallBacks } from '../lib/listeners' |
||||||
|
import { openDiff, openFile, saveToken, setModifiedDecorator, setPlugin, setUntrackedDecorator, statusChanged } from '../lib/pluginActions' |
||||||
|
import { gitActionsContext, pluginActionsContext } from '../state/context' |
||||||
|
import { gitReducer } from '../state/gitreducer' |
||||||
|
import { defaultGitState, defaultLoaderState, gitState, loaderState } from '../types' |
||||||
|
import { Accordion } from "react-bootstrap"; |
||||||
|
import { CommitMessage } from './buttons/commitmessage' |
||||||
|
import { Commits } from './panels/commits' |
||||||
|
import { Branches } from './panels/branches' |
||||||
|
import { SourceControlNavigation } from './navigation/sourcecontrol' |
||||||
|
import { BranchesNavigation } from './navigation/branches' |
||||||
|
import { CommitsNavigation } from './navigation/commits' |
||||||
|
import '../style/index.css' |
||||||
|
import { CloneNavigation } from './navigation/clone' |
||||||
|
import { Clone } from './panels/clone' |
||||||
|
import { Commands } from './panels/commands' |
||||||
|
import { CommandsNavigation } from './navigation/commands' |
||||||
|
import { RemotesNavigation } from './navigation/remotes' |
||||||
|
import { Remotes } from './panels/remotes' |
||||||
|
import { ViewPlugin } from '@remixproject/engine-web' |
||||||
|
import { GitHubNavigation } from './navigation/github' |
||||||
|
import { loaderReducer } from '../state/loaderReducer' |
||||||
|
import { GetDeviceCode } from './github/devicecode' |
||||||
|
import { LogNavigation } from './navigation/log' |
||||||
|
import LogViewer from './panels/log' |
||||||
|
import { SourceControlBase } from './buttons/sourceControlBase' |
||||||
|
import { BranchHeader } from './branchHeader' |
||||||
|
import { SourceControl } from './panels/sourcontrol' |
||||||
|
import { GitHubCredentials } from './panels/githubcredentials' |
||||||
|
import { Setup } from './panels/setup' |
||||||
|
import { Init } from './panels/init' |
||||||
|
import { CustomRemixApi } from "@remix-api"; |
||||||
|
import { Plugin } from "@remixproject/engine"; |
||||||
|
import { Disabled } from './disabled' |
||||||
|
|
||||||
|
export const gitPluginContext = React.createContext<gitState>(defaultGitState) |
||||||
|
export const loaderContext = React.createContext<loaderState>(defaultLoaderState) |
||||||
|
|
||||||
|
interface IGitUi { |
||||||
|
plugin: Plugin<any, CustomRemixApi> |
||||||
|
} |
||||||
|
|
||||||
|
export const GitUI = (props: IGitUi) => { |
||||||
|
const plugin = props.plugin |
||||||
|
const [gitState, gitDispatch] = useReducer(gitReducer, defaultGitState) |
||||||
|
const [loaderState, loaderDispatch] = useReducer(loaderReducer, defaultLoaderState) |
||||||
|
const [activePanel, setActivePanel] = useState<string>("0") |
||||||
|
const [setup, setSetup] = useState<boolean>(false) |
||||||
|
const [needsInit, setNeedsInit] = useState<boolean>(true) |
||||||
|
const [appLoaded, setAppLoaded] = useState<boolean>(false) |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
plugin.emit('statusChanged', { |
||||||
|
key: 'loading', |
||||||
|
type: 'info', |
||||||
|
title: 'Loading Git Plugin' |
||||||
|
}) |
||||||
|
setTimeout(() => { |
||||||
|
setAppLoaded(true) |
||||||
|
}, 2000) |
||||||
|
}, []) |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
if (!appLoaded) return |
||||||
|
setCallBacks(plugin, gitDispatch, loaderDispatch, setActivePanel) |
||||||
|
setPlugin(plugin, gitDispatch, loaderDispatch) |
||||||
|
loaderDispatch({ type: 'plugin', payload: true }) |
||||||
|
|
||||||
|
}, [appLoaded]) |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
if (!appLoaded) return |
||||||
|
async function checkconfig() { |
||||||
|
|
||||||
|
const username = await plugin.call('settings', 'get', 'settings/github-user-name') |
||||||
|
const email = await plugin.call('settings', 'get', 'settings/github-email') |
||||||
|
const token = await plugin.call('settings', 'get', 'settings/gist-access-token') |
||||||
|
|
||||||
|
setSetup(!(username && email)) |
||||||
|
} |
||||||
|
checkconfig() |
||||||
|
}, [gitState.gitHubAccessToken, gitState.gitHubUser, gitState.userEmails, gitState.commits, gitState.branches]) |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
if (!appLoaded) return |
||||||
|
async function setDecorators(gitState: gitState) { |
||||||
|
await plugin.call('fileDecorator', 'clearFileDecorators') |
||||||
|
await setModifiedDecorator(gitState.modified) |
||||||
|
await setUntrackedDecorator(gitState.untracked) |
||||||
|
} |
||||||
|
|
||||||
|
setTimeout(() => { |
||||||
|
setDecorators(gitState) |
||||||
|
}) |
||||||
|
|
||||||
|
}, [gitState.fileStatusResult]) |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
if (!appLoaded) return |
||||||
|
async function updatestate() { |
||||||
|
|
||||||
|
if (gitState.currentBranch && gitState.currentBranch.remote && gitState.currentBranch.remote.url) { |
||||||
|
remoteCommits(gitState.currentBranch.remote.url, gitState.currentBranch.name, 1) |
||||||
|
} |
||||||
|
} |
||||||
|
setTimeout(() => { |
||||||
|
updatestate() |
||||||
|
}) |
||||||
|
|
||||||
|
let needsInit = false |
||||||
|
if (!(gitState.currentBranch && gitState.currentBranch.name !== '') && gitState.currentHead === '') { |
||||||
|
needsInit = true |
||||||
|
} |
||||||
|
|
||||||
|
setNeedsInit(needsInit) |
||||||
|
|
||||||
|
}, [gitState.gitHubUser, gitState.currentBranch, gitState.remotes, gitState.gitHubAccessToken, gitState.currentHead]) |
||||||
|
|
||||||
|
const gitActionsProviderValue = { |
||||||
|
commit, |
||||||
|
addall, |
||||||
|
add, |
||||||
|
checkoutfile, |
||||||
|
rm, |
||||||
|
checkout, |
||||||
|
createBranch, |
||||||
|
clone, |
||||||
|
repositories, |
||||||
|
remoteBranches, |
||||||
|
getCommitChanges, |
||||||
|
getBranchCommits, |
||||||
|
getBranchDifferences, |
||||||
|
diff, |
||||||
|
resolveRef, |
||||||
|
setUpstreamRemote, |
||||||
|
loadGitHubUserFromToken, |
||||||
|
getBranches, |
||||||
|
getRemotes, |
||||||
|
fetch, |
||||||
|
pull, |
||||||
|
push, |
||||||
|
setDefaultRemote, |
||||||
|
addRemote, |
||||||
|
removeRemote, |
||||||
|
sendToGitLog, |
||||||
|
clearGitLog, |
||||||
|
getFileStatusMatrix, |
||||||
|
gitlog, |
||||||
|
init |
||||||
|
} |
||||||
|
|
||||||
|
const pluginActionsProviderValue = { |
||||||
|
statusChanged, |
||||||
|
loadFiles, |
||||||
|
openFile, |
||||||
|
openDiff, |
||||||
|
saveToken, |
||||||
|
saveGitHubCredentials, |
||||||
|
getGitHubCredentialsFromLocalStorage, |
||||||
|
showAlert |
||||||
|
} |
||||||
|
|
||||||
|
return ( |
||||||
|
<>{(!gitState.canUseApp) ? <Disabled></Disabled> : |
||||||
|
<div className="m-1"> |
||||||
|
<gitPluginContext.Provider value={gitState}> |
||||||
|
<loaderContext.Provider value={loaderState}> |
||||||
|
<gitActionsContext.Provider value={gitActionsProviderValue}> |
||||||
|
<pluginActionsContext.Provider value={pluginActionsProviderValue}> |
||||||
|
<BranchHeader /> |
||||||
|
|
||||||
|
{setup && !needsInit ? <Setup></Setup> : null} |
||||||
|
{needsInit ? <Init></Init> : null} |
||||||
|
{!setup && !needsInit ? |
||||||
|
<Accordion activeKey={activePanel} defaultActiveKey="0"> |
||||||
|
<SourceControlNavigation eventKey="0" activePanel={activePanel} callback={setActivePanel} /> |
||||||
|
|
||||||
|
<Accordion.Collapse className='bg-light' eventKey="0"> |
||||||
|
<> |
||||||
|
<SourceControlBase><CommitMessage /></SourceControlBase> |
||||||
|
<SourceControl /> |
||||||
|
</> |
||||||
|
</Accordion.Collapse> |
||||||
|
<hr></hr> |
||||||
|
<CommandsNavigation eventKey="1" activePanel={activePanel} callback={setActivePanel} /> |
||||||
|
<Accordion.Collapse className='bg-light' eventKey="1"> |
||||||
|
<> |
||||||
|
<Commands></Commands> |
||||||
|
</> |
||||||
|
</Accordion.Collapse> |
||||||
|
<hr></hr> |
||||||
|
<CommitsNavigation title={`COMMITS`} eventKey="3" activePanel={activePanel} callback={setActivePanel} showButtons={true} /> |
||||||
|
<Accordion.Collapse className='bg-light' eventKey="3"> |
||||||
|
<> |
||||||
|
<Commits /> |
||||||
|
</> |
||||||
|
</Accordion.Collapse> |
||||||
|
<hr></hr> |
||||||
|
<BranchesNavigation eventKey="2" activePanel={activePanel} callback={setActivePanel} /> |
||||||
|
<Accordion.Collapse className='bg-light' eventKey="2"> |
||||||
|
<> |
||||||
|
<Branches /></> |
||||||
|
</Accordion.Collapse> |
||||||
|
<hr></hr> |
||||||
|
<RemotesNavigation eventKey="5" activePanel={activePanel} callback={setActivePanel} /> |
||||||
|
<Accordion.Collapse className='bg-light' eventKey="5"> |
||||||
|
<> |
||||||
|
<Remotes></Remotes> |
||||||
|
</> |
||||||
|
</Accordion.Collapse> |
||||||
|
<hr></hr> |
||||||
|
<CloneNavigation eventKey="4" activePanel={activePanel} callback={setActivePanel} /> |
||||||
|
<Accordion.Collapse className='bg-light' eventKey="4"> |
||||||
|
<> |
||||||
|
<Clone /></> |
||||||
|
</Accordion.Collapse> |
||||||
|
<hr></hr> |
||||||
|
<GitHubNavigation eventKey="7" activePanel={activePanel} callback={setActivePanel} /> |
||||||
|
<Accordion.Collapse className='bg-light' eventKey="7"> |
||||||
|
<> |
||||||
|
<GetDeviceCode></GetDeviceCode> |
||||||
|
<hr></hr> |
||||||
|
<GitHubCredentials></GitHubCredentials> |
||||||
|
</> |
||||||
|
</Accordion.Collapse> |
||||||
|
<hr></hr> |
||||||
|
<LogNavigation eventKey="6" activePanel={activePanel} callback={setActivePanel} /> |
||||||
|
<Accordion.Collapse className='bg-light' eventKey="6"> |
||||||
|
<> |
||||||
|
<LogViewer /> |
||||||
|
</> |
||||||
|
</Accordion.Collapse> |
||||||
|
|
||||||
|
</Accordion> |
||||||
|
: null} |
||||||
|
</pluginActionsContext.Provider> |
||||||
|
</gitActionsContext.Provider> |
||||||
|
</loaderContext.Provider> |
||||||
|
</gitPluginContext.Provider> |
||||||
|
</div>} |
||||||
|
</> |
||||||
|
) |
||||||
|
} |
@ -0,0 +1,97 @@ |
|||||||
|
import { faCaretUp, faCaretDown, faCaretRight, faArrowUp, faArrowDown, faArrowRotateRight, faArrowsUpDown, faGlobe, faCheckCircle, faToggleOff, faToggleOn, faSync } from "@fortawesome/free-solid-svg-icons"; |
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; |
||||||
|
import React, { useContext, useEffect } from "react"; |
||||||
|
import { gitActionsContext } from "../../state/context"; |
||||||
|
import { branch } from "../../types"; |
||||||
|
import GitUIButton from "../buttons/gituibutton"; |
||||||
|
import { gitPluginContext } from "../gitui"; |
||||||
|
|
||||||
|
interface BrancheDetailsNavigationProps { |
||||||
|
eventKey: string; |
||||||
|
activePanel: string; |
||||||
|
callback: (eventKey: string) => void; |
||||||
|
branch: branch; |
||||||
|
checkout: (branch: branch) => void; |
||||||
|
} |
||||||
|
|
||||||
|
export const BrancheDetailsNavigation = (props: BrancheDetailsNavigationProps) => { |
||||||
|
const { eventKey, activePanel, callback, branch, checkout } = props; |
||||||
|
const context = React.useContext(gitPluginContext) |
||||||
|
const actions = React.useContext(gitActionsContext) |
||||||
|
const handleClick = () => { |
||||||
|
if (!callback) return |
||||||
|
if (activePanel === eventKey) { |
||||||
|
callback('') |
||||||
|
} else { |
||||||
|
callback(eventKey) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const getRemote = () => { |
||||||
|
return context.upstream ? context.upstream : context.defaultRemote ? context.defaultRemote : null |
||||||
|
} |
||||||
|
|
||||||
|
const openRemote = () => { |
||||||
|
const remote = branch.remote || getRemote() |
||||||
|
window.open(`${remote.url}/tree/${branch.name}`, '_blank'); |
||||||
|
} |
||||||
|
|
||||||
|
const reloadBranch = () => { |
||||||
|
actions.getBranchCommits(branch, 1) |
||||||
|
} |
||||||
|
|
||||||
|
const canFetch = () => { |
||||||
|
if (getRemote()) |
||||||
|
return context.branches.find((b) => b.name === branch.name && b.remote && b.remote.url === getRemote().url) ? true : false |
||||||
|
} |
||||||
|
|
||||||
|
const fetchBranch = async () => { |
||||||
|
await actions.fetch({ |
||||||
|
remote: null, |
||||||
|
ref: branch, |
||||||
|
singleBranch: true, |
||||||
|
relative: true |
||||||
|
}) |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
return ( |
||||||
|
<> |
||||||
|
<div className="d-flex flex-row w-100 mb-2 mt-2"> |
||||||
|
<div data-id={`branches-${context.currentBranch.name === branch.name ? 'current-' : ''}branch-${branch.name}`} onClick={() => handleClick()} role={'button'} className='pointer d-flex flex-row w-100 commit-navigation'> |
||||||
|
{ |
||||||
|
activePanel === eventKey ? <FontAwesomeIcon className='' icon={faCaretDown}></FontAwesomeIcon> : <FontAwesomeIcon className='' icon={faCaretRight}></FontAwesomeIcon> |
||||||
|
} |
||||||
|
<i className="fa fa-code-branch ml-1"></i> |
||||||
|
<div className={`ml-1 ${context.currentBranch.name === branch.name ? 'text-success' : ''}`}>{branch.name} {branch.remote ? `on ${branch.remote.name}` : ''}</div> |
||||||
|
|
||||||
|
</div> |
||||||
|
{context.currentBranch && context.currentBranch.name === branch.name ? |
||||||
|
<GitUIButton data-id={`branches-toggle-current-branch-${branch.name}`} className="btn btn-sm p-0 mr-1" onClick={() => { }}> |
||||||
|
<FontAwesomeIcon className='pointer text-success' icon={faToggleOff} ></FontAwesomeIcon> |
||||||
|
</GitUIButton> |
||||||
|
: |
||||||
|
<GitUIButton data-id={`branches-toggle-branch-${branch.name}`} className="btn btn-sm p-0 mr-1" onClick={() => checkout(branch)}> |
||||||
|
<FontAwesomeIcon icon={faToggleOn}></FontAwesomeIcon> |
||||||
|
</GitUIButton> |
||||||
|
} |
||||||
|
{!branch.remote && canFetch() && <> |
||||||
|
<GitUIButton className="btn btn-sm p-0 mr-1 text-muted" onClick={() => fetchBranch()}><FontAwesomeIcon icon={faSync} ></FontAwesomeIcon></GitUIButton> |
||||||
|
<GitUIButton className="btn btn-sm p-0 mr-1 text-muted" onClick={() => openRemote()}><FontAwesomeIcon icon={faGlobe} ></FontAwesomeIcon></GitUIButton> |
||||||
|
</>} |
||||||
|
{branch.remote?.url && <> |
||||||
|
<GitUIButton className="btn btn-sm p-0 mr-1 text-muted" onClick={() => reloadBranch()}> |
||||||
|
<FontAwesomeIcon icon={faSync} ></FontAwesomeIcon> |
||||||
|
</GitUIButton> |
||||||
|
</>} |
||||||
|
|
||||||
|
{branch.remote?.url && <> |
||||||
|
<GitUIButton className="btn btn-sm p-0 mr-1 text-muted" onClick={() => openRemote()}> |
||||||
|
<FontAwesomeIcon icon={faGlobe} ></FontAwesomeIcon> |
||||||
|
</GitUIButton> |
||||||
|
</>} |
||||||
|
|
||||||
|
</div> |
||||||
|
</> |
||||||
|
); |
||||||
|
} |
@ -0,0 +1,32 @@ |
|||||||
|
import { faCaretDown, faCaretRight } from "@fortawesome/free-solid-svg-icons"; |
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; |
||||||
|
import React, { } from "react"; |
||||||
|
import { gitActionsContext, pluginActionsContext } from "../../state/context"; |
||||||
|
import LoaderIndicator from "./loaderindicator"; |
||||||
|
|
||||||
|
export const BranchesNavigation = ({ eventKey, activePanel, callback }) => { |
||||||
|
const pluginactions = React.useContext(pluginActionsContext) |
||||||
|
const context = React.useContext(gitActionsContext) |
||||||
|
|
||||||
|
const handleClick = () => { |
||||||
|
if (!callback) return |
||||||
|
if (activePanel === eventKey) { |
||||||
|
callback('') |
||||||
|
} else { |
||||||
|
callback(eventKey) |
||||||
|
} |
||||||
|
} |
||||||
|
return ( |
||||||
|
<> |
||||||
|
<div className={'d-flex justify-content-between pt-1 ' + (activePanel === eventKey? 'bg-light': '')}> |
||||||
|
<span data-id='branches-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">BRANCHES</label> |
||||||
|
<LoaderIndicator></LoaderIndicator> |
||||||
|
</span> |
||||||
|
</div> |
||||||
|
</> |
||||||
|
); |
||||||
|
} |
@ -0,0 +1,29 @@ |
|||||||
|
import { faCaretUp, faCaretDown, faArrowUp, faArrowDown, faArrowRotateRight, faCaretRight } from "@fortawesome/free-solid-svg-icons"; |
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; |
||||||
|
import React, { useContext, useEffect } from "react"; |
||||||
|
import LoaderIndicator from "./loaderindicator"; |
||||||
|
|
||||||
|
export const CloneNavigation = ({ eventKey, activePanel, callback }) => { |
||||||
|
|
||||||
|
const handleClick = () => { |
||||||
|
if (!callback) return |
||||||
|
if (activePanel === eventKey) { |
||||||
|
callback('') |
||||||
|
} else { |
||||||
|
callback(eventKey) |
||||||
|
} |
||||||
|
} |
||||||
|
return ( |
||||||
|
<> |
||||||
|
<div className={'d-flex justify-content-between pb-1 pt-1 ' + (activePanel === eventKey? 'bg-light': '')}> |
||||||
|
<span data-id='clone-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">CLONE</label> |
||||||
|
<LoaderIndicator></LoaderIndicator> |
||||||
|
</span> |
||||||
|
</div> |
||||||
|
</> |
||||||
|
); |
||||||
|
} |
@ -0,0 +1,35 @@ |
|||||||
|
import { faCaretUp, faCaretDown, faArrowUp, faArrowDown, faArrowRotateRight, faCaretRight, faCircleCheck, faArrowsUpDown, faSpinner } from "@fortawesome/free-solid-svg-icons"; |
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; |
||||||
|
import React, { useContext, useEffect } from "react"; |
||||||
|
import { pluginActionsContext } from "../../state/context"; |
||||||
|
import GitUIButton from "../buttons/gituibutton"; |
||||||
|
import { SourceControlButtons } from "../buttons/sourcecontrolbuttons"; |
||||||
|
import LoaderIndicator from "./loaderindicator"; |
||||||
|
|
||||||
|
export const CommandsNavigation = ({ eventKey, activePanel, callback }) => { |
||||||
|
const pluginactions = React.useContext(pluginActionsContext) |
||||||
|
|
||||||
|
const handleClick = () => { |
||||||
|
if (!callback) return |
||||||
|
if (activePanel === eventKey) { |
||||||
|
callback('') |
||||||
|
} else { |
||||||
|
callback(eventKey) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return ( |
||||||
|
<> |
||||||
|
<div className={'d-flex justify-content-between ' + (activePanel === eventKey ? 'bg-light' : '')}> |
||||||
|
<span 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> |
||||||
|
|
||||||
|
</span> |
||||||
|
</div> |
||||||
|
</> |
||||||
|
); |
||||||
|
} |
@ -0,0 +1,37 @@ |
|||||||
|
import { faCaretUp, faCaretDown, faCaretRight, faArrowUp, faArrowDown, faArrowRotateRight } from "@fortawesome/free-solid-svg-icons"; |
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; |
||||||
|
import React, { useContext, useEffect } from "react"; |
||||||
|
import { CommitSummary } from "../panels/commits/commitsummary"; |
||||||
|
import { ReadCommitResult } from "isomorphic-git" |
||||||
|
|
||||||
|
interface CommitDetailsNavigationProps { |
||||||
|
commit: ReadCommitResult, |
||||||
|
checkout: (oid: string) => void |
||||||
|
eventKey: string |
||||||
|
activePanel: string |
||||||
|
callback: (eventKey: string) => void |
||||||
|
isAheadOfRepo: boolean |
||||||
|
} |
||||||
|
|
||||||
|
export const CommitDetailsNavigation = (props: CommitDetailsNavigationProps) => { |
||||||
|
const { commit, checkout, eventKey, activePanel, callback, isAheadOfRepo } = props; |
||||||
|
const handleClick = () => { |
||||||
|
if (!callback) return |
||||||
|
if (activePanel === eventKey) { |
||||||
|
callback('') |
||||||
|
} else { |
||||||
|
callback(eventKey) |
||||||
|
} |
||||||
|
} |
||||||
|
return ( |
||||||
|
<> |
||||||
|
<div onClick={() => handleClick()} role={'button'} className={`pointer mb-2 mt-2 w-100 d-flex flex-row commit-navigation ${isAheadOfRepo ? 'text-success' : ''}`}> |
||||||
|
{ |
||||||
|
activePanel === eventKey ? <FontAwesomeIcon className='' icon={faCaretDown}></FontAwesomeIcon> : <FontAwesomeIcon className='' icon={faCaretRight}></FontAwesomeIcon> |
||||||
|
} |
||||||
|
|
||||||
|
<CommitSummary isAheadOfRepo={isAheadOfRepo} commit={commit} checkout={checkout}></CommitSummary> |
||||||
|
</div> |
||||||
|
</> |
||||||
|
); |
||||||
|
} |
@ -0,0 +1,63 @@ |
|||||||
|
import { faCaretDown, faArrowUp, faArrowDown, faArrowRotateRight, faCaretRight, faArrowsUpDown, faCloudArrowUp, faCloudArrowDown } from "@fortawesome/free-solid-svg-icons"; |
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; |
||||||
|
import { CustomTooltip } from "@remix-ui/helper"; |
||||||
|
import React, { useEffect } from "react"; |
||||||
|
import { FormattedMessage } from "react-intl"; |
||||||
|
import { pluginActionsContext } from "../../state/context"; |
||||||
|
import { branch, remote } from "../../types"; |
||||||
|
import { SourceControlBase } from "../buttons/sourceControlBase"; |
||||||
|
import { SourceControlButtons } from "../buttons/sourcecontrolbuttons"; |
||||||
|
import { gitPluginContext } from "../gitui"; |
||||||
|
import LoaderIndicator from "./loaderindicator"; |
||||||
|
|
||||||
|
export interface CommitsNavigationProps { |
||||||
|
title: string, |
||||||
|
eventKey: string, |
||||||
|
activePanel: string, |
||||||
|
callback: (eventKey: string) => void |
||||||
|
branch?: branch, |
||||||
|
remote?: remote |
||||||
|
showButtons?: boolean |
||||||
|
ahead?: boolean, |
||||||
|
behind?: boolean, |
||||||
|
} |
||||||
|
|
||||||
|
export const CommitsNavigation = ({ eventKey, activePanel, callback, title, branch, remote, showButtons, ahead, behind }: CommitsNavigationProps) => { |
||||||
|
const pluginactions = React.useContext(pluginActionsContext) |
||||||
|
const [pullEnabled, setPullEnabled] = React.useState(true) |
||||||
|
const [pushEnabled, setPushEnabled] = React.useState(true) |
||||||
|
const [syncEnabled, setSyncEnabled] = React.useState(false) |
||||||
|
const [fetchEnabled, setFetchEnabled] = React.useState(true) |
||||||
|
const context = React.useContext(gitPluginContext) |
||||||
|
|
||||||
|
const handleClick = () => { |
||||||
|
if (!callback) return |
||||||
|
if (activePanel === eventKey) { |
||||||
|
callback('') |
||||||
|
} else { |
||||||
|
callback(eventKey) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return ( |
||||||
|
<> |
||||||
|
<div className={`d-flex justify-content-between ${activePanel === eventKey ? 'bg-light' : ''} ${ahead || behind? 'text-success':''}`}> |
||||||
|
<span data-id={`commits-panel${ahead?'-ahead':''}${behind?'-behind':''}`} onClick={() => handleClick()} role={'button'} className='nav d-flex justify-content-start align-items-center w-100'> |
||||||
|
{ |
||||||
|
activePanel === eventKey ? <FontAwesomeIcon className='' icon={faCaretDown}></FontAwesomeIcon> : <FontAwesomeIcon className='' icon={faCaretRight}></FontAwesomeIcon> |
||||||
|
} |
||||||
|
{ahead? <FontAwesomeIcon className='ml-1' icon={faCloudArrowUp}></FontAwesomeIcon> : null} |
||||||
|
{behind? <FontAwesomeIcon className='ml-1' icon={faCloudArrowDown}></FontAwesomeIcon> : null} |
||||||
|
<label className={`pl-1 nav form-check-label ${ahead || behind? 'text-success':''}`}>{title}</label> |
||||||
|
<LoaderIndicator></LoaderIndicator> |
||||||
|
|
||||||
|
</span> |
||||||
|
{showButtons ? |
||||||
|
<SourceControlBase branch={branch} remote={remote}> |
||||||
|
<SourceControlButtons /> |
||||||
|
</SourceControlBase> : null} |
||||||
|
|
||||||
|
</div> |
||||||
|
</> |
||||||
|
); |
||||||
|
} |
@ -0,0 +1,29 @@ |
|||||||
|
import { faCaretDown, faCaretRight } from "@fortawesome/free-solid-svg-icons"; |
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; |
||||||
|
import React, { } from "react"; |
||||||
|
import { pluginActionsContext } from "../../state/context"; |
||||||
|
|
||||||
|
export const GitHubNavigation = ({ eventKey, activePanel, callback }) => { |
||||||
|
const pluginactions = React.useContext(pluginActionsContext) |
||||||
|
|
||||||
|
const handleClick = () => { |
||||||
|
if (!callback) return |
||||||
|
if (activePanel === eventKey) { |
||||||
|
callback('') |
||||||
|
} else { |
||||||
|
callback(eventKey) |
||||||
|
} |
||||||
|
} |
||||||
|
return ( |
||||||
|
<> |
||||||
|
<div className={'d-flex justify-content-between pt-1 pb-1 ' + (activePanel === eventKey? 'bg-light': '')}> |
||||||
|
<span data-id='github-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">GITHUB SETUP</label> |
||||||
|
</span> |
||||||
|
</div> |
||||||
|
</> |
||||||
|
); |
||||||
|
} |
@ -0,0 +1,20 @@ |
|||||||
|
import React, { useContext } from 'react' |
||||||
|
import { gitPluginContext } from '../gitui' |
||||||
|
|
||||||
|
interface LoaderIndicatorProps { |
||||||
|
type?: string; |
||||||
|
isLoadingCondition?: boolean; // Optional additional disabling condition
|
||||||
|
} |
||||||
|
|
||||||
|
// This component extends a button, disabling it when loading is true
|
||||||
|
const LoaderIndicator = ({ type, isLoadingCondition }: LoaderIndicatorProps) => { |
||||||
|
const { loading } = React.useContext(gitPluginContext) |
||||||
|
|
||||||
|
const isLoading = loading || isLoadingCondition |
||||||
|
if (!isLoading) return null |
||||||
|
return ( |
||||||
|
<i style={{ fontSize: 'x-small' }} className="ml-1 fas fa-spinner fa-spin fa-4x"></i> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default LoaderIndicator; |
@ -0,0 +1,87 @@ |
|||||||
|
import { faBan, faCaretDown, faCaretRight, faCircleCheck, faCircleInfo, faInfo, faTrash, faTriangleExclamation, faWarning } from "@fortawesome/free-solid-svg-icons"; |
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; |
||||||
|
import React, { useContext, useEffect, useState } from "react"; |
||||||
|
import { gitActionsContext, pluginActionsContext } from "../../state/context"; |
||||||
|
import { gitPluginContext } from "../gitui"; |
||||||
|
|
||||||
|
export const LogNavigation = ({ eventKey, activePanel, callback }) => { |
||||||
|
const context = useContext(gitPluginContext) |
||||||
|
const actions = React.useContext(gitActionsContext) |
||||||
|
|
||||||
|
const [logState, setLogState] = useState({ |
||||||
|
errorCount: 0, |
||||||
|
warningCount: 0, |
||||||
|
infoCount: 0, |
||||||
|
successCount: 0 |
||||||
|
}); |
||||||
|
const handleClick = () => { |
||||||
|
if (!callback) return |
||||||
|
if (activePanel === eventKey) { |
||||||
|
callback('') |
||||||
|
} else { |
||||||
|
callback(eventKey) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
if (!context.log) return |
||||||
|
// count different types of logs
|
||||||
|
const errorCount = context.log.filter(log => log.type === 'error').length |
||||||
|
const warningCount = context.log.filter(log => log.type === 'warning').length |
||||||
|
const infoCount = context.log.filter(log => log.type === 'info').length |
||||||
|
const successCount = context.log.filter(log => log.type === 'success').length |
||||||
|
// update the state
|
||||||
|
setLogState({ |
||||||
|
errorCount, |
||||||
|
warningCount, |
||||||
|
infoCount, |
||||||
|
successCount |
||||||
|
}) |
||||||
|
}, [context.log]) |
||||||
|
|
||||||
|
const clearLogs = () => { |
||||||
|
actions.clearGitLog() |
||||||
|
} |
||||||
|
|
||||||
|
return ( |
||||||
|
<> |
||||||
|
<div className={'d-flex justify-content-between pt-1 pb-1 ' + (activePanel === eventKey ? 'bg-light' : '')}> |
||||||
|
<span onClick={() => handleClick()} role={'button'} className='nav d-flex justify-content-start align-items-center w-75'> |
||||||
|
{ |
||||||
|
activePanel === eventKey ? <FontAwesomeIcon className='' icon={faCaretDown}></FontAwesomeIcon> : <FontAwesomeIcon className='' icon={faCaretRight}></FontAwesomeIcon> |
||||||
|
} |
||||||
|
<label className="pl-1 nav form-check-label mr-2">LOG</label> |
||||||
|
{logState.errorCount > 0 && ( |
||||||
|
<div className="text-danger mr-1"> |
||||||
|
{logState.errorCount} |
||||||
|
<FontAwesomeIcon className="ml-1" icon={faTriangleExclamation} /> |
||||||
|
</div> |
||||||
|
)} |
||||||
|
|
||||||
|
{logState.warningCount > 0 && ( |
||||||
|
<div className="text-warning mr-1"> |
||||||
|
{logState.warningCount} |
||||||
|
<FontAwesomeIcon className="ml-1" icon={faWarning} /> |
||||||
|
</div> |
||||||
|
)} |
||||||
|
|
||||||
|
{logState.infoCount > 0 && ( |
||||||
|
<div className="text-info mr-1"> |
||||||
|
{logState.infoCount} |
||||||
|
<FontAwesomeIcon className="ml-1" icon={faCircleInfo} /> |
||||||
|
</div> |
||||||
|
)} |
||||||
|
|
||||||
|
{logState.successCount > 0 && ( |
||||||
|
<div className="text-success"> |
||||||
|
{logState.successCount} |
||||||
|
<FontAwesomeIcon className="ml-1" icon={faCircleCheck} /> |
||||||
|
</div> |
||||||
|
)} |
||||||
|
</span> |
||||||
|
{context.log && context.log.length > 0 && ( |
||||||
|
<FontAwesomeIcon onClick={clearLogs} className='btn btn-sm' icon={faBan}></FontAwesomeIcon>)} |
||||||
|
</div> |
||||||
|
</> |
||||||
|
); |
||||||
|
} |
@ -0,0 +1,49 @@ |
|||||||
|
import { count } from "console" |
||||||
|
import { CustomIconsToggle, CustomMenu, CustomTooltip } from "@remix-ui/helper" |
||||||
|
import React, { useState } from "react" |
||||||
|
import { Dropdown } from "react-bootstrap" |
||||||
|
import { FormattedMessage } from "react-intl" |
||||||
|
|
||||||
|
export const SourceControlMenu = () => { |
||||||
|
const [showIconsMenu, hideIconsMenu] = useState<boolean>(false) |
||||||
|
return ( |
||||||
|
<Dropdown id="workspacesMenuDropdown" data-id="sourceControlMenuDropdown" onToggle={() => hideIconsMenu(!showIconsMenu)} show={showIconsMenu}> |
||||||
|
<Dropdown.Toggle |
||||||
|
onClick={() => { |
||||||
|
hideIconsMenu(!showIconsMenu) |
||||||
|
}} |
||||||
|
as={CustomIconsToggle} |
||||||
|
icon={'fas fa-bars'} |
||||||
|
></Dropdown.Toggle> |
||||||
|
<Dropdown.Menu as={CustomMenu} data-id="wsdropdownMenu" className='custom-dropdown-items remixui_menuwidth' rootCloseEvent="click"> |
||||||
|
<Dropdown.Item key={0}> |
||||||
|
<CustomTooltip |
||||||
|
placement="right-start" |
||||||
|
tooltipId="cloneWorkspaceTooltip" |
||||||
|
tooltipClasses="text-nowrap" |
||||||
|
tooltipText={<FormattedMessage id='filePanel.workspace.clone' defaultMessage='Clone Git Repository' />} |
||||||
|
> |
||||||
|
<div |
||||||
|
data-id='cloneGitRepository' |
||||||
|
onClick={() => { |
||||||
|
hideIconsMenu(!showIconsMenu) |
||||||
|
}} |
||||||
|
key={`cloneGitRepository-fe-ws`} |
||||||
|
> |
||||||
|
<span |
||||||
|
id='cloneGitRepository' |
||||||
|
data-id='cloneGitRepository' |
||||||
|
onClick={() => { |
||||||
|
hideIconsMenu(!showIconsMenu) |
||||||
|
}} |
||||||
|
className='fab fa-github pl-2' |
||||||
|
> |
||||||
|
</span> |
||||||
|
<span className="pl-3"><FormattedMessage id='filePanel.clone' defaultMessage='Clone' /></span> |
||||||
|
</div> |
||||||
|
</CustomTooltip> |
||||||
|
</Dropdown.Item> |
||||||
|
</Dropdown.Menu> |
||||||
|
</Dropdown> |
||||||
|
) |
||||||
|
} |
@ -0,0 +1,32 @@ |
|||||||
|
import { faCaretDown, faCaretRight } from "@fortawesome/free-solid-svg-icons"; |
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; |
||||||
|
import React, { } from "react"; |
||||||
|
import { gitActionsContext, pluginActionsContext } from "../../state/context"; |
||||||
|
import LoaderIndicator from "./loaderindicator"; |
||||||
|
|
||||||
|
export const RemotesNavigation = ({ eventKey, activePanel, callback }) => { |
||||||
|
const pluginactions = React.useContext(pluginActionsContext) |
||||||
|
const context = React.useContext(gitActionsContext) |
||||||
|
|
||||||
|
const handleClick = () => { |
||||||
|
if (!callback) return |
||||||
|
if (activePanel === eventKey) { |
||||||
|
callback('') |
||||||
|
} else { |
||||||
|
callback(eventKey) |
||||||
|
} |
||||||
|
} |
||||||
|
return ( |
||||||
|
<> |
||||||
|
<div className={'d-flex justify-content-between pt-1 pb-1 ' + (activePanel === eventKey? 'bg-light': '')}> |
||||||
|
<span data-id='remotes-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">REMOTES</label> |
||||||
|
<LoaderIndicator></LoaderIndicator> |
||||||
|
</span> |
||||||
|
</div> |
||||||
|
</> |
||||||
|
); |
||||||
|
} |
@ -0,0 +1,68 @@ |
|||||||
|
import { faCaretDown, faCaretRight, faArrowRightArrowLeft, faGlobe, faToggleOff, faToggleOn, faTrash, faCheck, faSync } from "@fortawesome/free-solid-svg-icons"; |
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; |
||||||
|
import { CustomTooltip } from "@remix-ui/helper"; |
||||||
|
import React, { useContext, useEffect } from "react"; |
||||||
|
import { gitActionsContext } from "../../state/context"; |
||||||
|
import { branch, remote } from "../../types"; |
||||||
|
import GitUIButton from "../buttons/gituibutton"; |
||||||
|
import { gitPluginContext } from "../gitui"; |
||||||
|
|
||||||
|
interface RemotesDetailsNavigationProps { |
||||||
|
eventKey: string; |
||||||
|
activePanel: string; |
||||||
|
callback: (eventKey: string) => void; |
||||||
|
remote: remote; |
||||||
|
} |
||||||
|
|
||||||
|
export const RemotesDetailsNavigation = (props: RemotesDetailsNavigationProps) => { |
||||||
|
const { eventKey, activePanel, callback, remote } = props; |
||||||
|
const context = React.useContext(gitPluginContext) |
||||||
|
const actions = React.useContext(gitActionsContext) |
||||||
|
|
||||||
|
const handleClick = () => { |
||||||
|
if (!callback) return |
||||||
|
if (activePanel === eventKey) { |
||||||
|
callback('') |
||||||
|
} else { |
||||||
|
callback(eventKey) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const openRemote = () => { |
||||||
|
window.open(`${remote.url}`, '_blank'); |
||||||
|
} |
||||||
|
|
||||||
|
const setAsDefault = () => { |
||||||
|
actions.setDefaultRemote(remote) |
||||||
|
} |
||||||
|
|
||||||
|
return ( |
||||||
|
<> |
||||||
|
<div className="d-flex flex-row w-100 mb-2 mt-2"> |
||||||
|
<div data-id={`remote-detail-${remote.name}${context.defaultRemote && context.defaultRemote?.url === remote.url ? '-default' : ''}`} onClick={() => handleClick()} role={'button'} className='pointer long-and-truncated d-flex flex-row commit-navigation'> |
||||||
|
{ |
||||||
|
activePanel === eventKey ? <FontAwesomeIcon className='' icon={faCaretDown}></FontAwesomeIcon> : <FontAwesomeIcon className='' icon={faCaretRight}></FontAwesomeIcon> |
||||||
|
} |
||||||
|
<CustomTooltip tooltipText={remote.url} placement="top"> |
||||||
|
<div className={`long-and-truncated ml-1 ${context.defaultRemote && context.defaultRemote?.url === remote.url ? 'text-success' : ''}`}> |
||||||
|
{remote.name} <FontAwesomeIcon className='' icon={faArrowRightArrowLeft}></FontAwesomeIcon> {remote.url} |
||||||
|
</div> |
||||||
|
</CustomTooltip> |
||||||
|
|
||||||
|
</div> |
||||||
|
{context.defaultRemote && context.defaultRemote?.url === remote.url ? |
||||||
|
<GitUIButton className="btn btn-sm" onClick={() => { }} disabledCondition={true}><FontAwesomeIcon className='text-success' icon={faCheck} ></FontAwesomeIcon></GitUIButton> |
||||||
|
: |
||||||
|
<GitUIButton className="btn btn-sm" onClick={setAsDefault}><FontAwesomeIcon icon={faToggleOn}></FontAwesomeIcon></GitUIButton> |
||||||
|
} |
||||||
|
<GitUIButton data-id={`remote-sync-${remote.name}`} className="btn btn-sm" onClick={async () => { |
||||||
|
await actions.fetch({ |
||||||
|
remote |
||||||
|
}) |
||||||
|
}}><FontAwesomeIcon icon={faSync} ></FontAwesomeIcon></GitUIButton> |
||||||
|
<GitUIButton data-id={`remote-rm-${remote.name}`} className="btn btn-sm" onClick={() => actions.removeRemote(remote)}><FontAwesomeIcon className='text-danger' icon={faTrash} ></FontAwesomeIcon></GitUIButton> |
||||||
|
{remote?.url && <GitUIButton className="btn btn-sm pr-0" onClick={() => openRemote()}><FontAwesomeIcon icon={faGlobe} ></FontAwesomeIcon></GitUIButton>} |
||||||
|
</div> |
||||||
|
</> |
||||||
|
); |
||||||
|
} |
@ -0,0 +1,41 @@ |
|||||||
|
import { faCaretUp, faCaretDown, faArrowUp, faArrowDown, faArrowRotateRight, faCaretRight, faArrowsUpDown, faTriangleExclamation } from "@fortawesome/free-solid-svg-icons"; |
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; |
||||||
|
import { CustomTooltip } from "@remix-ui/helper"; |
||||||
|
import React, { useContext, useEffect } from "react"; |
||||||
|
import { FormattedMessage } from "react-intl"; |
||||||
|
import { pluginActionsContext } from "../../state/context"; |
||||||
|
|
||||||
|
export const SettingsNavigation = ({ eventKey, activePanel, callback }) => { |
||||||
|
const pluginactions = React.useContext(pluginActionsContext) |
||||||
|
|
||||||
|
const handleClick = () => { |
||||||
|
if (!callback) return |
||||||
|
if (activePanel === eventKey) { |
||||||
|
callback('') |
||||||
|
} else { |
||||||
|
callback(eventKey) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return ( |
||||||
|
<> |
||||||
|
<div className={'d-flex justify-content-between ' + (activePanel === eventKey ? 'bg-light' : '')}> |
||||||
|
<span onClick={() => handleClick()} role={'button'} className='nav d-flex justify-content-start align-items-center w-75'> |
||||||
|
{ |
||||||
|
activePanel === eventKey ? <FontAwesomeIcon className='' icon={faCaretDown}></FontAwesomeIcon> : <FontAwesomeIcon className='' icon={faCaretRight}></FontAwesomeIcon> |
||||||
|
} |
||||||
|
<label className="nav pl-1 form-check-label">SETTINGS</label> |
||||||
|
|
||||||
|
</span> |
||||||
|
|
||||||
|
<span className='d-flex justify-content-end align-items-center w-25'> |
||||||
|
<CustomTooltip tooltipText={<FormattedMessage id="Missing values" />}> |
||||||
|
<button onClick={async () => { await pluginactions.loadFiles() }} className='btn btn-sm text-warning'><FontAwesomeIcon icon={faTriangleExclamation} className="" /></button> |
||||||
|
</CustomTooltip> |
||||||
|
|
||||||
|
</span> |
||||||
|
|
||||||
|
</div> |
||||||
|
</> |
||||||
|
); |
||||||
|
} |
@ -0,0 +1,42 @@ |
|||||||
|
import { faCaretUp, faCaretDown, faArrowUp, faArrowDown, faArrowRotateRight, faCaretRight, faArrowsUpDown } from "@fortawesome/free-solid-svg-icons"; |
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; |
||||||
|
import { CustomTooltip } from "@remix-ui/helper"; |
||||||
|
import React, { useContext, useEffect } from "react"; |
||||||
|
import { FormattedMessage } from "react-intl"; |
||||||
|
import { pluginActionsContext } from "../../state/context"; |
||||||
|
import { SourceControlBase } from "../buttons/sourceControlBase"; |
||||||
|
import { SourceControlButtons } from "../buttons/sourcecontrolbuttons"; |
||||||
|
import { gitPluginContext } from "../gitui"; |
||||||
|
import LoaderIndicator from "./loaderindicator"; |
||||||
|
import { SourceControlMenu } from "./menu/sourcecontrolmenu"; |
||||||
|
|
||||||
|
export const SourceControlNavigation = ({ eventKey, activePanel, callback }) => { |
||||||
|
const pluginactions = React.useContext(pluginActionsContext) |
||||||
|
const context = React.useContext(gitPluginContext) |
||||||
|
const handleClick = () => { |
||||||
|
if (!callback) return |
||||||
|
if (activePanel === eventKey) { |
||||||
|
callback('') |
||||||
|
} else { |
||||||
|
callback(eventKey) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return ( |
||||||
|
<> |
||||||
|
<div className={'d-flex justify-content-between ' + (activePanel === eventKey ? 'bg-light' : '')}> |
||||||
|
<span 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> |
||||||
|
<LoaderIndicator></LoaderIndicator> |
||||||
|
|
||||||
|
</span> |
||||||
|
|
||||||
|
<SourceControlBase><SourceControlButtons/></SourceControlBase> |
||||||
|
|
||||||
|
</div> |
||||||
|
</> |
||||||
|
); |
||||||
|
} |
@ -0,0 +1,52 @@ |
|||||||
|
import { faCaretUp, faCaretDown, faArrowUp, faArrowDown, faArrowRotateRight, faCaretRight, faArrowsUpDown, faPlus, faMinus } from "@fortawesome/free-solid-svg-icons"; |
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; |
||||||
|
import { CustomTooltip } from "@remix-ui/helper"; |
||||||
|
import React, { useContext, useEffect } from "react"; |
||||||
|
import { FormattedMessage } from "react-intl"; |
||||||
|
import { gitActionsContext, pluginActionsContext } from "../../state/context"; |
||||||
|
import { sourceControlGroup } from "../../types"; |
||||||
|
import { gitPluginContext } from "../gitui"; |
||||||
|
|
||||||
|
interface SourceControlGroupNavigationProps { |
||||||
|
eventKey: string; |
||||||
|
activePanel: string; |
||||||
|
callback: (eventKey: string) => void; |
||||||
|
group: sourceControlGroup |
||||||
|
} |
||||||
|
|
||||||
|
export const SourceControlGroupNavigation = (props: SourceControlGroupNavigationProps) => { |
||||||
|
const { eventKey, activePanel, callback, group } = props; |
||||||
|
const actions = React.useContext(gitActionsContext) |
||||||
|
const pluginActions = React.useContext(pluginActionsContext) |
||||||
|
const context = React.useContext(gitPluginContext) |
||||||
|
const handleClick = () => { |
||||||
|
if (!callback) return |
||||||
|
if (activePanel === eventKey) { |
||||||
|
callback('') |
||||||
|
} else { |
||||||
|
callback(eventKey) |
||||||
|
} |
||||||
|
} |
||||||
|
return ( |
||||||
|
<> |
||||||
|
<div className={'d-flex justify-content-between pt-1 ' + (activePanel === eventKey? 'bg-light': '')}> |
||||||
|
<span onClick={()=>handleClick()} role={'button'} className='nav d-flex justify-content-start align-items-center w-75'> |
||||||
|
{ |
||||||
|
activePanel === eventKey ? <FontAwesomeIcon className='' icon={faCaretDown}></FontAwesomeIcon> : <FontAwesomeIcon className='' icon={faCaretRight}></FontAwesomeIcon> |
||||||
|
} |
||||||
|
<label className="pl-1 nav form-check-label">{group.name}</label> |
||||||
|
</span> |
||||||
|
{ |
||||||
|
activePanel === eventKey ? |
||||||
|
<span className='d-flex justify-content-end align-items-center w-25'> |
||||||
|
{group.name === 'Changes' ? |
||||||
|
<CustomTooltip tooltipText={<FormattedMessage id="git.stageall" />}> |
||||||
|
<button data-id='sourcecontrol-add-all' onClick={async () => { await actions.addall(context.allchangesnotstaged) }} className='btn btn-sm'><FontAwesomeIcon icon={faPlus} className="" /></button> |
||||||
|
</CustomTooltip>: null} |
||||||
|
|
||||||
|
</span> : null |
||||||
|
} |
||||||
|
</div> |
||||||
|
</> |
||||||
|
); |
||||||
|
} |
@ -0,0 +1,60 @@ |
|||||||
|
import React, { useEffect, useState } from "react"; |
||||||
|
import { Alert } from "react-bootstrap"; |
||||||
|
import { gitActionsContext } from "../../state/context"; |
||||||
|
import { remote } from "../../types"; |
||||||
|
import GitUIButton from "../buttons/gituibutton"; |
||||||
|
import { gitPluginContext } from "../gitui"; |
||||||
|
import { LocalBranchDetails } from "./branches/localbranchdetails"; |
||||||
|
import { RemoteBranchDetails } from "./branches/remotebranchedetails"; |
||||||
|
|
||||||
|
export const Branches = () => { |
||||||
|
const context = React.useContext(gitPluginContext) |
||||||
|
const actions = React.useContext(gitActionsContext) |
||||||
|
const [newBranch, setNewBranch] = useState({ value: "" }); |
||||||
|
|
||||||
|
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { |
||||||
|
setNewBranch({ value: e.currentTarget.value }); |
||||||
|
}; |
||||||
|
|
||||||
|
return ( |
||||||
|
<> |
||||||
|
<div data-id='branches-panel-content' className="pt-1"> |
||||||
|
{context.branches && context.branches.length ? |
||||||
|
<div> |
||||||
|
{context.branches && context.branches.filter((branch, index) => !branch.remote).map((branch, index) => { |
||||||
|
return ( |
||||||
|
<LocalBranchDetails key={index} branch={branch}></LocalBranchDetails> |
||||||
|
); |
||||||
|
})} |
||||||
|
<hr /> |
||||||
|
|
||||||
|
</div> : null} |
||||||
|
{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> |
||||||
|
: null} |
||||||
|
<label>create branch</label> |
||||||
|
<div className="form-group"> |
||||||
|
|
||||||
|
<input |
||||||
|
placeholder="branch name" |
||||||
|
onChange={handleChange} |
||||||
|
className="form-control w-md-25 w-100" |
||||||
|
data-id="newbranchname" |
||||||
|
type="text" |
||||||
|
id="newbranchname" |
||||||
|
/> |
||||||
|
</div> |
||||||
|
<GitUIButton |
||||||
|
data-id="sourcecontrol-create-branch" |
||||||
|
onClick={async () => actions.createBranch(newBranch.value)} |
||||||
|
className="btn w-md-25 w-100 btn-primary" |
||||||
|
id="createbranch-btn" |
||||||
|
> |
||||||
|
create new branch |
||||||
|
</GitUIButton> |
||||||
|
</div> |
||||||
|
</> |
||||||
|
); |
||||||
|
} |
@ -0,0 +1,48 @@ |
|||||||
|
import { ReadCommitResult } from "isomorphic-git"; |
||||||
|
import { Accordion } from "react-bootstrap"; |
||||||
|
import React, { useEffect, useState } from "react"; |
||||||
|
import { CommitDetails } from "../commits/commitdetails"; |
||||||
|
import { CommitsNavigation } from "../../navigation/commits"; |
||||||
|
import { branch, remote } from "../../../types"; |
||||||
|
import { gitActionsContext } from "../../../state/context"; |
||||||
|
import { gitPluginContext } from "../../gitui"; |
||||||
|
|
||||||
|
export interface BrancheDifferenceProps { |
||||||
|
commits: ReadCommitResult[]; |
||||||
|
title: string, |
||||||
|
remote?: remote, |
||||||
|
branch?: branch |
||||||
|
ahead?: boolean, |
||||||
|
behind?: boolean |
||||||
|
} |
||||||
|
|
||||||
|
export const BranchDifferenceDetails = (props: BrancheDifferenceProps) => { |
||||||
|
const { commits, title, branch, remote, ahead, behind } = props; |
||||||
|
const [activePanel, setActivePanel] = useState<string>(""); |
||||||
|
const context = React.useContext(gitPluginContext) |
||||||
|
const actions = React.useContext(gitActionsContext) |
||||||
|
|
||||||
|
if (commits.length === 0) return null |
||||||
|
|
||||||
|
const getRemote = () => { |
||||||
|
return remote ? remote : context.upstream ? context.upstream : context.defaultRemote ? context.defaultRemote : null |
||||||
|
} |
||||||
|
|
||||||
|
const getCommitChanges = async (commit: ReadCommitResult) => { |
||||||
|
await actions.getCommitChanges(commit.oid, commit.commit.parent[0], null, getRemote()) |
||||||
|
} |
||||||
|
|
||||||
|
return ( |
||||||
|
<Accordion activeKey={activePanel} defaultActiveKey=""> |
||||||
|
<CommitsNavigation ahead={ahead} behind={behind} branch={branch} remote={remote} title={title} eventKey="0" activePanel={activePanel} callback={setActivePanel} /> |
||||||
|
<Accordion.Collapse className="pl-2 border-left ml-1" eventKey="0"> |
||||||
|
<div data-id={`branchdifference-commits-${branch.name}${ahead?'-ahead':''}${behind?'-behind':''}`} className="ml-1"> |
||||||
|
{commits && commits.map((commit, index) => { |
||||||
|
return ( |
||||||
|
<CommitDetails branch={branch} getCommitChanges={getCommitChanges} key={index} checkout={()=>{}} commit={commit}></CommitDetails> |
||||||
|
); |
||||||
|
})} |
||||||
|
</div> |
||||||
|
</Accordion.Collapse> |
||||||
|
</Accordion>) |
||||||
|
} |
@ -0,0 +1,39 @@ |
|||||||
|
import { branch, remote } from "../../../types"; |
||||||
|
import React, { useEffect, useState } from "react"; |
||||||
|
import { gitPluginContext } from "../../gitui"; |
||||||
|
import { CommitDetails } from "../commits/commitdetails"; |
||||||
|
import { BranchDifferenceDetails } from "./branchdifferencedetails"; |
||||||
|
|
||||||
|
export interface BrancheDetailsProps { |
||||||
|
branch: branch; |
||||||
|
showSummary?: boolean; |
||||||
|
} |
||||||
|
|
||||||
|
export const BranchDifferences = (props: BrancheDetailsProps) => { |
||||||
|
const { branch, showSummary } = props; |
||||||
|
const context = React.useContext(gitPluginContext) |
||||||
|
|
||||||
|
const getRemote = (): remote | null => { |
||||||
|
return context.upstream ? context.upstream : context.defaultRemote ? context.defaultRemote : null |
||||||
|
} |
||||||
|
|
||||||
|
const commitsAhead = (remote: remote) => { |
||||||
|
if (!remote) return []; |
||||||
|
return context.branchDifferences[`${remote.name}/${branch.name}`]?.uniqueHeadCommits || []; |
||||||
|
} |
||||||
|
|
||||||
|
const commitsBehind = (remote: remote) => { |
||||||
|
if (!remote) return []; |
||||||
|
return context.branchDifferences[`${remote.name}/${branch.name}`]?.uniqueRemoteCommits || []; |
||||||
|
} |
||||||
|
|
||||||
|
if (!getRemote()) return null; |
||||||
|
|
||||||
|
return ( |
||||||
|
|
||||||
|
<div> |
||||||
|
<BranchDifferenceDetails ahead={true} branch={branch} remote={getRemote()} title={`ahead of ${getRemote().name} by ${commitsAhead(getRemote()).length} commit(s)`} commits={commitsAhead(getRemote())}></BranchDifferenceDetails> |
||||||
|
<BranchDifferenceDetails behind={true} branch={branch} remote={getRemote()} title={`behind ${getRemote().name} by ${commitsBehind(getRemote()).length} commit(s)`} commits={commitsBehind(getRemote())}></BranchDifferenceDetails> |
||||||
|
{commitsAhead(getRemote()).length === 0 && commitsBehind(getRemote()).length === 0 ? null : <hr></hr>} |
||||||
|
</div>) |
||||||
|
} |
@ -0,0 +1,86 @@ |
|||||||
|
import { ReadCommitResult } from "isomorphic-git" |
||||||
|
import React, { useEffect, useState } from "react"; |
||||||
|
import { Accordion } from "react-bootstrap"; |
||||||
|
import { CommitDetailsNavigation } from "../../navigation/commitdetails"; |
||||||
|
import { gitActionsContext } from "../../../state/context"; |
||||||
|
import { gitPluginContext } from "../../gitui"; |
||||||
|
import { branch } from "../../../types"; |
||||||
|
import { BrancheDetailsNavigation } from "../../navigation/branchedetails"; |
||||||
|
import { CommitDetailsItems } from "../commits/commitdetailsitem"; |
||||||
|
import { CommitDetails } from "../commits/commitdetails"; |
||||||
|
import { BranchDifferences } from "./branchdifferences"; |
||||||
|
import GitUIButton from "../../buttons/gituibutton"; |
||||||
|
|
||||||
|
export interface BrancheDetailsProps { |
||||||
|
branch: branch; |
||||||
|
} |
||||||
|
|
||||||
|
export const LocalBranchDetails = (props: BrancheDetailsProps) => { |
||||||
|
const { branch } = props; |
||||||
|
const actions = React.useContext(gitActionsContext) |
||||||
|
const context = React.useContext(gitPluginContext) |
||||||
|
const [activePanel, setActivePanel] = useState<string>(""); |
||||||
|
const [hasNextPage, setHasNextPage] = useState<boolean>(false) |
||||||
|
const [lastPageNumber, setLastPageNumber] = useState<number>(0) |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
if (activePanel === "0") { |
||||||
|
|
||||||
|
if (lastPageNumber === 0) |
||||||
|
actions.getBranchCommits(branch, 1) |
||||||
|
actions.getBranchDifferences(branch, null, context) |
||||||
|
} |
||||||
|
}, [activePanel]) |
||||||
|
|
||||||
|
const checkout = (branch: branch) => { |
||||||
|
actions.checkout({ |
||||||
|
ref: branch.name, |
||||||
|
remote: branch.remote && branch.remote.name || null, |
||||||
|
refresh: true |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
const loadNextPage = () => { |
||||||
|
actions.getBranchCommits(branch, lastPageNumber + 1) |
||||||
|
} |
||||||
|
|
||||||
|
const checkoutCommit = async (oid: string) => { |
||||||
|
try { |
||||||
|
|
||||||
|
actions.checkout({ ref: oid }) |
||||||
|
; |
||||||
|
} catch (e) { |
||||||
|
//
|
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const getRemote = () => { |
||||||
|
return context.upstream ? context.upstream : context.defaultRemote ? context.defaultRemote : null |
||||||
|
} |
||||||
|
|
||||||
|
const getCommitChanges = async (commit: ReadCommitResult) => { |
||||||
|
await actions.getCommitChanges(commit.oid, commit.commit.parent[0], null, getRemote()) |
||||||
|
} |
||||||
|
|
||||||
|
return (<Accordion activeKey={activePanel} defaultActiveKey=""> |
||||||
|
<BrancheDetailsNavigation checkout={checkout} branch={branch} eventKey="0" activePanel={activePanel} callback={setActivePanel} /> |
||||||
|
<Accordion.Collapse className="pl-2 border-left ml-1" eventKey="0"> |
||||||
|
<> |
||||||
|
<div className="ml-1"> |
||||||
|
<BranchDifferences branch={branch}></BranchDifferences> |
||||||
|
<div data-id={`local-branch-commits-${branch && branch.name}`}> |
||||||
|
{context.localBranchCommits && Object.entries(context.localBranchCommits).map(([key, value]) => { |
||||||
|
|
||||||
|
if (key == branch.name) { |
||||||
|
return value.map((commit, index) => { |
||||||
|
return (<CommitDetails branch={branch} key={index} getCommitChanges={getCommitChanges} checkout={checkoutCommit} commit={commit}></CommitDetails>) |
||||||
|
}) |
||||||
|
} |
||||||
|
})} |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
{hasNextPage && <GitUIButton className="mb-1 ml-2 btn btn-sm" onClick={loadNextPage}>Load more</GitUIButton>} |
||||||
|
</> |
||||||
|
</Accordion.Collapse> |
||||||
|
</Accordion>) |
||||||
|
} |
@ -0,0 +1,111 @@ |
|||||||
|
import { ReadCommitResult } from "isomorphic-git" |
||||||
|
import React, { useEffect, useState } from "react"; |
||||||
|
import { Accordion } from "react-bootstrap"; |
||||||
|
import { CommitDetailsNavigation } from "../../navigation/commitdetails"; |
||||||
|
import { gitActionsContext } from "../../../state/context"; |
||||||
|
import { gitPluginContext } from "../../gitui"; |
||||||
|
import { branch } from "../../../types"; |
||||||
|
import { BrancheDetailsNavigation } from "../../navigation/branchedetails"; |
||||||
|
import { CommitDetailsItems } from "../commits/commitdetailsitem"; |
||||||
|
import { CommitDetails } from "../commits/commitdetails"; |
||||||
|
import GitUIButton from "../../buttons/gituibutton"; |
||||||
|
|
||||||
|
export interface BrancheDetailsProps { |
||||||
|
branch: branch; |
||||||
|
} |
||||||
|
|
||||||
|
export const RemoteBranchDetails = (props: BrancheDetailsProps) => { |
||||||
|
const { branch } = props; |
||||||
|
const actions = React.useContext(gitActionsContext) |
||||||
|
const context = React.useContext(gitPluginContext) |
||||||
|
const [activePanel, setActivePanel] = useState<string>(""); |
||||||
|
const [hasNextPage, setHasNextPage] = useState<boolean>(false) |
||||||
|
const [lastPageNumber, setLastPageNumber] = useState<number>(0) |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
if (activePanel === "0") { |
||||||
|
|
||||||
|
if (lastPageNumber === 0) |
||||||
|
actions.getBranchCommits(branch, 1) |
||||||
|
} |
||||||
|
}, [activePanel]) |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
let hasNextPage = false |
||||||
|
let lastPageNumber = 0 |
||||||
|
context.remoteBranchCommits && Object.entries(context.remoteBranchCommits).map(([key, value]) => { |
||||||
|
if (key == branch.name) { |
||||||
|
value.map((page, index) => { |
||||||
|
hasNextPage = page.hasNextPage |
||||||
|
lastPageNumber = page.page |
||||||
|
}) |
||||||
|
} |
||||||
|
}) |
||||||
|
setHasNextPage(hasNextPage) |
||||||
|
setLastPageNumber(lastPageNumber) |
||||||
|
}, [context.remoteBranchCommits]) |
||||||
|
|
||||||
|
const checkout = async (branch: branch) => { |
||||||
|
await actions.fetch({ |
||||||
|
remote: branch.remote, |
||||||
|
ref: branch, |
||||||
|
depth: 20, |
||||||
|
singleBranch: true, |
||||||
|
relative: false, |
||||||
|
quiet: true |
||||||
|
}) |
||||||
|
await actions.checkout({ |
||||||
|
ref: branch.name, |
||||||
|
remote: branch.remote && branch.remote.name || null, |
||||||
|
refresh: true |
||||||
|
}); |
||||||
|
await actions.getBranches() |
||||||
|
} |
||||||
|
|
||||||
|
const loadNextPage = () => { |
||||||
|
actions.getBranchCommits(branch, lastPageNumber + 1) |
||||||
|
} |
||||||
|
|
||||||
|
const checkoutCommit = async (oid: string) => { |
||||||
|
try { |
||||||
|
actions.checkout({ ref: oid }) |
||||||
|
} catch (e) { |
||||||
|
//
|
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const getCommitChanges = async (commit: ReadCommitResult) => { |
||||||
|
const changes = await actions.getCommitChanges(commit.oid, commit.commit.parent[0], branch, branch.remote) |
||||||
|
if (!changes) { |
||||||
|
await actions.fetch({ |
||||||
|
remote: branch.remote, |
||||||
|
ref: branch, |
||||||
|
depth: 20, |
||||||
|
singleBranch: true, |
||||||
|
relative: false, |
||||||
|
quiet: true |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return (<Accordion activeKey={activePanel} defaultActiveKey=""> |
||||||
|
<BrancheDetailsNavigation checkout={checkout} branch={branch} eventKey="0" activePanel={activePanel} callback={setActivePanel} /> |
||||||
|
<Accordion.Collapse className="pl-2 border-left ml-1" eventKey="0"> |
||||||
|
<> |
||||||
|
<div data-id={`remote-branch-commits-${branch && branch.name}`} className="ml-1"> |
||||||
|
{context.remoteBranchCommits && Object.entries(context.remoteBranchCommits).map(([key, value]) => { |
||||||
|
if (key == branch.name) { |
||||||
|
return value.map((page, index) => { |
||||||
|
return page.commits.map((commit, index) => { |
||||||
|
return (<CommitDetails branch={branch} getCommitChanges={getCommitChanges} key={index} checkout={checkoutCommit} commit={commit}></CommitDetails>) |
||||||
|
}) |
||||||
|
}) |
||||||
|
} |
||||||
|
})} |
||||||
|
|
||||||
|
</div> |
||||||
|
{hasNextPage && <GitUIButton className="mb-1 ml-2 btn btn-sm" onClick={loadNextPage}>Load more</GitUIButton>} |
||||||
|
</> |
||||||
|
</Accordion.Collapse> |
||||||
|
</Accordion>) |
||||||
|
} |
@ -0,0 +1,101 @@ |
|||||||
|
|
||||||
|
import React, { useState } from "react"; |
||||||
|
import { Alert, Form, FormControl, InputGroup } from "react-bootstrap"; |
||||||
|
import { useLocalStorage } from "../../hooks/useLocalStorage"; |
||||||
|
import { gitActionsContext } from "../../state/context"; |
||||||
|
import { gitPluginContext } from "../gitui"; |
||||||
|
import { SelectAndCloneRepositories } from "../github/selectandclonerepositories"; |
||||||
|
import { RemixUiCheckbox } from "@remix-ui/checkbox"; |
||||||
|
import GitUIButton from "../buttons/gituibutton"; |
||||||
|
|
||||||
|
export const Clone = () => { |
||||||
|
const context = React.useContext(gitPluginContext) |
||||||
|
const actions = React.useContext(gitActionsContext) |
||||||
|
const [cloneUrl, setCloneUrl] = useLocalStorage( |
||||||
|
"CLONE_URL", |
||||||
|
'' |
||||||
|
); |
||||||
|
|
||||||
|
const [cloneDepth, setCloneDepth] = useLocalStorage( |
||||||
|
"CLONE_DEPTH", |
||||||
|
1 |
||||||
|
); |
||||||
|
|
||||||
|
const [cloneBranch, setCloneBranch] = useLocalStorage( |
||||||
|
"CLONE_BRANCH", |
||||||
|
'' |
||||||
|
); |
||||||
|
|
||||||
|
const [url, setUrl] = useLocalStorage( |
||||||
|
"GITHUB_URL", |
||||||
|
'' |
||||||
|
); |
||||||
|
|
||||||
|
const clone = async () => { |
||||||
|
await actions.clone({ |
||||||
|
url: cloneUrl, |
||||||
|
branch: cloneBranch, |
||||||
|
depth: cloneDepth, |
||||||
|
singleBranch: !cloneAllBranches |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
const onCloneBranchChange = (value: string) => { |
||||||
|
setCloneBranch(value) |
||||||
|
} |
||||||
|
|
||||||
|
const onGitHubCloneUrlChange = (value: string) => { |
||||||
|
setCloneUrl(value) |
||||||
|
} |
||||||
|
|
||||||
|
const onDepthChange = (value: number) => { |
||||||
|
setCloneDepth(value) |
||||||
|
} |
||||||
|
|
||||||
|
const [cloneAllBranches, setcloneAllBranches] = useLocalStorage( |
||||||
|
"GITHUB_CLONE_ALL_BRANCES", |
||||||
|
false |
||||||
|
); |
||||||
|
|
||||||
|
const onAllBranchChange = () => { |
||||||
|
setcloneAllBranches((e: any) => !e) |
||||||
|
} |
||||||
|
|
||||||
|
return ( |
||||||
|
<> |
||||||
|
<div data-id="clone-panel-content"> |
||||||
|
<InputGroup className="mb-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" /> |
||||||
|
<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> |
||||||
|
<InputGroup className="mt-1 mb-1"> |
||||||
|
<InputGroup.Prepend> |
||||||
|
<InputGroup.Text id="clonedepthprepend"> |
||||||
|
--depth |
||||||
|
</InputGroup.Text> |
||||||
|
</InputGroup.Prepend> |
||||||
|
<FormControl id="clonedepth" type="number" value={cloneDepth} onChange={e => onDepthChange(parseInt(e.target.value))} aria-describedby="clonedepthprepend" /> |
||||||
|
</InputGroup> |
||||||
|
|
||||||
|
<RemixUiCheckbox |
||||||
|
id={`cloneAllBranches`} |
||||||
|
inputType="checkbox" |
||||||
|
name="cloneAllBranches" |
||||||
|
label={`Clone all branches`} |
||||||
|
onClick={() => onAllBranchChange()} |
||||||
|
checked={cloneAllBranches} |
||||||
|
onChange={() => { }} |
||||||
|
/> |
||||||
|
|
||||||
|
<hr></hr> |
||||||
|
</div> |
||||||
|
</>) |
||||||
|
} |
@ -0,0 +1,14 @@ |
|||||||
|
import React, { useEffect, useState } from "react"; |
||||||
|
import { PushPull } from "./commands/pushpull"; |
||||||
|
import { Fetch } from "./commands/fetch"; |
||||||
|
import { Merge } from "./commands/merge"; |
||||||
|
|
||||||
|
export const Commands = () => { |
||||||
|
|
||||||
|
return ( |
||||||
|
<> |
||||||
|
<PushPull></PushPull> |
||||||
|
<hr></hr> |
||||||
|
<Fetch></Fetch> |
||||||
|
</>) |
||||||
|
} |
@ -0,0 +1,25 @@ |
|||||||
|
import React, { useEffect, useState } from "react"; |
||||||
|
import { gitActionsContext } from "../../../state/context"; |
||||||
|
import GitUIButton from "../../buttons/gituibutton"; |
||||||
|
import { gitPluginContext } from "../../gitui"; |
||||||
|
|
||||||
|
export const Fetch = () => { |
||||||
|
const actions = React.useContext(gitActionsContext) |
||||||
|
const context = React.useContext(gitPluginContext) |
||||||
|
|
||||||
|
const fetchIsDisabled = () => { |
||||||
|
return (!context.upstream) || context.remotes.length === 0 |
||||||
|
} |
||||||
|
return ( |
||||||
|
<> |
||||||
|
<div className="btn-group w-100" role="group"> |
||||||
|
<GitUIButton 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> |
||||||
|
<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> |
||||||
|
</div> |
||||||
|
</>) |
||||||
|
} |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue