├── web ├── .eslintignore ├── src │ ├── pages │ │ ├── demo │ │ │ ├── Test.tsx │ │ │ ├── Charts │ │ │ │ ├── data.d.ts │ │ │ │ ├── mock │ │ │ │ │ ├── pie.ts │ │ │ │ │ ├── treemap.ts │ │ │ │ │ └── ls.ts │ │ │ │ ├── Treemap.tsx │ │ │ │ ├── Pie.tsx │ │ │ │ ├── index.tsx │ │ │ │ ├── Candlestick.tsx │ │ │ │ └── LineScatter.tsx │ │ │ ├── Demo.tsx │ │ │ ├── RedirectTest │ │ │ │ └── index.tsx │ │ │ ├── ComponentTest │ │ │ │ └── index.tsx │ │ │ ├── GridLayout │ │ │ │ └── index.tsx │ │ │ ├── FileManagerTest │ │ │ │ └── index.tsx │ │ │ ├── index.tsx │ │ │ ├── ConsensusDistributionChart │ │ │ │ └── index.tsx │ │ │ ├── LocalStorage │ │ │ │ └── index.tsx │ │ │ ├── RectangleChart │ │ │ │ └── index.tsx │ │ │ └── GalleryModuleTest │ │ │ │ └── index.tsx │ │ ├── Admin.tsx │ │ ├── document │ │ │ ├── Document.tsx │ │ │ ├── Manual │ │ │ │ ├── Style.less │ │ │ │ ├── index.tsx │ │ │ │ ├── ManualAnchor.tsx │ │ │ │ └── anchorList.ts │ │ │ ├── Gallery │ │ │ │ └── index.tsx │ │ │ ├── index.tsx │ │ │ └── Menu │ │ │ │ └── index.tsx │ │ ├── gallery │ │ │ ├── Gallery.tsx │ │ │ ├── Overview │ │ │ │ └── index.tsx │ │ │ ├── index.tsx │ │ │ └── Dataset │ │ │ │ └── index.tsx │ │ ├── 404.tsx │ │ ├── test.less │ │ ├── test.tsx │ │ ├── user │ │ │ └── login │ │ │ │ ├── index.tsx │ │ │ │ ├── Logout.tsx │ │ │ │ ├── Register.tsx │ │ │ │ ├── Invitation.tsx │ │ │ │ └── index.less │ │ └── Welcome.tsx │ ├── components │ │ ├── Login │ │ │ ├── index.ts │ │ │ ├── Login.less │ │ │ └── Login.tsx │ │ ├── Gallery │ │ │ ├── ModulePanel │ │ │ │ ├── Collections │ │ │ │ │ ├── table │ │ │ │ │ │ └── styles.less │ │ │ │ │ ├── NestedModule │ │ │ │ │ │ ├── util.ts │ │ │ │ │ │ ├── Header │ │ │ │ │ │ │ └── TabItemArticaleInput.tsx │ │ │ │ │ │ └── data.d.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── miscellaneous │ │ │ │ │ │ ├── Common.less │ │ │ │ │ │ └── EmbedLink.tsx │ │ │ │ │ ├── graph │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── Pie.tsx │ │ │ │ │ │ ├── Bar.tsx │ │ │ │ │ │ ├── Line.tsx │ │ │ │ │ │ ├── LineBar.tsx │ │ │ │ │ │ ├── Scatter.tsx │ │ │ │ │ │ ├── LineScatter.tsx │ │ │ │ │ │ └── common │ │ │ │ │ │ │ ├── DisplayForm.tsx │ │ │ │ │ │ │ ├── DisplayFormThemeSelector.tsx │ │ │ │ │ │ │ └── DisplayFormSeriesPie.tsx │ │ │ │ │ ├── multiMedia │ │ │ │ │ │ └── Text.tsx │ │ │ │ │ └── file │ │ │ │ │ │ └── linkToExternalAddress.ts │ │ │ │ ├── Panel │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── ControllerButtons.tsx │ │ │ │ │ ├── ModulePanelFooter.tsx │ │ │ │ │ └── ModuleDescription.tsx │ │ │ │ └── Generator │ │ │ │ │ └── Common.less │ │ │ ├── Dashboard │ │ │ │ ├── DashboardContainer │ │ │ │ │ ├── style.less │ │ │ │ │ └── nestedDedicatedContext.ts │ │ │ │ ├── index.ts │ │ │ │ ├── IsSavemodal.tsx │ │ │ │ ├── DashboardController │ │ │ │ │ └── Common.less │ │ │ │ └── DashboardContext.ts │ │ │ ├── index.ts │ │ │ ├── Overview │ │ │ │ ├── OverviewSider │ │ │ │ │ └── Sider.tsx │ │ │ │ ├── OverviewContainer │ │ │ │ │ └── Container.tsx │ │ │ │ └── OverviewController │ │ │ │ │ └── Controller.tsx │ │ │ ├── Dataset │ │ │ │ ├── DatasetController │ │ │ │ │ ├── index.ts │ │ │ │ │ └── QueryField.tsx │ │ │ │ ├── index.ts │ │ │ │ └── QuerySelector │ │ │ │ │ ├── SelectorOrderItems.tsx │ │ │ │ │ ├── QuerySelectorForm.tsx │ │ │ │ │ └── QuerySelectorModal.tsx │ │ │ ├── Configuration │ │ │ │ └── index.ts │ │ │ ├── Tag │ │ │ │ ├── index.ts │ │ │ │ ├── DraggableTagPanel.tsx │ │ │ │ ├── data.d.ts │ │ │ │ ├── SelectableTags.tsx │ │ │ │ └── SearchableTagsPanel.tsx │ │ │ ├── Misc │ │ │ │ ├── FileUploadConfig.ts │ │ │ │ ├── TextBuilder.tsx │ │ │ │ └── TagBuildModal.tsx │ │ │ └── Utils │ │ │ │ ├── data.d.ts │ │ │ │ └── rawDataTransform.ts │ │ ├── Footer │ │ │ └── index.tsx │ │ ├── Editor │ │ │ ├── index.ts │ │ │ └── EditorButton.tsx │ │ ├── PageLoading │ │ │ └── index.tsx │ │ ├── FileUploadModal │ │ │ ├── index.ts │ │ │ └── FileExtractModal.tsx │ │ ├── TextEditor │ │ │ ├── index.ts │ │ │ └── TextEditorPresenter.tsx │ │ ├── EchartsPro │ │ │ └── theme.less │ │ ├── HeaderDropdown │ │ │ ├── index.less │ │ │ └── index.tsx │ │ ├── Article │ │ │ ├── index.ts │ │ │ ├── TagCreationModal.tsx │ │ │ ├── ArticleToolbar.tsx │ │ │ ├── TagModificationModal.tsx │ │ │ └── data.d.ts │ │ ├── SpaceBetween │ │ │ └── index.tsx │ │ ├── Emoji │ │ │ └── index.tsx │ │ ├── HeaderSearch │ │ │ ├── index.less │ │ │ └── index.tsx │ │ ├── PrivateRoute │ │ │ └── index.tsx │ │ ├── RightContent │ │ │ ├── index.less │ │ │ ├── index.tsx │ │ │ └── AvatarDropdown.tsx │ │ └── DraggablePanel │ │ │ └── DraggablePanel.tsx │ ├── locales │ │ ├── zh-CN │ │ │ ├── pages.ts │ │ │ ├── pwa.ts │ │ │ ├── globalHeader.ts │ │ │ ├── settingDrawer.ts │ │ │ ├── component.ts │ │ │ ├── settings.ts │ │ │ └── menu.ts │ │ ├── en-US │ │ │ ├── pages.ts │ │ │ ├── pwa.ts │ │ │ ├── globalHeader.ts │ │ │ ├── settingDrawer.ts │ │ │ ├── component.ts │ │ │ └── menu.ts │ │ ├── zh-CN.ts │ │ └── en-US.ts │ ├── models │ │ ├── testModel.ts │ │ ├── testModel copy.ts │ │ └── tempCopy.ts │ ├── redux │ │ ├── counter │ │ │ ├── counterAPI.ts │ │ │ ├── counterSlice.spec.ts │ │ │ ├── Counter.module.css │ │ │ └── Counter.tsx │ │ ├── hooks.ts │ │ └── store.ts │ ├── access.ts │ ├── utils │ │ ├── utils.less │ │ ├── utils.ts │ │ └── utils.test.ts │ ├── wrappers │ │ └── test.tsx │ ├── services │ │ ├── InnAPI.d.ts │ │ ├── user.ts │ │ ├── auth.ts │ │ ├── login.ts │ │ ├── inn.ts │ │ └── API.d.ts │ ├── manifest.json │ ├── utilities │ │ └── utils.ts │ ├── typings.d.ts │ ├── e2e │ │ └── baseLayout.e2e.js │ ├── service-worker.js │ ├── global.less │ └── global.tsx ├── .prettierrc.js ├── .stylelintrc.js ├── mock │ └── route.ts ├── tsconfig.build.json ├── nest-cli.json ├── jsconfig.json ├── README.md ├── config │ ├── proxy.template.json │ ├── defaultSettings.ts │ ├── proxy.template.ts │ ├── demoRoute.ts │ └── config.ts ├── jest.config.js ├── .editorconfig ├── .prettierignore ├── tsconfig.json └── .eslintrc.js ├── .vscode └── settings.json └── .gitignore /web/.eslintignore: -------------------------------------------------------------------------------- 1 | /lambda/ 2 | /scripts 3 | /config 4 | .history -------------------------------------------------------------------------------- /web/src/pages/demo/Test.tsx: -------------------------------------------------------------------------------- 1 | export default ()=>( 2 |
123
3 | ) -------------------------------------------------------------------------------- /web/.prettierrc.js: -------------------------------------------------------------------------------- 1 | const fabric = require('@umijs/fabric'); 2 | 3 | module.exports = { 4 | ...fabric.prettier, 5 | }; 6 | -------------------------------------------------------------------------------- /web/.stylelintrc.js: -------------------------------------------------------------------------------- 1 | const fabric = require('@umijs/fabric'); 2 | 3 | module.exports = { 4 | ...fabric.stylelint, 5 | }; 6 | -------------------------------------------------------------------------------- /web/mock/route.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | '/api/auth_routes': { 3 | '/form/advanced-form': { authority: ['admin', 'user'] }, 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /web/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts", "frontend"] 4 | } 5 | -------------------------------------------------------------------------------- /web/src/components/Login/index.ts: -------------------------------------------------------------------------------- 1 | import {Login} from "./Login" 2 | import {Registration} from "./Registration" 3 | 4 | export {Login, Registration} 5 | -------------------------------------------------------------------------------- /web/src/pages/Admin.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 2/4/2020. 3 | */ 4 | 5 | 6 | export default () => ( 7 | <>constructing... 8 | ) 9 | -------------------------------------------------------------------------------- /web/src/components/Gallery/ModulePanel/Collections/table/styles.less: -------------------------------------------------------------------------------- 1 | .ant-tabs-content-holder { 2 | .ant-tabs-content { 3 | height: 100%; 4 | } 5 | } -------------------------------------------------------------------------------- /web/src/components/Gallery/Dashboard/DashboardContainer/style.less: -------------------------------------------------------------------------------- 1 | .templateContentPanel___guYLa{ 2 | .ant-layout{ 3 | height: 100% !important; 4 | } 5 | } -------------------------------------------------------------------------------- /web/src/components/Gallery/Dashboard/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 9/28/2020. 3 | */ 4 | 5 | import {Dashboard} from "./Dashboard" 6 | 7 | export {Dashboard} 8 | 9 | -------------------------------------------------------------------------------- /web/src/components/Gallery/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 9/28/2020. 3 | */ 4 | 5 | import * as GalleryDataType from "./GalleryDataType" 6 | 7 | export {GalleryDataType} 8 | -------------------------------------------------------------------------------- /web/src/components/Gallery/ModulePanel/Panel/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 10/11/2020. 3 | */ 4 | 5 | import {ModulePanel} from "./ModulePanel" 6 | 7 | export {ModulePanel} 8 | 9 | -------------------------------------------------------------------------------- /web/src/pages/demo/Charts/data.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 2/23/2021 3 | */ 4 | 5 | 6 | export interface ChartsProps { 7 | chartHeight: number 8 | theme: string 9 | } 10 | 11 | -------------------------------------------------------------------------------- /web/src/pages/document/Document.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 2/2/2021 3 | */ 4 | 5 | 6 | export default () => { 7 | 8 | return ( 9 | <>Under Construction 10 | ) 11 | } 12 | 13 | -------------------------------------------------------------------------------- /web/src/pages/gallery/Gallery.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 2/2/2021 3 | */ 4 | 5 | 6 | export default () => { 7 | 8 | return ( 9 | <>Under Construction 10 | ) 11 | } 12 | 13 | -------------------------------------------------------------------------------- /web/src/locales/zh-CN/pages.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 2/9/2021 3 | */ 4 | 5 | export default { 6 | "pages.welcome.title1": "更新公告", 7 | "pages.document.project.title1": "系统目录", 8 | } 9 | 10 | -------------------------------------------------------------------------------- /web/src/components/Footer/index.tsx: -------------------------------------------------------------------------------- 1 | import {DefaultFooter} from '@ant-design/pro-layout' 2 | 3 | export default () => ( 4 | 8 | ) 9 | -------------------------------------------------------------------------------- /web/nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "collection": "@nestjs/schematics", 3 | "sourceRoot": "server", 4 | "compilerOptions": { 5 | "assets": [ 6 | "resources/*.json", 7 | "assets/*" 8 | ] 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /web/src/components/Gallery/Overview/OverviewSider/Sider.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 10/12/2020. 3 | */ 4 | 5 | 6 | export default () => { 7 | 8 | return ( 9 | <>Template 10 | ) 11 | } 12 | 13 | -------------------------------------------------------------------------------- /web/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "emitDecoratorMetadata": true, 4 | "experimentalDecorators": true, 5 | "baseUrl": ".", 6 | "paths": { 7 | "@/*": ["./src/*"] 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /web/src/components/Editor/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 10/16/2020. 3 | */ 4 | 5 | import {Editor} from "./Editor" 6 | import {EditorButton} from "./EditorButton" 7 | 8 | export {Editor, EditorButton} 9 | 10 | -------------------------------------------------------------------------------- /web/src/locales/en-US/pages.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 2/9/2021 3 | */ 4 | 5 | export default { 6 | "pages.welcome.title1": "Update notice", 7 | "pages.document.project.title1": "System menu", 8 | } 9 | 10 | -------------------------------------------------------------------------------- /web/src/components/Gallery/Dataset/DatasetController/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 11/20/2020 3 | */ 4 | 5 | import {Header} from "./Header" 6 | import {Sider} from "./Sider" 7 | 8 | export {Header, Sider} 9 | 10 | -------------------------------------------------------------------------------- /web/src/components/PageLoading/index.tsx: -------------------------------------------------------------------------------- 1 | import {PageLoading} from '@ant-design/pro-layout' 2 | 3 | // loading components from code split 4 | // https://umijs.org/plugin/umi-plugin-react.html#dynamicimport 5 | export default PageLoading 6 | -------------------------------------------------------------------------------- /web/src/models/testModel.ts: -------------------------------------------------------------------------------- 1 | const model = { 2 | namespace: 'test', 3 | state: { 4 | a:1, 5 | b:2 6 | }, 7 | reducers: {}, 8 | effects: {}, 9 | subscriptions: {}, 10 | }; 11 | export default model; -------------------------------------------------------------------------------- /web/src/locales/zh-CN/pwa.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'app.pwa.offline': '当前处于离线状态', 3 | 'app.pwa.serviceworker.updated': '有新内容', 4 | 'app.pwa.serviceworker.updated.hint': '请点击“刷新”按钮或者手动刷新页面', 5 | 'app.pwa.serviceworker.updated.ok': '刷新', 6 | }; 7 | -------------------------------------------------------------------------------- /web/src/models/testModel copy.ts: -------------------------------------------------------------------------------- 1 | const model = { 2 | namespace: 'test2', 3 | state: { 4 | a:11, 5 | b:22 6 | }, 7 | reducers: {}, 8 | effects: {}, 9 | subscriptions: {}, 10 | }; 11 | export default model; -------------------------------------------------------------------------------- /web/src/components/Gallery/ModulePanel/Generator/Common.less: -------------------------------------------------------------------------------- 1 | .editorField { 2 | width : 100%; 3 | height : calc(~"100% - 50px"); 4 | position : relative; 5 | top : 40%; 6 | padding : 0 5px 0 5px; 7 | text-align: center; 8 | } -------------------------------------------------------------------------------- /web/src/pages/document/Manual/Style.less: -------------------------------------------------------------------------------- 1 | 2 | .manualImg { 3 | display: block; 4 | width: 50%; 5 | margin: 20px auto; 6 | border-radius: 5px; 7 | background: #e9eef1; 8 | box-shadow: -5px -5px 5px #a3a7a9, 5px 5px 5px #ffffff; 9 | } 10 | 11 | -------------------------------------------------------------------------------- /web/src/components/Gallery/ModulePanel/Collections/NestedModule/util.ts: -------------------------------------------------------------------------------- 1 | export const COLS_NUM = 6 2 | export const DEFAULT_MARGIN = 10 3 | export const DEFAULT_ROW_HEIGHT = 70 4 | export const RGL_CLASSNAME = "layout" 5 | export const NSMid = "nested-simple-module-editor" -------------------------------------------------------------------------------- /web/src/redux/counter/counterAPI.ts: -------------------------------------------------------------------------------- 1 | // A mock function to mimic making an async request for data 2 | export function fetchCount(amount = 1) { 3 | return new Promise<{ data: number }>((resolve) => 4 | setTimeout(() => resolve({ data: amount }), 500) 5 | ); 6 | } 7 | -------------------------------------------------------------------------------- /web/src/access.ts: -------------------------------------------------------------------------------- 1 | // src/access.ts 2 | export default function access(initialState: { currentUser?: API.CurrentUser | undefined }) { 3 | const {currentUser} = initialState || {} 4 | return { 5 | canAdmin: currentUser && currentUser.access === 'admin', 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /web/README.md: -------------------------------------------------------------------------------- 1 | # Cyber Brick Web 2 | 3 | ## Route 4 | 5 | Check [config/config.ts](config/config.ts) for detail. 6 | 7 | ## Mark 8 | 9 | For some detailed project components. 10 | 11 | ### Gallery 12 | 13 | ![Data Structure](public/document/GalleryDataStructure.svg) 14 | -------------------------------------------------------------------------------- /web/config/proxy.template.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api/": { 3 | "target": "http://localhost:8030", 4 | "changeOrigin": true, 5 | "pathRewrite": { "^": "" } 6 | }, 7 | "/gateway/": { 8 | "target": "http://localhost:8010", 9 | "changeOrigin": true 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /web/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testURL: 'http://localhost:8000', 3 | testEnvironment: './tests/PuppeteerEnvironment', 4 | verbose: false, 5 | globals: { 6 | ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION: false, 7 | localStorage: null, 8 | }, 9 | }; 10 | -------------------------------------------------------------------------------- /web/src/components/Gallery/ModulePanel/Collections/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 10/11/2020. 3 | */ 4 | 5 | import {collectionSelector} from "./collectionSelector" 6 | 7 | import {moduleList} from "./moduleList" 8 | 9 | export {moduleList, collectionSelector} 10 | 11 | -------------------------------------------------------------------------------- /web/src/components/FileUploadModal/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 10/16/2020. 3 | */ 4 | 5 | import {FileExtractModal} from "./FileExtractModal" 6 | import {FileInsertModal} from "./FileInsertModal" 7 | 8 | export { 9 | FileExtractModal, 10 | FileInsertModal, 11 | } 12 | 13 | -------------------------------------------------------------------------------- /web/src/components/TextEditor/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 8/18/2020. 3 | */ 4 | 5 | import {TextEditorPresenter} from "./TextEditorPresenter" 6 | import {TextEditorModifier} from "./TextEditorModifier" 7 | 8 | export { 9 | TextEditorPresenter, 10 | TextEditorModifier, 11 | } 12 | -------------------------------------------------------------------------------- /web/src/locales/en-US/pwa.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'app.pwa.offline': 'You are offline now', 3 | 'app.pwa.serviceworker.updated': 'New content is available', 4 | 'app.pwa.serviceworker.updated.hint': 'Please press the "Refresh" button to reload current page', 5 | 'app.pwa.serviceworker.updated.ok': 'Refresh', 6 | }; 7 | -------------------------------------------------------------------------------- /web/src/components/Login/Login.less: -------------------------------------------------------------------------------- 1 | #normal-login .login-form { 2 | max-width: 300px; 3 | } 4 | 5 | #normal-login .login-form-forgot { 6 | float: right; 7 | } 8 | 9 | #normal-login .ant-col-rtl .login-form-forgot { 10 | float: left; 11 | } 12 | 13 | #normal-login .login-form-button { 14 | width: 100%; 15 | } 16 | -------------------------------------------------------------------------------- /web/.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | 15 | [Makefile] 16 | indent_style = tab 17 | -------------------------------------------------------------------------------- /web/src/components/EchartsPro/theme.less: -------------------------------------------------------------------------------- 1 | 2 | 3 | .themePlanGroup { 4 | height: 30px; 5 | border: 1px solid #eee; 6 | padding: 5px; 7 | border-radius: 4px; 8 | } 9 | 10 | .themePlanColor { 11 | width: 20px; 12 | height: 20px; 13 | margin-left: 2px; 14 | margin-right: 2px; 15 | border-radius: 2px 16 | } 17 | 18 | -------------------------------------------------------------------------------- /web/src/utils/utils.less: -------------------------------------------------------------------------------- 1 | // mixins for clearfix 2 | // ------------------------ 3 | .clearfix() { 4 | zoom: 1; 5 | &::before, 6 | &::after { 7 | display: table; 8 | content: ' '; 9 | } 10 | &::after { 11 | clear: both; 12 | height: 0; 13 | font-size: 0; 14 | visibility: hidden; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /web/src/pages/demo/Demo.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 2/2/2021 3 | */ 4 | 5 | import { multiply } from "@umijs/deps/compiled/lodash" 6 | import { Select } from "antd" 7 | 8 | export default () => { 9 | 10 | return ( 11 | 13 | ) 14 | } 15 | 16 | -------------------------------------------------------------------------------- /web/src/components/Gallery/Dataset/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 11/23/2020 3 | */ 4 | 5 | import {Dataset} from "./Dataset" 6 | import {QuerySelectorForm} from "./QuerySelector/QuerySelectorForm" 7 | import {QuerySelectorModal} from "./QuerySelector/QuerySelectorModal" 8 | 9 | export {Dataset, QuerySelectorForm, QuerySelectorModal} 10 | 11 | -------------------------------------------------------------------------------- /web/src/pages/document/Gallery/index.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 9/17/2020. 3 | */ 4 | 5 | 6 | const srcGallery = "/api/document/gallery" 7 | 8 | export default () => { 9 | 10 | return ( 11 | gallery 17 | ) 18 | } 19 | 20 | -------------------------------------------------------------------------------- /web/.prettierignore: -------------------------------------------------------------------------------- 1 | **/*.svg 2 | package.json 3 | src/client/.umi 4 | .umi-production 5 | /dist 6 | .dockerignore 7 | .DS_Store 8 | .eslintignore 9 | *.png 10 | *.toml 11 | docker 12 | .editorconfig 13 | Dockerfile* 14 | ../.gitignore 15 | .prettierignore 16 | LICENSE 17 | .eslintcache 18 | *.lock 19 | yarn-error.log 20 | .history 21 | CNAME 22 | /frontend 23 | /backend 24 | -------------------------------------------------------------------------------- /web/src/components/Gallery/Configuration/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 10/10/2020. 3 | */ 4 | 5 | import {CategoryConfigTable} from "./CategoryConfigTable" 6 | import {DashboardConfigTable} from "./DashboardConfigTable" 7 | import {StorageConfigTable} from "./StorageConfigTable" 8 | 9 | export {CategoryConfigTable, DashboardConfigTable, StorageConfigTable} 10 | 11 | -------------------------------------------------------------------------------- /web/src/redux/hooks.ts: -------------------------------------------------------------------------------- 1 | import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'; 2 | import type { RootState, AppDispatch } from './store'; 3 | 4 | // Use throughout your app instead of plain `useDispatch` and `useSelector` 5 | export const useAppDispatch = () => useDispatch(); 6 | export const useAppSelector: TypedUseSelectorHook = useSelector; 7 | -------------------------------------------------------------------------------- /web/src/models/tempCopy.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 3/1/2021 3 | */ 4 | 5 | import {useState, useCallback} from "react" 6 | 7 | export default () => { 8 | const [redirectInfo, setRedirectInfo] = useState() 9 | 10 | const copyRedirectInfo = useCallback((t: string) => setRedirectInfo(t), []) 11 | 12 | return {redirectInfo, copyRedirectInfo} 13 | } 14 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.analysis.extraPaths": ["./server"], 3 | "python.autoComplete.extraPaths": ["./server"], 4 | "eslint.workingDirectories": ["web"], 5 | "cSpell.words": [ 6 | "antd", 7 | "cascader", 8 | "coord", 9 | "cyberbrick", 10 | "echarts", 11 | "lura", 12 | "sider", 13 | "treemap" 14 | ], 15 | "files.watcherExclude": { 16 | "**/target": true 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /web/src/components/HeaderDropdown/index.less: -------------------------------------------------------------------------------- 1 | @import '../../../node_modules/antd/es/style/themes/default.less'; 2 | 3 | .container > * { 4 | background-color: @popover-bg; 5 | border-radius: 4px; 6 | box-shadow: @shadow-1-down; 7 | } 8 | 9 | @media screen and (max-width: @screen-xs) { 10 | .container { 11 | width: 100% !important; 12 | } 13 | 14 | .container > * { 15 | border-radius: 0 !important; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /web/src/pages/404.tsx: -------------------------------------------------------------------------------- 1 | import {Button, Result} from 'antd' 2 | import {history} from 'umi' 3 | 4 | const NoFoundPage = () => ( 5 | history.push('/')}> 11 | Back Home 12 | 13 | } 14 | /> 15 | ) 16 | 17 | export default NoFoundPage 18 | -------------------------------------------------------------------------------- /web/src/pages/test.less: -------------------------------------------------------------------------------- 1 | 2 | .testBox{ 3 | display: flex; 4 | width: 100px; 5 | height: 200px; 6 | border: 1px solid; 7 | flex-direction: column; 8 | .top{ 9 | width: 50px; 10 | height: 50px; 11 | background-color: orange; 12 | } 13 | .btm{ 14 | width: 50px; 15 | // height: px; 16 | background-color: yellowgreen; 17 | flex-grow:1; 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /web/src/pages/demo/Charts/mock/pie.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 2/23/2021 3 | */ 4 | 5 | export const data = [ 6 | {year: 2011, v1: 86.5, v2: 92.1, v3: 85.7, v4: 83.1, v5: 73.4, v6: 55.1}, 7 | {year: 2012, v1: 41.1, v2: 30.4, v3: 65.1, v4: 53.3, v5: 83.8, v6: 98.7}, 8 | {year: 2013, v1: 24.1, v2: 67.2, v3: 79.5, v4: 86.4, v5: 65.2, v6: 82.5}, 9 | {year: 2014, v1: 55.2, v2: 67.1, v3: 69.2, v4: 72.4, v5: 53.9, v6: 39.1}, 10 | ] 11 | -------------------------------------------------------------------------------- /web/src/components/Gallery/Tag/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 12/15/2020 3 | */ 4 | 5 | import {EditableTagPanel} from "./EditableTagPanel" 6 | import {SelectableTags} from "./SelectableTags" 7 | import {SearchableTagsPanel} from "./SearchableTagsPanel" 8 | import {DraggableTagPanel} from "./DraggableTagPanel" 9 | 10 | export { 11 | SelectableTags, 12 | DraggableTagPanel, 13 | EditableTagPanel, 14 | SearchableTagsPanel 15 | } 16 | -------------------------------------------------------------------------------- /web/src/wrappers/test.tsx: -------------------------------------------------------------------------------- 1 | import { Redirect } from 'umi' 2 | import { Modal } from 'antd' 3 | export default (props) => { 4 | // alert('test') 5 | 6 | // const isLogin = false 7 | // if (isLogin) { 8 | // return
{props.children}
; 9 | // } else { 10 | // return ; 11 | // } 12 | // return console.log(444, props)} visible={true}> 13 | return props.children 14 | } -------------------------------------------------------------------------------- /web/src/components/Gallery/ModulePanel/Collections/miscellaneous/Common.less: -------------------------------------------------------------------------------- 1 | .targetPrice-stock-base { 2 | font-size : 26px; 3 | padding-bottom: 6px; 4 | } 5 | 6 | .targetPrice-stock { 7 | .targetPrice-stock-base(); 8 | // border-bottom : 1px #ccc solid; 9 | margin-bottom : 12px; 10 | // pointer-events: none; 11 | } 12 | 13 | // .targetPrice-stock-input { 14 | // .ant-input { 15 | // .targetPrice-stock-base(); 16 | // } 17 | // }/ -------------------------------------------------------------------------------- /web/src/services/InnAPI.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 2/3/2021 3 | */ 4 | 5 | declare namespace InnAPI { 6 | 7 | export interface Update { 8 | id: string 9 | date: string 10 | tags?: Tag[] 11 | title: string 12 | data: string 13 | createdAt?: string 14 | updatedAt?: string 15 | } 16 | 17 | export interface Tag { 18 | id?: string 19 | updates?: Update[] 20 | name: string 21 | description?: string 22 | color?: string 23 | } 24 | } 25 | 26 | -------------------------------------------------------------------------------- /web/src/components/Article/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 2/5/2021 3 | */ 4 | 5 | import {ArticleCreationModal} from "./ArticleCreationModal" 6 | import {TagCreationModal} from "./TagCreationModal" 7 | import {TagModificationModal} from "./TagModificationModal" 8 | import {ArticleToolbar} from "./ArticleToolbar" 9 | import {Article} from "./Article" 10 | 11 | export { 12 | ArticleCreationModal, 13 | TagCreationModal, 14 | TagModificationModal, 15 | ArticleToolbar, 16 | Article 17 | } 18 | -------------------------------------------------------------------------------- /web/src/pages/test.tsx: -------------------------------------------------------------------------------- 1 | 2 | import { divide } from '@umijs/deps/compiled/lodash'; 3 | import React, { useRef } from 'react' 4 | import { useAppSelector, useAppDispatch } from '@/redux/hooks' 5 | import './test.less' 6 | export default () => { 7 | 8 | 9 | return ( 10 |
11 |
12 | 123 13 |
14 |
15 | 456 16 |
17 |
18 | ) 19 | } 20 | 21 | 22 | -------------------------------------------------------------------------------- /web/src/redux/store.ts: -------------------------------------------------------------------------------- 1 | import { configureStore, ThunkAction, Action } from '@reduxjs/toolkit'; 2 | import counterReducer from './counter/counterSlice'; 3 | 4 | export const store = configureStore({ 5 | reducer: { 6 | counter: counterReducer, 7 | }, 8 | }); 9 | 10 | export type AppDispatch = typeof store.dispatch; 11 | export type RootState = ReturnType; 12 | export type AppThunk = ThunkAction< 13 | ReturnType, 14 | RootState, 15 | unknown, 16 | Action 17 | >; 18 | -------------------------------------------------------------------------------- /web/src/components/SpaceBetween/index.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 11/20/2020 3 | */ 4 | 5 | import React from 'react' 6 | import {Space} from "antd" 7 | 8 | export interface SpaceBetweenProps { 9 | children: React.ReactElement[] 10 | style?: React.CSSProperties 11 | } 12 | 13 | export const SpaceBetween = (props: SpaceBetweenProps) => { 14 | 15 | return ( 16 | 17 | {props.children} 18 | 19 | ) 20 | } 21 | 22 | -------------------------------------------------------------------------------- /web/src/components/Emoji/index.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 4/27/2020. 3 | */ 4 | 5 | 6 | interface EmojiProps { 7 | label: string 8 | symbol: string 9 | size?: number 10 | } 11 | 12 | export const Emoji = (props: EmojiProps) => 13 | 22 | 23 | 24 | export default Emoji 25 | -------------------------------------------------------------------------------- /web/src/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Ant Design Pro", 3 | "short_name": "Ant Design Pro", 4 | "display": "standalone", 5 | "start_url": "./?utm_source=homescreen", 6 | "theme_color": "#002140", 7 | "background_color": "#001529", 8 | "icons": [ 9 | { 10 | "src": "icons/icon-192x192.png", 11 | "sizes": "192x192" 12 | }, 13 | { 14 | "src": "icons/icon-128x128.png", 15 | "sizes": "128x128" 16 | }, 17 | { 18 | "src": "icons/icon-512x512.png", 19 | "sizes": "512x512" 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /web/src/components/Gallery/ModulePanel/Collections/graph/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 1/25/2021 3 | */ 4 | 5 | import { Line } from "./Line" 6 | import { Bar } from "./Bar" 7 | import { LineBar } from "./LineBar" 8 | import { Scatter } from "./Scatter" 9 | import { LineScatter } from "./LineScatter" 10 | import { Pie } from "./Pie" 11 | import { ConsensusDistribution } from './ConsensusDistribution' 12 | 13 | export { 14 | Line, 15 | Bar, 16 | LineBar, 17 | Scatter, 18 | LineScatter, 19 | Pie, 20 | ConsensusDistribution 21 | } 22 | -------------------------------------------------------------------------------- /web/config/defaultSettings.ts: -------------------------------------------------------------------------------- 1 | import { Settings as LayoutSettings } from '@ant-design/pro-layout' 2 | 3 | const Settings: LayoutSettings & { 4 | pwa?: boolean 5 | logo?: string 6 | } = { 7 | navTheme: 'light', 8 | layout: 'top', 9 | contentWidth: 'Fluid', 10 | fixedHeader: true, 11 | fixSiderbar: true, 12 | colorWeak: false, 13 | menu: { 14 | locale: true, 15 | }, 16 | title: 'CyberBrick', 17 | pwa: false, 18 | 19 | iconfontUrl: '', 20 | splitMenus: true, 21 | } as LayoutSettings & { 22 | pwa: boolean 23 | } 24 | 25 | export default Settings 26 | -------------------------------------------------------------------------------- /web/src/components/Gallery/ModulePanel/Collections/NestedModule/Header/TabItemArticaleInput.tsx: -------------------------------------------------------------------------------- 1 | import { Input } from "antd" 2 | 3 | export interface TIAInputProps { 4 | key: string 5 | placeholder: string 6 | value: string 7 | onChange: (e: React.ChangeEvent) => void 8 | } 9 | export const TabItemArticalInput = (props: TIAInputProps) => { 10 | 11 | return
12 | 17 |
18 | } -------------------------------------------------------------------------------- /web/src/components/HeaderSearch/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/es/style/themes/default.less'; 2 | 3 | .headerSearch { 4 | display: inline-flex; 5 | align-items: center; 6 | .input { 7 | width: 0; 8 | min-width: 0; 9 | overflow: hidden; 10 | background: transparent; 11 | border-radius: 0; 12 | transition: width 0.3s, margin-left 0.3s; 13 | :global(.ant-select-selection) { 14 | background: transparent; 15 | } 16 | input { 17 | box-shadow: none !important; 18 | } 19 | 20 | &.show { 21 | width: 210px; 22 | margin-left: 8px; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /web/src/pages/document/Manual/index.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 1/19/2021 3 | */ 4 | 5 | import {Card, Col, Row} from "antd" 6 | 7 | import {ManualAnchor} from "./ManualAnchor" 8 | import {ManualContent} from "./ManualContent" 9 | 10 | 11 | export default () => { 12 | 13 | return ( 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | ) 25 | } 26 | 27 | -------------------------------------------------------------------------------- /web/config/proxy.template.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 在生产环境 代理是无法生效的,所以这里没有生产环境的配置 3 | * The agent cannot take effect in the production environment 4 | * so there is no configuration of the production environment 5 | * For details, please see 6 | * https://pro.ant.design/docs/deploy 7 | */ 8 | export default { 9 | dev: { 10 | '/api/': { 11 | target: 'http://localhost:8030', 12 | changeOrigin: true, 13 | pathRewrite: {'^': ''}, 14 | }, 15 | '/gateway/': { 16 | target: 'http://localhost:8010', 17 | changeOrigin: true, 18 | pathRewrite: {'/gateway': ''}, 19 | }, 20 | }, 21 | } 22 | -------------------------------------------------------------------------------- /web/src/services/user.ts: -------------------------------------------------------------------------------- 1 | import { check } from "./auth" 2 | import { request } from "umi" 3 | 4 | export async function query() { 5 | return request('/api/users') 6 | } 7 | //TODO: query with cookies; how to get cookie from server 8 | export async function queryCurrent() { 9 | // return request('/api/auth') 10 | return check() 11 | } 12 | 13 | export async function queryNotices(): Promise { 14 | return request<{ data: API.NoticeIconData[] }>('/api/notices') 15 | } 16 | 17 | export async function queryRandomUnicorn() { 18 | return request("/api/misc/random-unicorn") 19 | } 20 | -------------------------------------------------------------------------------- /web/src/components/Gallery/Overview/OverviewContainer/Container.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 10/12/2020. 3 | */ 4 | 5 | import {useEffect, useState} from "react" 6 | 7 | import * as DataType from "../../GalleryDataType" 8 | 9 | 10 | export interface ContainerProps { 11 | 12 | contents: DataType.Content[] 13 | } 14 | 15 | 16 | export const Container = (props: ContainerProps) => { 17 | 18 | const [contents, setContents] = useState(props.contents) 19 | 20 | useEffect(() => setContents(props.contents), [props.contents]) 21 | 22 | return ( 23 | <>{contents} 24 | ) 25 | } 26 | 27 | -------------------------------------------------------------------------------- /web/src/pages/demo/Charts/Treemap.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 2/17/2021 3 | */ 4 | 5 | import {EChartOption} from "echarts" 6 | import ReactEcharts from "echarts-for-react" 7 | 8 | import {data} from "./mock/treemap" 9 | import {ChartsProps} from "./data" 10 | 11 | 12 | const chartOption: EChartOption = { 13 | tooltip: {}, 14 | series: [ 15 | { 16 | type: "treemap", 17 | data, 18 | } 19 | ] 20 | } 21 | 22 | 23 | export const Treemap = (props: ChartsProps) => 24 | 29 | 30 | -------------------------------------------------------------------------------- /web/src/pages/user/login/index.tsx: -------------------------------------------------------------------------------- 1 | 2 | import { LoginPage } from "./Login" 3 | import { RegisterPage } from "./Register" 4 | import { LogoutPage } from "./Logout" 5 | import { Invitation } from "./Invitation" 6 | import { Route, Switch } from "react-router-dom" 7 | 8 | export default () => { 9 | 10 | return ( 11 | 12 | 13 | 14 | 15 | 16 | 17 | ) 18 | } -------------------------------------------------------------------------------- /web/src/components/Gallery/ModulePanel/Collections/graph/Pie.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 2/24/2021. 3 | */ 4 | 5 | 6 | import {ModuleGenerator} from "../../Generator/ModuleGenerator" 7 | import {generateCommonEditorField, generateCommonPresenterField} from "./common/Common" 8 | import {generatePieOption} from "../../../Utils/chartGenerators" 9 | import {ChartOptionGenerator} from "@/components/Gallery/Utils/data" 10 | 11 | const EditorField = generateCommonEditorField("pie") 12 | const PresenterField = generateCommonPresenterField(generatePieOption() as ChartOptionGenerator) 13 | 14 | export const Pie = new ModuleGenerator(EditorField, PresenterField).generate() 15 | 16 | -------------------------------------------------------------------------------- /web/src/components/Gallery/ModulePanel/Collections/graph/Bar.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 10/19/2020. 3 | */ 4 | 5 | 6 | import {ModuleGenerator} from "../../Generator/ModuleGenerator" 7 | import {generateCommonEditorField, generateCommonPresenterField} from "./common/Common" 8 | import {generateCommonOption} from "../../../Utils/chartGenerators" 9 | import {ChartOptionGenerator} from "@/components/Gallery/Utils/data" 10 | 11 | 12 | const EditorField = generateCommonEditorField() 13 | const PresenterField = generateCommonPresenterField(generateCommonOption("bar") as ChartOptionGenerator) 14 | 15 | export const Bar = new ModuleGenerator(EditorField, PresenterField).generate() 16 | 17 | -------------------------------------------------------------------------------- /web/src/components/Gallery/ModulePanel/Collections/graph/Line.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 10/16/2020. 3 | */ 4 | 5 | 6 | import {ModuleGenerator} from "../../Generator/ModuleGenerator" 7 | import {generateCommonEditorField, generateCommonPresenterField} from "./common/Common" 8 | import {generateCommonOption} from "../../../Utils/chartGenerators" 9 | import {ChartOptionGenerator} from "@/components/Gallery/Utils/data" 10 | 11 | 12 | const EditorField = generateCommonEditorField() 13 | const PresenterField = generateCommonPresenterField(generateCommonOption("line") as ChartOptionGenerator) 14 | 15 | export const Line = new ModuleGenerator(EditorField, PresenterField).generate() 16 | 17 | -------------------------------------------------------------------------------- /web/src/components/Gallery/ModulePanel/Collections/graph/LineBar.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 10/20/2020. 3 | */ 4 | 5 | import {ModuleGenerator} from "../../Generator/ModuleGenerator" 6 | import {generateCommonEditorField, generateCommonPresenterField} from "./common/Common" 7 | import {generateCommonOption} from "../../../Utils/chartGenerators" 8 | import {ChartOptionGenerator} from "@/components/Gallery/Utils/data" 9 | 10 | 11 | const EditorField = generateCommonEditorField("lineBar") 12 | const PresenterField = generateCommonPresenterField(generateCommonOption("lineBar") as ChartOptionGenerator) 13 | 14 | export const LineBar = new ModuleGenerator(EditorField, PresenterField).generate() 15 | -------------------------------------------------------------------------------- /web/src/components/Gallery/ModulePanel/Collections/graph/Scatter.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 1/22/2021 3 | */ 4 | 5 | import {ModuleGenerator} from "../../Generator/ModuleGenerator" 6 | import {generateCommonEditorField, generateCommonPresenterField} from "./common/Common" 7 | import {generateCommonOption} from "../../../Utils/chartGenerators" 8 | import {ChartOptionGenerator} from "@/components/Gallery/Utils/data" 9 | 10 | 11 | const EditorField = generateCommonEditorField("scatter") 12 | const PresenterField = generateCommonPresenterField(generateCommonOption("scatter") as ChartOptionGenerator) 13 | 14 | export const Scatter = new ModuleGenerator(EditorField, PresenterField).generate() 15 | 16 | -------------------------------------------------------------------------------- /web/src/components/Gallery/ModulePanel/Collections/graph/LineScatter.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 1/25/2021 3 | */ 4 | 5 | import {ModuleGenerator} from "../../Generator/ModuleGenerator" 6 | import {generateCommonEditorField, generateCommonPresenterField} from "./common/Common" 7 | import {generateCommonOption} from "../../../Utils/chartGenerators" 8 | import {ChartOptionGenerator} from "@/components/Gallery/Utils/data" 9 | 10 | 11 | const EditorField = generateCommonEditorField("lineScatter") 12 | const PresenterField = generateCommonPresenterField(generateCommonOption("lineScatter") as ChartOptionGenerator) 13 | 14 | export const LineScatter = new ModuleGenerator(EditorField, PresenterField).generate() 15 | 16 | 17 | -------------------------------------------------------------------------------- /web/src/pages/demo/Charts/mock/treemap.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 2/17/2021 3 | */ 4 | 5 | export const data = [ 6 | { 7 | name: 'nodeA', // First tree 8 | value: 10, 9 | children: [{ 10 | name: 'nodeAa', // First leaf of first tree 11 | value: 4 12 | }, { 13 | name: 'nodeAb', // Second leaf of first tree 14 | value: 6 15 | }] 16 | }, 17 | { 18 | name: 'nodeB', // Second tree 19 | value: 20, 20 | children: [{ 21 | name: 'nodeBa', // Son of first tree 22 | value: 20, 23 | children: [{ 24 | name: 'nodeBa1', // Granson of first tree 25 | value: 20 26 | }] 27 | }] 28 | } 29 | ] 30 | 31 | -------------------------------------------------------------------------------- /web/src/pages/document/Manual/ManualAnchor.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 1/20/2021 3 | */ 4 | 5 | import {Anchor} from "antd" 6 | 7 | import {anchorList, AnchorList} from "./anchorList" 8 | 9 | 10 | const anchorLinkGenerator = (anchorList: AnchorList[], index: number | string = 0) => 11 | anchorList.map((a: AnchorList, idx) => 12 | a.children ? 13 | 14 | {anchorLinkGenerator(a.children, idx)} 15 | : 16 | 17 | ) 18 | 19 | export const ManualAnchor = () => 20 | 21 | {anchorLinkGenerator(anchorList)} 22 | 23 | 24 | -------------------------------------------------------------------------------- /web/src/pages/demo/RedirectTest/index.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 3/2/2021 3 | */ 4 | 5 | import {useModel} from "umi" 6 | 7 | 8 | const Link = (props: {l: string | undefined}) => 9 | props.l ? 10 | 11 | {props.l} 12 | : Null 13 | 14 | export default () => { 15 | const {redirectInfo} = useModel("tempCopy") 16 | 17 | const genHref = () => { 18 | if (redirectInfo) { 19 | const {parentInfo, eleId} = JSON.parse(redirectInfo) 20 | 21 | // todo: component all loaded then call `#id` redirect 22 | return `/gallery/dashboard?anchor=${JSON.stringify(parentInfo)}#${eleId}` 23 | } 24 | return undefined 25 | } 26 | 27 | return 28 | } 29 | 30 | -------------------------------------------------------------------------------- /web/src/pages/user/login/Logout.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react" 2 | 3 | import { logout } from "@/services/auth" 4 | 5 | interface LogoutPageProps { 6 | setLoginState: React.Dispatch> 7 | } 8 | 9 | export const LogoutPage = (props: LogoutPageProps) => { 10 | 11 | const { setLoginState } = props 12 | 13 | const [succeeded, setSucceeded] = useState(false) 14 | 15 | useEffect(() => { 16 | logout().then(_ => { 17 | setLoginState(false) 18 | setSucceeded(true) 19 | }) 20 | }, [setLoginState]) 21 | 22 | return succeeded ? 23 | <> 24 | You've successfully logged out! 25 | : 26 | <> 27 | Please wait... Logging you out... 28 | 29 | } 30 | -------------------------------------------------------------------------------- /web/src/pages/document/index.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 2/1/2021 3 | */ 4 | 5 | import { Route, Switch, HashRouter as Router } from "react-router-dom" 6 | import { Modal } from 'antd' 7 | 8 | import Document from "./Document" 9 | import Manual from "./Manual" 10 | import Menu from "./Menu" 11 | import Gallery from "./Gallery" 12 | 13 | 14 | export default () => { 15 | 16 | return ( 17 | 18 |
19 | 20 | 21 | 22 | 23 |
24 |
25 | ) 26 | } 27 | 28 | -------------------------------------------------------------------------------- /web/src/components/HeaderDropdown/index.tsx: -------------------------------------------------------------------------------- 1 | import {DropDownProps} from 'antd/es/dropdown' 2 | import {Dropdown} from 'antd' 3 | import React from 'react' 4 | import classNames from 'classnames' 5 | import styles from './index.less' 6 | 7 | declare type OverlayFunc = () => React.ReactNode; 8 | 9 | export interface HeaderDropdownProps extends Omit { 10 | overlayClassName?: string; 11 | overlay: React.ReactNode | OverlayFunc | any; 12 | placement?: 'bottomLeft' | 'bottomRight' | 'topLeft' | 'topCenter' | 'topRight' | 'bottomCenter'; 13 | } 14 | 15 | const HeaderDropdown: React.FC = ({overlayClassName: cls, ...restProps}) => ( 16 | 17 | ) 18 | 19 | export default HeaderDropdown 20 | -------------------------------------------------------------------------------- /web/src/locales/zh-CN/globalHeader.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'component.globalHeader.search': '站内搜索', 3 | 'component.globalHeader.search.example1': '搜索提示一', 4 | 'component.globalHeader.search.example2': '搜索提示二', 5 | 'component.globalHeader.search.example3': '搜索提示三', 6 | 'component.globalHeader.help': '使用文档', 7 | 'component.globalHeader.notification': '通知', 8 | 'component.globalHeader.notification.empty': '你已查看所有通知', 9 | 'component.globalHeader.message': '消息', 10 | 'component.globalHeader.message.empty': '您已读完所有消息', 11 | 'component.globalHeader.event': '待办', 12 | 'component.globalHeader.event.empty': '你已完成所有待办', 13 | 'component.noticeIcon.clear': '清空', 14 | 'component.noticeIcon.cleared': '清空了', 15 | 'component.noticeIcon.empty': '暂无数据', 16 | 'component.noticeIcon.view-more': '查看更多', 17 | }; 18 | -------------------------------------------------------------------------------- /web/src/components/Gallery/Dashboard/IsSavemodal.tsx: -------------------------------------------------------------------------------- 1 | import { Modal } from 'antd' 2 | import { useState, useImperativeHandle, forwardRef } from 'react' 3 | interface isSaveModalProps { 4 | modalData?: any 5 | onOk: (obj: any) => void 6 | } 7 | export interface isSaveModalRef { 8 | setIsModal: React.Dispatch> 9 | } 10 | export default forwardRef((props: isSaveModalProps, ref) => { 11 | 12 | const [isModal, setIsModal] = useState(false) 13 | useImperativeHandle(ref, () => { 14 | return { 15 | setIsModal 16 | } 17 | }) 18 | return ( 19 | { 23 | props.onOk(props.modalData) 24 | setIsModal(false) 25 | }} 26 | onCancel={() => setIsModal(false)} 27 | > 28 | 内容未保存, 是否离开? 29 | 30 | ) 31 | 32 | }) -------------------------------------------------------------------------------- /web/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "esnext", 4 | "target": "esnext", 5 | "lib": ["esnext", "dom"], 6 | "sourceMap": true, 7 | "baseUrl": ".", 8 | "jsx": "react-jsx", 9 | "allowSyntheticDefaultImports": true, 10 | "moduleResolution": "node", 11 | "forceConsistentCasingInFileNames": true, 12 | "noImplicitReturns": true, 13 | "suppressImplicitAnyIndexErrors": true, 14 | "noUnusedLocals": true, 15 | "allowJs": true, 16 | "skipLibCheck": true, 17 | "experimentalDecorators": true, 18 | "strict": true, 19 | "noEmit": true, 20 | "paths": { 21 | "@/*": ["./src/*"], 22 | "@@/*": ["./src/.umi/*"] 23 | } 24 | }, 25 | "exclude": [ 26 | "node_modules", 27 | "dist", 28 | "scripts", 29 | "src/.umi/*", 30 | "webpack", 31 | "jest", 32 | "frontend", 33 | "backend" 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /web/src/components/Gallery/ModulePanel/Collections/graph/common/DisplayForm.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 2/25/2021 3 | */ 4 | 5 | import { Mixin } from "@/components/Gallery/Utils/data" 6 | import { DisplayFormCartesianCoord } from "./DisplayFormCartesianCoord" 7 | import { DisplayFormSeriesPie } from "./DisplayFormSeriesPie" 8 | import { FormInstance } from 'antd' 9 | 10 | interface DisplayFormProps { 11 | mixin: Mixin 12 | columns?: string[] 13 | formRef: React.MutableRefObject | undefined> | undefined 14 | } 15 | 16 | export const DisplayForm = (props: DisplayFormProps) => { 17 | switch (props.mixin) { 18 | case "pie": 19 | return 20 | default: 21 | return 22 | } 23 | } 24 | 25 | -------------------------------------------------------------------------------- /web/src/locales/zh-CN.ts: -------------------------------------------------------------------------------- 1 | import component from './zh-CN/component'; 2 | import globalHeader from './zh-CN/globalHeader'; 3 | import menu from './zh-CN/menu'; 4 | import pwa from './zh-CN/pwa'; 5 | import settingDrawer from './zh-CN/settingDrawer'; 6 | import settings from './zh-CN/settings'; 7 | import gallery from './zh-CN/gallery' 8 | import pages from './zh-CN/pages' 9 | 10 | export default { 11 | 'navBar.lang': '语言', 12 | 'layout.user.link.help': '帮助', 13 | 'layout.user.link.privacy': '隐私', 14 | 'layout.user.link.terms': '条款', 15 | 'layout.global.error.title': '错误', 16 | 'layout.global.error.stack': '栈', 17 | 'app.preview.down.block': '下载此页面到本地项目', 18 | 'app.welcome.link.fetch-blocks': '获取全部区块', 19 | 'app.welcome.link.block-list': '基于 block 开发,快速构建标准页面', 20 | ...globalHeader, 21 | ...menu, 22 | ...settingDrawer, 23 | ...settings, 24 | ...pwa, 25 | ...component, 26 | ...gallery, 27 | ...pages 28 | } 29 | 30 | -------------------------------------------------------------------------------- /web/src/pages/demo/ComponentTest/index.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 11/25/2020 3 | */ 4 | 5 | import {QuerySelectorForm} from "@/components/Gallery/Dataset" 6 | import * as GalleryService from "@/services/gallery" 7 | import * as DataType from "@/components/Gallery/GalleryDataType" 8 | 9 | 10 | export default () => { 11 | 12 | const fetchStorages = () => 13 | GalleryService.getAllStorageSimple() as Promise 14 | 15 | const fetchTableList = (id: string) => 16 | GalleryService.databaseListTable(id) 17 | 18 | const fetchTableColumns = (storageId: string, tableName: string) => 19 | GalleryService.databaseGetTableColumns(storageId, tableName) 20 | 21 | return ( 22 | console.log(v)} 27 | /> 28 | ) 29 | } 30 | 31 | -------------------------------------------------------------------------------- /web/src/locales/en-US/globalHeader.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'component.globalHeader.search': 'Search', 3 | 'component.globalHeader.search.example1': 'Search example 1', 4 | 'component.globalHeader.search.example2': 'Search example 2', 5 | 'component.globalHeader.search.example3': 'Search example 3', 6 | 'component.globalHeader.help': 'Help', 7 | 'component.globalHeader.notification': 'Notification', 8 | 'component.globalHeader.notification.empty': 'You have viewed all notifications.', 9 | 'component.globalHeader.message': 'Message', 10 | 'component.globalHeader.message.empty': 'You have viewed all messsages.', 11 | 'component.globalHeader.event': 'Event', 12 | 'component.globalHeader.event.empty': 'You have viewed all events.', 13 | 'component.noticeIcon.clear': 'Clear', 14 | 'component.noticeIcon.cleared': 'Cleared', 15 | 'component.noticeIcon.empty': 'No notifications', 16 | 'component.noticeIcon.view-more': 'View more', 17 | }; 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | **/node_modules 5 | # roadhog-api-doc ignore 6 | /src/utils/request-temp.js 7 | _roadhog-api-doc 8 | 9 | # production 10 | /dist 11 | 12 | # misc 13 | .DS_Store 14 | npm-debug.log* 15 | yarn-error.log 16 | 17 | /coverage 18 | .idea 19 | #yarn.lock 20 | #package-lock.json 21 | *bak 22 | 23 | # visual studio code 24 | .history 25 | *.log 26 | .temp/** 27 | .vscode/* 28 | !.vscode/settings.json 29 | !.vscode/tasks.json 30 | !.vscode/launch.json 31 | !.vscode/extensions.json 32 | *.code-workspace 33 | 34 | # umi 35 | .umi 36 | .umi-production 37 | 38 | # screenshot 39 | screenshot 40 | .firebase 41 | .eslintcache 42 | 43 | out 44 | **/frontend 45 | **/backend 46 | 47 | cache 48 | !cache/.gitkeep 49 | data 50 | !data/.gitkeep 51 | test 52 | 53 | *.pyc 54 | makefile.bak 55 | .myAppPID 56 | 57 | web/config/proxy.json 58 | 59 | ./test.ts 60 | -------------------------------------------------------------------------------- /web/src/services/auth.ts: -------------------------------------------------------------------------------- 1 | 2 | import { request } from "umi" 3 | 4 | 5 | const base = `/gateway` 6 | // const base = `` 7 | export const sendInvitation = async (param: API.Invitation): Promise => { 8 | return request(`${base}/api/auth/invitation`, { 9 | method: "post", 10 | data: param 11 | }) 12 | } 13 | 14 | export const register = async (id: string): Promise => { 15 | return request(`${base}/api/auth/register/${id}`) 16 | } 17 | 18 | export const login = async (param: API.Login) => { 19 | console.log(188, param) 20 | return request(`${base}/api/auth`, { method: "post", credentials: 'include', data: param }) 21 | } 22 | 23 | export const check = async (): Promise => { 24 | return request(`${base}/api/auth`, { credentials: 'include' }) 25 | } 26 | 27 | export const logout = async (): Promise => { 28 | return request(`${base}/api/auth`, { credentials: 'include', method: "delete" }) 29 | } 30 | -------------------------------------------------------------------------------- /web/src/pages/gallery/Overview/index.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 9/10/2020. 3 | */ 4 | 5 | import {Controller} from "@/components/Gallery/Overview/OverviewController/Controller" 6 | 7 | import * as DataType from "@/components/Gallery/GalleryDataType" 8 | 9 | const categoryNames = [ 10 | "DevOps", 11 | "Language" 12 | ] 13 | 14 | const marks: DataType.Mark[] = [ 15 | { 16 | id: "1", 17 | name: "000001.SZ" 18 | }, 19 | { 20 | id: "2", 21 | name: "600000.SH" 22 | } 23 | ] 24 | 25 | const categoryOnSelect = (cn: string): Promise => 26 | new Promise((rs, rj) => { 27 | setTimeout(() => rs(marks), 500) 28 | }) 29 | 30 | export default () => { 31 | 32 | return ( 33 | <> 34 | {}} 38 | onEdit={e => {}} 39 | /> 40 | 41 | ) 42 | } 43 | 44 | -------------------------------------------------------------------------------- /web/src/components/Article/TagCreationModal.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 2/5/2021 3 | */ 4 | 5 | import {useState} from "react" 6 | import {useIntl} from "umi" 7 | 8 | import {TagCreationModalProps, GenericTag} from "./data" 9 | import {CreationModal} from "@/components/Gallery/Misc/CreationModal" 10 | 11 | 12 | export const TagCreationModal = (props: TagCreationModalProps) => { 13 | const intl = useIntl() 14 | const [visible, setVisible] = useState(false) 15 | 16 | return ( 17 | <> 18 | <> 19 | {props.trigger({onClick: () => setVisible(true)})} 20 | 21 | { 25 | props.onSubmit(v as GenericTag) 26 | setVisible(false) 27 | }} 28 | onCancel={() => setVisible(false)} 29 | colorSelector 30 | /> 31 | 32 | ) 33 | } 34 | 35 | -------------------------------------------------------------------------------- /web/src/components/Gallery/ModulePanel/Collections/graph/common/DisplayFormThemeSelector.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 2/25/2021 3 | */ 4 | 5 | import ProForm from "@ant-design/pro-form" 6 | import {Form, Select} from "antd" 7 | import {FormattedMessage} from "umi" 8 | 9 | import {themeSelections} from "@/components/EchartsPro/themeSelections" 10 | 11 | 12 | export const DisplayFormThemeSelector = () => 13 | } 15 | > 16 | } 19 | > 20 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /web/src/services/login.ts: -------------------------------------------------------------------------------- 1 | import {request} from "umi" 2 | 3 | export interface LoginParamsType { 4 | username: string 5 | password: string 6 | mobile: string 7 | captcha: string 8 | type: string 9 | } 10 | 11 | export async function login(body: API.LoginParams, options?: {[key: string]: any}) { 12 | return request('/api/login/account', { 13 | method: 'POST', 14 | headers: { 15 | 'Content-Type': 'application/json', 16 | }, 17 | data: body, 18 | ...(options || {}), 19 | }) 20 | } 21 | 22 | export async function getFakeCaptcha( 23 | params: { 24 | // query 25 | /** 手机号 */ 26 | phone?: string 27 | }, 28 | options?: {[key: string]: any}, 29 | ) { 30 | return request('/api/login/captcha', { 31 | method: 'POST', 32 | params: { 33 | ...params, 34 | }, 35 | ...(options || {}), 36 | }) 37 | } 38 | 39 | export async function outLogin() { 40 | return request('/api/login/outLogin') 41 | } 42 | -------------------------------------------------------------------------------- /web/src/components/PrivateRoute/index.tsx: -------------------------------------------------------------------------------- 1 | import { Modal } from "antd" 2 | import { useState } from "react" 3 | import { Route, Redirect } from "react-router-dom" 4 | import { history } from 'umi' 5 | export const PrivateRoute = ({ component: Component, ...rest }) => { 6 | const [isVisible, setIsVisible] = useState(true) 7 | console.log(666, location) 8 | return ( 9 | { 12 | setIsVisible(false) 13 | history.goBack() 14 | }}> 15 | // true ? ( 16 | // 17 | // ) : ( 18 | // 24 | // ) 25 | } 26 | /> 27 | ) 28 | } -------------------------------------------------------------------------------- /web/src/redux/counter/counterSlice.spec.ts: -------------------------------------------------------------------------------- 1 | import counterReducer, { 2 | CounterState, 3 | increment, 4 | decrement, 5 | incrementByAmount, 6 | } from './counterSlice'; 7 | 8 | describe('counter reducer', () => { 9 | const initialState: CounterState = { 10 | value: 3, 11 | status: 'idle', 12 | }; 13 | it('should handle initial state', () => { 14 | expect(counterReducer(undefined, { type: 'unknown' })).toEqual({ 15 | value: 0, 16 | status: 'idle', 17 | }); 18 | }); 19 | 20 | it('should handle increment', () => { 21 | const actual = counterReducer(initialState, increment()); 22 | expect(actual.value).toEqual(4); 23 | }); 24 | 25 | it('should handle decrement', () => { 26 | const actual = counterReducer(initialState, decrement()); 27 | expect(actual.value).toEqual(2); 28 | }); 29 | 30 | it('should handle incrementByAmount', () => { 31 | const actual = counterReducer(initialState, incrementByAmount(2)); 32 | expect(actual.value).toEqual(5); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /web/src/locales/en-US.ts: -------------------------------------------------------------------------------- 1 | import component from './en-US/component'; 2 | import globalHeader from './en-US/globalHeader'; 3 | import menu from './en-US/menu'; 4 | import pwa from './en-US/pwa'; 5 | import settingDrawer from './en-US/settingDrawer'; 6 | import settings from './en-US/settings'; 7 | import gallery from './en-US/gallery' 8 | import pages from './en-US/pages' 9 | 10 | export default { 11 | 'navBar.lang': 'Languages', 12 | 'layout.user.link.help': 'Help', 13 | 'layout.user.link.privacy': 'Privacy', 14 | 'layout.user.link.terms': 'Terms', 15 | 'layout.global.error.title': 'Error', 16 | 'layout.global.error.stack': 'Stack', 17 | 'app.preview.down.block': 'Download this page to your local project', 18 | 'app.welcome.link.fetch-blocks': 'Get all block', 19 | 'app.welcome.link.block-list': 'Quickly build standard, pages based on `block` development', 20 | ...globalHeader, 21 | ...menu, 22 | ...settingDrawer, 23 | ...settings, 24 | ...pwa, 25 | ...component, 26 | ...gallery, 27 | ...pages, 28 | } 29 | 30 | -------------------------------------------------------------------------------- /web/src/pages/document/Menu/index.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 2/9/2021. 3 | */ 4 | 5 | import {Typography} from "antd" 6 | import {FormattedMessage} from "umi" 7 | import connectorNodeV1 from "@opuscapita/react-filemanager-connector-node-v1" 8 | import {FileManager, FileNavigator} from "@opuscapita/react-filemanager" 9 | 10 | 11 | const apiOptions = { 12 | ...connectorNodeV1.apiOptions, 13 | apiRoot: "/api/cyberbrick", 14 | } 15 | 16 | export default () => { 17 | 18 | return ( 19 | 20 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | ) 34 | } 35 | 36 | -------------------------------------------------------------------------------- /web/src/components/Gallery/Tag/DraggableTagPanel.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 12/17/2020 3 | */ 4 | 5 | import {useEffect, useState} from "react" 6 | import {Tag} from "antd" 7 | import _ from "lodash" 8 | 9 | import {DraggablePanel} from "@/components/DraggablePanel/DraggablePanel" 10 | 11 | import {DraggableTagPanelProps, GenericDataInput} from "./data" 12 | 13 | 14 | export const DraggableTagPanel = (props: DraggableTagPanelProps) => { 15 | 16 | const [items, setItems] = useState(props.data.map(i => ({...i, id: i.name}))) 17 | 18 | useEffect(() => props.elementOnChange(items), [items]) 19 | 20 | const onChange = (ids: string[]) => { 21 | const newItems = _.sortBy(items, i => ids.indexOf(i.name)) 22 | setItems(newItems) 23 | } 24 | 25 | return ( 26 | 30 | { 31 | items.map(i => {i.name}) 32 | } 33 | 34 | ) 35 | } 36 | 37 | -------------------------------------------------------------------------------- /web/src/utilities/utils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 3/11/2020. 3 | */ 4 | 5 | import moment from 'moment' 6 | import {useCallback, useEffect, useRef, useState} from 'react' 7 | 8 | const dateFormat = 'YYYYMMDD' 9 | const dateFormat2 = 'YYYY-MM-DD' 10 | 11 | export const currentTimeStamp = () => moment().format() 12 | export const stringToTimestamp = (time: string) => moment(time).format() 13 | export const timeStampToDateString = (time: moment.Moment) => time.format(dateFormat) 14 | export const numberToDateString = (time: string) => moment(time).format(dateFormat2) 15 | 16 | export const today = () => moment(new Date()) 17 | export const todayString = () => today().format(dateFormat) 18 | 19 | export const useDidMountEffect = (func: () => void, deps: any) => { 20 | const didMount = useRef(false) 21 | 22 | useEffect(() => { 23 | if (didMount.current) func() 24 | else didMount.current = true 25 | }, deps) 26 | } 27 | 28 | export const useForceUpdate = () => { 29 | const [, setTick] = useState(0) 30 | const update = useCallback(() => { 31 | setTick(tick => tick + 1) 32 | }, []) 33 | return update() 34 | } 35 | 36 | -------------------------------------------------------------------------------- /web/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | parserOptions: { 4 | project: 'tsconfig.json', 5 | sourceType: 'module', 6 | }, 7 | plugins: ['@typescript-eslint/eslint-plugin'], 8 | extends: [ 9 | 'plugin:@typescript-eslint/eslint-recommended', 10 | 'plugin:@typescript-eslint/recommended', 11 | 'prettier', 12 | 'prettier/@typescript-eslint', 13 | ], 14 | root: true, 15 | env: { 16 | node: true, 17 | jest: true, 18 | }, 19 | rules: { 20 | '@typescript-eslint/interface-name-prefix': 'off', 21 | '@typescript-eslint/explicit-function-return-type': 'off', 22 | '@typescript-eslint/explicit-module-boundary-types': 'off', 23 | '@typescript-eslint/no-explicit-any': 'off', 24 | '@typescript-eslint/no-unused-vars': 'off', 25 | '@typescript-eslint/no-non-null-assertion': 'off', 26 | '@typescript-eslint/no-inferrable-types': 'off', 27 | '@typescript-eslint/no-empty-function': 'off', 28 | }, 29 | ignorePatterns: ["/*.*"], 30 | globals: { 31 | ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION: true, 32 | page: true, 33 | REACT_APP_ENV: true, 34 | }, 35 | }; 36 | -------------------------------------------------------------------------------- /web/src/utils/utils.ts: -------------------------------------------------------------------------------- 1 | import {parse} from 'querystring' 2 | 3 | const reg = /(((^https?:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)$/ 4 | 5 | export const isUrl = (path: string): boolean => reg.test(path) 6 | 7 | export const isAntDesignPro = (): boolean => { 8 | if (ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION === 'site') { 9 | return true 10 | } 11 | return window.location.hostname === 'preview.pro.ant.design' 12 | } 13 | 14 | // 给官方演示站点用,用于关闭真实开发环境不需要使用的特性 15 | export const isAntDesignProOrDev = (): boolean => { 16 | const {NODE_ENV} = process.env 17 | if (NODE_ENV === 'development') { 18 | return true 19 | } 20 | return isAntDesignPro() 21 | } 22 | 23 | export const getPageQuery = () => { 24 | const {href} = window.location 25 | const qsIndex = href.indexOf('?') 26 | const sharpIndex = href.indexOf('#') 27 | 28 | if (qsIndex !== -1) { 29 | if (qsIndex > sharpIndex) { 30 | return parse(href.split('?')[1]) 31 | } 32 | 33 | return parse(href.slice(qsIndex + 1, sharpIndex)) 34 | } 35 | 36 | return {} 37 | } 38 | -------------------------------------------------------------------------------- /web/src/components/TextEditor/TextEditorPresenter.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 8/17/2020. 3 | */ 4 | 5 | import React, { useEffect, useMemo, useState } from "react" 6 | import ReactQuill from "react-quill" 7 | 8 | import 'react-quill/dist/quill.bubble.css' 9 | 10 | export interface DisplayPresenterProps { 11 | content: string 12 | styling?: string 13 | style?: React.CSSProperties 14 | } 15 | 16 | /** 17 | * ReactQuill is an uncontrolled component, should force render by using `useEffect` when props.content changed 18 | */ 19 | export const TextEditorPresenter = (props: DisplayPresenterProps) => { 20 | const style = props.styling ? props.styling : undefined 21 | 22 | const [value, setValue] = useState(props.content) 23 | const [update, setUpdate] = useState(false) 24 | 25 | useEffect(() => { 26 | setValue(props.content) 27 | setUpdate(!update) 28 | }, [props.content]) 29 | 30 | return useMemo(() => setValue(e)} 35 | readOnly 36 | style={props.style} 37 | />, [update]) 38 | } 39 | -------------------------------------------------------------------------------- /web/src/components/Gallery/Tag/data.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 12/17/2020 3 | */ 4 | 5 | import React from "react" 6 | 7 | export interface GenericDataInput { 8 | id?: string 9 | index?: number 10 | name: string 11 | description?: string 12 | color?: string 13 | } 14 | 15 | export interface SelectableTagsProps { 16 | tags: GenericDataInput[] 17 | onSelectTags: (value: string[]) => void 18 | } 19 | 20 | export interface SelectableTagsRef { 21 | clearSelected: () => void 22 | } 23 | 24 | export interface EditableTagPanelProps { 25 | name?: string 26 | textCreation?: string | React.ReactNode 27 | textModification?: string | React.ReactNode 28 | textDeletion?: string 29 | colorSelector?: boolean 30 | 31 | data: T[] 32 | editable: boolean 33 | elementOnChange: (data: T[]) => void 34 | draggable?: boolean 35 | } 36 | 37 | export interface SearchableTagsProps { 38 | searchable: boolean 39 | data: T[] 40 | elementOnSearch: (value: string[]) => void 41 | } 42 | 43 | export interface DraggableTagPanelProps { 44 | editable: boolean 45 | data: T[] 46 | elementOnChange: (data: T[]) => void 47 | } 48 | 49 | -------------------------------------------------------------------------------- /web/src/typings.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'slash2' 2 | declare module '*.css' 3 | declare module '*.less' 4 | declare module '*.scss' 5 | declare module '*.sass' 6 | declare module '*.svg' 7 | declare module '*.png' 8 | declare module '*.jpg' 9 | declare module '*.jpeg' 10 | declare module '*.gif' 11 | declare module '*.bmp' 12 | declare module '*.tiff' 13 | declare module '@opuscapita/react-filemanager' 14 | declare module '@opuscapita/react-filemanager-connector-node-v1' 15 | 16 | // google analytics interface 17 | interface GAFieldsObject { 18 | eventCategory: string 19 | eventAction: string 20 | eventLabel?: string 21 | eventValue?: number 22 | nonInteraction?: boolean 23 | } 24 | 25 | interface Window { 26 | ga: ( 27 | command: 'send', 28 | hitType: 'event' | 'pageview', 29 | fieldsObject: GAFieldsObject | string, 30 | ) => void 31 | reloadAuthorized: () => void 32 | } 33 | 34 | // eslint-disable-next-line @typescript-eslint/ban-types 35 | declare let ga: Function 36 | 37 | // preview.pro.ant.design only do not use in your production 38 | // preview.pro.ant.design 专用环境变量,请不要在你的项目中使用它。 39 | declare let ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION: 'site' | undefined 40 | 41 | declare const REACT_APP_ENV: 'test' | 'dev' | 'pre' | false 42 | -------------------------------------------------------------------------------- /web/src/pages/demo/GridLayout/index.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 11/25/2020 3 | */ 4 | 5 | import RGL, {WidthProvider} from "react-grid-layout" 6 | 7 | const GridLayout = WidthProvider(RGL) 8 | 9 | const gridLayoutDefaultProps = { 10 | className: "layout", 11 | cols: 12, 12 | rowHeight: 20, 13 | } 14 | 15 | export default () => { 16 | 17 | const layout = [ 18 | {i: 'a', x: 0, y: 0, w: 1, h: 2}, 19 | {i: 'b', x: 1, y: 0, w: 3, h: 2, minW: 2, maxW: 4}, 20 | {i: 'c', x: 4, y: 0, w: 1, h: 2} 21 | ] 22 | 23 | return ( 24 | 29 |
30 | 34 |
a
35 |
b
36 |
c
37 |
38 |
39 |
b
40 |
c
41 |
42 | ) 43 | } 44 | 45 | -------------------------------------------------------------------------------- /web/src/pages/demo/Charts/Pie.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 2/23/2021 3 | */ 4 | 5 | import {useState} from "react" 6 | import {Radio, Space} from "antd" 7 | import ReactEcharts from "echarts-for-react" 8 | import _ from "lodash" 9 | 10 | import {data} from "./mock/pie" 11 | import {ChartsProps} from "./data" 12 | import {generatePieOption} from "@/components/Gallery/Utils/chartGenerators" 13 | 14 | 15 | const pieOpt = generatePieOption() 16 | type Dir = "vertical" | "horizontal" 17 | 18 | /** 19 | * multi-series pie 20 | */ 21 | export const Pie = (props: ChartsProps) => { 22 | 23 | const [dir, setDir] = useState("vertical") 24 | 25 | const getOption = (v: Dir) => 26 | pieOpt(data, {select: "year", seriesDir: v}) 27 | 28 | return ( 29 | <> 30 | 31 |
Data direction:
32 | setDir(e.target.value)} value={dir}> 33 | Vertical 34 | Horizontal 35 | 36 |
37 | 44 | 45 | ) 46 | } 47 | 48 | -------------------------------------------------------------------------------- /web/src/components/Gallery/Tag/SelectableTags.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 8/27/2020. 3 | */ 4 | 5 | import React, {forwardRef, useEffect, useImperativeHandle, useState} from "react" 6 | import {Tag} from "antd" 7 | 8 | import {SelectableTagsProps, SelectableTagsRef} from "./data" 9 | 10 | 11 | export const SelectableTags = forwardRef((props: SelectableTagsProps, ref: React.Ref) => { 12 | 13 | const [selectedTagNames, setSelectedTagNames] = useState([]) 14 | 15 | useEffect(() => { 16 | props.onSelectTags(selectedTagNames) 17 | }, [selectedTagNames]) 18 | 19 | const handleChange = (tag: string, checked: boolean) => { 20 | const nextSelectedTags = checked ? 21 | [...selectedTagNames, tag] : 22 | selectedTagNames.filter(t => t !== tag) 23 | setSelectedTagNames(nextSelectedTags) 24 | } 25 | 26 | const clearSelected = () => setSelectedTagNames([]) 27 | 28 | useImperativeHandle(ref, () => ({clearSelected})) 29 | 30 | return ( 31 | <> 32 | { 33 | props.tags.map(t => 34 | -1} 37 | onChange={c => handleChange(t.name, c)} 38 | > 39 | {t.name} 40 | 41 | ) 42 | } 43 | 44 | ) 45 | }) 46 | -------------------------------------------------------------------------------- /web/src/locales/zh-CN/settingDrawer.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'app.setting.pagestyle': '整体风格设置', 3 | 'app.setting.pagestyle.dark': '暗色菜单风格', 4 | 'app.setting.pagestyle.light': '亮色菜单风格', 5 | 'app.setting.content-width': '内容区域宽度', 6 | 'app.setting.content-width.fixed': '定宽', 7 | 'app.setting.content-width.fluid': '流式', 8 | 'app.setting.themecolor': '主题色', 9 | 'app.setting.themecolor.dust': '薄暮', 10 | 'app.setting.themecolor.volcano': '火山', 11 | 'app.setting.themecolor.sunset': '日暮', 12 | 'app.setting.themecolor.cyan': '明青', 13 | 'app.setting.themecolor.green': '极光绿', 14 | 'app.setting.themecolor.daybreak': '拂晓蓝(默认)', 15 | 'app.setting.themecolor.geekblue': '极客蓝', 16 | 'app.setting.themecolor.purple': '酱紫', 17 | 'app.setting.navigationmode': '导航模式', 18 | 'app.setting.sidemenu': '侧边菜单布局', 19 | 'app.setting.topmenu': '顶部菜单布局', 20 | 'app.setting.fixedheader': '固定 Header', 21 | 'app.setting.fixedsidebar': '固定侧边菜单', 22 | 'app.setting.fixedsidebar.hint': '侧边菜单布局时可配置', 23 | 'app.setting.hideheader': '下滑时隐藏 Header', 24 | 'app.setting.hideheader.hint': '固定 Header 时可配置', 25 | 'app.setting.othersettings': '其他设置', 26 | 'app.setting.weakmode': '色弱模式', 27 | 'app.setting.copy': '拷贝设置', 28 | 'app.setting.copyinfo': '拷贝成功,请到 src/defaultSettings.js 中替换默认配置', 29 | 'app.setting.production.hint': 30 | '配置栏只在开发环境用于预览,生产环境不会展现,请拷贝后手动修改配置文件', 31 | }; 32 | -------------------------------------------------------------------------------- /web/src/components/Gallery/Dashboard/DashboardContainer/nestedDedicatedContext.ts: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import * as DataType from "@/components/Gallery/GalleryDataType" 3 | 4 | //DashboardContext上下文类型,快速传递属性。 5 | export interface nestedDedicatedContextProps { 6 | setSubmoduleDateList?: React.Dispatch> 7 | isNested: boolean | undefined 8 | setContent: React.Dispatch> | undefined 9 | content: DataType.Content | undefined 10 | parentInfo?: { 11 | selectedCategoryName: string, 12 | dashboardInfo: DataType.Dashboard 13 | templateInfo: DataType.Template 14 | 15 | } 16 | elements: DataType.Element[] | undefined 17 | setElements: React.Dispatch> | undefined 18 | dateList: string[] 19 | setDateList: React.Dispatch> 20 | elementName: string 21 | // nested当前所选的模块名字 22 | currentModuleName: string | undefined 23 | setCurrentModuleName: React.Dispatch> | undefined 24 | // 该模块的信息 25 | element: DataType.Element, 26 | setElement: React.Dispatch> 27 | // 右上角的编辑 28 | editable: boolean | undefined 29 | } 30 | 31 | 32 | export const nestedDedicatedContext = React.createContext(undefined) 33 | 34 | -------------------------------------------------------------------------------- /web/src/pages/demo/FileManagerTest/index.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 12/8/2021 3 | */ 4 | 5 | import {useRef, useState} from "react" 6 | import {FormattedMessage} from "umi" 7 | import {Card, Switch} from "antd" 8 | 9 | import * as DataType from "@/components/Gallery/GalleryDataType" 10 | import {FileView} from "@/components/Gallery/ModulePanel/Collections/file/FileView" 11 | import {ConvertFwRef} from "@/components/Gallery/ModulePanel/Generator/data" 12 | 13 | 14 | export default () => { 15 | 16 | const moduleFwRef = useRef(null) 17 | 18 | const [content, setContent] = useState() 19 | 20 | const switchOnClick = (v: boolean) => { 21 | if (moduleFwRef.current) moduleFwRef.current.edit(v) 22 | } 23 | 24 | 25 | return ( 26 | } 28 | extra={ 29 | 32 | } 33 | style={{height: "85vh"}} 34 | bodyStyle={{height: "100%"}} 35 | > 36 | { 39 | console.log(v) 40 | }} 41 | content={content} 42 | contentHeight={500} 43 | setContent={(v) => { 44 | console.log(v) 45 | }} 46 | /> 47 | 48 | ) 49 | } 50 | 51 | -------------------------------------------------------------------------------- /web/src/components/Article/ArticleToolbar.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 2/9/2021 3 | */ 4 | 5 | import {ArticleToolbarProps} from "./data" 6 | 7 | import {ArticleCreationModal} from "@/components/Article/ArticleCreationModal" 8 | import {Editor} from "@/components/Editor" 9 | import {TagModificationModal} from "@/components/Article/TagModificationModal" 10 | 11 | 12 | export const ArticleToolbar = (props: ArticleToolbarProps) => 13 | <> 14 | { 15 | props.editable ? 16 | <> 17 | 19 | c.onClick()} 22 | /> 23 | } 24 | onSubmit={props.articleCreationOnSubmit} 25 | tags={props.tags} 26 | modalHeight={"70vh"} 27 | modalWidth={"70vw"} 28 | /> 29 | 30 | 32 | c.onClick()} 35 | /> 36 | } 37 | onSubmit={props.tagModificationModal} 38 | tags={props.tags} 39 | /> 40 | : <> 41 | } 42 | 46 | 47 | -------------------------------------------------------------------------------- /web/src/pages/demo/index.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 2/1/2021 3 | */ 4 | 5 | import { Route, Switch } from "react-router-dom" 6 | 7 | import Demo from "./Demo" 8 | import Charts from "./Charts" 9 | import ComponentTest from "./ComponentTest" 10 | import GalleryModuleTest from "./GalleryModuleTest" 11 | import GridLayout from "./GridLayout" 12 | import RedirectTest from "./RedirectTest" 13 | import LocalStorage from "./LocalStorage" 14 | import RectangleChart from "./RectangleChart" 15 | import ConsensusDistributionChart from "./ConsensusDistributionChart" 16 | import FileManagerTest from "./FileManagerTest" 17 | 18 | export default () => { 19 | 20 | return ( 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | ) 34 | } 35 | 36 | -------------------------------------------------------------------------------- /web/src/components/Gallery/Dashboard/DashboardController/Common.less: -------------------------------------------------------------------------------- 1 | .moduleSelectionLabel { 2 | display: block; 3 | //width: 400px; 4 | } 5 | 6 | .moduleSelectionLabel>input { 7 | /* HIDE RADIO */ 8 | position : absolute; 9 | /* Remove input from document flow */ 10 | visibility: hidden; 11 | /* Makes input not-clickable */ 12 | } 13 | 14 | .moduleSelectionLabel>input+div { 15 | /* DIV STYLES */ 16 | border: 2px solid transparent; 17 | cursor: pointer; 18 | } 19 | 20 | .moduleSelectionLabel>input:checked+div { 21 | /* (RADIO CHECKED) DIV STYLES */ 22 | background : #1890ff; 23 | border-radius: 5px; 24 | box-shadow : 2px 2px 5px #1890ff, -2px -2px 5px #1890ff; 25 | } 26 | 27 | .selectionCard { 28 | display : flex; 29 | align-items : center; 30 | justify-content: center; 31 | height : 60px; 32 | background : #e7ecee; 33 | border-radius : 5px; 34 | box-shadow : 2px 2px 5px #c4c9ca, -2px -2px 5px #fff; 35 | } 36 | 37 | .selectionCardDisabled { 38 | display : flex; 39 | align-items : center; 40 | justify-content: center; 41 | height : 60px; 42 | color : #e7ecee; 43 | background : repeating-linear-gradient(45deg, 44 | #222, 45 | #222 10px, 46 | #333 10px, 47 | #333 20px); 48 | border-radius: 5px; 49 | box-shadow : 2px 2px 5px #838a8e, -2px -2px 5px #b1bac0; 50 | } -------------------------------------------------------------------------------- /web/src/components/Gallery/ModulePanel/Collections/NestedModule/data.d.ts: -------------------------------------------------------------------------------- 1 | import { Layout } from "react-grid-layout"; 2 | import { Content, ElementType } from "@/components/Gallery/GalleryDataType"; 3 | 4 | export interface tabItem extends Layout { 5 | //minimal of the two (width and height) of a tab item; used to calculate font-size 6 | minDim?: FontSize | undefined; 7 | module?: simpleEmbededModule, 8 | tabContent?: tabContentChoice | string, 9 | tabType?: tabType 10 | } 11 | 12 | export interface SimpleEmbededModule { 13 | name: string, 14 | timeSeries: boolean, 15 | elementType: ElementType, 16 | content?: Content 17 | } 18 | 19 | export enum TabType { 20 | icon = "icon", 21 | text = "text", 22 | number = "number", 23 | } 24 | 25 | export interface TabChoice { 26 | key: string, 27 | icon: JSX.Element, 28 | tabType: string, 29 | } 30 | 31 | export enum tabContentChoice { 32 | AreaChartOutlined = "AreaChartOutlined", 33 | PieChartOutlined = "PieChartOutlined", 34 | BarChartOutlined = "BarChartOutlined", 35 | DotChartOutlined = "DotChartOutlined", 36 | LineChartOutlined = "LineChartOutlined", 37 | FallOutlined = "FallOutlined", 38 | RiseOutlined = "RiseOutlined", 39 | StockOutlined = "StockOutlined", 40 | AccountBookOutlined = "AccountBookOutlined", 41 | BellOutlined = "BellOutlined", 42 | } 43 | 44 | export interface prRef { 45 | getTabItem: () => tabItem[] 46 | } -------------------------------------------------------------------------------- /web/src/components/Gallery/Dashboard/DashboardContext.ts: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import * as DataType from "../GalleryDataType" 3 | import { ContainerRef } from './DashboardContainer/Container' 4 | //DashboardContext上下文类型,快速传递属性。 5 | export interface DashboardContextType { 6 | //获取content 7 | fetchElementContent?: (id: string, date?: string, isNested?: boolean) => Promise 8 | 9 | //保存维度 10 | saveTemplate?: (template: DataType.Template) => Promise 11 | 12 | //保存模块s 13 | updateElements?: (ele: DataType.Element[]) => any 14 | 15 | //在更新NewestContent前加入公司信息 16 | setDashboardInfoInNewestContent?: (content: DataType.Content) => void 17 | 18 | //最外层的contents 19 | allContent?: DataType.Content[] 20 | 21 | //最外层的contents的set 22 | setAllContent?: React.Dispatch> 23 | 24 | //右上角的编辑变量 25 | edit?: boolean 26 | setEdit?: React.Dispatch> 27 | 28 | //最外层contents的暂存值的set 29 | // setNewestContent?:React.Dispatch> 30 | //内容的ref 31 | ContainerRef: React.Ref 32 | 33 | // getTemplateElements: (templateId: string, isSubmodule?: boolean) => Promise 34 | 35 | contentIdsToBeDelect: string[] 36 | setContentIdsToBeDelect: React.Dispatch> 37 | } 38 | 39 | 40 | export const DashboardContext = React.createContext(undefined) 41 | 42 | -------------------------------------------------------------------------------- /web/src/components/Article/TagModificationModal.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 2/6/2021 3 | */ 4 | 5 | import {useState} from "react" 6 | import { Modal} from "antd"; 7 | import {useIntl} from "umi" 8 | 9 | import {TagModificationModalProps, GenericTag} from "./data" 10 | import {EditableTagPanel} from "@/components/Gallery/Tag" 11 | 12 | 13 | export const TagModificationModal = (props: TagModificationModalProps) => { 14 | const intl = useIntl() 15 | const [visible, setVisible] = useState(false) 16 | const [data, setData] = useState() 17 | 18 | const onSubmit = () => { 19 | if (data) { 20 | props.onSubmit(data) 21 | setVisible(false) 22 | } 23 | } 24 | 25 | return ( 26 | <> 27 | <> 28 | {props.trigger({onClick: () => setVisible(true)})} 29 | 30 | setVisible(false)} 35 | maskClosable={false} 36 | width={props.modalWidth} 37 | bodyStyle={{height: props.modalHeight}} 38 | > 39 | 40 | textCreation={"create tag"} 41 | textModification={"modify tag"} 42 | textDeletion={"delete tag"} 43 | data={props.tags} 44 | editable={true} 45 | elementOnChange={setData} 46 | draggable={false} 47 | colorSelector 48 | /> 49 | 50 | 51 | ) 52 | } 53 | 54 | -------------------------------------------------------------------------------- /web/src/pages/demo/ConsensusDistributionChart/index.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 10/20/2021 3 | * 4 | * https://www.highcharts.com/demo/bullet-graph 5 | */ 6 | 7 | import Highcharts from 'highcharts' 8 | import HighchartsReact from 'highcharts-react-official' 9 | import bullet from "highcharts/modules/bullet" 10 | 11 | 12 | // 初始化 bullet 模块(必须!) 13 | bullet(Highcharts) 14 | 15 | // options 16 | const options: Highcharts.Options = { 17 | chart: { 18 | // 横向展示 19 | inverted: true, 20 | }, 21 | title: { 22 | // 标题 23 | text: 'My chart' 24 | }, 25 | xAxis: { 26 | // x轴注释 27 | categories: ['Revenue
U.S. $ (1,000s)'] 28 | }, 29 | yAxis: { 30 | // y轴视觉分隔 31 | gridLineWidth: 0, 32 | // y轴色彩条 33 | plotBands: [ 34 | { 35 | from: 0, 36 | to: 150, 37 | color: '#666' 38 | }, 39 | { 40 | from: 150, 41 | to: 225, 42 | color: '#999' 43 | }, 44 | { 45 | from: 225, 46 | to: 9e9, 47 | color: '#bbb' 48 | } 49 | ], 50 | }, 51 | series: [ 52 | { 53 | // 数据类型 54 | type: 'bullet', 55 | data: [ 56 | { 57 | y: 275, 58 | target: 250, 59 | }, 60 | ] 61 | } 62 | ], 63 | tooltip: { 64 | // 提示 65 | pointFormat: '{point.y} (with target at {point.target})' 66 | } 67 | } 68 | 69 | 70 | export default () => { 71 | return ( 72 | 73 | ) 74 | } 75 | -------------------------------------------------------------------------------- /web/src/components/Gallery/Misc/FileUploadConfig.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 11/19/2020 3 | */ 4 | 5 | import axios, {AxiosResponse} from "axios" 6 | 7 | export const fileExtractUrl = "/api/upload/extract" 8 | export const fileInsertUrl = "/api/database/insertByFile" 9 | 10 | 11 | export interface FileExtractOption extends Record { 12 | fileOptions?: string[] 13 | numberRounding?: number 14 | dateFormat?: string 15 | transpose?: boolean 16 | } 17 | 18 | export const fileExtract = (option: FileExtractOption, data: any): Promise => { 19 | let u = `${fileExtractUrl}?` 20 | u += `multiSheets=${option.fileOptions?.includes("multiSheets") || false}&` 21 | if (option.numberRounding) u += `numberRounding=${option.numberRounding}&` 22 | if (option.dateFormat) u += `dateFormat=${option.dateFormat}&` 23 | if (option.transpose) u += `transpose=${option.transpose}&` 24 | 25 | return axios.post(u, data) 26 | } 27 | 28 | export interface FileInsertOption extends Record { 29 | id?: string 30 | tableName?: string 31 | insertOption?: string 32 | transpose?: boolean 33 | sheetPrefix?: string 34 | } 35 | 36 | export const fileInsert = (option: FileInsertOption, data: any): Promise => { 37 | let u = `${fileInsertUrl}?id=${option.id}&tableName=${option.tableName}&` 38 | if (option.insertOption) u += `insertOption=${option.insertOption}&` 39 | if (option.transpose) u += `transpose=${option.transpose}&` 40 | if (option.sheetPrefix) u += `sheetPrefix=${option.sheetPrefix}&` 41 | 42 | return axios.post(u, data) 43 | } 44 | 45 | -------------------------------------------------------------------------------- /web/src/components/Editor/EditorButton.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 11/20/2020 3 | */ 4 | 5 | import React, { useState } from 'react' 6 | import { Button } from "antd" 7 | 8 | export interface EditorButtonProps { 9 | icons: { open: React.ReactNode, close: React.ReactNode } 10 | name: { open: React.ReactNode, close: React.ReactNode } 11 | type?: "text" | "link" | "ghost" | "default" | "primary" | "dashed" | undefined 12 | size?: "large" | "middle" | "small" 13 | shape?: "circle" | "round" | undefined 14 | defaultOpen?: boolean 15 | onChange: (value: boolean) => void 16 | } 17 | 18 | export const EditorButton = (props: EditorButtonProps) => { 19 | 20 | const buttonProps = { 21 | type: props.type, 22 | size: props.size || "middle", 23 | shape: props.shape 24 | } 25 | 26 | const [editable, setEditable] = useState(props.defaultOpen || false) 27 | 28 | const editableOnChange = () => { 29 | setEditable(!editable) 30 | props.onChange(!editable) 31 | } 32 | 33 | return editable ? 34 | : 41 | 48 | } 49 | 50 | EditorButton.defaultProps = { 51 | defaultOpen: false 52 | } as Partial 53 | 54 | -------------------------------------------------------------------------------- /web/src/components/Gallery/ModulePanel/Collections/graph/common/DisplayFormSeriesPie.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 2/24/2021 3 | */ 4 | 5 | import ProForm, {ProFormRadio, ProFormSelect} from "@ant-design/pro-form" 6 | import {Divider} from "antd" 7 | import {FormattedMessage} from "umi" 8 | 9 | import {DisplayFormThemeSelector} from "./DisplayFormThemeSelector" 10 | 11 | 12 | const seriesDirOptions = [ 13 | { 14 | value: "vertical", 15 | label: 16 | }, 17 | { 18 | value: "horizontal", 19 | label: 20 | }, 21 | ] 22 | 23 | export interface DisplayFormSeriesPieProps { 24 | columns?: string[] 25 | } 26 | 27 | export const DisplayFormSeriesPie = (props: DisplayFormSeriesPieProps) => 28 | props.columns ? ( 29 | <> 30 | } 32 | > 33 | } 36 | options={props.columns.map(c => ({value: c, label: c}))} 37 | width="lg" 38 | /> 39 | } 42 | options={seriesDirOptions} 43 | /> 44 | 45 | 46 | 47 | 48 | 49 | 50 | ) : <> 51 | 52 | -------------------------------------------------------------------------------- /web/src/locales/en-US/settingDrawer.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'app.setting.pagestyle': 'Page style setting', 3 | 'app.setting.pagestyle.dark': 'Dark style', 4 | 'app.setting.pagestyle.light': 'Light style', 5 | 'app.setting.content-width': 'Content Width', 6 | 'app.setting.content-width.fixed': 'Fixed', 7 | 'app.setting.content-width.fluid': 'Fluid', 8 | 'app.setting.themecolor': 'Theme Color', 9 | 'app.setting.themecolor.dust': 'Dust Red', 10 | 'app.setting.themecolor.volcano': 'Volcano', 11 | 'app.setting.themecolor.sunset': 'Sunset Orange', 12 | 'app.setting.themecolor.cyan': 'Cyan', 13 | 'app.setting.themecolor.green': 'Polar Green', 14 | 'app.setting.themecolor.daybreak': 'Daybreak Blue (default)', 15 | 'app.setting.themecolor.geekblue': 'Geek Glue', 16 | 'app.setting.themecolor.purple': 'Golden Purple', 17 | 'app.setting.navigationmode': 'Navigation Mode', 18 | 'app.setting.sidemenu': 'Side Menu Layout', 19 | 'app.setting.topmenu': 'Top Menu Layout', 20 | 'app.setting.fixedheader': 'Fixed Header', 21 | 'app.setting.fixedsidebar': 'Fixed Sidebar', 22 | 'app.setting.fixedsidebar.hint': 'Works on Side Menu Layout', 23 | 'app.setting.hideheader': 'Hidden Header when scrolling', 24 | 'app.setting.hideheader.hint': 'Works when Hidden Header is enabled', 25 | 'app.setting.othersettings': 'Other Settings', 26 | 'app.setting.weakmode': 'Weak Mode', 27 | 'app.setting.copy': 'Copy Setting', 28 | 'app.setting.copyinfo': 'copy success,please replace defaultSettings in src/models/setting.js', 29 | 'app.setting.production.hint': 30 | 'Setting panel shows in development environment only, please manually modify', 31 | }; 32 | -------------------------------------------------------------------------------- /web/src/pages/gallery/index.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 2/1/2021 3 | */ 4 | 5 | import { Route, Switch, HashRouter as Router, Prompt } from "react-router-dom" 6 | import { useState } from 'react' 7 | // import { Lifecycle } from 'react-router' 8 | 9 | import Gallery from "./Gallery" 10 | import Configuration from "./Configuration" 11 | import Dataset from "./Dataset" 12 | import Dashboard from "./Dashboard" 13 | import DashboardTemplate from "./DashboardTemplate" 14 | import { PrivateRoute } from '@/components/PrivateRoute' 15 | import { Modal } from "antd" 16 | export default () => { 17 | return ( 18 | // { 19 | // console.log(199) 20 | // Modal.confirm({ 21 | // title: message, 22 | // onCancel: () => { 23 | // callback(false); 24 | // }, 25 | // onOk: () => { 26 | // callback(true); 27 | // } 28 | // }); 29 | // }}> 30 | 31 | {/* // {} */} 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | // 43 | 44 | ) 45 | } 46 | 47 | -------------------------------------------------------------------------------- /web/src/components/Gallery/Dataset/DatasetController/QueryField.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 11/24/2020 3 | */ 4 | 5 | import {useState} from "react" 6 | import {Button, Input, Space} from "antd" 7 | import {CaretRightOutlined, MinusOutlined, PlusOutlined} from "@ant-design/icons" 8 | import {FormattedMessage} from "umi" 9 | 10 | import {EditorButton} from "@/components/Editor" 11 | 12 | export const QueryViewer = (props: { onClick: (value: boolean) => void }) => 13 | , close: }} 15 | name={{ 16 | open: , 17 | close: 18 | }} 19 | size="small" 20 | onChange={props.onClick} 21 | /> 22 | 23 | interface QueryFieldProps { 24 | queryVisible: boolean 25 | onExecute: (sql: string) => void 26 | } 27 | 28 | export const QueryField = (props: QueryFieldProps) => { 29 | const [sqlStr, setSqlStr] = useState() 30 | 31 | const onExecute = () => { 32 | if (sqlStr) props.onExecute(sqlStr) 33 | } 34 | 35 | return props.queryVisible ? 36 | 37 | setSqlStr(e.target.value)} 41 | /> 42 | 50 | : <> 51 | } 52 | 53 | -------------------------------------------------------------------------------- /web/src/redux/counter/Counter.module.css: -------------------------------------------------------------------------------- 1 | .row { 2 | display: flex; 3 | align-items: center; 4 | justify-content: center; 5 | } 6 | 7 | .row > button { 8 | margin-left: 4px; 9 | margin-right: 8px; 10 | } 11 | 12 | .row:not(:last-child) { 13 | margin-bottom: 16px; 14 | } 15 | 16 | .value { 17 | font-size: 78px; 18 | padding-left: 16px; 19 | padding-right: 16px; 20 | margin-top: 2px; 21 | font-family: 'Courier New', Courier, monospace; 22 | } 23 | 24 | .button { 25 | appearance: none; 26 | background: none; 27 | font-size: 32px; 28 | padding-left: 12px; 29 | padding-right: 12px; 30 | outline: none; 31 | border: 2px solid transparent; 32 | color: rgb(112, 76, 182); 33 | padding-bottom: 4px; 34 | cursor: pointer; 35 | background-color: rgba(112, 76, 182, 0.1); 36 | border-radius: 2px; 37 | transition: all 0.15s; 38 | } 39 | 40 | .textbox { 41 | font-size: 32px; 42 | padding: 2px; 43 | width: 64px; 44 | text-align: center; 45 | margin-right: 4px; 46 | } 47 | 48 | .button:hover, 49 | .button:focus { 50 | border: 2px solid rgba(112, 76, 182, 0.4); 51 | } 52 | 53 | .button:active { 54 | background-color: rgba(112, 76, 182, 0.2); 55 | } 56 | 57 | .asyncButton { 58 | composes: button; 59 | position: relative; 60 | } 61 | 62 | .asyncButton:after { 63 | content: ''; 64 | background-color: rgba(112, 76, 182, 0.15); 65 | display: block; 66 | position: absolute; 67 | width: 100%; 68 | height: 100%; 69 | left: 0; 70 | top: 0; 71 | opacity: 0; 72 | transition: width 1s linear, opacity 0.5s ease 1s; 73 | } 74 | 75 | .asyncButton:active:after { 76 | width: 0%; 77 | opacity: 1; 78 | transition: 0s; 79 | } 80 | -------------------------------------------------------------------------------- /web/src/pages/demo/Charts/mock/ls.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 2/8/2021 3 | */ 4 | 5 | export const data = [ 6 | {time: '2008', 'trick': .3, 'alpha': 59.4, 'beta': 67.8, 'trend1': 53.4, 'area1': .1, 'trend2': 13.4, 'area2': .9}, 7 | {time: '2009', 'trick': .2, 'alpha': 68.9, 'beta': 59.9, 'trend1': 52.1, 'area1': .2, 'trend2': 22.1, 'area2': .11}, 8 | {time: '2010', 'trick': .1, 'alpha': 71.1, 'beta': 79.6, 'trend1': 49.2, 'area1': .4, 'trend2': 39.2, 'area2': .22}, 9 | {time: '2011', 'trick': .2, 'alpha': 62.1, 'beta': 73.3, 'trend1': 59.6, 'area1': .2, 'trend2': 49.6, 'area2': .25}, 10 | {time: '2012', 'trick': .4, 'alpha': 60.5, 'beta': 75.6, 'trend1': 56.5, 'area1': .9, 'trend2': 26.5, 'area2': .45}, 11 | {time: '2013', 'trick': .5, 'alpha': 55.3, 'beta': 79.5, 'trend1': 58.8, 'area1': .6, 'trend2': 18.8, 'area2': .23}, 12 | {time: '2014', 'trick': .3, 'alpha': 58.1, 'beta': 82.2, 'trend1': 63.2, 'area1': .3, 'trend2': 33.2, 'area2': .54}, 13 | {time: '2015', 'trick': .7, 'alpha': 62.4, 'beta': 85.4, 'trend1': 62.5, 'area1': .2, 'trend2': 42.5, 'area2': .38}, 14 | {time: '2016', 'trick': .8, 'alpha': 61.8, 'beta': 81.2, 'trend1': 53.2, 'area1': .1, 'trend2': 23.2, 'area2': .48}, 15 | {time: '2017', 'trick': .9, 'alpha': 43.3, 'beta': 77.8, 'trend1': 58.7, 'area1': .8, 'trend2': 18.7, 'area2': .29}, 16 | {time: '2018', 'trick': .8, 'alpha': 83.1, 'beta': 73.4, 'trend1': 55.1, 'area1': .2, 'trend2': 35.1, 'area2': .12}, 17 | {time: '2019', 'trick': .6, 'alpha': 86.4, 'beta': 65.2, 'trend1': 82.5, 'area1': .5, 'trend2': 22.5, 'area2': .19}, 18 | {time: '2020', 'trick': .3, 'alpha': 72.4, 'beta': 53.9, 'trend1': 39.1, 'area1': .1, 'trend2': 19.1, 'area2': .8}, 19 | ] 20 | 21 | -------------------------------------------------------------------------------- /web/src/components/Gallery/Tag/SearchableTagsPanel.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 12/15/2020 3 | */ 4 | 5 | import {useEffect, useRef, useState} from "react" 6 | import {Button, Divider, Space, Tag, Tooltip} from "antd" 7 | 8 | import {GenericDataInput, SelectableTagsRef} from "./data" 9 | import {SelectableTags} from "./SelectableTags" 10 | import {SearchableTagsProps} from "./data" 11 | 12 | 13 | export const SearchableTagsPanel = (props: SearchableTagsProps) => { 14 | 15 | const selectableTagsRef = useRef(null) 16 | 17 | const [selectedDataNames, setSelectedDataNames] = useState([]) 18 | 19 | const clearSelected = () => { 20 | setSelectedDataNames([]) 21 | if (selectableTagsRef.current) selectableTagsRef.current.clearSelected() 22 | } 23 | 24 | useEffect(clearSelected, [props.searchable]) 25 | 26 | return props.searchable ? 27 | <> 28 | 33 | 34 | 35 | 42 | 48 | 49 | : 50 | <> 51 | { 52 | props.data.map(t => 53 | 54 | 55 | {t.name} 56 | 57 | 58 | ) 59 | } 60 | 61 | } 62 | 63 | -------------------------------------------------------------------------------- /web/src/locales/zh-CN/component.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 2/9/2021 3 | */ 4 | 5 | export default { 6 | "component.tagSelect.expand": "展开", 7 | 'component.tagSelect.collapse': "收起", 8 | "component.tagSelect.all": "全部", 9 | 10 | "component.fileUploadModal.baseModal.title": "请完成以下选项", 11 | "component.fileUploadModal.baseModal.file": "文件", 12 | "component.fileUploadModal.baseModal.upload": "上传", 13 | "component.fileUploadModal.baseModal.transpose": "转置", 14 | "component.fileUploadModal.baseModal.transposeText": "横向数据需要转置", 15 | "component.fileUploadModal.baseModal.sheetPrefix": "表单前缀", 16 | 17 | "component.fileUploadModal.fileExtractModal.fileOptions": "文件选项", 18 | "component.fileUploadModal.fileExtractModal.multiSheets": "多工作簿", 19 | "component.fileUploadModal.fileExtractModal.cellOptions": "数据单元选项", 20 | "component.fileUploadModal.fileExtractModal.rounding": "保留小数点位数", 21 | "component.fileUploadModal.fileExtractModal.dateFormat": "日期格式", 22 | 23 | "component.fileUploadModal.fileInsertModal.databaseOptions": "数据库选项", 24 | "component.fileUploadModal.fileInsertModal.tableName": "表名称", 25 | "component.fileUploadModal.fileInsertModal.insertOption": "插入选项", 26 | "component.fileUploadModal.fileInsertModal.replace": "替换", 27 | "component.fileUploadModal.fileInsertModal.append": "新增", 28 | "component.fileUploadModal.fileInsertModal.fail": "失败", 29 | 30 | "component.article.articleCreationModal.head": "请输入内容", 31 | "component.article.articleCreationModal.title": "请输入标题", 32 | "component.article.articleCreationModal.select": "请选择标签", 33 | "component.article.articleEditable.confirmDelete": "确认删除该文章?", 34 | "component.article.tagCreationModal.title": "创建一个新标签", 35 | "component.article.tagModificationModal.title": "管理标签", 36 | } 37 | -------------------------------------------------------------------------------- /web/src/utils/utils.test.ts: -------------------------------------------------------------------------------- 1 | import {isUrl} from './utils' 2 | 3 | describe('isUrl tests', (): void => { 4 | it('should return false for invalid and corner case inputs', (): void => { 5 | expect(isUrl([] as any)).toBeFalsy() 6 | expect(isUrl({} as any)).toBeFalsy() 7 | expect(isUrl(false as any)).toBeFalsy() 8 | expect(isUrl(true as any)).toBeFalsy() 9 | expect(isUrl(NaN as any)).toBeFalsy() 10 | expect(isUrl(null as any)).toBeFalsy() 11 | expect(isUrl(undefined as any)).toBeFalsy() 12 | expect(isUrl('')).toBeFalsy() 13 | }) 14 | 15 | it('should return false for invalid URLs', (): void => { 16 | expect(isUrl('foo')).toBeFalsy() 17 | expect(isUrl('bar')).toBeFalsy() 18 | expect(isUrl('bar/test')).toBeFalsy() 19 | expect(isUrl('http:/example.com/')).toBeFalsy() 20 | expect(isUrl('ttp://example.com/')).toBeFalsy() 21 | }) 22 | 23 | it('should return true for valid URLs', (): void => { 24 | expect(isUrl('http://example.com/')).toBeTruthy() 25 | expect(isUrl('https://example.com/')).toBeTruthy() 26 | expect(isUrl('http://example.com/test/123')).toBeTruthy() 27 | expect(isUrl('https://example.com/test/123')).toBeTruthy() 28 | expect(isUrl('http://example.com/test/123?foo=bar')).toBeTruthy() 29 | expect(isUrl('https://example.com/test/123?foo=bar')).toBeTruthy() 30 | expect(isUrl('http://www.example.com/')).toBeTruthy() 31 | expect(isUrl('https://www.example.com/')).toBeTruthy() 32 | expect(isUrl('http://www.example.com/test/123')).toBeTruthy() 33 | expect(isUrl('https://www.example.com/test/123')).toBeTruthy() 34 | expect(isUrl('http://www.example.com/test/123?foo=bar')).toBeTruthy() 35 | expect(isUrl('https://www.example.com/test/123?foo=bar')).toBeTruthy() 36 | }) 37 | }) 38 | -------------------------------------------------------------------------------- /web/src/components/RightContent/index.less: -------------------------------------------------------------------------------- 1 | @import '../../../node_modules/antd/es/style/themes/default.less'; 2 | 3 | @pro-header-hover-bg: rgba(0, 0, 0, 0.025); 4 | 5 | .menu { 6 | :global(.anticon) { 7 | margin-right: 8px; 8 | } 9 | 10 | :global(.ant-dropdown-menu-item) { 11 | min-width: 160px; 12 | } 13 | } 14 | 15 | .right { 16 | display: flex; 17 | float: right; 18 | height: 48px; 19 | margin-left: auto; 20 | overflow: hidden; 21 | 22 | .action { 23 | display: flex; 24 | align-items: center; 25 | height: 48px; 26 | padding: 0 12px; 27 | cursor: pointer; 28 | transition: all 0.3s; 29 | 30 | &:hover { 31 | background: @pro-header-hover-bg; 32 | } 33 | 34 | &:global(.opened) { 35 | background: @pro-header-hover-bg; 36 | } 37 | } 38 | 39 | .search { 40 | padding: 0 12px; 41 | 42 | &:hover { 43 | background: transparent; 44 | } 45 | } 46 | 47 | .account { 48 | .avatar { 49 | margin-right: 8px; 50 | color: @primary-color; 51 | vertical-align: top; 52 | background: rgba(255, 255, 255, 0.85); 53 | } 54 | } 55 | } 56 | 57 | .dark { 58 | .action { 59 | &:hover { 60 | background: #252a3d; 61 | } 62 | 63 | &:global(.opened) { 64 | background: #252a3d; 65 | } 66 | } 67 | } 68 | 69 | @media only screen and (max-width: @screen-md) { 70 | :global(.ant-divider-vertical) { 71 | vertical-align: unset; 72 | } 73 | 74 | .name { 75 | display: none; 76 | } 77 | 78 | .right { 79 | position: absolute; 80 | top: 0; 81 | right: 12px; 82 | 83 | .account { 84 | .avatar { 85 | margin-right: 0; 86 | } 87 | } 88 | 89 | .search { 90 | display: none; 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /web/src/services/inn.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 2/3/2021 3 | */ 4 | 5 | import {request} from "umi" 6 | 7 | const base = "/api/inn" 8 | 9 | // Update 10 | 11 | export const getAllUpdate = async (): Promise => 12 | request(`${base}/updates`) 13 | 14 | export const getUpdateById = async (id: string): Promise => 15 | request(`${base}/update?id=${id}`) 16 | 17 | export const saveUpdate = async (update: InnAPI.Update): Promise => 18 | request(`${base}/update`, { 19 | method: "post", 20 | data: update 21 | }) 22 | 23 | export const deleteUpdate = async (id: string): Promise => 24 | request(`${base}/update?id=${id}`, { 25 | method: "delete" 26 | }) 27 | 28 | export const getLatestUpdate = async (pagination?: [number, number]): Promise => { 29 | let path = `${base}/getLatestUpdate` 30 | if (pagination) 31 | path += `?pagination=${pagination.join(",")}` 32 | return request(path) 33 | } 34 | 35 | export const getUpdateCount = async (): Promise => 36 | request(`${base}/getUpdateCount`) 37 | 38 | 39 | // Tag 40 | 41 | export const getAllTag = async (): Promise => 42 | request(`${base}/tags`) 43 | 44 | export const getTagById = async (id: string): Promise => 45 | request(`${base}/tag?id=${id}`) 46 | 47 | export const saveTag = async (tag: InnAPI.Tag): Promise => 48 | request(`${base}/tag`, { 49 | method: "post", 50 | data: tag 51 | }) 52 | 53 | export const deleteTag = async (id: string): Promise => 54 | request(`${base}/tag?id=${id}`, { 55 | method: "delete" 56 | }) 57 | 58 | export const modifyTags = async (tags: InnAPI.Tag[]): Promise => 59 | request(`${base}/modifyTags`, { 60 | method: "post", 61 | data: tags 62 | }) 63 | 64 | -------------------------------------------------------------------------------- /web/src/components/Gallery/Misc/TextBuilder.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 9/21/2020. 3 | */ 4 | 5 | import React, {useState} from "react" 6 | import {Input, Space, Tag} from "antd" 7 | import {CheckOutlined, EditOutlined, PlusOutlined} from '@ant-design/icons' 8 | 9 | export interface TextBuilderProps { 10 | create?: boolean 11 | text?: string | React.ReactNode 12 | saveNewText: (value: string) => void 13 | } 14 | 15 | export const TextBuilder = (props: TextBuilderProps) => { 16 | 17 | const [editable, setEditable] = useState(false) 18 | const [newText, setNewText] = useState() 19 | 20 | const inputOnChange = (e: React.ChangeEvent) => 21 | setNewText(e.target.value) 22 | 23 | const saveNewText = () => { 24 | if (newText) props.saveNewText(newText) 25 | setEditable(false) 26 | } 27 | 28 | const inputDefaultValue = () => { 29 | if (props.create === false) 30 | return {defaultValue: props.text as string} 31 | return {} 32 | } 33 | 34 | const edit = () => 35 | 36 | 41 | 44 | 45 | 46 | const nonEdit = () => 47 | props.create ? 48 | } 50 | onClick={() => setEditable(true)} 51 | > 52 | {props.text} 53 | : 54 | 55 | {props.text} 56 | setEditable(true)} 58 | /> 59 | 60 | 61 | return <>{editable ? edit() : nonEdit()} 62 | 63 | } 64 | 65 | TextBuilder.defaultProps = { 66 | create: false 67 | } as Partial 68 | 69 | -------------------------------------------------------------------------------- /web/src/pages/user/login/Register.tsx: -------------------------------------------------------------------------------- 1 | import * as auth from "@/services/auth" 2 | import { Registration } from "@/components/Login" 3 | import { useState } from "react" 4 | import { Link, SelectLang } from "umi" 5 | import Footer from "@/components/Footer" 6 | import styles from './index.less' 7 | // TODO: API from services 8 | export const RegisterPage = () => { 9 | const [registerSuccess, setRegisterSuccess] = useState(false) 10 | const onRegister = async (value: API.Invitation) => { 11 | return auth.sendInvitation({ email: value.email, nickname: value.nickname, password: value.password }) 12 | .then((_: any) => setRegisterSuccess(true)) 13 | } 14 | const onRegisterFailed = (errorInfo: any) => { 15 | // console.log('Failed:', errorInfo) 16 | } 17 | 18 | const register = 21 | 22 | 23 | const registerSuccessPage =
24 | Register Succeed! Please check your email to confirm the invitation! 25 |
26 | 27 | return
28 |
{SelectLang && }
29 |
30 |
31 |
32 | 33 | Registration 34 | 35 |
36 |
@Infore
37 |
38 | 39 |
40 | 41 |
42 | {registerSuccess ? registerSuccessPage : register} 43 |
44 |
45 |
46 | 47 | } 48 | -------------------------------------------------------------------------------- /web/src/pages/Welcome.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 9/24/2020. 3 | */ 4 | 5 | import { Typography } from "antd" 6 | import { FormattedMessage, useStore, connect, useSelector } from "umi" 7 | 8 | import { GenericArticle, GenericTag } from "@/components/Article/data" 9 | import { Article } from "@/components/Article" 10 | import * as innService from "@/services/inn" 11 | 12 | //test 13 | import Test from './test' 14 | import { Provider } from 'react-redux'; 15 | import { store } from '@/redux/store'; 16 | const getArticles = (pagination?: [number, number]) => 17 | innService.getLatestUpdate(pagination) as Promise 18 | 19 | const getArticlesCount = () => 20 | innService.getUpdateCount() 21 | 22 | const getTags = () => 23 | innService.getAllTag() as Promise 24 | 25 | const modifyArticle = (v: GenericArticle) => 26 | innService.saveUpdate(v as InnAPI.Update) 27 | 28 | const deleteArticle = (v: string) => 29 | innService.deleteUpdate(v) 30 | 31 | const modifyTags = (v: GenericTag[]) => 32 | innService.modifyTags(v as InnAPI.Tag[]) 33 | 34 | export default () => { 35 | return ( 36 | 37 | 38 | {/* */} 39 | 40 | 41 | 42 | 43 |
51 | 52 | 53 | 54 | ) 55 | } 56 | -------------------------------------------------------------------------------- /web/src/pages/gallery/Dataset/index.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 11/23/2020 3 | */ 4 | 5 | 6 | import { Dataset } from "@/components/Gallery/Dataset" 7 | import { fileInsert, fileInsertUrl } from "@/components/Gallery/Misc/FileUploadConfig" 8 | import * as GalleryService from "@/services/gallery" 9 | import * as DataType from "@/components/Gallery/GalleryDataType" 10 | 11 | 12 | export default () => { 13 | 14 | const fetchStorages = () => 15 | GalleryService.getAllStorageSimple() 16 | 17 | const storageOnSelect = (id: string) => 18 | GalleryService.databaseListTable(id) 19 | 20 | const tableOnSelect = (id: string, name: string) => 21 | GalleryService.databaseGetTableColumns(id, name) 22 | 23 | const tableOnClick = (id: string, tableName: string) => 24 | GalleryService.read(id, { tableName }, DataType.StorageType.PG) 25 | 26 | const tableOnRename = (id: string, tableName: string, replacement: string) => 27 | GalleryService.databaseRenameTable(id, { tableName, replacement }) 28 | 29 | const tableOnDelete = (id: string, tableName: string) => 30 | GalleryService.databaseDropTable(id, tableName) 31 | 32 | const queryOnSelect = (id: string, value: Record) => 33 | GalleryService.read(id, value as DataType.Read, DataType.StorageType.PG) 34 | 35 | const sqlOnExecute = (id: string, sql: string) => 36 | GalleryService.executeSql(id, sql) 37 | 38 | return ( 39 | 51 | ) 52 | } 53 | 54 | -------------------------------------------------------------------------------- /web/src/components/Gallery/Dataset/QuerySelector/SelectorOrderItems.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 3/2/2021 3 | */ 4 | 5 | import { ProFormGroup, ProFormList, ProFormRadio, ProFormSelect } from "@ant-design/pro-form" 6 | import { SelectProps } from "antd" 7 | import { FormattedMessage } from "umi" 8 | 9 | 10 | const directionOptions = [ 11 | { 12 | label: , 13 | value: "asc" 14 | }, 15 | { 16 | label: , 17 | value: "desc" 18 | }, 19 | ] 20 | 21 | type OptionType = SelectProps["options"] 22 | 23 | interface SelectorOrderItemsProps { 24 | columnOptions?: OptionType 25 | } 26 | 27 | export const SelectorOrderItems = (props: SelectorOrderItemsProps) => 28 | <> 29 | } 36 | creatorRecord={{ direction: "asc" }} 37 | > 38 | 39 | } 43 | options={props.columnOptions} 44 | /> 45 | 46 | } 50 | options={directionOptions} 51 | /> 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /web/config/demoRoute.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 1/18/2021 3 | */ 4 | 5 | 6 | export const demoRoute = (env?: string) => env === "dev" ? 7 | [ 8 | { 9 | path: '/demo', 10 | name: 'demo', 11 | icon: 'ExperimentOutlined', 12 | wrappers: ['@/wrappers/test'], 13 | component: "./demo", 14 | layout: { 15 | hideFooter: true 16 | }, 17 | routes: [ 18 | { 19 | name: 'local-storage', 20 | path: '/demo/local-storage', 21 | component: './demo/LocalStorage', 22 | 23 | }, 24 | { 25 | name: 'rectangle-chart', 26 | path: '/demo/rectangle-chart', 27 | component: './demo/RectangleChart', 28 | }, 29 | { 30 | name: 'charts', 31 | path: '/demo/charts', 32 | component: './demo/Charts', 33 | }, 34 | { 35 | name: 'grid-layout', 36 | path: '/demo/grid-layout', 37 | component: './demo/GridLayout', 38 | }, 39 | { 40 | name: 'redirect-test', 41 | path: '/demo/redirect-test', 42 | component: './demo/RedirectTest', 43 | }, 44 | { 45 | name: 'module-test', 46 | path: '/demo/module-test', 47 | component: './demo/GalleryModuleTest', 48 | }, 49 | { 50 | name: 'component-test', 51 | path: '/demo/component-test', 52 | component: './demo/ComponentTest', 53 | }, 54 | { 55 | name: 'consensus-distribution-chart', 56 | path: '/demo/consensus-distribution-chart', 57 | component: './demo/ConsensusDistributionChart', 58 | }, 59 | { 60 | name: 'file-manager-test', 61 | path: '/demo/file-manager-test', 62 | component: './demo/FileManagerTest', 63 | }, 64 | ], 65 | }, 66 | ] : [] 67 | -------------------------------------------------------------------------------- /web/src/components/Article/data.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 2/5/2021 3 | */ 4 | 5 | import React from "react" 6 | 7 | 8 | export interface GenericTag { 9 | id?: string 10 | name: string 11 | description?: string 12 | color?: string 13 | } 14 | 15 | export interface GenericArticle { 16 | id: string 17 | date: string 18 | title: string 19 | data: string 20 | tags: GenericTag[] 21 | } 22 | 23 | export interface CreationTriggerActions { 24 | onClick: () => void 25 | } 26 | 27 | export interface ArticleCreationModalProps { 28 | trigger: React.FC 29 | tags?: GenericTag[] 30 | initialValue?: GenericArticle 31 | onSubmit: (value: GenericArticle) => void 32 | modalWidth?: string | number 33 | modalHeight?: string | number 34 | } 35 | 36 | export interface TagCreationModalProps { 37 | trigger: React.FC 38 | onSubmit: (value: GenericTag) => void 39 | modalWidth?: string | number 40 | modalHeight?: string | number 41 | } 42 | 43 | export interface TagModificationModalProps { 44 | trigger: React.FC 45 | tags: GenericTag[] 46 | onSubmit: (v: any) => void 47 | modalWidth?: string | number 48 | modalHeight?: string | number 49 | } 50 | 51 | interface ArticleToolbarProps { 52 | tags: GenericTag[] 53 | editable: boolean 54 | onEdit: (v: boolean) => void 55 | articleCreationOnSubmit: (v: any) => void 56 | tagModificationModal: (v: any) => void 57 | } 58 | 59 | export interface ArticleProps { 60 | getArticles: (pagination?: [number, number]) => Promise 61 | getArticlesCount: () => Promise 62 | getTags: () => Promise 63 | modifyArticle: (value: GenericArticle) => Promise 64 | deleteArticle: (id: string) => Promise 65 | modifyTags: (value: GenericTag[]) => Promise 66 | defaultPageSize?: number 67 | title?: React.ReactNode 68 | } 69 | 70 | -------------------------------------------------------------------------------- /web/src/components/Gallery/Dataset/QuerySelector/QuerySelectorForm.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 11/26/2020 3 | */ 4 | 5 | import React, { useRef } from "react" 6 | import ProForm from "@ant-design/pro-form" 7 | import { Form } from 'antd' 8 | import { SelectorAssembledItems, SelectorAssembledItemsRef } from "./SelectorAssembledItems" 9 | import * as DataType from "@/components/Gallery/GalleryDataType" 10 | 11 | export interface QuerySelectorFormProps { 12 | initialValues?: Record 13 | storagesOnFetch: () => Promise 14 | storageOnSelect: (id: string) => Promise 15 | tableOnSelect: (id: string, name: string) => Promise 16 | onSubmit: (value: Record) => Promise | any 17 | style?: React.CSSProperties 18 | columnsRequired?: boolean 19 | } 20 | 21 | export const QuerySelectorForm = (props: QuerySelectorFormProps) => { 22 | const ref = useRef(null) 23 | const [formRef] = Form.useForm() 24 | const onValuesChange = (value: Record) => { 25 | if (ref.current) ref.current.onValuesChange(value) 26 | } 27 | 28 | return ( 29 | 37 | 46 | 47 | ) 48 | } 49 | 50 | -------------------------------------------------------------------------------- /web/src/e2e/baseLayout.e2e.js: -------------------------------------------------------------------------------- 1 | const {uniq} = require('lodash'); 2 | const RouterConfig = require('../../config/config').default.routes; 3 | 4 | const BASE_URL = `http://localhost:${process.env.PORT || 8000}`; 5 | 6 | function formatter(routes, parentPath = '') { 7 | const fixedParentPath = parentPath.replace(/\/{1,}/g, '/'); 8 | let result = []; 9 | routes.forEach((item) => { 10 | if (item.path) { 11 | result.push(`${fixedParentPath}/${item.path}`.replace(/\/{1,}/g, '/')); 12 | } 13 | if (item.routes) { 14 | result = result.concat( 15 | formatter(item.routes, item.path ? `${fixedParentPath}/${item.path}` : parentPath), 16 | ); 17 | } 18 | }); 19 | return uniq(result.filter((item) => !!item)); 20 | } 21 | 22 | beforeEach(async () => { 23 | await page.goto(`${BASE_URL}`); 24 | await page.evaluate(() => { 25 | localStorage.setItem('antd-pro-authority', '["admin"]'); 26 | }); 27 | }); 28 | 29 | describe('Ant Design Pro E2E test', () => { 30 | const testPage = (path) => async () => { 31 | await page.goto(`${BASE_URL}${path}`); 32 | await page.waitForSelector('footer', { 33 | timeout: 2000, 34 | }); 35 | const haveFooter = await page.evaluate( 36 | () => document.getElementsByTagName('footer').length > 0, 37 | ); 38 | expect(haveFooter).toBeTruthy(); 39 | }; 40 | 41 | const routers = formatter(RouterConfig); 42 | routers.forEach((route) => { 43 | it(`test pages ${route}`, testPage(route)); 44 | }); 45 | 46 | it('topmenu should have footer', async () => { 47 | const params = '?navTheme=light&layout=topmenu'; 48 | await page.goto(`${BASE_URL}${params}`); 49 | await page.waitForSelector('footer', { 50 | timeout: 2000, 51 | }); 52 | const haveFooter = await page.evaluate( 53 | () => document.getElementsByTagName('footer').length > 0, 54 | ); 55 | expect(haveFooter).toBeTruthy(); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /web/src/components/Gallery/ModulePanel/Collections/multiMedia/Text.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 10/3/2020. 3 | */ 4 | 5 | import { useState } from "react" 6 | 7 | import { TextEditorModifier, TextEditorPresenter } from "@/components/TextEditor" 8 | import { ModuleGenerator } from "../../Generator/ModuleGenerator" 9 | import { ModuleEditorField, ModulePresenterField } from "../../Generator/data" 10 | import * as DataType from "../../../GalleryDataType" 11 | import _ from "lodash"; 12 | 13 | const EditorField = (props: ModuleEditorField) => { 14 | 15 | const onChangeContent = (value: string) => { 16 | // console.log(content) 17 | const ctt = //only update data.text property 18 | { ...props.content, data: { ...props.content?.data, text: value }, date: props.content?.date || DataType.today() } 19 | const ctWithDb = { ...ctt, storageType: DataType.StorageType.MONGO } 20 | console.log(22, ctWithDb) 21 | if (props.setContent) { 22 | props.setContent(() => ctWithDb) 23 | } 24 | } 25 | 26 | console.log(277, props.styling) 27 | return ( 28 |
29 | 37 |
38 | ) 39 | } 40 | 41 | const PresenterField = (props: ModulePresenterField) => { 42 | console.log(42, props.content) 43 | return
44 | 47 |
48 | } 49 | 50 | 51 | export const Text = new ModuleGenerator(EditorField, PresenterField, true).generate() 52 | 53 | -------------------------------------------------------------------------------- /web/src/pages/demo/Charts/index.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 10/16/2020. 3 | */ 4 | 5 | import {useState, useRef, useLayoutEffect} from "react" 6 | import {Select, Tabs} from "antd" 7 | 8 | import {themeSelections} from "@/components/EchartsPro/themeSelections" 9 | import {LineScatter} from "./LineScatter" 10 | import {Candlestick} from "./Candlestick" 11 | import {Treemap} from "./Treemap" 12 | import {Pie} from "./Pie" 13 | 14 | 15 | export default () => { 16 | const chartRef = useRef(null) 17 | 18 | const [chartHeight, setChartHeight] = useState(0) 19 | const [theme, setTheme] = useState("default") 20 | 21 | useLayoutEffect(() => { 22 | if (chartRef.current) setChartHeight(chartRef.current.offsetHeight) 23 | }) 24 | 25 | return ( 26 |
27 | setTheme(t)} 31 | style={{width: 250}} 32 | > 33 | { 34 | themeSelections.map(t => 35 | {t.ele} 36 | ) 37 | } 38 | 39 | }} 40 | style={{height: "100%"}} 41 | defaultActiveKey={"4"} 42 | > 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 |
57 | ) 58 | } 59 | 60 | -------------------------------------------------------------------------------- /web/src/locales/en-US/component.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 2/9/2021 3 | */ 4 | 5 | export default { 6 | "component.tagSelect.expand": "Expand", 7 | "component.tagSelect.collapse": "Collapse", 8 | "component.tagSelect.all": "All", 9 | 10 | "component.fileUploadModal.baseModal.title": "Please complete options below", 11 | "component.fileUploadModal.baseModal.file": "File", 12 | "component.fileUploadModal.baseModal.upload": "Upload", 13 | "component.fileUploadModal.baseModal.transpose": "Transpose", 14 | "component.fileUploadModal.baseModal.transposeText": "Horizontal displayed data needs transpose", 15 | "component.fileUploadModal.baseModal.sheetPrefix": "Sheet prefix", 16 | 17 | "component.fileUploadModal.fileExtractModal.fileOptions": "File options", 18 | "component.fileUploadModal.fileExtractModal.multiSheets": "Multiple sheets", 19 | "component.fileUploadModal.fileExtractModal.cellOptions": "Cell options", 20 | "component.fileUploadModal.fileExtractModal.rounding": "Rounding", 21 | "component.fileUploadModal.fileExtractModal.dateFormat": "Date format", 22 | 23 | "component.fileUploadModal.fileInsertModal.databaseOptions": "Database options", 24 | "component.fileUploadModal.fileInsertModal.tableName": "Table name", 25 | "component.fileUploadModal.fileInsertModal.insertOption": "Insert option", 26 | "component.fileUploadModal.fileInsertModal.replace": "Replace", 27 | "component.fileUploadModal.fileInsertModal.append": "Append", 28 | "component.fileUploadModal.fileInsertModal.fail": "Fail", 29 | 30 | "component.article.articleCreationModal.head": "Please complete the content", 31 | "component.article.articleCreationModal.title": "Please select tags", 32 | "component.article.articleCreationModal.select": "Please select tags", 33 | "component.article.articleEditable.confirmDelete": "Are you sure to delete this article?", 34 | "component.article.tagCreationModal.title": "Create a new tag", 35 | "component.article.tagModificationModal.title": "Manage tags", 36 | } 37 | -------------------------------------------------------------------------------- /web/src/redux/counter/Counter.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | 3 | import { useAppSelector, useAppDispatch } from '../hooks'; 4 | import { 5 | decrement, 6 | increment, 7 | incrementByAmount, 8 | incrementAsync, 9 | incrementIfOdd, 10 | selectCount, 11 | } from './counterSlice'; 12 | import styles from './Counter.module.css'; 13 | 14 | export function Counter() { 15 | const count = useAppSelector(selectCount); 16 | const dispatch = useAppDispatch(); 17 | const [incrementAmount, setIncrementAmount] = useState('2'); 18 | 19 | const incrementValue = Number(incrementAmount) || 0; 20 | 21 | return ( 22 |
23 | {/*
24 | 31 | {count} 32 | 39 |
40 |
41 | setIncrementAmount(e.target.value)} 46 | /> 47 | 53 | 54 |
*/} 55 |
56 | ); 57 | } 58 | -------------------------------------------------------------------------------- /web/src/components/Gallery/ModulePanel/Collections/miscellaneous/EmbedLink.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 9/22/2020. 3 | */ 4 | 5 | import { useState } from "react" 6 | import { Button } from "antd" 7 | import { useIntl } from "umi" 8 | 9 | import { ModuleGenerator } from "../../Generator/ModuleGenerator" 10 | import { ModuleEditorField, ModulePresenterField } from "../../Generator/data" 11 | import * as DataType from "../../../GalleryDataType" 12 | import { ModalForm, ProFormText } from "@ant-design/pro-form" 13 | 14 | 15 | const EditorField = (props: ModuleEditorField) => { 16 | const intl = useIntl() 17 | const [content, setContent] = useState(props.content) 18 | 19 | const onSubmit = async (values: Record) => { 20 | const ctt = { 21 | ...content, 22 | date: content?.date || DataType.today(), 23 | data: values 24 | } 25 | setContent(ctt) 26 | if (props.setContent) { 27 | 28 | props.setContent(ctt) 29 | } 30 | 31 | 32 | return true 33 | } 34 | 35 | return ( 36 |
37 | {intl.formatMessage({ id: "gallery.component.general13" })}} 40 | initialValues={{ link: content?.data?.link }} 41 | onFinish={onSubmit} 42 | > 43 | 44 | 45 |
46 | ) 47 | } 48 | 49 | const PresenterField = (props: ModulePresenterField) => 50 | props.content ? 51 | : 52 | 53 | export const EmbedLink = new ModuleGenerator(EditorField, PresenterField).generate() 54 | 55 | -------------------------------------------------------------------------------- /web/src/components/Gallery/Utils/data.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 12/4/2020 3 | */ 4 | 5 | import { EChartOption } from "echarts" 6 | 7 | /** 8 | * Generic data type for table & chart 9 | */ 10 | 11 | export type ViewStyle = "default" | "xlsx" 12 | 13 | export type ColumnTypeOptions = "default" | "date" | "number" | "percent" | "bar" 14 | 15 | export type DataSelectedType = "dataset" | "file" 16 | 17 | export interface DisplayType { 18 | column: string 19 | type: ColumnTypeOptions 20 | } 21 | 22 | export interface GeneralTableConfigInterface { 23 | type: DataSelectedType 24 | display: DisplayType[] 25 | style: ViewStyle 26 | view: string[] 27 | } 28 | 29 | export interface XlsxTableConfigInterface { 30 | hideOptions: string[] 31 | } 32 | 33 | export interface XAxis { 34 | name?: string 35 | column: string 36 | type: "value" | "category" | "time" | "log" 37 | } 38 | 39 | export interface YAxis { 40 | name?: string 41 | position: "left" | "right" | undefined 42 | columns: string[] 43 | } 44 | 45 | export interface Scatter { 46 | column: string 47 | size?: string 48 | min?: number 49 | max?: number 50 | } 51 | 52 | export interface CartesianCoordSysChartConfig { 53 | bar?: string[] 54 | scatter?: Scatter[] 55 | x: XAxis 56 | y: YAxis[] 57 | display?: DisplayType[] 58 | styling?: string 59 | } 60 | 61 | export interface ChartData { 62 | id: string 63 | selects: string[] 64 | tableName: string 65 | date: string 66 | } 67 | 68 | export interface Chart { 69 | config: CartesianCoordSysChartConfig, 70 | data: ChartData 71 | } 72 | 73 | export interface SeriesPieChartConfig { 74 | select: string 75 | seriesDir: "vertical" | "horizontal" 76 | display?: DisplayType[] 77 | styling?: string 78 | } 79 | 80 | export type UnionChartConfig = CartesianCoordSysChartConfig | SeriesPieChartConfig 81 | 82 | export type ChartOptionGenerator = (data: any[], config: UnionChartConfig) => EChartOption 83 | 84 | export type Mixin = "line" | "bar" | "scatter" | "lineBar" | "lineScatter" | "pie" | undefined 85 | 86 | -------------------------------------------------------------------------------- /web/src/components/Gallery/Misc/TagBuildModal.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 9/21/2020. 3 | */ 4 | 5 | import React, { useState } from "react" 6 | import { Space, Tag } from "antd" 7 | import { EditOutlined, PlusOutlined } from '@ant-design/icons' 8 | import { CreationModal, CreationModalValue } from "./CreationModal" 9 | 10 | export interface TextBuilderProps { 11 | create?: boolean 12 | text?: string | React.ReactNode 13 | name: string 14 | title: string | React.ReactNode 15 | onSubmit: (name: string, type: string, description?: string) => void 16 | colorSelector: boolean 17 | categoryTypeSelector: string[] 18 | } 19 | 20 | const DEFAULT_TYPE = "dashboard" 21 | 22 | export const TagBuildModal = (props: TextBuilderProps) => { 23 | 24 | const [modalVisible, setModalVisible] = useState(false) 25 | 26 | const onSubmit = (value: CreationModalValue) => { 27 | props.onSubmit(value.name, value.type || DEFAULT_TYPE, value.description) 28 | setModalVisible(false) 29 | } 30 | 31 | 32 | const nonEdit = () => 33 | props.create ? 34 | } 36 | onClick={() => setModalVisible(true)} 37 | > 38 | {props.title} 39 | : 40 | 41 | {props.title} 42 | setModalVisible(true)} 44 | /> 45 | 46 | 47 | return <> 48 | {nonEdit()} 49 | setModalVisible(false)} 55 | colorSelector={props.colorSelector} 56 | isTypeSelector={true} 57 | typeSelector={props.categoryTypeSelector} 58 | /> 59 | 60 | 61 | } 62 | 63 | TagBuildModal.defaultProps = { 64 | create: false, 65 | colorSelector: false 66 | } as Partial 67 | 68 | -------------------------------------------------------------------------------- /web/src/pages/user/login/Invitation.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from "antd" 2 | import * as auth from "@/services/auth" 3 | import { Redirect, useLocation } from "react-router-dom" 4 | import { useState } from "react" 5 | import { Link, SelectLang } from "umi" 6 | import Footer from "@/components/Footer" 7 | import styles from './index.less' 8 | export const Invitation = () => { 9 | const search = useLocation().search 10 | const id = new URLSearchParams(search).get('id') 11 | const email = new URLSearchParams(search).get('email') 12 | 13 | const [succeeded, setSucceeded] = useState(false) 14 | 15 | const onRegister = async () => { 16 | if (id && email) 17 | return auth.register(id).then(_ => setSucceeded(true)) 18 | } 19 | 20 | const invited = id && email ? 21 | <> 22 |

Welcome to join Cyberbrick!

23 |

Your email is {email}

24 | 25 | : 26 | <> 27 | Your invitation is invalid! Please register again 28 | 29 | 30 | return ( 31 | succeeded ? : 32 |
33 |
{SelectLang && }
34 |
35 |
36 |
37 | 38 | Registration 39 | 40 |
41 |
@Infore
42 |
43 | 44 |
45 |
46 | {invited} 47 |
48 |
49 |
50 |
51 | ) 52 | } 53 | -------------------------------------------------------------------------------- /web/src/components/Gallery/Overview/OverviewController/Controller.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 10/12/2020. 3 | */ 4 | 5 | import {useState} from "react" 6 | import {Select, Space} from "antd" 7 | import {SaveTwoTone, SettingTwoTone} from '@ant-design/icons' 8 | 9 | import {Editor} from "@/components/Editor" 10 | import {SpaceBetween} from "@/components/SpaceBetween" 11 | import * as DataType from "../../GalleryDataType" 12 | 13 | 14 | export interface ControllerProps { 15 | categoryNames: string[] 16 | categoryOnSelect: (categoryName: string) => Promise 17 | markOnSelect: (name: string) => void 18 | onEdit: (value: boolean) => void 19 | } 20 | 21 | export const Controller = (props: ControllerProps) => { 22 | 23 | const [marks, setMarks] = useState([]) 24 | 25 | const categoryOnSelect = (value: string) => 26 | props.categoryOnSelect(value).then(res => { 27 | if (res) setMarks(res) 28 | }) 29 | 30 | return ( 31 | 32 | 33 | 45 | 58 | 59 | 60 |
61 | , 64 | close: 65 | }} 66 | onChange={props.onEdit} 67 | /> 68 |
69 |
70 | ) 71 | } 72 | 73 | -------------------------------------------------------------------------------- /web/src/service-worker.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable eslint-comments/disable-enable-pair */ 2 | /* eslint-disable no-restricted-globals */ 3 | /* eslint-disable no-underscore-dangle */ 4 | /* globals workbox */ 5 | workbox.core.setCacheNameDetails({ 6 | prefix: 'antd-pro', 7 | suffix: 'v1', 8 | }); 9 | // Control all opened tabs ASAP 10 | workbox.clientsClaim(); 11 | 12 | /** 13 | * Use precaching list generated by workbox in build process. 14 | * https://developers.google.com/web/tools/workbox/reference-docs/latest/workbox.precaching 15 | */ 16 | workbox.precaching.precacheAndRoute(self.__precacheManifest || []); 17 | 18 | /** 19 | * Register a navigation route. 20 | * https://developers.google.com/web/tools/workbox/modules/workbox-routing#how_to_register_a_navigation_route 21 | */ 22 | workbox.routing.registerNavigationRoute('/index.html'); 23 | 24 | /** 25 | * Use runtime cache: 26 | * https://developers.google.com/web/tools/workbox/reference-docs/latest/workbox.routing#.registerRoute 27 | * 28 | * Workbox provides all common caching strategies including CacheFirst, NetworkFirst etc. 29 | * https://developers.google.com/web/tools/workbox/reference-docs/latest/workbox.strategies 30 | */ 31 | 32 | /** 33 | * Handle API requests 34 | */ 35 | workbox.routing.registerRoute(/\/api\//, workbox.strategies.networkFirst()); 36 | 37 | /** 38 | * Handle third party requests 39 | */ 40 | workbox.routing.registerRoute( 41 | /^https:\/\/gw\.alipayobjects\.com\//, 42 | workbox.strategies.networkFirst(), 43 | ); 44 | workbox.routing.registerRoute( 45 | /^https:\/\/cdnjs\.cloudflare\.com\//, 46 | workbox.strategies.networkFirst(), 47 | ); 48 | workbox.routing.registerRoute(/\/color.less/, workbox.strategies.networkFirst()); 49 | 50 | /** 51 | * Response to client after skipping waiting with MessageChannel 52 | */ 53 | addEventListener('message', (event) => { 54 | const replyPort = event.ports[0]; 55 | const message = event.data; 56 | if (replyPort && message && message.type === 'skip-waiting') { 57 | event.waitUntil( 58 | self.skipWaiting().then( 59 | () => 60 | replyPort.postMessage({ 61 | error: null, 62 | }), 63 | (error) => 64 | replyPort.postMessage({ 65 | error, 66 | }), 67 | ), 68 | ); 69 | } 70 | }); 71 | -------------------------------------------------------------------------------- /web/config/config.ts: -------------------------------------------------------------------------------- 1 | // https://umijs.org/config/ 2 | import path from "path" 3 | import fs from "fs" 4 | import {defineConfig} from 'umi' 5 | 6 | import defaultSettings from './defaultSettings' 7 | import {mainRoutes, errorRoutes} from "./routes" 8 | import {demoRoute} from "./demoRoute" 9 | 10 | const {REACT_APP_ENV} = process.env 11 | 12 | const readProxy = () => { 13 | if (REACT_APP_ENV === "dev") { 14 | let p = path.resolve(__dirname, './proxy.json') 15 | let f = fs.readFileSync(p, 'utf8') 16 | return JSON.parse(f) 17 | } else { 18 | return {} 19 | } 20 | } 21 | 22 | 23 | export default defineConfig({ 24 | // links: [{ rel: 'icon', href: '/favicon.ico' }], 25 | outputPath: "frontend", 26 | // history: { type: "hash" }, 27 | hash: true, 28 | antd: {}, 29 | dva: { 30 | hmr: true, 31 | immer: true, 32 | }, 33 | lessLoader: { 34 | options: { 35 | javascriptEnabled: true 36 | } 37 | }, 38 | layout: { 39 | name: 'CyberBrick', 40 | locale: true, 41 | // siderWidth: 150, 42 | logo: '/api/homeLogo', 43 | ...defaultSettings 44 | }, 45 | locale: { 46 | default: 'zh-CN', 47 | // default true, when it is true, will use `navigator.language` overwrite default 48 | antd: true, 49 | baseNavigator: true, 50 | }, 51 | dynamicImport: { 52 | loading: '@ant-design/pro-layout/es/PageLoading', 53 | }, 54 | targets: { 55 | ie: 11, 56 | }, 57 | // umi routes: https://umijs.org/docs/routing 58 | routes: [ 59 | ...mainRoutes, 60 | ...demoRoute(REACT_APP_ENV), 61 | ...errorRoutes 62 | ], 63 | // Theme for antd: https://ant.design/docs/react/customize-theme-cn 64 | theme: { 65 | 'primary-color': '#5fa8d3', 66 | 'info-color': '#5fa8d3', 67 | 'link-color': '#5fa8d3', 68 | 'success-color': '#74b384', 69 | 'warning-color': '#e4a554', 70 | 'error-color': '#f55a4e', 71 | 'heading-color': '#212121', 72 | }, 73 | title: false, 74 | ignoreMomentLocale: true, 75 | proxy: readProxy(), 76 | manifest: { 77 | basePath: '/', 78 | }, 79 | nodeModulesTransform: { 80 | type: 'none', 81 | exclude: [] 82 | }, 83 | // favicon: './static/favicon.ico' 84 | }) 85 | -------------------------------------------------------------------------------- /web/src/components/Login/Login.tsx: -------------------------------------------------------------------------------- 1 | import { Form, Input, Button } from 'antd' 2 | import { UserOutlined, LockOutlined } from '@ant-design/icons' 3 | 4 | import './Login.less' 5 | // import { useIntl } from 'umi' 6 | 7 | 8 | // TODO: 9 | export interface LoginProps { 10 | onFinish: (value: any) => Promise 11 | onFinishFailed: (errorInfo: any) => void 12 | forgetPasswordHref: string 13 | registrationHref: string 14 | } 15 | 16 | export const Login = (props: LoginProps) => { 17 | // const intl = useIntl() 18 | return ( 19 |
26 | 31 | } placeholder="E-mail" /> 32 | 33 | 34 | 39 | } 41 | type="password" 42 | placeholder="Password" 43 | /> 44 | 45 | 46 | {/* 49 | 50 | Remember me 51 | 52 | 53 | 54 | Forgot password 55 | 56 | */} 57 | 58 | 61 | 64 | Or register now! 65 | 66 |
67 | ) 68 | } 69 | -------------------------------------------------------------------------------- /web/src/components/Gallery/ModulePanel/Panel/ControllerButtons.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 10/15/2020. 3 | */ 4 | 5 | import { Button, Tooltip } from "antd" 6 | 7 | import { Emoji } from "@/components/Emoji" 8 | import { Editor } from "@/components/Editor" 9 | 10 | 11 | export const DragButton = () => 12 | 13 | 21 | 22 | 23 | export const TimeSetButton = (props: { show: boolean | undefined, onClick: () => void }) => 24 | props.show ? 25 | 26 | 34 | : <> 35 | 36 | export const EditButton = (props) => { 37 | console.log(3737, props.setContent) 38 | return ( 39 | 40 | 50 | 51 | ) 52 | } 53 | 54 | export const DeleteButton = (props: { confirmDelete: () => void }) => 55 | 56 | 64 | 65 | 66 | export const TimePickButton = (props: { onClick: () => void }) => { 67 | return 68 | 76 | 77 | } 78 | -------------------------------------------------------------------------------- /web/src/components/RightContent/index.tsx: -------------------------------------------------------------------------------- 1 | import {Tooltip, Tag, Space} from 'antd' 2 | import {QuestionCircleOutlined} from '@ant-design/icons' 3 | import React from 'react' 4 | import {useModel, SelectLang} from 'umi' 5 | import Avatar from './AvatarDropdown' 6 | import HeaderSearch from '../HeaderSearch' 7 | import styles from './index.less' 8 | 9 | export type SiderTheme = 'light' | 'dark'; 10 | 11 | const ENVTagColor = { 12 | dev: 'orange', 13 | test: 'green', 14 | pre: '#87d068', 15 | } 16 | 17 | const GlobalHeaderRight = () => { 18 | const {initialState} = useModel('@@initialState') 19 | 20 | if (!initialState || !initialState.settings) { 21 | return null 22 | } 23 | 24 | const {navTheme, layout} = initialState.settings 25 | let className = styles.right 26 | 27 | if ((navTheme === 'dark' && layout === 'top') || layout === 'mix') { 28 | className = `${styles.right} ${styles.dark}` 29 | } 30 | return ( 31 | 32 | umi ui, value: 'umi ui'}, 38 | { 39 | label: Ant Design, 40 | value: 'Ant Design', 41 | }, 42 | { 43 | label: Pro Table, 44 | value: 'Pro Table', 45 | }, 46 | { 47 | label: Pro Layout, 48 | value: 'Pro Layout', 49 | }, 50 | ]} 51 | // onSearch={value => { 52 | // // search handler 53 | // }} 54 | /> 55 | 56 | { 59 | window.location.href = 'https://pro.ant.design/docs/getting-started' 60 | }} 61 | > 62 | 63 | 64 | 65 | 66 | {REACT_APP_ENV && ( 67 | 68 | {REACT_APP_ENV} 69 | 70 | )} 71 | 72 | 73 | ) 74 | } 75 | export default GlobalHeaderRight 76 | -------------------------------------------------------------------------------- /web/src/pages/demo/LocalStorage/index.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 8/12/2020. 3 | */ 4 | 5 | import {useState} from "react" 6 | import {Button, Input, Space} from "antd" 7 | import {PageHeaderWrapper} from '@ant-design/pro-layout' 8 | 9 | import {ExpiryType, LocalStorageHelper} from "@/utils/localStorageHelper" 10 | 11 | 12 | const lsIdentifier = "ls-test" 13 | const expiry = [1, "minute"] as ExpiryType 14 | const ls = new LocalStorageHelper(lsIdentifier, {expiry}) 15 | const lsKey = "name" 16 | 17 | export default () => { 18 | 19 | const [inputValue, setInputValue] = useState("") 20 | 21 | const clearLs = () => ls.clear() 22 | 23 | const inputOnChange = (value: any) => 24 | setInputValue(value.target.value) 25 | 26 | const submitLS = () => 27 | ls.add(lsKey, inputValue) 28 | 29 | const dataDisplay = () => { 30 | const e = ls.get(lsKey) 31 | if (e !== null) 32 | return `Local storage data: ${e.data}` 33 | return "No data set" 34 | } 35 | 36 | const expiryDisplay = () => { 37 | const e = ls.get(lsKey) 38 | if (e?.expiry !== undefined) 39 | return `Local storage expiry: ${e.expiry}` 40 | return "No expiry set" 41 | } 42 | 43 | 44 | return ( 45 | 46 | 47 |

