graph.tsx 12.4 KB
Newer Older
P
Peter Pan 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
/**
 * Copyright 2020 Baidu Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

P
Peter Pan 已提交
17
import Aside, {AsideSection} from '~/components/Aside';
18
import type {Documentation, OpenedResult, Properties, SearchItem, SearchResult} from '~/resource/graph/types';
P
Peter Pan 已提交
19
import GraphComponent, {GraphRef} from '~/components/GraphPage/Graph';
20
import React, {FunctionComponent, useCallback, useEffect, useMemo, useRef, useState} from 'react';
21
import Select, {SelectProps} from '~/components/Select';
P
Peter Pan 已提交
22
import {actions, selectors} from '~/store';
23
import {primaryColor, rem, size} from '~/utils/style';
P
Peter Pan 已提交
24
import {useDispatch, useSelector} from 'react-redux';
25

P
Peter Pan 已提交
26
import type {BlobResponse} from '~/utils/fetch';
P
Peter Pan 已提交
27 28
import Button from '~/components/Button';
import Checkbox from '~/components/Checkbox';
29 30
import Content from '~/components/Content';
import Field from '~/components/Field';
31
import HashLoader from 'react-spinners/HashLoader';
P
Peter Pan 已提交
32 33 34
import ModelPropertiesDialog from '~/components/GraphPage/ModelPropertiesDialog';
import NodeDocumentationSidebar from '~/components/GraphPage/NodeDocumentationSidebar';
import NodePropertiesSidebar from '~/components/GraphPage/NodePropertiesSidebar';
P
Peter Pan 已提交
35 36
import RadioButton from '~/components/RadioButton';
import RadioGroup from '~/components/RadioGroup';
P
Peter Pan 已提交
37
import Search from '~/components/GraphPage/Search';
38
import Title from '~/components/Title';
P
Peter Pan 已提交
39
import Uploader from '~/components/GraphPage/Uploader';
40
import styled from 'styled-components';
41
import useRequest from '~/hooks/useRequest';
42
import {useTranslation} from 'react-i18next';
43

P
Peter Pan 已提交
44
const FullWidthButton = styled(Button)`
45 46 47
    width: 100%;
`;

48 49 50 51
const FullWidthSelect = styled<React.FunctionComponent<SelectProps<NonNullable<OpenedResult['selected']>>>>(Select)`
    width: 100%;
`;

P
Peter Pan 已提交
52
const ExportButtonWrapper = styled.div`
53
    display: flex;
P
Peter Pan 已提交
54
    justify-content: space-between;
55

P
Peter Pan 已提交
56 57
    > * {
        flex: 1 1 auto;
58

P
Peter Pan 已提交
59 60
        &:not(:last-child) {
            margin-right: ${rem(20)};
61
        }
62 63 64
    }
`;

P
Peter Pan 已提交
65 66 67 68 69
// TODO: better way to auto fit height
const SearchSection = styled(AsideSection)`
    max-height: calc(100% - ${rem(40)});
    display: flex;
    flex-direction: column;
70

P
Peter Pan 已提交
71 72 73 74
    &:not(:last-child) {
        padding-bottom: 0;
    }
`;
75

76 77 78 79 80 81 82 83 84 85 86 87
const Loading = styled.div`
    ${size('100%', '100%')}
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    overscroll-behavior: none;
    cursor: progress;
    font-size: ${rem(16)};
    line-height: ${rem(60)};
`;

88
const Graph: FunctionComponent = () => {
P
Peter Pan 已提交
89
    const {t} = useTranslation(['graph', 'common']);
90

P
Peter Pan 已提交
91 92
    const storeDispatch = useDispatch();
    const storeModel = useSelector(selectors.graph.model);
93

P
Peter Pan 已提交
94 95
    const graph = useRef<GraphRef>(null);
    const file = useRef<HTMLInputElement>(null);
P
Peter Pan 已提交
96
    const [files, setFiles] = useState<FileList | File[] | null>(storeModel);
97 98 99 100 101 102 103
    const setModelFile = useCallback(
        (f: FileList | File[]) => {
            storeDispatch(actions.graph.setModel(f));
            setFiles(f);
        },
        [storeDispatch]
    );
P
Peter Pan 已提交
104 105 106 107
    const onClickFile = useCallback(() => {
        if (file.current) {
            file.current.value = '';
            file.current.click();
108
        }
P
Peter Pan 已提交
109
    }, []);
110 111 112 113
    const onChangeFile = useCallback(
        (e: React.ChangeEvent<HTMLInputElement>) => {
            const target = e.target;
            if (target && target.files && target.files.length) {
114
                setModelFile(target.files);
115 116
            }
        },
117
        [setModelFile]
118 119
    );

P
Peter Pan 已提交
120
    const {data, loading} = useRequest<BlobResponse>(files ? null : '/graph/graph');
121

122 123
    useEffect(() => {
        if (data?.data.size) {
P
Peter Pan 已提交
124
            setFiles([new File([data.data], data.filename || 'unknown_model')]);
125 126
        }
    }, [data]);
P
Peter Pan 已提交
127

128 129 130 131 132 133 134 135 136 137 138
    const [modelGraphs, setModelGraphs] = useState<OpenedResult['graphs']>([]);
    const [selectedGraph, setSelectedGraph] = useState<NonNullable<OpenedResult['selected']>>('');
    const setOpenedModel = useCallback((data: OpenedResult) => {
        setModelGraphs(data.graphs);
        setSelectedGraph(data.selected || '');
    }, []);
    const changeGraph = useCallback((name: string) => {
        setSelectedGraph(name);
        graph.current?.changeGraph(name);
    }, []);

P
Peter Pan 已提交
139 140 141 142 143 144 145 146 147 148 149 150 151 152 153
    const [search, setSearch] = useState('');
    const [searching, setSearching] = useState(false);
    const [searchResult, setSearchResult] = useState<SearchResult>({text: '', result: []});
    const onSearch = useCallback((value: string) => {
        setSearch(value);
        graph.current?.search(value);
    }, []);
    const onSelect = useCallback((item: SearchItem) => {
        setSearch(item.name);
        graph.current?.select(item);
    }, []);

    const [showAttributes, setShowAttributes] = useState(false);
    const [showInitializers, setShowInitializers] = useState(true);
    const [showNames, setShowNames] = useState(false);
P
Peter Pan 已提交
154
    const [horizontal, setHorizontal] = useState(false);
P
Peter Pan 已提交
155 156 157 158 159

    const [modelData, setModelData] = useState<Properties | null>(null);
    const [nodeData, setNodeData] = useState<Properties | null>(null);
    const [nodeDocumentation, setNodeDocumentation] = useState<Documentation | null>(null);

160 161 162 163
    useEffect(() => {
        setSearch('');
        setSearchResult({text: '', result: []});
    }, [files, showAttributes, showInitializers, showNames]);
P
Peter Pan 已提交
164 165 166 167 168

    const bottom = useMemo(
        () =>
            searching ? null : (
                <FullWidthButton type="primary" rounded onClick={onClickFile}>
P
Peter Pan 已提交
169
                    {t('graph:change-model')}
P
Peter Pan 已提交
170
                </FullWidthButton>
171
            ),
P
Peter Pan 已提交
172
        [t, onClickFile, searching]
173 174
    );

P
Peter Pan 已提交
175 176 177
    const [rendered, setRendered] = useState(false);

    const aside = useMemo(() => {
178
        if (!rendered || loading) {
179 180
            return null;
        }
P
Peter Pan 已提交
181 182 183 184 185 186
        if (nodeDocumentation) {
            return (
                <Aside width={rem(360)}>
                    <NodeDocumentationSidebar data={nodeDocumentation} onClose={() => setNodeDocumentation(null)} />
                </Aside>
            );
187
        }
P
Peter Pan 已提交
188 189 190 191 192 193
        if (nodeData) {
            return (
                <Aside width={rem(360)}>
                    <NodePropertiesSidebar
                        data={nodeData}
                        onClose={() => setNodeData(null)}
P
Peter Pan 已提交
194
                        showNodeDocumentation={() => graph.current?.showNodeDocumentation(nodeData)}
P
Peter Pan 已提交
195 196 197
                    />
                </Aside>
            );
198 199
        }
        return (
P
Peter Pan 已提交
200 201 202 203 204 205 206 207 208 209 210 211 212 213 214
            <Aside bottom={bottom}>
                <SearchSection>
                    <Search
                        text={search}
                        data={searchResult}
                        onChange={onSearch}
                        onSelect={onSelect}
                        onActive={() => setSearching(true)}
                        onDeactive={() => setSearching(false)}
                    />
                </SearchSection>
                {!searching && (
                    <>
                        <AsideSection>
                            <FullWidthButton onClick={() => graph.current?.showModelProperties()}>
P
Peter Pan 已提交
215
                                {t('graph:model-properties')}
P
Peter Pan 已提交
216 217
                            </FullWidthButton>
                        </AsideSection>
218 219 220 221 222 223 224
                        {modelGraphs.length > 1 && (
                            <AsideSection>
                                <Field label={t('graph:subgraph')}>
                                    <FullWidthSelect list={modelGraphs} value={selectedGraph} onChange={changeGraph} />
                                </Field>
                            </AsideSection>
                        )}
P
Peter Pan 已提交
225
                        <AsideSection>
P
Peter Pan 已提交
226
                            <Field label={t('graph:display-data')}>
P
Peter Pan 已提交
227 228
                                <div>
                                    <Checkbox value={showAttributes} onChange={setShowAttributes}>
P
Peter Pan 已提交
229
                                        {t('graph:show-attributes')}
P
Peter Pan 已提交
230 231 232 233
                                    </Checkbox>
                                </div>
                                <div>
                                    <Checkbox value={showInitializers} onChange={setShowInitializers}>
P
Peter Pan 已提交
234
                                        {t('graph:show-initializers')}
P
Peter Pan 已提交
235 236 237 238
                                    </Checkbox>
                                </div>
                                <div>
                                    <Checkbox value={showNames} onChange={setShowNames}>
P
Peter Pan 已提交
239
                                        {t('graph:show-node-names')}
P
Peter Pan 已提交
240 241 242 243
                                    </Checkbox>
                                </div>
                            </Field>
                        </AsideSection>
P
Peter Pan 已提交
244 245 246 247 248 249 250 251
                        <AsideSection>
                            <Field label={t('graph:direction')}>
                                <RadioGroup value={horizontal} onChange={setHorizontal}>
                                    <RadioButton value={false}>{t('graph:vertical')}</RadioButton>
                                    <RadioButton value={true}>{t('graph:horizontal')}</RadioButton>
                                </RadioGroup>
                            </Field>
                        </AsideSection>
P
Peter Pan 已提交
252
                        <AsideSection>
P
Peter Pan 已提交
253
                            <Field label={t('graph:export-file')}>
P
Peter Pan 已提交
254 255
                                <ExportButtonWrapper>
                                    <Button onClick={() => graph.current?.export('png')}>
P
Peter Pan 已提交
256
                                        {t('graph:export-png')}
P
Peter Pan 已提交
257 258
                                    </Button>
                                    <Button onClick={() => graph.current?.export('svg')}>
P
Peter Pan 已提交
259
                                        {t('graph:export-svg')}
P
Peter Pan 已提交
260 261 262 263 264 265 266
                                    </Button>
                                </ExportButtonWrapper>
                            </Field>
                        </AsideSection>
                    </>
                )}
            </Aside>
267
        );
P
Peter Pan 已提交
268 269 270 271 272 273
    }, [
        t,
        bottom,
        search,
        searching,
        searchResult,
274 275 276
        modelGraphs,
        selectedGraph,
        changeGraph,
P
Peter Pan 已提交
277 278 279 280 281
        onSearch,
        onSelect,
        showAttributes,
        showInitializers,
        showNames,
P
Peter Pan 已提交
282
        horizontal,
P
Peter Pan 已提交
283
        rendered,
284
        loading,
P
Peter Pan 已提交
285 286 287 288
        nodeData,
        nodeDocumentation
    ]);

289 290 291 292
    const uploader = useMemo(() => <Uploader onClickUpload={onClickFile} onDropFiles={setModelFile} />, [
        onClickFile,
        setModelFile
    ]);
293

294 295
    return (
        <>
P
Peter Pan 已提交
296
            <Title>{t('common:graph')}</Title>
P
Peter Pan 已提交
297 298
            <ModelPropertiesDialog data={modelData} onClose={() => setModelData(null)} />
            <Content aside={aside}>
299 300 301 302 303
                {loading ? (
                    <Loading>
                        <HashLoader size="60px" color={primaryColor} />
                    </Loading>
                ) : (
P
Peter Pan 已提交
304
                    <GraphComponent
305 306 307 308 309 310
                        ref={graph}
                        files={files}
                        uploader={uploader}
                        showAttributes={showAttributes}
                        showInitializers={showInitializers}
                        showNames={showNames}
P
Peter Pan 已提交
311
                        horizontal={horizontal}
312
                        onRendered={() => setRendered(true)}
313
                        onOpened={setOpenedModel}
314 315 316 317 318 319 320 321 322
                        onSearch={data => setSearchResult(data)}
                        onShowModelProperties={data => setModelData(data)}
                        onShowNodeProperties={data => {
                            setNodeData(data);
                            setNodeDocumentation(null);
                        }}
                        onShowNodeDocumentation={data => setNodeDocumentation(data)}
                    />
                )}
P
Peter Pan 已提交
323 324 325 326 327 328 329 330 331
                <input
                    ref={file}
                    type="file"
                    multiple={false}
                    onChange={onChangeFile}
                    style={{
                        display: 'none'
                    }}
                />
332 333 334 335 336
            </Content>
        </>
    );
};

P
Peter Pan 已提交
337
export default Graph;