diff --git a/packages/ui-default/build/config/webpack.ts b/packages/ui-default/build/config/webpack.ts index 98d83b58c..0de553a60 100644 --- a/packages/ui-default/build/config/webpack.ts +++ b/packages/ui-default/build/config/webpack.ts @@ -7,14 +7,11 @@ import FriendlyErrorsPlugin from 'friendly-errors-webpack-plugin'; import ExtractCssPlugin from 'mini-css-extract-plugin'; import MonacoWebpackPlugin from 'monaco-editor-webpack-plugin'; import { dirname } from 'path'; -import SpeedMeasurePlugin from 'speed-measure-webpack-plugin'; import webpack from 'webpack'; import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'; import WebpackBar from 'webpackbar'; import root from '../utils/root'; -const smp = new SpeedMeasurePlugin(); - export default function (env: { production?: boolean, measure?: boolean } = {}) { function esbuildLoader() { return { @@ -252,11 +249,18 @@ export default function (env: { production?: boolean, measure?: boolean } = {}) id: 'vs/language/yaml/yamlWorker', entry: require.resolve('monaco-yaml/yaml.worker.js'), }, + }, { + label: 'graphql', + entry: require.resolve('monaco-graphql/esm/monaco.contribution.js'), + worker: { + id: 'vs/language/graphql/graphqlWorker', + entry: require.resolve('monaco-graphql/esm/graphql.worker.js'), + }, }], }), ...env.measure ? [new BundleAnalyzerPlugin({ analyzerPort: 'auto' })] : [], ], }; - return env.measure ? smp.wrap(config) : config; + return config; } diff --git a/packages/ui-default/package.json b/packages/ui-default/package.json index edb4cec47..1ab436478 100644 --- a/packages/ui-default/package.json +++ b/packages/ui-default/package.json @@ -1,6 +1,6 @@ { "name": "@hydrooj/ui-default", - "version": "4.39.10", + "version": "4.39.11", "author": "undefined ", "license": "AGPL-3.0", "main": "hydro.js", @@ -65,6 +65,7 @@ "monaco-editor": "^0.33.0", "monaco-editor-nls": "^2.0.0", "monaco-editor-webpack-plugin": "^7.0.1", + "monaco-graphql": "^1.1.2", "monaco-themes": "^0.4.2", "monaco-yaml": "^4.0.0", "nanoid": "^4.0.0", @@ -91,7 +92,6 @@ "redux-thunk": "^2.4.1", "rupture": "^0.7.1", "slideout": "^1.0.1", - "speed-measure-webpack-plugin": "^1.5.0", "sticky-kit": "^1.1.3", "style-loader": "^3.3.1", "stylus": "^0.59.0", diff --git a/packages/ui-default/pages/api.page.tsx b/packages/ui-default/pages/api.page.tsx index f6faf465c..6524c2847 100644 --- a/packages/ui-default/pages/api.page.tsx +++ b/packages/ui-default/pages/api.page.tsx @@ -1,50 +1,171 @@ -/* eslint-disable react/function-component-definition */ -import 'graphiql/graphiql.css'; +import 'graphiql/src/css/doc-explorer.css'; +import type { GraphQLSchema } from 'graphql'; +import { load as loadYaml } from 'js-yaml'; +import type * as monaco from 'monaco-editor'; +import type { SchemaConfig } from 'monaco-graphql/src/typings'; import React from 'react'; -import ReactDOM from 'react-dom/client'; +import { createRoot } from 'react-dom/client'; import { NamedPage } from 'vj/misc/Page'; import request from 'vj/utils/request'; -const Logo = () => Hydro API Console ; const defaultQuery = `\ -query { - user(id: 1) { +query Example( + $name: String! +) { + user(uname: $name) { + _id uname } }`; +const variablesString = 'name: Hydro'; +const resultsString = '{}'; +const schemaSdlString = `\ +""" +Loading schema... +"""`; const page = new NamedPage('api', async () => { - const [{ default: GraphiQL }, { buildSchema }, res] = await Promise.all([ - import('graphiql/esm/index.js'), - import('graphql'), - request.get('/api?schema'), + const [{ DocExplorer }, { buildClientSchema, getIntrospectionQuery, printSchema }, { load }] = await Promise.all([ + import('graphiql/esm/components/DocExplorer'), + import('graphql/utilities'), + import('vj/components/monaco/loader'), ]); - // @ts-ignore - GraphiQL.Logo = Logo; - const App = () => ( - { - const data = await fetch( - '', - { - method: 'POST', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - }, - body: JSON.stringify(graphQLParams), - credentials: 'same-origin', - }, - ); - return data.json().catch(() => data.text()); - }} - /> + const { monaco } = await load(['graphql', 'json', 'yaml']); + const { initializeMode } = await import('monaco-graphql/esm/initializeMode'); + + const variablesModel = monaco.editor.createModel( + variablesString, + 'yaml', + monaco.Uri.file('/1/variables.yaml'), + ); + const variablesEditor = monaco.editor.create( + document.getElementById('variables') as HTMLElement, + { + model: variablesModel, + language: 'yaml', + formatOnPaste: true, + formatOnType: true, + comments: { + insertSpace: true, + ignoreEmptyLines: true, + }, + }, ); - ReactDOM.createRoot(document.getElementById('graphiql')).render(); + const operationModel = monaco.editor.createModel( + defaultQuery, + 'graphql', + monaco.Uri.file('/1/operation.graphql'), + ); + const operationEditor = monaco.editor.create( + document.getElementById('operation') as HTMLElement, + { + model: operationModel, + formatOnPaste: true, + formatOnType: true, + folding: true, + language: 'graphql', + }, + ); + + const schemaModel = monaco.editor.createModel( + schemaSdlString, + 'graphql', + monaco.Uri.file('/1/schema.graphqls'), + ); + const schemaEditor = monaco.editor.create( + document.getElementById('schema-sdl') as HTMLElement, + { + model: schemaModel, + formatOnPaste: true, + formatOnType: true, + folding: true, + readOnly: true, + language: 'graphql', + }, + ); + + const resultsModel = monaco.editor.createModel( + resultsString, + 'json', + monaco.Uri.file('/1/results.json'), + ); + const resultsEditor = monaco.editor.create( + document.getElementById('results') as HTMLElement, + { + model: resultsModel, + language: 'json', + wordWrap: 'on', + readOnly: true, + showFoldingControls: 'always', + }, + ); + + const monacoGraphQLAPI = initializeMode({ + formattingOptions: { + prettierConfig: { + printWidth: 120, + }, + }, + }); + + const toolbar = $('#toolbar')!; + toolbar.html(); + const executeOpButton = $(''); + toolbar.append(executeOpButton); + + const operationUri = operationModel.uri.toString(); + let schema: SchemaConfig; + let clientSchema: GraphQLSchema; + try { + const { data } = await request.post('/api?schema', { + query: getIntrospectionQuery(), + operationName: 'IntrospectionQuery', + }); + clientSchema = buildClientSchema(data); + schema = { + introspectionJSON: data, + documentString: printSchema(clientSchema), + uri: monaco.Uri.parse('/api?schema').toString(), + }; + } catch { + schemaModel.setValue('"""\nFailed to load schema.\n"""'); + } + monacoGraphQLAPI.setSchemaConfig([ + { ...schema, fileMatch: [operationUri, schemaModel.uri.toString()] }, + ]); + schemaEditor.setValue(schema.documentString || ''); + + const operationHandler = async () => { + try { + resultsEditor.setValue(JSON.stringify({ message: 'Executing...' }, null, 2)); + const result = await request.post('/api?schema', { + query: operationEditor.getValue(), + variables: loadYaml(variablesEditor.getValue()), + }); + resultsEditor.setValue(JSON.stringify(result, null, 2)); + } catch (err) { + if (err instanceof Error) { + resultsEditor.setValue(err.toString()); + } + } + }; + executeOpButton.on('click', operationHandler); + executeOpButton.on('touchend', operationHandler); + const opAction: monaco.editor.IActionDescriptor = { + id: 'graphql-run', + label: 'Run Operation', + contextMenuOrder: 0, + contextMenuGroupId: 'graphql', + keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter], + run: operationHandler, + }; + operationEditor.addAction(opAction); + variablesEditor.addAction(opAction); + resultsEditor.addAction(opAction); + + createRoot(document.getElementById('docs')).render(); }); export default page; diff --git a/packages/ui-default/templates/api.html b/packages/ui-default/templates/api.html index 445c111d6..fb7f1215b 100644 --- a/packages/ui-default/templates/api.html +++ b/packages/ui-default/templates/api.html @@ -1,4 +1,43 @@ {% extends "layout/basic.html" %} {% block content %} -
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {% endblock %}