identifier: {lsIdentifier}

48 |
49 | 54 | 61 | 68 |
69 |
70 |

{dataDisplay()}

71 |

{expiryDisplay()}

72 |

ls.getAllItemsByIdentifier():

73 |
74 |             {JSON.stringify(ls.getAllItemsByIdentifier(), null, 2)}
75 |           
76 |

ls.getAllValuesByIdentifier():

77 |
78 |             {JSON.stringify(ls.getAllValuesByIdentifier(), null, 2)}
79 |           
80 |
81 |
82 |
83 | ) 84 | } 85 | 86 | -------------------------------------------------------------------------------- /web/src/global.less: -------------------------------------------------------------------------------- 1 | @import '~antd/es/style/themes/default.less'; 2 | @import '~react-grid-layout/css/styles.css'; 3 | @import '~react-resizable/css/styles.css'; 4 | 5 | html { 6 | // html scroll bar redesign 7 | ::-webkit-scrollbar { 8 | width: 8px; 9 | height: 8px; 10 | } 11 | 12 | ::-webkit-scrollbar-track { 13 | background: #f1f1f1; 14 | } 15 | 16 | ::-webkit-scrollbar-thumb { 17 | background: #5e6472; 18 | } 19 | 20 | ::-webkit-scrollbar-thumb:hover { 21 | background: #555; 22 | } 23 | } 24 | 25 | html, 26 | body, 27 | #root { 28 | height: 100%; 29 | } 30 | 31 | .colorWeak { 32 | filter: invert(80%); 33 | } 34 | 35 | .ant-layout { 36 | min-height: 100vh; 37 | } 38 | 39 | canvas { 40 | display: block; 41 | } 42 | 43 | body { 44 | text-rendering: optimizeLegibility; 45 | -webkit-font-smoothing: antialiased; 46 | -moz-osx-font-smoothing: grayscale; 47 | } 48 | 49 | ul, 50 | ol { 51 | list-style: none; 52 | } 53 | 54 | @media (max-width: @screen-xs) { 55 | .ant-table { 56 | width: 100%; 57 | overflow-x: auto; 58 | 59 | &-thead > tr, 60 | &-tbody > tr { 61 | > th, 62 | > td { 63 | white-space: pre; 64 | 65 | > span { 66 | display: block; 67 | } 68 | } 69 | } 70 | } 71 | } 72 | 73 | // 兼容IE11 74 | @media screen and(-ms-high-contrast: active), (-ms-high-contrast: none) { 75 | body .ant-design-pro > .ant-layout { 76 | min-height: 100vh; 77 | } 78 | } 79 | 80 | // custom 81 | body, 82 | #root { 83 | .ant-pro-sider-menu-sider.light .ant-pro-sider-menu-logo h1 { 84 | margin-left: 0; 85 | } 86 | 87 | .ant-pro-page-container-children-content { 88 | height: 80vh; 89 | } 90 | 91 | .ant-breadcrumb + .ant-page-header-heading { 92 | margin-top: 0; 93 | } 94 | 95 | .ant-page-header.has-breadcrumb { 96 | padding-top: 5px; 97 | } 98 | 99 | .ant-page-header { 100 | padding-bottom: 5px; 101 | } 102 | 103 | .ant-layout-content { 104 | margin-inline: 3%; 105 | min-width: 768px; 106 | } 107 | 108 | .ant-pro-global-footer { 109 | margin-top: 5px; 110 | margin-bottom: 5px; 111 | } 112 | 113 | .ant-pro-page-header-wrap-children-content { 114 | margin: 12px; 115 | } 116 | 117 | .ant-layout-header { 118 | background-color: #5e6472; 119 | } 120 | 121 | } 122 | -------------------------------------------------------------------------------- /web/src/pages/user/login/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/es/style/themes/default.less'; 2 | 3 | .container { 4 | display: flex; 5 | flex-direction: column; 6 | height: 100vh; 7 | overflow: auto; 8 | background: @layout-body-background; 9 | } 10 | 11 | .lang { 12 | width: 100%; 13 | height: 40px; 14 | line-height: 44px; 15 | text-align: right; 16 | :global(.ant-dropdown-trigger) { 17 | margin-right: 24px; 18 | } 19 | } 20 | 21 | .content { 22 | flex: 1; 23 | padding: 32px 0; 24 | } 25 | 26 | @media (min-width: @screen-md-min) { 27 | .container { 28 | background-image: url('https://gw.alipayobjects.com/zos/rmsportal/TVYTbAXWheQpRcWDaDMu.svg'); 29 | background-repeat: no-repeat; 30 | background-position: center 110px; 31 | background-size: 100%; 32 | } 33 | 34 | .content { 35 | padding: 32px 0 24px; 36 | } 37 | } 38 | 39 | .top { 40 | text-align: center; 41 | } 42 | 43 | .info { 44 | .top; 45 | font-size:large; 46 | } 47 | 48 | .header { 49 | height: 44px; 50 | line-height: 44px; 51 | a { 52 | text-decoration: none; 53 | } 54 | } 55 | 56 | .logo { 57 | height: 44px; 58 | margin-right: 16px; 59 | vertical-align: top; 60 | } 61 | 62 | .title { 63 | position: relative; 64 | top: 2px; 65 | color: @heading-color; 66 | font-weight: 600; 67 | font-size: 33px; 68 | font-family: Avenir, 'Helvetica Neue', Arial, Helvetica, sans-serif; 69 | } 70 | 71 | .desc { 72 | margin-top: 12px; 73 | margin-bottom: 40px; 74 | color: @text-color-secondary; 75 | font-size: @font-size-base; 76 | } 77 | 78 | .main { 79 | width: 328px; 80 | margin: 0 auto; 81 | @media screen and (max-width: @screen-sm) { 82 | width: 95%; 83 | max-width: 328px; 84 | } 85 | 86 | :global { 87 | .@{ant-prefix}-tabs-nav-list { 88 | margin: auto; 89 | font-size: 16px; 90 | } 91 | } 92 | 93 | .icon { 94 | margin-left: 16px; 95 | color: rgba(0, 0, 0, 0.2); 96 | font-size: 24px; 97 | vertical-align: middle; 98 | cursor: pointer; 99 | transition: color 0.3s; 100 | 101 | &:hover { 102 | color: @primary-color; 103 | } 104 | } 105 | 106 | .other { 107 | margin-top: 24px; 108 | line-height: 22px; 109 | text-align: left; 110 | .register { 111 | float: right; 112 | } 113 | } 114 | 115 | .prefixIcon { 116 | color: @primary-color; 117 | font-size: @font-size-base; 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /web/src/pages/document/Manual/anchorList.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 1/20/2021 3 | */ 4 | 5 | 6 | export interface AnchorList { 7 | id: string 8 | name: string 9 | children?: AnchorList[] 10 | } 11 | 12 | export const anchorList: AnchorList[] = [ 13 | { 14 | id: "1-base-config", 15 | name: "1. 基础配置", 16 | children: [ 17 | { 18 | id: "1-a-database-config", 19 | name: "a. 数据库" 20 | }, 21 | { 22 | id: "1-b-category", 23 | name: "b. 族群" 24 | }, 25 | { 26 | id: "1-c-dashboard", 27 | name: "c. 仪表盘" 28 | }, 29 | ] 30 | }, 31 | { 32 | id: "2-dataset", 33 | name: "2. 数据集", 34 | children: [ 35 | { 36 | id: "2-a-import-export", 37 | name: "a. 导入/导出数据" 38 | }, 39 | { 40 | id: "2-b-query", 41 | name: "b. 查询数据" 42 | }, 43 | ] 44 | }, 45 | { 46 | id: "3-dashboard", 47 | name: "3. 仪表盘", 48 | children: [ 49 | { 50 | id: "3-a-view", 51 | name: "a. 查看" 52 | }, 53 | { 54 | id: "3-b-edit", 55 | name: "b. 编辑", 56 | }, 57 | { 58 | id: "3-c-module", 59 | name: "c. 模板", 60 | children: [ 61 | { 62 | id: "3-c-module-link", 63 | name: "[模板]链接" 64 | }, 65 | { 66 | id: "3-c-module-text", 67 | name: "[模板]文章" 68 | }, 69 | { 70 | id: "3-c-module-target-price", 71 | name: "[模板]目标价" 72 | }, 73 | { 74 | id: "3-c-module-file-management", 75 | name: "[模板]文件管理" 76 | }, 77 | { 78 | id: "3-c-module-table", 79 | name: "[模板]表格" 80 | }, 81 | { 82 | id: "3-c-module-line", 83 | name: "[模板]折线图" 84 | }, 85 | { 86 | id: "3-c-module-bar", 87 | name: "[模板]柱状图" 88 | }, 89 | { 90 | id: "3-c-module-line-bar", 91 | name: "[模板]折线柱状混合图" 92 | }, 93 | { 94 | id: "3-c-module-scatter", 95 | name: "[模板]气泡图" 96 | }, 97 | { 98 | id: "3-c-module-line-scatter", 99 | name: "[模板]折线气泡混合图" 100 | }, 101 | 102 | ] 103 | }, 104 | ] 105 | }, 106 | { 107 | id: "4-overview", 108 | name: "4. 综合查询", 109 | children: [] 110 | } 111 | ] 112 | -------------------------------------------------------------------------------- /web/src/pages/demo/Charts/Candlestick.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 2/8/2021 3 | */ 4 | 5 | import {EChartOption} from "echarts" 6 | import ReactEcharts from "echarts-for-react" 7 | 8 | import {data} from "./mock/candlestick" 9 | import {ChartsProps} from "./data" 10 | 11 | 12 | const chartOption: EChartOption = { 13 | tooltip: { 14 | // trigger: "axis", 15 | axisPointer: {type: "cross"} 16 | }, 17 | legend: { 18 | data: ["candlestick", "volume"], 19 | left: "center", 20 | }, 21 | dataset: [{source: data}], 22 | grid: [ 23 | { 24 | left: "5%", 25 | right: "5%", 26 | top: "5%", 27 | height: "65%" 28 | }, 29 | { 30 | left: "5%", 31 | right: "5%", 32 | top: "75%", 33 | height: "20%" 34 | }, 35 | ], 36 | axisPointer: { 37 | link: [ 38 | {xAxisIndex: 'all'} 39 | ], 40 | label: { 41 | backgroundColor: '#777' 42 | } 43 | }, 44 | xAxis: [ 45 | { 46 | type: "category" 47 | }, 48 | { 49 | type: "category", 50 | gridIndex: 1 51 | }, 52 | ], 53 | yAxis: [ 54 | { 55 | scale: true 56 | }, 57 | { 58 | scale: true, 59 | gridIndex: 1, 60 | axisLabel: {show: false}, 61 | axisLine: {show: false}, 62 | axisTick: {show: false}, 63 | splitLine: {show: false} 64 | } 65 | ], 66 | series: [ 67 | { 68 | name: "candlestick", 69 | type: "candlestick", 70 | encode: { 71 | x: "date", 72 | y: ["open", "close", "high", "low"] 73 | }, 74 | tooltip: { 75 | formatter: ((params: any) => { 76 | return [ 77 | "Date: " + params.value["date"] + "
", 78 | "Open: " + params.value["open"] + "
", 79 | "High: " + params.value["high"] + "
", 80 | "Low: " + params.value["low"] + "
", 81 | "Close: " + params.value["close"] + "
", 82 | "Volume: " + params.value["volume"] + "
", 83 | ].join('') 84 | }) 85 | } 86 | }, 87 | { 88 | name: "volume", 89 | type: "bar", 90 | encode: { 91 | x: "date", 92 | y: "volume" 93 | }, 94 | xAxisIndex: 1, 95 | yAxisIndex: 1 96 | } 97 | ] 98 | } 99 | 100 | export const Candlestick = (props: ChartsProps) => 101 | 106 | 107 | -------------------------------------------------------------------------------- /web/src/components/Gallery/ModulePanel/Panel/ModulePanelFooter.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 9/22/2020. 3 | */ 4 | 5 | import { useState } from "react" 6 | import { message, Space } from "antd" 7 | import { CopyOutlined, KeyOutlined, MinusOutlined } from '@ant-design/icons' 8 | import { useIntl, useModel } from "umi" 9 | 10 | import * as DataType from "@/components/Gallery/GalleryDataType" 11 | 12 | import { Editor } from "@/components/Editor" 13 | import styles from "./Common.less" 14 | 15 | 16 | const IdViewer = (props: { onClick: (value: boolean) => void }) => 17 | , close: }} 19 | onChange={props.onClick} 20 | /> 21 | 22 | export interface ModulePanelFooterProps { 23 | type: DataType.ElementType 24 | parentInfo: object 25 | eleId?: string | undefined 26 | id?: string 27 | date?: string 28 | } 29 | 30 | export const ModulePanelFooter = (props: ModulePanelFooterProps) => { 31 | const intl = useIntl() 32 | const [viewId, setViewId] = useState(false) 33 | const { copyRedirectInfo } = useModel("tempCopy", (t) => ({ 34 | copyRedirectInfo: t.copyRedirectInfo, 35 | })) 36 | 37 | const onCopyClick = () => { 38 | if (props.eleId) { 39 | const info = { parentInfo: props.parentInfo, eleId: props.eleId } 40 | copyRedirectInfo(JSON.stringify(info)) 41 | message.success(intl.formatMessage({ id: "gallery.component.module-panel.panel.module-panel-footer1" })) 42 | } else { 43 | message.warn(intl.formatMessage({ id: "gallery.component.module-panel.panel.module-panel-footer2" })) 44 | } 45 | } 46 | 47 | const showIdAndType = () => 48 | 49 | 50 | { 51 | viewId ? 52 | 53 | 57 | 58 | 59 | Type: {props.type} 60 | ID: {props.id} 61 | 62 | : <> 63 | } 64 | 65 | 66 | return ( 67 |
68 | {props.id ? showIdAndType() : <>} 69 | {props.date ? Date: {DataType.timeToString(props.date)} : <>} 70 |
71 | ) 72 | } 73 | 74 | -------------------------------------------------------------------------------- /web/src/components/Gallery/Dataset/QuerySelector/QuerySelectorModal.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 11/26/2020 3 | */ 4 | 5 | import React, { useRef } from "react" 6 | import { ModalForm } from "@ant-design/pro-form" 7 | 8 | import { SelectorAssembledItems, SelectorAssembledItemsRef } from "./SelectorAssembledItems" 9 | import * as DataType from "@/components/Gallery/GalleryDataType" 10 | import { message, Form } from "antd" 11 | 12 | export interface QuerySelectorModalProps { 13 | initialValues?: Record 14 | trigger: React.ReactElement 15 | storagesOnFetch: () => Promise 16 | storageOnSelect: (id: string) => Promise 17 | tableOnSelect: (id: string, name: string) => Promise 18 | onSubmit: (value: Record) => Promise | any 19 | style?: React.CSSProperties 20 | columnsRequired?: boolean 21 | } 22 | 23 | export const QuerySelectorModal = (props: QuerySelectorModalProps) => { 24 | const ref = useRef(null) 25 | const [formRef] = Form.useForm() 26 | 27 | const onValuesChange = (value: Record) => { 28 | if (ref.current) ref.current.onValuesChange(value) 29 | 30 | } 31 | 32 | // function onFinish(v): Promise { 33 | 34 | // if (ref.current) { 35 | // console.log(3333, ref.current.colValue) 36 | // if (ref.current.colValue.length > 0) { 37 | // props.onSubmit({ 38 | // ...v, 39 | // selects: ref.current.colValue 40 | // }) 41 | // return Promise.resolve(true) 42 | // } 43 | // } 44 | // message.error('请选择列') 45 | // return Promise.reject(false) 46 | 47 | // } 48 | return ( 49 | 59 | 68 | 69 | ) 70 | } 71 | 72 | -------------------------------------------------------------------------------- /web/src/locales/zh-CN/settings.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'app.settings.menuMap.basic': '基本设置', 3 | 'app.settings.menuMap.security': '安全设置', 4 | 'app.settings.menuMap.binding': '账号绑定', 5 | 'app.settings.menuMap.notification': '新消息通知', 6 | 'app.settings.basic.avatar': '头像', 7 | 'app.settings.basic.change-avatar': '更换头像', 8 | 'app.settings.basic.email': '邮箱', 9 | 'app.settings.basic.email-message': '请输入您的邮箱!', 10 | 'app.settings.basic.nickname': '昵称', 11 | 'app.settings.basic.nickname-message': '请输入您的昵称!', 12 | 'app.settings.basic.profile': '个人简介', 13 | 'app.settings.basic.profile-message': '请输入个人简介!', 14 | 'app.settings.basic.profile-placeholder': '个人简介', 15 | 'app.settings.basic.country': '国家/地区', 16 | 'app.settings.basic.country-message': '请输入您的国家或地区!', 17 | 'app.settings.basic.geographic': '所在省市', 18 | 'app.settings.basic.geographic-message': '请输入您的所在省市!', 19 | 'app.settings.basic.address': '街道地址', 20 | 'app.settings.basic.address-message': '请输入您的街道地址!', 21 | 'app.settings.basic.phone': '联系电话', 22 | 'app.settings.basic.phone-message': '请输入您的联系电话!', 23 | 'app.settings.basic.update': '更新基本信息', 24 | 'app.settings.security.strong': '强', 25 | 'app.settings.security.medium': '中', 26 | 'app.settings.security.weak': '弱', 27 | 'app.settings.security.password': '账户密码', 28 | 'app.settings.security.password-description': '当前密码强度', 29 | 'app.settings.security.phone': '密保手机', 30 | 'app.settings.security.phone-description': '已绑定手机', 31 | 'app.settings.security.question': '密保问题', 32 | 'app.settings.security.question-description': '未设置密保问题,密保问题可有效保护账户安全', 33 | 'app.settings.security.email': '备用邮箱', 34 | 'app.settings.security.email-description': '已绑定邮箱', 35 | 'app.settings.security.mfa': 'MFA 设备', 36 | 'app.settings.security.mfa-description': '未绑定 MFA 设备,绑定后,可以进行二次确认', 37 | 'app.settings.security.modify': '修改', 38 | 'app.settings.security.set': '设置', 39 | 'app.settings.security.bind': '绑定', 40 | 'app.settings.binding.taobao': '绑定淘宝', 41 | 'app.settings.binding.taobao-description': '当前未绑定淘宝账号', 42 | 'app.settings.binding.alipay': '绑定支付宝', 43 | 'app.settings.binding.alipay-description': '当前未绑定支付宝账号', 44 | 'app.settings.binding.dingding': '绑定钉钉', 45 | 'app.settings.binding.dingding-description': '当前未绑定钉钉账号', 46 | 'app.settings.binding.bind': '绑定', 47 | 'app.settings.notification.password': '账户密码', 48 | 'app.settings.notification.password-description': '其他用户的消息将以站内信的形式通知', 49 | 'app.settings.notification.messages': '系统消息', 50 | 'app.settings.notification.messages-description': '系统消息将以站内信的形式通知', 51 | 'app.settings.notification.todo': '待办任务', 52 | 'app.settings.notification.todo-description': '待办任务将以站内信的形式通知', 53 | 'app.settings.open': '开', 54 | 'app.settings.close': '关', 55 | }; 56 | -------------------------------------------------------------------------------- /web/src/pages/demo/RectangleChart/index.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 9/10/2020. 3 | */ 4 | 5 | import {useState, useRef, useLayoutEffect} from "react" 6 | import {EChartOption} from "echarts" 7 | import ReactEcharts from "echarts-for-react" 8 | 9 | 10 | const data = [ 11 | ["a", "b", "c", "d", "e"], 12 | ["No.10", 16, "No.16", 3, 'A'], 13 | ["No.18", 26, "No.32", 5, 'B'], 14 | ["No.26", 56, "No.35", 3, 'C'], 15 | ["No.32", 43, "No.56", 8, 'D'], 16 | ["No.58", 35, "No.70", 1, 'E'], 17 | ] 18 | 19 | const dataStyles = [ 20 | "red", 21 | "blue", 22 | "green", 23 | "cyan", 24 | "orange", 25 | ] 26 | 27 | const xAxisData = [ 28 | "No.10", 29 | "No.16", 30 | "No.18", 31 | "No.26", 32 | "No.32", 33 | "No.35", 34 | "No.56", 35 | "No.58", 36 | "No.70" 37 | ] 38 | 39 | function renderFn(params: any, api: any) { 40 | const start = api.coord([api.value(0), api.value(1)]) 41 | const end = api.coord([api.value(2)]) 42 | const size = api.size([api.value(1), api.value(3)]) 43 | 44 | return { 45 | type: 'rect', 46 | shape: { 47 | x: start[0], 48 | y: start[1], 49 | width: end[0] - start[0], 50 | height: size[1] 51 | }, 52 | style: api.style({stroke: dataStyles[params.dataIndex]}) 53 | } 54 | } 55 | 56 | const chartOption: EChartOption = { 57 | title: { 58 | text: 'Profit', 59 | left: 'left' 60 | }, 61 | tooltip: {}, 62 | legend: {}, 63 | dataset: [ 64 | { 65 | source: data 66 | } 67 | ], 68 | xAxis: { 69 | scale: true, 70 | type: "category", 71 | data: xAxisData 72 | }, 73 | yAxis: {}, 74 | series: [ 75 | { 76 | datasetIndex: 0, 77 | name: "custom", 78 | type: 'custom', 79 | renderItem: renderFn, 80 | label: { 81 | show: true, 82 | position: 'top' 83 | }, 84 | dimensions: ['a', 'b', 'c', 'd', 'e'], 85 | encode: { 86 | x: "a", 87 | y: "b", 88 | width: "c", 89 | height: "d", 90 | }, 91 | itemStyle: { 92 | color: "transparent", 93 | borderColor: "blue", 94 | borderWidth: 2 95 | } 96 | } 97 | ] 98 | } 99 | 100 | export default () => { 101 | const chartRef = useRef(null) 102 | 103 | const [chartHeight, setChartHeight] = useState(0) 104 | 105 | useLayoutEffect(() => { 106 | if (chartRef.current) { 107 | setChartHeight(chartRef.current.offsetHeight) 108 | } 109 | }, []) 110 | 111 | return ( 112 |
113 | 117 |
118 | ) 119 | } 120 | 121 | -------------------------------------------------------------------------------- /web/src/services/API.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace API { 2 | type CurrentUser = { 3 | name?: string 4 | avatar?: string 5 | userid?: string 6 | email?: string 7 | signature?: string 8 | title?: string 9 | group?: string 10 | tags?: { key?: string; label?: string }[] 11 | notifyCount?: number 12 | unreadCount?: number 13 | country?: string 14 | access?: string 15 | geographic?: { 16 | province?: { label?: string; key?: string } 17 | city?: { label?: string; key?: string } 18 | } 19 | address?: string 20 | phone?: string 21 | } 22 | 23 | type LoginResult = { 24 | status?: string 25 | type?: string 26 | currentAuthority?: string 27 | } 28 | 29 | type PageParams = { 30 | current?: number 31 | pageSize?: number 32 | } 33 | 34 | type RuleListItem = { 35 | key?: number 36 | disabled?: boolean 37 | href?: string 38 | avatar?: string 39 | name?: string 40 | owner?: string 41 | desc?: string 42 | callNo?: number 43 | status?: number 44 | updatedAt?: string 45 | createdAt?: string 46 | progress?: number 47 | } 48 | 49 | type RuleList = { 50 | data?: RuleListItem[] 51 | /** 列表的内容总数 */ 52 | total?: number 53 | success?: boolean 54 | } 55 | 56 | type FakeCaptcha = { 57 | code?: number 58 | status?: string 59 | } 60 | 61 | type LoginParams = { 62 | username?: string 63 | password?: string 64 | autoLogin?: boolean 65 | type?: string 66 | } 67 | 68 | type ErrorResponse = { 69 | /** 业务约定的错误码 */ 70 | errorCode: string 71 | /** 业务上的错误信息 */ 72 | errorMessage?: string 73 | /** 业务上的请求是否成功 */ 74 | success?: boolean 75 | } 76 | 77 | type NoticeIconList = { 78 | data?: NoticeIconItem[] 79 | /** 列表的内容总数 */ 80 | total?: number 81 | success?: boolean 82 | } 83 | 84 | type NoticeIconItemType = 'notification' | 'message' | 'event' 85 | 86 | type NoticeIconItem = { 87 | id?: string 88 | extra?: string 89 | key?: string 90 | read?: boolean 91 | avatar?: string 92 | title?: string 93 | status?: string 94 | datetime?: string 95 | description?: string 96 | type?: NoticeIconItemType 97 | } 98 | 99 | interface Invitation { 100 | nickname: string, 101 | email: string, 102 | password: string, 103 | } 104 | interface Registration { 105 | email: string, 106 | } 107 | 108 | interface Login { 109 | email: string, 110 | password: string, 111 | } 112 | 113 | interface LoginCheck { 114 | data: LoginCheckData 115 | } 116 | 117 | interface LoginCheckData { 118 | email: string, 119 | role: string, 120 | nickname: string, 121 | } 122 | } 123 | 124 | 125 | -------------------------------------------------------------------------------- /web/src/components/DraggablePanel/DraggablePanel.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 12/17/2020 3 | */ 4 | 5 | import React, {useEffect, useState} from 'react' 6 | import {DragDropContext, Droppable, Draggable, DropResult} from 'react-beautiful-dnd' 7 | 8 | 9 | export interface DraggableElement { 10 | id: string 11 | } 12 | 13 | export type DraggableElementType = React.ReactElement 14 | 15 | const reorder = (data: DraggableElementType[], 16 | startIndex: number, 17 | endIndex: number) => { 18 | const result = Array.from(data) 19 | const [removed] = result.splice(startIndex, 1) 20 | result.splice(endIndex, 0, removed) 21 | 22 | return result 23 | } 24 | 25 | 26 | export interface DraggablePanelProps { 27 | editable: boolean 28 | children: DraggableElementType[] 29 | onChange?: (ids: string[]) => void 30 | direction?: "horizontal" | "vertical" | undefined 31 | } 32 | 33 | export const DraggablePanel = (props: DraggablePanelProps) => { 34 | const [items, setItems] = useState[]>(props.children) 35 | 36 | useEffect(() => setItems(props.children), [props.children]) 37 | 38 | const onDragEnd = (result: DropResult) => { 39 | if (!result.destination) return 40 | 41 | const newItems = reorder(items, result.source.index, result.destination.index) 42 | setItems(newItems) 43 | if (props.onChange) props.onChange(newItems.map(i => i.props.id)) 44 | } 45 | 46 | return props.editable ? 47 | 48 | 49 | {(provided) => ( 50 |
55 | {items.map((item, index) => ( 56 | 57 | {(provided) => ( 58 |
63 | {item} 64 |
65 | )} 66 |
67 | ))} 68 | {provided.placeholder} 69 |
70 | )} 71 |
72 |
: 73 |
74 | {props.children} 75 |
76 | } 77 | 78 | DraggablePanel.defaultProps = { 79 | direction: "horizontal" 80 | } as Partial 81 | 82 | -------------------------------------------------------------------------------- /web/src/locales/zh-CN/menu.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'menu.welcome': '欢迎', 3 | 'menu.more-blocks': '更多模块', 4 | 'menu.home': '主页', 5 | 'menu.admin': '管理员', 6 | 'menu.admin.sub-page': '二级界面', 7 | 'menu.login': '登录', 8 | 'menu.registration': '注册', 9 | 'menu.dataset': '数据集', 10 | 'menu.register': '注册', 11 | 'menu.logout': '登出', 12 | 'menu.invitation': '邮件验证', 13 | 'menu.register.result': '注册结果', 14 | 'menu.dashboard': 'Dashboard', 15 | 'menu.dashboard.analysis': '分析页', 16 | 'menu.dashboard.monitor': '监控页', 17 | 'menu.dashboard.workplace': '工作台', 18 | 'menu.exception.403': '403', 19 | 'menu.exception.404': '404', 20 | 'menu.exception.500': '500', 21 | 'menu.form': '表单页', 22 | 'menu.form.basic-form': '基础表单', 23 | 'menu.form.step-form': '分步表单', 24 | 'menu.form.step-form.info': '分步表单(填写转账信息)', 25 | 'menu.form.step-form.confirm': '分步表单(确认转账信息)', 26 | 'menu.form.step-form.result': '分步表单(完成)', 27 | 'menu.form.advanced-form': '高级表单', 28 | 'menu.list': '列表页', 29 | 'menu.list.table-list': '查询表格', 30 | 'menu.list.basic-list': '标准列表', 31 | 'menu.list.card-list': '卡片列表', 32 | 'menu.list.search-list': '搜索列表', 33 | 'menu.list.search-list.articles': '搜索列表(文章)', 34 | 'menu.list.search-list.projects': '搜索列表(项目)', 35 | 'menu.list.search-list.applications': '搜索列表(应用)', 36 | 'menu.profile': '详情页', 37 | 'menu.profile.basic': '基础详情页', 38 | 'menu.profile.advanced': '高级详情页', 39 | 'menu.result': '结果页', 40 | 'menu.result.success': '成功页', 41 | 'menu.result.fail': '失败页', 42 | 'menu.exception': '异常页', 43 | 'menu.exception.not-permission': '403', 44 | 'menu.exception.not-find': '404', 45 | 'menu.exception.server-error': '500', 46 | 'menu.exception.trigger': '触发错误', 47 | 'menu.account': '个人页', 48 | 'menu.account.center': '个人中心', 49 | 'menu.account.settings': '个人设置', 50 | 'menu.account.trigger': '触发报错', 51 | 'menu.account.logout': '退出登录', 52 | 'menu.editor': '图形编辑器', 53 | 'menu.editor.flow': '流程编辑器', 54 | 'menu.editor.mind': '脑图编辑器', 55 | 'menu.editor.koni': '拓扑编辑器', 56 | 57 | 'menu.gallery': '展馆', 58 | 'menu.gallery.configuration': '配置', 59 | 'menu.gallery.dataset': '数据集', 60 | 'menu.gallery.overview': '总览', 61 | 'menu.gallery.dashboard': '仪表板', 62 | 'menu.gallery.dashboardTemplate': '模板库', 63 | 64 | 'menu.demo': '样品', 65 | 'menu.demo.local-storage': 'Local Storage', 66 | 'menu.demo.rectangle-chart': 'Rec Chart', 67 | 'menu.demo.charts': 'Charts', 68 | 'menu.demo.grid-layout': 'Grid Layout', 69 | 'menu.demo.redirect-test': 'Redirect Test', 70 | 'menu.demo.module-test': 'Module Test', 71 | 'menu.demo.component-test': 'Component Test', 72 | 'menu.demo.consensus-distribution-chart': 'Consensus Distribution', 73 | 'menu.demo.file-manager-test': 'File Manager Test', 74 | 75 | 'menu.document': '文档', 76 | 'menu.document.manual': '手册', 77 | 'menu.document.menu': '目录', 78 | 'menu.document.gallery': '展馆', 79 | 80 | } 81 | -------------------------------------------------------------------------------- /web/src/pages/demo/GalleryModuleTest/index.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 1/17/2021 3 | */ 4 | 5 | import {useRef, useState} from "react" 6 | import {Card, Switch} from "antd" 7 | import {FormattedMessage} from "umi" 8 | 9 | import * as DataType from "@/components/Gallery/GalleryDataType" 10 | import * as GalleryService from "@/services/gallery" 11 | import {ConvertFwRef} from "@/components/Gallery/ModulePanel/Generator/data" 12 | import styles from "@/components/Gallery/ModulePanel/Panel/Common.less" 13 | 14 | import {Pie} from "@/components/Gallery/ModulePanel/Collections/graph" 15 | import {TargetPrice} from "@/components/Gallery/ModulePanel/Collections/miscellaneous/TargetPrice" 16 | import {Text} from "@/components/Gallery/ModulePanel/Collections/multiMedia/Text" 17 | import {FileView} from "@/components/Gallery/ModulePanel/Collections/file/FileView" 18 | 19 | export default () => { 20 | 21 | const moduleFwRef = useRef(null) 22 | 23 | const [content, setContent] = useState({}) 24 | 25 | const switchOnClick = (v: boolean) => { 26 | if (moduleFwRef.current) moduleFwRef.current.setEdit(v) 27 | } 28 | 29 | const fetchStorages = () => 30 | GalleryService.getAllStorageSimple() as Promise 31 | 32 | const fetchTableList = (id: string) => 33 | GalleryService.databaseListTable(id) 34 | 35 | const fetchTableColumns = (storageId: string, tableName: string) => 36 | GalleryService.databaseGetTableColumns(storageId, tableName) 37 | 38 | const fetchQueryData = (value: DataType.Content) => { 39 | const id = value.data?.id 40 | const option = value.data as DataType.Read 41 | return GalleryService.read(id, option, DataType.StorageType.PG) 42 | } 43 | 44 | return ( 45 | } 47 | extra={ 48 | 51 | } 52 | style={{height: "85vh"}} 53 | bodyStyle={{height: "100%"}} 54 | > 55 | {/* */} 66 | 67 | {/* */} 72 | 73 | {/* */} 78 | 79 | 84 | 85 | 86 | ) 87 | } 88 | 89 | -------------------------------------------------------------------------------- /web/src/global.tsx: -------------------------------------------------------------------------------- 1 | import {Button, message, notification} from 'antd' 2 | 3 | import React from 'react' 4 | import {useIntl} from 'umi' 5 | import defaultSettings from '../config/defaultSettings' 6 | 7 | const {pwa} = defaultSettings 8 | 9 | // if pwa is true 10 | if (pwa) { 11 | // Notify user if offline now 12 | window.addEventListener('sw.offline', () => { 13 | message.warning(useIntl().formatMessage({id: 'app.pwa.offline'})) 14 | }) 15 | 16 | // Pop up a prompt on the page asking the user if they want to use the latest version 17 | window.addEventListener('sw.updated', (event: Event) => { 18 | const e = event as CustomEvent 19 | const reloadSW = async () => { 20 | // Check if there is sw whose state is waiting in ServiceWorkerRegistration 21 | // https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration 22 | const worker = e.detail && e.detail.waiting 23 | if (!worker) { 24 | return true 25 | } 26 | // Send skip-waiting event to waiting SW with MessageChannel 27 | await new Promise((resolve, reject) => { 28 | const channel = new MessageChannel() 29 | channel.port1.onmessage = (msgEvent) => { 30 | if (msgEvent.data.error) { 31 | reject(msgEvent.data.error) 32 | } else { 33 | resolve(msgEvent.data) 34 | } 35 | } 36 | worker.postMessage({type: 'skip-waiting'}, [channel.port2]) 37 | }) 38 | // Refresh current page to use the updated HTML and other assets after SW has skiped waiting 39 | window.location.reload() 40 | return true 41 | } 42 | const key = `open${Date.now()}` 43 | const btn = ( 44 | 53 | ) 54 | notification.open({ 55 | message: useIntl().formatMessage({id: 'app.pwa.serviceworker.updated'}), 56 | description: useIntl().formatMessage({id: 'app.pwa.serviceworker.updated.hint'}), 57 | btn, 58 | key, 59 | onClose: async () => {}, 60 | }) 61 | }) 62 | } else if ('serviceWorker' in navigator) { 63 | // unregister service worker 64 | const {serviceWorker} = navigator 65 | if (serviceWorker.getRegistrations) { 66 | serviceWorker.getRegistrations().then((sws) => { 67 | sws.forEach((sw) => { 68 | sw.unregister().finally() 69 | }) 70 | }) 71 | } 72 | serviceWorker.getRegistration().then((sw) => { 73 | if (sw) sw.unregister().finally() 74 | }) 75 | 76 | // remove all caches 77 | if (window.caches && window.caches.keys) { 78 | caches.keys().then((keys) => { 79 | keys.forEach((key) => { 80 | caches.delete(key).finally() 81 | }) 82 | }) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /web/src/components/Gallery/Utils/rawDataTransform.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 1/14/2021 3 | */ 4 | 5 | import { ColumnsType } from "antd/lib/table/interface" 6 | import _ from "lodash" 7 | import moment from "moment" 8 | 9 | import { 10 | DataSelectedType, 11 | DisplayType, 12 | GeneralTableConfigInterface 13 | } from "@/components/Gallery/Utils/data" 14 | 15 | 16 | export const getColumnsFromRawData = (data0: Record, 17 | type: DataSelectedType): ColumnsType => { 18 | switch (type) { 19 | case "dataset": 20 | return _.keys(data0).map((k: string) => ({ 21 | key: k, 22 | title: k, 23 | dataIndex: k 24 | })) 25 | case "file": 26 | return _.keys(data0).map((k: string) => ({ 27 | key: k, 28 | title: data0[k], 29 | dataIndex: k 30 | })) 31 | } 32 | } 33 | 34 | 35 | export const genDisplayConfig = (data: Record[], 36 | display: DisplayType[], 37 | type: DataSelectedType) => { 38 | switch (type) { 39 | case "dataset": 40 | return _.reduce(display, (acc: Record, v: DisplayType) => { 41 | return { ...acc, [v.column]: v.type } 42 | }, {}) 43 | case "file": 44 | const fileKeyMap = _.invert(data[0]) 45 | return _.reduce(display, (acc: Record, v: DisplayType) => { 46 | return { ...acc, [fileKeyMap[v.column]]: v.type } 47 | }, {}) 48 | } 49 | } 50 | 51 | export const transformRowDataForTable = (row: Record, display: Record) => { 52 | return _.transform(row, (r: Record, v: any, k: string) => { 53 | if (display[k] === "date") 54 | r[k] = moment(v).format("YYYY-MM-DD") || undefined 55 | else if (display[k] === "number") 56 | r[k] = (+v).toFixed(2) || undefined 57 | else if (display[k] === "percent") 58 | r[k] = `${(+v * 100).toFixed(2)} %` || undefined 59 | else 60 | r[k] = v 61 | }) 62 | } 63 | 64 | export const transformRowDataForChart = (row: Record, display: Record) => { 65 | return _.transform(row, (r: Record, v: any, k: string) => { 66 | if (display[k] === "date") 67 | r[k] = moment(v).format("YYYY-MM-DD") || undefined 68 | else if (display[k] === "number") 69 | r[k] = (+v).toFixed(2) || undefined 70 | else if (display[k] === "percent") 71 | r[k] = (+v * 100).toFixed(2) || undefined 72 | else 73 | r[k] = v 74 | }) 75 | } 76 | 77 | export const transformRawDataBySourceType = (data: Record[], 78 | config: GeneralTableConfigInterface, 79 | type: DataSelectedType): Record[] => { 80 | const display = genDisplayConfig(data, config.display, type) 81 | 82 | return data.map((i, idx) => { 83 | if (type === "file" && idx === 0) { 84 | return i 85 | } 86 | const trans = transformRowDataForTable(i, display) 87 | 88 | return { ...trans, key: idx } 89 | }) 90 | } 91 | 92 | -------------------------------------------------------------------------------- /web/src/components/HeaderSearch/index.tsx: -------------------------------------------------------------------------------- 1 | import {SearchOutlined} from '@ant-design/icons' 2 | import {AutoComplete, Input} from 'antd' 3 | import useMergedState from 'rc-util/es/hooks/useMergedState' 4 | import type {AutoCompleteProps} from 'antd/es/auto-complete' 5 | import React, {useRef} from 'react' 6 | 7 | import classNames from 'classnames' 8 | import styles from './index.less' 9 | 10 | export type HeaderSearchProps = { 11 | onSearch?: (value?: string) => void 12 | onChange?: (value?: string) => void 13 | onVisibleChange?: (b: boolean) => void 14 | className?: string 15 | placeholder?: string 16 | options: AutoCompleteProps['options'] 17 | defaultVisible?: boolean 18 | visible?: boolean 19 | defaultValue?: string 20 | value?: string 21 | } 22 | 23 | const HeaderSearch: React.FC = (props) => { 24 | const { 25 | className, 26 | defaultValue, 27 | onVisibleChange, 28 | placeholder, 29 | visible, 30 | defaultVisible, 31 | ...restProps 32 | } = props 33 | 34 | const inputRef = useRef(null) 35 | 36 | const [value, setValue] = useMergedState(defaultValue, { 37 | value: props.value, 38 | onChange: props.onChange, 39 | }) 40 | 41 | const [searchMode, setSearchMode] = useMergedState(defaultVisible ?? false, { 42 | value: props.visible, 43 | onChange: onVisibleChange, 44 | }) 45 | 46 | const inputClass = classNames(styles.input, { 47 | [styles.show]: searchMode, 48 | }) 49 | return ( 50 |
{ 53 | setSearchMode(true) 54 | if (searchMode && inputRef.current) { 55 | inputRef.current.focus() 56 | } 57 | }} 58 | onTransitionEnd={({propertyName}) => { 59 | if (propertyName === 'width' && !searchMode) { 60 | if (onVisibleChange) { 61 | onVisibleChange(searchMode) 62 | } 63 | } 64 | }} 65 | > 66 | 72 | 79 | { 86 | if (e.key === 'Enter') { 87 | if (restProps.onSearch) { 88 | restProps.onSearch(value) 89 | } 90 | } 91 | }} 92 | onBlur={() => { 93 | setSearchMode(false) 94 | }} 95 | /> 96 | 97 |
98 | ) 99 | } 100 | 101 | export default HeaderSearch 102 | -------------------------------------------------------------------------------- /web/src/components/Gallery/ModulePanel/Collections/file/linkToExternalAddress.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 1/13/2021 3 | */ 4 | 5 | 6 | export const linkToExternalAddress = '\n' + 21 | ' \n' + 23 | ' \n' + 24 | ' \n' + 26 | ' image/svg+xml\n' + 27 | ' \n' + 29 | ' \n' + 30 | ' \n' + 31 | ' \n' + 32 | ' \n' + 34 | ' \n' + 54 | ' \n' + 57 | ' \n' + 62 | ' \n' + 63 | '' 64 | -------------------------------------------------------------------------------- /web/src/pages/demo/Charts/LineScatter.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 1/28/2021 3 | */ 4 | 5 | import {EChartOption} from "echarts" 6 | import ReactEcharts from "echarts-for-react" 7 | 8 | import {data} from "./mock/ls" 9 | import {ChartsProps} from "./data" 10 | 11 | 12 | const chartOption: EChartOption = { 13 | tooltip: {}, 14 | legend: { 15 | data: ['trick', 'alpha', 'trend1', 'trend2'] 16 | }, 17 | dataset: [ 18 | { 19 | source: data 20 | } 21 | ], 22 | xAxis: [ 23 | {type: 'category'}, 24 | ], 25 | yAxis: [ 26 | { 27 | type: "value", 28 | name: "Line", 29 | position: "left", 30 | }, 31 | { 32 | type: "value", 33 | name: "Scatter1", 34 | position: "right", 35 | splitLine: {show: false} 36 | }, 37 | { 38 | type: "value", 39 | name: "Scatter2", 40 | position: "right", 41 | splitLine: {show: false}, 42 | offset: 100 43 | }, 44 | 45 | ], 46 | series: [ 47 | { 48 | type: 'line', 49 | yAxisIndex: 0, 50 | name: "trick", 51 | encode: { 52 | x: "time", 53 | y: "trick", 54 | tooltip: ["trick"] 55 | } 56 | }, 57 | { 58 | type: 'scatter', 59 | yAxisIndex: 1, 60 | name: "alpha", 61 | encode: { 62 | x: "time", 63 | y: "alpha", 64 | tooltip: ["time", "alpha", "beta"] 65 | }, 66 | symbolSize: (data: Record) => (data["beta"] || 50) 67 | }, 68 | { 69 | type: 'scatter', 70 | yAxisIndex: 1, 71 | name: "trend1", 72 | encode: { 73 | x: "time", 74 | y: "trend1", 75 | tooltip: ["time", "trend1", "area1"] 76 | }, 77 | symbolSize: (data: Record) => data["area1"] 78 | }, 79 | { 80 | type: 'scatter', 81 | yAxisIndex: 2, 82 | name: "trend2", 83 | encode: { 84 | x: "time", 85 | y: "trend2", 86 | tooltip: ["time", "trend2", "area2"] 87 | }, 88 | symbolSize: (data: Record) => data["area2"] 89 | }, 90 | ], 91 | visualMap: [ 92 | { 93 | show: false, 94 | dimension: "beta", 95 | seriesIndex: [1], 96 | min: 0, 97 | max: 100, 98 | inRange: { 99 | symbolSize: [0, 100] 100 | } 101 | }, 102 | { 103 | show: false, 104 | dimension: "area1", 105 | seriesIndex: [2], 106 | min: 0, 107 | max: 1, 108 | inRange: { 109 | symbolSize: [0, 100] 110 | } 111 | }, 112 | { 113 | show: false, 114 | dimension: "area2", 115 | seriesIndex: [3], 116 | min: 0, 117 | max: 1, 118 | inRange: { 119 | symbolSize: [0, 100] 120 | } 121 | }, 122 | ] 123 | } 124 | 125 | 126 | /** 127 | * multi y-axes line & scatter mixin plot 128 | */ 129 | export const LineScatter = (props: ChartsProps) => 130 | 135 | 136 | -------------------------------------------------------------------------------- /web/src/components/FileUploadModal/FileExtractModal.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jacob Xie on 11/19/2020 3 | */ 4 | 5 | import { 6 | Checkbox, 7 | Divider, 8 | Form, 9 | InputNumber, 10 | Select, 11 | } from "antd" 12 | import {FormattedMessage} from "umi" 13 | 14 | import {BaseModal} from "./BaseModal" 15 | 16 | 17 | export interface FileExtractModalProps { 18 | setVisible: (value: boolean) => void 19 | visible: boolean 20 | uploadAddress?: String 21 | upload: (option: Record, data: any) => Promise 22 | uploadResHandle: (value: any) => void 23 | multiSheetDisable?: boolean 24 | } 25 | 26 | export const FileExtractModal = (props: FileExtractModalProps) => { 27 | 28 | return ( 29 | 37 | { 38 | props.multiSheetDisable ? <> : 39 | <> 40 | 41 | 42 | 43 | 44 | } 47 | > 48 | 49 | 52 | 53 | 54 | 55 | 56 | 57 | } 58 | 59 | 60 | 61 | 62 | 63 | } 66 | > 67 | 72 | 73 | } 76 | > 77 | 91 | 92 | 93 | ) 94 | } 95 | 96 | FileExtractModal.defaultProps = { 97 | multiSheetDisable: false 98 | } as Partial 99 | 100 | -------------------------------------------------------------------------------- /web/src/components/Gallery/ModulePanel/Panel/ModuleDescription.tsx: -------------------------------------------------------------------------------- 1 | import { Button, Col, Row, Tooltip } from "antd"; 2 | import TextArea from "antd/lib/input/TextArea" 3 | // import { useRef } from "dva/node_modules/@types/react"; 4 | import { useEffect, useState, useRef } from "react" 5 | import { useIntl } from "umi"; 6 | // import {TextAreaRef} from "antd/lib/input/TextArea" 7 | export interface ModuleDescriptionProps { 8 | editable: boolean 9 | initialValue: string 10 | onSave: (v: string) => void 11 | } 12 | 13 | export const ModuleDescirption = (props: ModuleDescriptionProps) => { 14 | const [value, setValue] = useState(props.initialValue) 15 | const [editable, setEditable] = useState(true) 16 | const intl = useIntl() 17 | //!ts类型待修正 18 | const TextAreaREF = useRef(null); 19 | useEffect(() => { 20 | setValue(props.initialValue) 21 | }, [props.initialValue]) 22 | 23 | const onSave = () => { 24 | props.onSave(value) 25 | setEditable(false) 26 | } 27 | 28 | const genDescriptionText = () => { 29 | let arr: any[] = []; 30 | value.split('\n').forEach((item, index) => arr.push(

{item.trim()}

)); 31 | return arr; 32 | } 33 | 34 | const onChange = ({ target: { value } }: any) => { 35 | setValue(value); 36 | }; 37 | 38 | // const defaultStyle = { paddingLeft: 10, paddingRight: 10, height: "5%", maxHeight: 100 } 39 | 40 | // const descriptionStyle = { ...defaultStyle, overflow: "auto" } 41 | 42 | const editableyDescription = 43 | { 44 | setEditable(true); 45 | 46 | }} 47 | /* style={descriptionStyle} */> 48 | {genDescriptionText()} 49 | 50 | 51 | 52 | 53 | const displayDescription = 54 | {genDescriptionText()} 55 | 56 | return ( 57 | props.editable ? 58 | editable ? 59 | 60 | 61 |