11import { Button } from '@components/Button' ;
22import { Dialog , useDialog } from '@components/Dialog' ;
3- import type { Resource , Server } from '@tomic/react' ;
3+ import type { JSONValue , Resource , Server } from '@tomic/react' ;
44import { useId , useRef , useState } from 'react' ;
55import { FaPlus } from 'react-icons/fa6' ;
6- import {
7- TextWriter ,
8- Uint8ArrayReader ,
9- ZipReader ,
10- type Entry ,
11- } from '@zip.js/zip.js' ;
126import { styled } from 'styled-components' ;
137import { Column , Row } from '@components/Row' ;
148import { JSONEditor } from '@components/JSONEditor' ;
159import Markdown from '@components/datatypes/Markdown' ;
16- import { useCreatePlugin , type PluginMetadata } from './createPlugin' ;
10+ import { useCreatePlugin } from '@views/Plugin/createPlugin' ;
11+ import { readZip , type PluginMetadata } from './plugins' ;
12+ import { ConfigReference } from '@views/Plugin/ConfigReference' ;
1713
1814interface NewPluginButtonProps {
1915 drive : Resource < Server . Drive > ;
2016}
2117
22- export const NewPluginButton : React . FC < NewPluginButtonProps > = ( { drive } ) => {
18+ const NewPluginButton : React . FC < NewPluginButtonProps > = ( { drive } ) => {
2319 const configLabelId = useId ( ) ;
2420 const [ error , setError ] = useState < string > ( ) ;
2521 const [ file , setFile ] = useState < File | null > ( null ) ;
2622 const fileInputRef = useRef < HTMLInputElement > ( null ) ;
2723 const [ metadata , setMetadata ] = useState < PluginMetadata > ( ) ;
2824 const [ configValid , setConfigValid ] = useState ( true ) ;
25+ const [ config , setConfig ] = useState < JSONValue > ( ) ;
26+
2927 const { createPluginResource, installPlugin } = useCreatePlugin ( ) ;
28+
29+ const reset = ( ) => {
30+ setError ( undefined ) ;
31+ setFile ( null ) ;
32+ setMetadata ( undefined ) ;
33+ setConfig ( undefined ) ;
34+ setConfigValid ( true ) ;
35+ fileInputRef . current ! . value = '' ;
36+ } ;
37+
3038 const [ dialogProps , show , hide ] = useDialog ( {
31- onCancel : ( ) => {
32- setError ( undefined ) ;
33- setFile ( null ) ;
34- setMetadata ( undefined ) ;
35- fileInputRef . current ! . value = '' ;
36- } ,
39+ onCancel : reset ,
3740 onSuccess : async ( ) => {
3841 if ( ! metadata || ! file ) {
3942 return setError ( 'Please fill in all fields' ) ;
4043 }
4144
4245 try {
43- const plugin = await createPluginResource ( { metadata, file, drive } ) ;
46+ const plugin = await createPluginResource ( {
47+ metadata,
48+ file,
49+ drive,
50+ config,
51+ } ) ;
4452 await installPlugin ( plugin , drive ) ;
4553 } catch ( err ) {
4654 setError ( `Failed to install plugin, error: ${ err . message } ` ) ;
4755 } finally {
48- setError ( undefined ) ;
49- setFile ( null ) ;
50- setMetadata ( undefined ) ;
51- fileInputRef . current ! . value = '' ;
56+ reset ( ) ;
5257 }
5358 } ,
5459 } ) ;
@@ -62,6 +67,7 @@ export const NewPluginButton: React.FC<NewPluginButtonProps> = ({ drive }) => {
6267 try {
6368 const readMetadata = await readZip ( targetFile ) ;
6469 setMetadata ( readMetadata ) ;
70+ setConfig ( readMetadata . defaultConfig ) ;
6571 setFile ( targetFile ) ;
6672 setError ( undefined ) ;
6773 show ( ) ;
@@ -112,11 +118,20 @@ export const NewPluginButton: React.FC<NewPluginButtonProps> = ({ drive }) => {
112118 < JSONEditor
113119 labelId = { configLabelId }
114120 initialValue = { JSON . stringify ( metadata . defaultConfig , null , 2 ) }
115- onChange = { ( ) => { } }
121+ onChange = { val => {
122+ try {
123+ setConfig ( JSON . parse ( val ) ) ;
124+ } catch ( e ) {
125+ // Do nothing
126+ }
127+ } }
116128 schema = { metadata . configSchema }
117129 showErrorStyling = { ! configValid }
118130 onValidationChange = { setConfigValid }
119131 />
132+ { metadata . configSchema && (
133+ < ConfigReference schema = { metadata . configSchema } />
134+ ) }
120135 </ Column >
121136 ) }
122137 { ! metadata && (
@@ -149,50 +164,7 @@ export const NewPluginButton: React.FC<NewPluginButtonProps> = ({ drive }) => {
149164 ) ;
150165} ;
151166
152- async function readZip ( file : File ) : Promise < PluginMetadata > {
153- const zip = new ZipReader ( new Uint8ArrayReader ( await file . bytes ( ) ) ) ;
154- const entries = await zip . getEntries ( ) ;
155-
156- if ( ! validateZip ( entries ) ) {
157- throw new Error ( 'Invalid plugin zip file.' ) ;
158- }
159-
160- for ( const entry of entries ) {
161- if ( ! entry . directory && entry . filename === 'plugin.json' ) {
162- const metadata = await entry . getData ( new TextWriter ( ) ) ;
163-
164- return JSON . parse ( metadata ) as PluginMetadata ;
165- }
166- }
167-
168- throw new Error ( 'Plugin metadata not found in zip file.' ) ;
169- }
170-
171- function validateZip ( entries : Entry [ ] ) : boolean {
172- const allowedRootFiles = [ 'plugin.json' , 'plugin.wasm' ] ;
173- let foundWasm = false ;
174- let foundJson = false ;
175-
176- for ( const entry of entries ) {
177- if ( entry . filename . startsWith ( 'assets/' ) ) {
178- continue ;
179- }
180-
181- if ( ! allowedRootFiles . includes ( entry . filename ) ) {
182- return false ;
183- }
184-
185- if ( entry . filename === 'plugin.wasm' ) {
186- foundWasm = true ;
187- }
188-
189- if ( entry . filename === 'plugin.json' ) {
190- foundJson = true ;
191- }
192- }
193-
194- return foundWasm && foundJson ;
195- }
167+ export default NewPluginButton ;
196168
197169const PluginName = styled . span `
198170 font-weight: bold;
0 commit comments