99import { ThemeUI } from './theme' ;
1010import { CustomTooltip } from '@remix-ui/helper' ;
1111import { AppContext } from '../../contexts' ;
12+ import IpfsHttpClient from 'ipfs-http-client' ;
13+ import { readDappFiles } from '../EditHtmlTemplate' ;
14+ import { InBrowserVite } from '../../InBrowserVite' ;
1215
1316function DeployPanel ( ) : JSX . Element {
1417 const intl = useIntl ( )
@@ -18,6 +21,73 @@ function DeployPanel(): JSX.Element {
1821 shortname : localStorage . getItem ( '__DISQUS_SHORTNAME' ) || '' ,
1922 shareTo : [ ] ,
2023 } ) ;
24+
25+ const [ showIpfsSettings , setShowIpfsSettings ] = useState ( false ) ;
26+ const [ ipfsHost , setIpfsHost ] = useState ( '' ) ;
27+ const [ ipfsPort , setIpfsPort ] = useState ( '' ) ;
28+ const [ ipfsProtocol , setIpfsProtocol ] = useState ( '' ) ;
29+ const [ ipfsProjectId , setIpfsProjectId ] = useState ( '' ) ;
30+ const [ ipfsProjectSecret , setIpfsProjectSecret ] = useState ( '' ) ;
31+ const [ isDeploying , setIsDeploying ] = useState ( false ) ;
32+ const [ deployResult , setDeployResult ] = useState ( { cid : '' , error : '' } ) ;
33+
34+ const getRemixIpfsSettings = ( ) => {
35+ let result = null ;
36+
37+ for ( let i = 0 ; i < localStorage . length ; i ++ ) {
38+ const key = localStorage . key ( i ) ;
39+
40+ if ( key && key . includes ( 'remix.config' ) ) {
41+ try {
42+ const data = JSON . parse ( localStorage . getItem ( key ) ) ;
43+
44+ if ( data && data [ "settings/ipfs-url" ] ) {
45+ result = {
46+ url : data [ "settings/ipfs-url" ] || null ,
47+ port : data [ "settings/ipfs-port" ] || null ,
48+ protocol : data [ "settings/ipfs-protocol" ] || null ,
49+ projectId : data [ "settings/ipfs-project-id" ] || null ,
50+ projectSecret : data [ "settings/ipfs-project-secret" ] || null ,
51+ } ;
52+ break ;
53+ }
54+ } catch ( err ) {
55+ console . warn ( `⚠️ ${ key } JSON parse error:` , err ) ;
56+ }
57+ }
58+ }
59+
60+ return result ;
61+ }
62+
63+ useEffect ( ( ) => {
64+ const loadGlobalIpfsSettings = ( ) => {
65+ try {
66+ const ipfsSettings = getRemixIpfsSettings ( ) ;
67+
68+ if ( ipfsSettings && ipfsSettings . url ) {
69+ const {
70+ url : host ,
71+ port,
72+ protocol,
73+ projectId : id ,
74+ projectSecret : secret
75+ } = ipfsSettings ;
76+
77+ setIpfsHost ( host ) ;
78+ setIpfsPort ( port || '5001' ) ;
79+ setIpfsProtocol ( protocol || 'https' ) ;
80+ setIpfsProjectId ( id || '' ) ;
81+ setIpfsProjectSecret ( secret || '' ) ;
82+ }
83+ } catch ( e ) {
84+ console . error ( e ) ;
85+ }
86+ setShowIpfsSettings ( true ) ;
87+ } ;
88+ loadGlobalIpfsSettings ( ) ;
89+ } , [ ] ) ;
90+
2191 const setShareTo = ( type : string ) => {
2292 let shareTo = formVal . shareTo ;
2393 if ( formVal . shareTo . includes ( type ) ) {
@@ -27,6 +97,120 @@ function DeployPanel(): JSX.Element {
2797 }
2898 setFormVal ( { ...formVal , shareTo } ) ;
2999 } ;
100+
101+ const handleIpfsDeploy = async ( ) => {
102+ setIsDeploying ( true ) ;
103+ setDeployResult ( { cid : '' , error : '' } ) ;
104+
105+ let builder : InBrowserVite ;
106+ let jsResult : { js : string ; success : boolean ; error ?: string } ;
107+ let filesMap : Map < string , string > ;
108+
109+ try {
110+ builder = new InBrowserVite ( ) ;
111+ await builder . initialize ( ) ;
112+
113+ filesMap = new Map < string , string > ( ) ;
114+ await readDappFiles ( 'dapp' , filesMap ) ;
115+
116+ if ( filesMap . size === 0 ) {
117+ throw new Error ( "No DApp files" ) ;
118+ }
119+
120+ jsResult = await builder . build ( filesMap , '/src/main.jsx' ) ;
121+ if ( ! jsResult . success ) {
122+ throw new Error ( `DApp build failed: ${ jsResult . error } ` ) ;
123+ }
124+
125+ } catch ( e ) {
126+ console . error ( e ) ;
127+ setDeployResult ( { cid : '' , error : `DApp build failed: ${ e . message } ` } ) ;
128+ setIsDeploying ( false ) ;
129+ return ;
130+ }
131+
132+
133+ let ipfsClient ;
134+ try {
135+ const auth = ( ipfsProjectId && ipfsProjectSecret )
136+ ? 'Basic ' + Buffer . from ( ipfsProjectId + ':' + ipfsProjectSecret ) . toString ( 'base64' )
137+ : null ;
138+
139+ const headers = auth ? { Authorization : auth } : { } ;
140+
141+ let clientOptions ;
142+ if ( ipfsHost ) {
143+ clientOptions = {
144+ host : ipfsHost . replace ( / ^ ( h t t p s | h t t p ) : \/ \/ / , '' ) ,
145+ port : parseInt ( ipfsPort ) || 5001 ,
146+ protocol : ipfsProtocol || 'https' ,
147+ headers
148+ } ;
149+ } else {
150+ clientOptions = { host : 'ipfs.infura.io' , port : 5001 , protocol : 'https' , headers } ;
151+ }
152+
153+ ipfsClient = IpfsHttpClient ( clientOptions ) ;
154+
155+ } catch ( e ) {
156+ console . error ( e ) ;
157+ setDeployResult ( { cid : '' , error : `IPFS: ${ e . message } ` } ) ;
158+ setIsDeploying ( false ) ;
159+ return ;
160+ }
161+
162+ try {
163+ const indexHtmlContent = filesMap . get ( '/index.html' ) ;
164+ if ( ! indexHtmlContent ) {
165+ throw new Error ( "Cannot find index.html" ) ;
166+ }
167+
168+ let modifiedHtml = indexHtmlContent ;
169+
170+ modifiedHtml = modifiedHtml . replace (
171+ / < s c r i p t t y p e = " m o d u l e " [ ^ > ] * s r c = " (?: \/ | \. \/ ) ? s r c \/ m a i n \. j s x " [ ^ > ] * > < \/ s c r i p t > / ,
172+ '<script type="module" src="./app.js"></script>'
173+ ) ;
174+
175+ modifiedHtml = modifiedHtml . replace (
176+ / < l i n k r e l = " s t y l e s h e e t " [ ^ > ] * h r e f = " (?: \/ | \. \/ ) ? s r c \/ i n d e x \. c s s " [ ^ > ] * > / ,
177+ ''
178+ ) ;
179+
180+ const filesToUpload = [
181+ {
182+ path : 'index.html' ,
183+ content : modifiedHtml
184+ } ,
185+ {
186+ path : 'app.js' ,
187+ content : jsResult . js
188+ }
189+ ] ;
190+
191+ const addOptions = {
192+ wrapWithDirectory : true ,
193+ cidVersion : 1 ,
194+ } ;
195+
196+ let rootCid = '' ;
197+ for await ( const result of ipfsClient . addAll ( filesToUpload , addOptions ) ) {
198+ if ( result . path === '' ) {
199+ rootCid = result . cid . toString ( ) ;
200+ }
201+ }
202+
203+ setDeployResult ( { cid : rootCid , error : '' } ) ;
204+ setIsDeploying ( false ) ;
205+
206+ if ( showIpfsSettings && ipfsHost ) {
207+ }
208+
209+ } catch ( e ) {
210+ setDeployResult ( { cid : '' , error : `IPFS: ${ e . message } ` } ) ;
211+ setIsDeploying ( false ) ;
212+ }
213+ } ;
30214
31215 return (
32216 < div className = "d-inline-block" >
@@ -182,20 +366,72 @@ function DeployPanel(): JSX.Element {
182366 </ label >
183367 </ div >
184368 </ Form . Group >
369+
185370 < ThemeUI />
186371
187- < Button
188- data-id = "deployDapp-IPFS"
189- variant = "primary"
190- type = "button" // type="submit"에서 변경
191- className = "mt-3"
192- onClick = { ( ) => {
193- console . log ( "Deploying to IPFS/ENS... (TODO)" ) ;
194- } }
195- >
196- < FormattedMessage id = "quickDapp.deployToIPFS" defaultMessage = "Deploy to IPFS & ENS" />
197- </ Button >
198- </ Form >
372+ { showIpfsSettings && (
373+ < >
374+ < hr />
375+ < h5 className = "mb-2" > < FormattedMessage id = "quickDapp.ipfsSettings" defaultMessage = "IPFS Settings" /> </ h5 >
376+ < Alert variant = "info" className = "mb-2 small" >
377+ < FormattedMessage id = "quickDapp.ipfsSettings.info" defaultMessage = "No global IPFS settings found. Please provide credentials below, or configure them in the 'Settings' plugin." />
378+ </ Alert >
379+ < Form . Group className = "mb-2" controlId = "formIpfsHost" >
380+ < Form . Label className = "text-uppercase mb-0" > IPFS Host</ Form . Label >
381+ < Form . Control type = "text" placeholder = "e.g., ipfs.infura.io" value = { ipfsHost } onChange = { ( e ) => setIpfsHost ( e . target . value ) } />
382+ </ Form . Group >
383+ < Form . Group className = "mb-2" controlId = "formIpfsPort" >
384+ < Form . Label className = "text-uppercase mb-0" > IPFS Port</ Form . Label >
385+ < Form . Control type = "text" placeholder = "e.g., 5001" value = { ipfsPort } onChange = { ( e ) => setIpfsPort ( e . target . value ) } />
386+ </ Form . Group >
387+ < Form . Group className = "mb-2" controlId = "formIpfsProtocol" >
388+ < Form . Label className = "text-uppercase mb-0" > IPFS Protocol</ Form . Label >
389+ < Form . Control type = "text" placeholder = "e.g., https" value = { ipfsProtocol } onChange = { ( e ) => setIpfsProtocol ( e . target . value ) } />
390+ </ Form . Group >
391+ < Form . Group className = "mb-2" controlId = "formIpfsProjectId" >
392+ < Form . Label className = "text-uppercase mb-0" > Project ID (Optional)</ Form . Label >
393+ < Form . Control type = "text" placeholder = "Infura Project ID" value = { ipfsProjectId } onChange = { ( e ) => setIpfsProjectId ( e . target . value ) } />
394+ </ Form . Group >
395+ < Form . Group className = "mb-2" controlId = "formIpfsProjectSecret" >
396+ < Form . Label className = "text-uppercase mb-0" > Project Secret (Optional)</ Form . Label >
397+ < Form . Control type = "password" placeholder = "Infura Project Secret" value = { ipfsProjectSecret } onChange = { ( e ) => setIpfsProjectSecret ( e . target . value ) } />
398+ </ Form . Group >
399+ </ >
400+ ) }
401+ < hr />
402+
403+ < Button
404+ data-id = "deployDapp-IPFS"
405+ variant = "primary"
406+ type = "button"
407+ className = "mt-3 w-100"
408+ onClick = { handleIpfsDeploy }
409+ disabled = { isDeploying || ( showIpfsSettings && ! ipfsHost ) }
410+ >
411+ { isDeploying ? (
412+ < > < i className = "fas fa-spinner fa-spin me-1" > </ i > < FormattedMessage id = "quickDapp.deploying" defaultMessage = "Deploying..." /> </ >
413+ ) : (
414+ < FormattedMessage id = "quickDapp.deployToIPFS" defaultMessage = "Deploy to IPFS" />
415+ ) }
416+ </ Button >
417+
418+ { deployResult . cid && (
419+ < Alert variant = "success" className = "mt-3 small" style = { { wordBreak : 'break-all' } } >
420+ < div className = "fw-bold" > Deployed Successfully!</ div >
421+ < div > < strong > CID:</ strong > { deployResult . cid } </ div >
422+ < hr className = "my-2" />
423+ < a href = { `https://gateway.ipfs.io/ipfs/${ deployResult . cid } ` } target = "_blank" rel = "noopener noreferrer" >
424+ View on ipfs.io Gateway
425+ </ a >
426+ </ Alert >
427+ ) }
428+ { deployResult . error && (
429+ < Alert variant = "danger" className = "mt-3 small" >
430+ { deployResult . error }
431+ </ Alert >
432+ ) }
433+
434+ </ Form >
199435 </ div >
200436 ) ;
201437}
0 commit comments