mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-20 17:46:28 +08:00
ui: add cordis
This commit is contained in:
7 files changed
+105
-102
No files matched your search
@@ -3,7 +3,7 @@
|
|||||||
export * from './utils';
|
export * from './utils';
|
||||||
export { default as Notification } from './components/notification';
|
export { default as Notification } from './components/notification';
|
||||||
export * from './components/dialog';
|
export * from './components/dialog';
|
||||||
export * as bus from './bus';
|
export { Context, Service, ctx } from './context';
|
||||||
export { default as AnsiUp } from 'ansi_up';
|
export { default as AnsiUp } from 'ansi_up';
|
||||||
export { default as loadMonaco } from './components/monaco/loader';
|
export { default as loadMonaco } from './components/monaco/loader';
|
||||||
export { default as uploadFiles } from './components/upload';
|
export { default as uploadFiles } from './components/upload';
|
||||||
@@ -36,6 +36,8 @@ export default async function load(name: string) {
|
|||||||
return lazyModules[name];
|
return lazyModules[name];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface EventMap { }
|
||||||
|
|
||||||
import AutoComplete from './components/autocomplete';
|
import AutoComplete from './components/autocomplete';
|
||||||
import CustomSelectAutoComplete from './components/autocomplete/CustomSelectAutoComplete';
|
import CustomSelectAutoComplete from './components/autocomplete/CustomSelectAutoComplete';
|
||||||
import DomainSelectAutoComplete from './components/autocomplete/DomainSelectAutoComplete';
|
import DomainSelectAutoComplete from './components/autocomplete/DomainSelectAutoComplete';
|
||||||
|
|||||||
@@ -1,94 +0,0 @@
|
|||||||
/* eslint-disable no-await-in-loop */
|
|
||||||
import type * as monaco from 'monaco-editor';
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
||||||
const _hooks: Record<keyof any, Array<(...args: any[]) => any>> = {};
|
|
||||||
|
|
||||||
function isBailed(value: any) {
|
|
||||||
return value !== null && value !== false && value !== undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Disposable = () => void;
|
|
||||||
export type VoidReturn = Promise<any> | any;
|
|
||||||
|
|
||||||
export interface EventMap extends Record<string, any> {
|
|
||||||
'scratchpadEditorCreate': (editor: monaco.editor.IStandaloneCodeEditor, monaco: typeof import('monaco-editor')) => any;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getHooks<K extends keyof EventMap>(name: K) {
|
|
||||||
const hooks = (_hooks[name] ||= []);
|
|
||||||
if (hooks.length >= 2048) {
|
|
||||||
console.warn(
|
|
||||||
'max listener count (2048) for event "%s" exceeded, which may be caused by a memory leak',
|
|
||||||
name,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return hooks;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function removeListener<K extends keyof EventMap>(name: K, listener: EventMap[K]) {
|
|
||||||
const index = (_hooks[name] || []).findIndex((callback) => callback === listener);
|
|
||||||
if (index >= 0) {
|
|
||||||
_hooks[name].splice(index, 1);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function addListener<K extends keyof EventMap>(name: K, listener: EventMap[K]) {
|
|
||||||
getHooks(name).push(listener);
|
|
||||||
return () => removeListener(name, listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function prependListener<K extends keyof EventMap>(name: K, listener: EventMap[K]) {
|
|
||||||
getHooks(name).unshift(listener);
|
|
||||||
return () => removeListener(name, listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function once<K extends keyof EventMap>(name: K, listener: EventMap[K]) {
|
|
||||||
let dispose;
|
|
||||||
function l(...args: any[]) {
|
|
||||||
dispose();
|
|
||||||
return listener.apply(this, args);
|
|
||||||
}
|
|
||||||
l.toString = () => `// Once \n${listener.toString()}`;
|
|
||||||
dispose = addListener(name, l);
|
|
||||||
return dispose;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function on<K extends keyof EventMap>(name: K, listener: EventMap[K]) {
|
|
||||||
return addListener(name, listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function off<K extends keyof EventMap>(name: K, listener: EventMap[K]) {
|
|
||||||
return removeListener(name, listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function parallel<K extends keyof EventMap>(name: K, ...args: Parameters<EventMap[K]>): Promise<void> {
|
|
||||||
const tasks: Promise<any>[] = [];
|
|
||||||
for (const callback of _hooks[name] || []) {
|
|
||||||
tasks.push(callback.apply(this, args));
|
|
||||||
}
|
|
||||||
await Promise.all(tasks);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function emit<K extends keyof EventMap>(name: K, ...args: Parameters<EventMap[K]>) {
|
|
||||||
return parallel(name, ...args);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function serial<K extends keyof EventMap>(name: K, ...args: Parameters<EventMap[K]>): Promise<void> {
|
|
||||||
const hooks = Array.from(_hooks[name] || []);
|
|
||||||
for (const callback of hooks) {
|
|
||||||
await callback.apply(this, args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function bail<K extends keyof EventMap>(name: K, ...args: Parameters<EventMap[K]>): Promise<ReturnType<EventMap[K]>> {
|
|
||||||
const hooks = Array.from(_hooks[name] || []);
|
|
||||||
for (const callback of hooks) {
|
|
||||||
let result = callback.apply(this, args);
|
|
||||||
if (result instanceof Promise) result = await result;
|
|
||||||
if (isBailed(result)) return result;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
@@ -2,6 +2,7 @@ import type * as monaco from 'monaco-editor';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { load } from 'vj/components/monaco/loader';
|
import { load } from 'vj/components/monaco/loader';
|
||||||
|
import { ctx } from 'vj/context';
|
||||||
|
|
||||||
interface ScratchpadOptions {
|
interface ScratchpadOptions {
|
||||||
value?: string;
|
value?: string;
|
||||||
@@ -59,7 +60,7 @@ export default connect((state: any) => ({
|
|||||||
);
|
);
|
||||||
(window as any).editor = this.editor;
|
(window as any).editor = this.editor;
|
||||||
(window as any).monaco = monaco;
|
(window as any).monaco = monaco;
|
||||||
window.Hydro.bus.emit('scratchpadEditorCreate', this.editor, monaco);
|
ctx.scratchpad.init(this.editor, monaco);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+35
-5
@@ -1,11 +1,13 @@
|
|||||||
import ProblemIcon from '@vscode/codicons/src/icons/file.svg?react';
|
import ProblemIcon from '@vscode/codicons/src/icons/file.svg?react';
|
||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
import type * as monaco from 'monaco-editor';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector, useStore } from 'react-redux';
|
||||||
import SplitPane from 'react-split-pane';
|
import SplitPane from 'react-split-pane';
|
||||||
import Dom from 'vj/components/react/DomComponent';
|
import Dom from 'vj/components/react/DomComponent';
|
||||||
import SplitPaneFillOverlay from 'vj/components/react-splitpane/SplitPaneFillOverlayComponent';
|
import SplitPaneFillOverlay from 'vj/components/react-splitpane/SplitPaneFillOverlayComponent';
|
||||||
|
import { Context, ctx, Service } from 'vj/context';
|
||||||
import ScratchpadEditor from './ScratchpadEditorContainer';
|
import ScratchpadEditor from './ScratchpadEditorContainer';
|
||||||
import ScratchpadPretest from './ScratchpadPretestContainer';
|
import ScratchpadPretest from './ScratchpadPretestContainer';
|
||||||
import ScratchpadRecords from './ScratchpadRecordsContainer';
|
import ScratchpadRecords from './ScratchpadRecordsContainer';
|
||||||
@@ -40,23 +42,51 @@ const pages = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let rerenderCallback = null;
|
let rerenderCallback = null;
|
||||||
export function addPage(key, icon, component) {
|
class ScratchpadService extends Service {
|
||||||
|
constructor(public store) {
|
||||||
|
super(ctx, 'scratchpad', true);
|
||||||
|
this.load = new Promise((resolve) => {
|
||||||
|
this.loadCallback = resolve;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pages = pages;
|
||||||
|
load: Promise<void>;
|
||||||
|
loadCallback: () => void;
|
||||||
|
editor: monaco.editor.IStandaloneCodeEditor;
|
||||||
|
monaco: typeof import('monaco-editor');
|
||||||
|
|
||||||
|
init(editor: monaco.editor.IStandaloneCodeEditor, monaco: typeof import('monaco-editor')) {
|
||||||
|
this.editor = editor;
|
||||||
|
this.monaco = monaco;
|
||||||
|
this.loadCallback();
|
||||||
|
}
|
||||||
|
|
||||||
|
addPage(key, icon, component) {
|
||||||
pages[key] = {
|
pages[key] = {
|
||||||
icon,
|
icon,
|
||||||
component,
|
component,
|
||||||
};
|
};
|
||||||
rerenderCallback?.();
|
rerenderCallback?.();
|
||||||
}
|
}
|
||||||
window.Hydro.scratchpad = { addPage, pages };
|
}
|
||||||
|
Context.service('scratchpad', ScratchpadService);
|
||||||
|
declare module '../../context' {
|
||||||
|
interface Context {
|
||||||
|
scratchpad: ScratchpadService;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default function ScratchpadContainer() {
|
export default function ScratchpadContainer() {
|
||||||
const [, updateState] = React.useState();
|
const store = useStore();
|
||||||
|
ctx.scratchpad ||= new ScratchpadService(store);
|
||||||
|
const [, updateState] = React.useState<any>();
|
||||||
const forceUpdate = React.useCallback(() => updateState({}), []);
|
const forceUpdate = React.useCallback(() => updateState({}), []);
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
rerenderCallback = forceUpdate;
|
rerenderCallback = forceUpdate;
|
||||||
}, []);
|
}, []);
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const ui = useSelector((state) => state.ui, _.isEqual);
|
const ui = useSelector<any, any>((state) => state.ui, _.isEqual);
|
||||||
|
|
||||||
const handleChangeSize = _.debounce((uiElement, size) => {
|
const handleChangeSize = _.debounce((uiElement, size) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import * as cordis from 'cordis';
|
||||||
|
import type { EventMap } from './api';
|
||||||
|
|
||||||
|
export interface Events<C extends Context = Context> extends cordis.Events<C>, EventMap {
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Context {
|
||||||
|
[Context.events]: Events<Context>;
|
||||||
|
setTimeout: typeof setTimeout;
|
||||||
|
setInterval: typeof setInterval;
|
||||||
|
setImmediate: typeof setImmediate;
|
||||||
|
api: ApiMixin;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Context extends cordis.Context {
|
||||||
|
static readonly session = Symbol('session');
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace Context {
|
||||||
|
export interface Config extends cordis.Context.Config { }
|
||||||
|
}
|
||||||
|
|
||||||
|
export type MainScope = cordis.MainScope<Context>;
|
||||||
|
export type EffectScope = cordis.EffectScope<Context>;
|
||||||
|
export type ForkScope = cordis.ForkScope<Context>;
|
||||||
|
export type Plugin = cordis.Plugin<Context>;
|
||||||
|
export const Service = cordis.Service<Context>;
|
||||||
|
export namespace Plugin {
|
||||||
|
export type Function<T = any> = cordis.Plugin.Function<T, Context>;
|
||||||
|
export type Constructor<T = any> = cordis.Plugin.Constructor<T, Context>;
|
||||||
|
export type Object<S = any, T = any> = cordis.Plugin.Object<S, T, Context>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const T = <F extends (...args: any[]) => any>(origFunc: F, disposeFunc?) =>
|
||||||
|
function method(this: cordis.Service, ...args: Parameters<F>) {
|
||||||
|
const res = origFunc(...args);
|
||||||
|
this.caller?.on('dispose', () => (disposeFunc ? disposeFunc(res) : res()));
|
||||||
|
};
|
||||||
|
export class ApiMixin extends Service {
|
||||||
|
static readonly methods = ['addScript', 'setInterval', 'setTimeout', 'setImmediate', 'provideModule', 'inject', 'broadcast'];
|
||||||
|
setInterval = T(setInterval, clearInterval);
|
||||||
|
setTimeout = T(setTimeout, clearTimeout);
|
||||||
|
setImmediate = T(setImmediate, clearImmediate);
|
||||||
|
constructor(ctx) {
|
||||||
|
super(ctx, 'api', true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Context.service('api', ApiMixin);
|
||||||
|
export const ctx = new Context();
|
||||||
@@ -45,6 +45,7 @@
|
|||||||
"chalk": "^5.2.0",
|
"chalk": "^5.2.0",
|
||||||
"classnames": "^2.3.2",
|
"classnames": "^2.3.2",
|
||||||
"clipboard": "^2.0.11",
|
"clipboard": "^2.0.11",
|
||||||
|
"cordis": "^2.7.3",
|
||||||
"diff": "^5.1.0",
|
"diff": "^5.1.0",
|
||||||
"diff-dom": "^5.0.4",
|
"diff-dom": "^5.0.4",
|
||||||
"echarts": "^5.4.1",
|
"echarts": "^5.4.1",
|
||||||
|
|||||||
+15
-1
@@ -3,9 +3,22 @@ import React from 'react';
|
|||||||
import { createRoot } from 'react-dom/client';
|
import { createRoot } from 'react-dom/client';
|
||||||
import VjNotification from 'vj/components/notification';
|
import VjNotification from 'vj/components/notification';
|
||||||
import selectUser from 'vj/components/selectUser';
|
import selectUser from 'vj/components/selectUser';
|
||||||
|
import { Context, ctx, Service } from 'vj/context';
|
||||||
import { NamedPage } from 'vj/misc/Page';
|
import { NamedPage } from 'vj/misc/Page';
|
||||||
import { api, gql, loadReactRedux } from 'vj/utils';
|
import { api, gql, loadReactRedux } from 'vj/utils';
|
||||||
|
|
||||||
|
class MessagePadService extends Service {
|
||||||
|
constructor(public store, public WebSocket: typeof import('../components/socket').default) {
|
||||||
|
super(ctx, 'messagepad', true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Context.service('messagepad', MessagePadService);
|
||||||
|
declare module '../context' {
|
||||||
|
interface Context {
|
||||||
|
messagepad: MessagePadService;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const page = new NamedPage('home_messages', () => {
|
const page = new NamedPage('home_messages', () => {
|
||||||
let reduxStore;
|
let reduxStore;
|
||||||
|
|
||||||
@@ -29,7 +42,8 @@ const page = new NamedPage('home_messages', () => {
|
|||||||
const { Provider, store } = await loadReactRedux(MessagePadReducer);
|
const { Provider, store } = await loadReactRedux(MessagePadReducer);
|
||||||
|
|
||||||
reduxStore = store;
|
reduxStore = store;
|
||||||
window.store = reduxStore;
|
(window as any).store = reduxStore;
|
||||||
|
ctx.messagepad = new MessagePadService(store, WebSocket);
|
||||||
|
|
||||||
const sock = new WebSocket(`${UiContext.ws_prefix}home/messages-conn`);
|
const sock = new WebSocket(`${UiContext.ws_prefix}home/messages-conn`);
|
||||||
sock.onmessage = (message) => {
|
sock.onmessage = (message) => {
|
||||||
Reference in new issue
Block a user