Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
31ca6f4a78 | ||
|
|
1f9bc3a2d3 | ||
|
|
aad81bedbd | ||
|
|
1158c5fc1d | ||
|
|
a9727b990d | ||
|
|
a9f94a2cfa | ||
|
|
eb0b9bfc40 | ||
|
|
b4e1a99add | ||
|
|
c21d3ff504 | ||
|
|
2957d95133 | ||
|
|
d6ce727b0c | ||
|
|
c18c9fadd1 | ||
|
|
dce33716d9 | ||
|
|
cf8ba61943 | ||
|
|
26383aa79d | ||
|
|
136bd77d5f | ||
|
|
ba6620f040 | ||
|
|
4a8e7b7dc8 | ||
|
|
6ca48eb537 | ||
|
|
2864a098ab | ||
|
|
fec0cceba6 | ||
|
|
3bf39ce1b0 | ||
|
|
3a047b5bbe | ||
|
|
50145d4d1a | ||
|
|
4df2e8a48b | ||
|
|
3dae451776 | ||
|
|
4d038f37b3 | ||
|
|
a09ef4df65 | ||
|
|
628862d024 | ||
|
|
af83be1fcc | ||
|
|
05cb5527e3 | ||
|
|
7d910a0f75 | ||
|
|
ab74f6426e | ||
|
|
390bb8ff67 | ||
|
|
fcd108f9e7 |
@@ -1,4 +1,5 @@
|
||||
.idea
|
||||
example-project
|
||||
docs
|
||||
src/public/js
|
||||
src/public/js
|
||||
build
|
||||
|
||||
11
Dockerfile
Normal file
11
Dockerfile
Normal file
@@ -0,0 +1,11 @@
|
||||
FROM node:14-slim
|
||||
|
||||
WORKDIR /usr/src/codecrumbs
|
||||
|
||||
COPY package*.json ./
|
||||
|
||||
RUN yarn install
|
||||
|
||||
COPY . .
|
||||
|
||||
EXPOSE 2018 3018
|
||||
60
README.md
60
README.md
@@ -18,17 +18,28 @@
|
||||
<a href="#support">Support</a>
|
||||
</h3>
|
||||
|
||||
## What
|
||||
> **Have you ever got lost in a big or unknown codebase?** This tool will help you to solve that. Also, it will increase your development speed and give more knowledge about your application architecture.
|
||||
>
|
||||
**Have you ever got lost in a big or unknown codebase?** This tool will help you to solve that. Also, it will increase your development speed and give more knowledge about your application architecture.
|
||||
> If you like this project, follow me on Twitter [@bliashenko](https://twitter.com/bliashenko) to hear about things I am building.
|
||||
|
||||
## Codecrumbs v2
|
||||
Check out new version of this project as [standalone application](https://codecrumbs.io). Just in a few clicks you can start exploring a codebase in more efficient way, create interactive visual guides and share them with others on your own blog! See [quick guide here](https://codecrumbs.io/guides/web-app-with-github/).
|
||||
|
||||
<p align="center">
|
||||
<a href="https://codecrumbs.io" target="_blank">
|
||||
<img src="https://codecrumbs.io/external/img/common/app-ui-1.png" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
|
||||
## Demo
|
||||
Check out prepared example for [**standalone version running here**](https://codecrumbs.io/app).
|
||||
|
||||
## Codecrumbs v1
|
||||
|
||||
>**How it works?** You run `codecrumbs` command for a codebase, it analyzes source code and builds its visual representation. Write down a codecrumb-comment and codebase state will be reflected by visual client in browser on the fly.
|
||||
>
|
||||
> Check out [my talk at React-Finland](https://www.youtube.com/watch?v=S_1-1jzLxm4) for more details.
|
||||
>
|
||||
>-[@bliashenko](https://twitter.com/bliashenko)
|
||||
|
||||
## Demo
|
||||
Check out the example of [**standalone version running here**](https://codecrumbs.io/#showcase=todo-react-redux).
|
||||
|
||||
<img src="/docs/main-ui-3.png" width="100%"/>
|
||||
|
||||
@@ -45,14 +56,13 @@ Run codecrumbs with CLI params or specify static config file `codecrumbs.config.
|
||||
|
||||
CLI | Config file | Description | Example
|
||||
--- | --- | --- | ---
|
||||
```-d``` | ```projectDir``` | Relative path to project source code directory | ```-d src```
|
||||
```-e``` | ```entryPoint``` | Relative path to project source entry point file (must be inside ```dir```) | ```-e src/app.js```
|
||||
```-x``` | ```excludeDir``` | Relative path(or paths separated by ```,```) to directories for exclusion | ```-x src/doc,src/thirdparty```
|
||||
```-i``` | ```ideCmd``` | command to open file in IDE from bash (default 'webstorm') | ```-i code```
|
||||
```-p``` | ```clientPort``` | Port for Codecrumbs client (optional, default *2018*) | ```-p 2019```
|
||||
```-n``` | ```projectNameAlias``` | Project name alias (optional, default same as ```-d``` value) | ```-n my-hello-world```
|
||||
```-C``` | - | Path to codecrumbs.config.js (optional, by default will try to find the file in PWD) | ```-C config/codecrumbs.config.js```
|
||||
```-D``` | ```debugModeEnabled``` | Enable debug mode for logs (optional, default is ```false```) | ```-D```
|
||||
```d``` | ```projectDir``` | Relative path to project source code directory | ```-d src```
|
||||
```e``` | ```entryPoint``` | Relative path to project source entry point file (must be inside ```dir```) | ```-e src/app.js```
|
||||
```x``` | ```excludeDir``` | Relative path(or paths separated by ```,```) to directories for exclusion | ```-x src/doc,src/thirdparty```
|
||||
```p``` | ```clientPort``` | Port for Codecrumbs client (optional, default *2018*) | ```-p 2019```
|
||||
```n``` | ```projectNameAlias``` | Project name alias (optional, default same as ```-d``` value) | ```-n my-hello-world```
|
||||
```C``` | - | Path to codecrumbs.config.js (optional, by default will try to find the file in PWD) | ```-C config/codecrumbs.config.js```
|
||||
```D``` | ```debugModeEnabled``` | Enable debug mode for logs (optional, default is ```false```) | ```-D```
|
||||
|
||||
## Features
|
||||
### Breadcrumbs and trails
|
||||
@@ -108,12 +118,6 @@ Current version supports next programming languages:
|
||||
|
||||
Please file an issue to support other language you would like to have.
|
||||
|
||||
### Download & Upload (learn and share your knowledge)
|
||||
|
||||
You can take a snapshot of application state at any point of time and share it with others. Simply download the json file of codecrumbs store (*top-right corner, "setup -> download"*). This json file can be then uploaded to codecrumbs (*top-right corner, "setup -> upload"*) to represent exactly same picture, even without having that project locally!
|
||||
|
||||
<img src="/docs/upload-feature-2.gif" width="100%"/>
|
||||
|
||||
### Dependencies
|
||||
> Note: In current version only [JavaScript, TypeScript] offer this feature
|
||||
|
||||
@@ -131,18 +135,6 @@ UI explained:
|
||||
|
||||
[js2flowchart](https://github.com/Bogdan-Lyashenko/js-code-to-svg-flowchart) is used in the sidebar to draw flowchart for the selected file code.
|
||||
|
||||
### IDE integration
|
||||
> Check ```-i``` CLI param first to configure command.
|
||||
|
||||
Navigate from browser to your code editor simply by clicking ```Command+click```(or ```Alt+click```) on a file or a codecrumb to open file in your IDE.
|
||||
|
||||
<img src="/docs/ide-integration.gif" width="100%"/>
|
||||
|
||||
## Case studies
|
||||
The tool (codecrumbs) allows us to learn, document and explain a codebase much faster. Also, with *Download & Upload* feature it becomes super easy to collect and share your "investigation results".
|
||||
|
||||
The ultimate goal is to have many case studies hosting at [https://codecrumbs.io](https://codecrumbs.io/). **The library of projects "explained with codecrumbs", the place for collaborative learning**. More features around that coming soon, stay tuned.
|
||||
|
||||
## Support
|
||||
Any support is very much appreciated! 👍 😘 ❤️
|
||||
If you like this project, please, **put a :star: and tweet about it**. Thanks!
|
||||
@@ -174,6 +166,4 @@ yarn && yarn start
|
||||
|
||||
## WIP
|
||||
Next features are developing:
|
||||
- **eject codecrumbs** - ability to remove all "breadcrumbs" from source code in "one click"
|
||||
- **data transferring between cc trail steps**
|
||||
- **VS Code extension** - some neat features right inside the code editor. Checkout [the repo here](https://github.com/Bogdan-Lyashenko/vs-code-codecrumbs).
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.8 MiB |
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Requests\CommentRequest;
|
||||
|
||||
use App\Models\Post;
|
||||
use App\Models\Comment;
|
||||
use Auth;
|
||||
use Validator;
|
||||
|
||||
class CommentController extends Controller
|
||||
{
|
||||
function index()
|
||||
{
|
||||
// index
|
||||
}
|
||||
}
|
||||
18
example-project/src-php/Http/Controllers/PostController.php
Normal file
18
example-project/src-php/Http/Controllers/PostController.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Requests\PostRequest;
|
||||
use App\Models\Post;
|
||||
use App\Models\Category;
|
||||
use Hashids\Hashids;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class PostController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
// index
|
||||
}
|
||||
}
|
||||
15
example-project/src-php/Http/Requests/PostRequest.php
Normal file
15
example-project/src-php/Http/Requests/PostRequest.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use App\Rules\Category;
|
||||
|
||||
|
||||
class PostRequest extends FormRequest
|
||||
{
|
||||
public function authorize()
|
||||
{
|
||||
// authorize
|
||||
}
|
||||
}
|
||||
13
example-project/src-php/Models/Category.php
Normal file
13
example-project/src-php/Models/Category.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Category extends Model
|
||||
{
|
||||
public function posts()
|
||||
{
|
||||
// posts
|
||||
}
|
||||
}
|
||||
13
example-project/src-php/Models/Post.php
Normal file
13
example-project/src-php/Models/Post.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Post extends Model
|
||||
{
|
||||
public function user()
|
||||
{
|
||||
// user
|
||||
}
|
||||
}
|
||||
7
example-project/src-php/index.php
Normal file
7
example-project/src-php/index.php
Normal file
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace App;
|
||||
|
||||
use App\Http\Controllers\CommentController;
|
||||
use App\Http\Controllers\PostController;
|
||||
use App\Http\Requests\PostRequest;
|
||||
10
package.json
10
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "codecrumbs",
|
||||
"version": "1.6.17",
|
||||
"version": "1.8.3",
|
||||
"author": "Bohdan Liashenko",
|
||||
"license": "BSD-3-Clause",
|
||||
"repository": {
|
||||
@@ -16,9 +16,8 @@
|
||||
"server:cli": "node cli/index.cli.js -e example-project/src-client/index.js -d example-project/src-client/",
|
||||
"server-debug": "nodemon --inspect src/index.dev.js",
|
||||
"clean": "rm -rf build",
|
||||
"babel-compile-standalone": "babel src/public/js -d build --config-file ./src/public/babel.config.js --copy-files",
|
||||
"webpack-compile-local": "cd src/public && webpack --config webpack.local.js --progress",
|
||||
"build": "yarn clean && yarn babel-compile-standalone && yarn webpack-compile-local",
|
||||
"build": "yarn clean && yarn webpack-compile-local",
|
||||
"pretty": "prettier --write \"./src/public/js/**/*.js\""
|
||||
},
|
||||
"bin": {
|
||||
@@ -41,9 +40,10 @@
|
||||
"lodash": "^4.17.10",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"madge": "^3.4.4",
|
||||
"php-parser": "^3.0.2",
|
||||
"portscanner": "^2.2.0",
|
||||
"react": "^16.7.0",
|
||||
"react-dom": "^16.7.0",
|
||||
"react": "^16.8.6",
|
||||
"react-dom": "^16.8.6",
|
||||
"react-redux": "^5.0.7",
|
||||
"react-syntax-highlighter": "8.0.1",
|
||||
"redux": "^4.0.0",
|
||||
|
||||
@@ -24,6 +24,13 @@ const namespaceTypeScriptExample = {
|
||||
clientPort: 2018
|
||||
};
|
||||
|
||||
const namespacePhpExample = {
|
||||
projectNameAlias: 'php-example-server',
|
||||
projectDir: `example-project/src-php`,
|
||||
entryPoint: `example-project/src-php/index.php`,
|
||||
clientPort: 2018
|
||||
};
|
||||
|
||||
const namespaceDebug = {
|
||||
projectNameAlias: 'debug',
|
||||
projectDir: `example-project/debug`,
|
||||
@@ -41,7 +48,8 @@ const namespaceLanguageTest = {
|
||||
const args = process.argv.slice(2);
|
||||
const namespaces = {
|
||||
two: namespaceTwo,
|
||||
ts: namespaceTypeScriptExample
|
||||
ts: namespaceTypeScriptExample,
|
||||
php: namespacePhpExample
|
||||
};
|
||||
const namespace = namespaces[args[0]] !== undefined ? namespaces[args[0]] : namespaceOne;
|
||||
server.setup(namespace, { isDev: true });
|
||||
|
||||
@@ -59,9 +59,6 @@ const App = (props = {}) => {
|
||||
<Suspense fallback={null}>
|
||||
<SideBar />
|
||||
</Suspense>
|
||||
{props.extraLayout.appBodyBottom && (
|
||||
<props.extraLayout.appBodyBottom.Component {...props} />
|
||||
)}
|
||||
</div>
|
||||
|
||||
<footer className="footer">
|
||||
|
||||
@@ -11,7 +11,7 @@ const PADDING_TOP = 5;
|
||||
//TODO: add select with several themes
|
||||
//TODO: scrool to/highlight crumbed lines
|
||||
//https://github.com/conorhastings/react-syntax-highlighter/blob/master/README.md
|
||||
export default class extends React.Component {
|
||||
export default class extends React.PureComponent {
|
||||
fixScroll() {
|
||||
const { dependenciesLines = [], crumbedLines = [], lineHeight } = this.props;
|
||||
if (!this.codeRef || !this.codeRef.scrollTo) {
|
||||
|
||||
@@ -8,8 +8,7 @@ import 'antd/es/alert/style';
|
||||
|
||||
import { NO_TRAIL_FLOW } from '../../../../shared-constants';
|
||||
import { getCodeCrumbsUserChoice } from '../../../../core/dataBus/selectors';
|
||||
import { getNamespacesList } from '../../../../core/dataBus/selectors';
|
||||
import { gatherFlowStepsData } from '../../../treeDiagram/component/Tree/CodeCrumbs/helpers';
|
||||
import { getSharedFlowStepsData } from '../../../../core/namespaceIntegration/selectors';
|
||||
|
||||
import Code from '../Code';
|
||||
import './index.less';
|
||||
@@ -58,7 +57,6 @@ const CrumbsTab = props => {
|
||||
};
|
||||
|
||||
const mapStateToProps = (state, props) => {
|
||||
const namespacesList = getNamespacesList(state);
|
||||
const { namespace } = props;
|
||||
|
||||
const {
|
||||
@@ -69,10 +67,7 @@ const mapStateToProps = (state, props) => {
|
||||
namespace
|
||||
});
|
||||
|
||||
const { involvedNsData, sortedFlowSteps } = gatherFlowStepsData(state, {
|
||||
currentSelectedCrumbedFlowKey,
|
||||
namespacesList
|
||||
});
|
||||
const { involvedNsData, sortedFlowSteps } = getSharedFlowStepsData(state);
|
||||
|
||||
if (currentSelectedCrumbedFlowKey === NO_TRAIL_FLOW) {
|
||||
return {
|
||||
|
||||
@@ -12,7 +12,7 @@ import 'antd/es/icon/style';
|
||||
import { VIEW_TYPES } from '../../../../../core/controlsBus/constants';
|
||||
import './ViewSwitch.less';
|
||||
|
||||
class ViewSwitch extends React.Component {
|
||||
class ViewSwitch extends React.PureComponent {
|
||||
renderMenu() {
|
||||
const {
|
||||
name,
|
||||
@@ -78,7 +78,7 @@ class ViewSwitch extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { name, itemKey, subMenuItems, checkedState, toggleSwitch } = this.props;
|
||||
const { name, itemKey, subMenuItems, checkedState, disabledState, toggleSwitch } = this.props;
|
||||
|
||||
return (
|
||||
<div className="ViewSwitchItem">
|
||||
@@ -92,6 +92,7 @@ class ViewSwitch extends React.Component {
|
||||
size="small"
|
||||
checked={checkedState[itemKey]}
|
||||
onChange={v => toggleSwitch(itemKey, v)}
|
||||
disabled={disabledState[itemKey]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
}
|
||||
|
||||
.settingContainer {
|
||||
display: flex;
|
||||
display: none;
|
||||
flex-direction: row;
|
||||
|
||||
.spacer {
|
||||
@@ -26,4 +26,4 @@
|
||||
border-left: 1px solid #d9d9d9;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
overflow: auto;
|
||||
width: 100%;
|
||||
margin-top: 20px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.MainLoader {
|
||||
|
||||
@@ -35,7 +35,7 @@ const DetailsComponent = props => {
|
||||
};
|
||||
|
||||
const ccUnderlayPaddingH = 20;
|
||||
class CodeComponent extends React.Component {
|
||||
class CodeComponent extends React.PureComponent {
|
||||
state = {};
|
||||
|
||||
onMouseEnter = () => {
|
||||
@@ -48,8 +48,7 @@ class CodeComponent extends React.Component {
|
||||
|
||||
render() {
|
||||
const { position, crumbNodeLines, file, language, namespace } = this.props;
|
||||
const { fileCode } = file.data;
|
||||
|
||||
const { fileCode, path, name } = file.data;
|
||||
const { isExpanded } = this.state;
|
||||
|
||||
return (
|
||||
@@ -63,6 +62,12 @@ class CodeComponent extends React.Component {
|
||||
onMouseEnter={this.onMouseEnter}
|
||||
onMouseLeave={this.onMouseLeave}
|
||||
>
|
||||
<div className={'FilePath'}>
|
||||
<span>{path.replace(name, '')}</span>
|
||||
<span>
|
||||
<b>{name}</b>
|
||||
</span>
|
||||
</div>
|
||||
<Suspense fallback={null}>
|
||||
<Code
|
||||
namespace={namespace}
|
||||
@@ -101,7 +106,7 @@ const ExtraInfoSet = ({
|
||||
namespace,
|
||||
language
|
||||
}) => {
|
||||
if (!detailsEnabled && !codePreviewEnabled) return null;
|
||||
if ((!detailsEnabled && !codePreviewEnabled) || !sortedFlowSteps) return null;
|
||||
|
||||
const crumbs = sortedFlowSteps.length ? sortedFlowSteps : flowSteps;
|
||||
return (
|
||||
|
||||
@@ -22,6 +22,12 @@
|
||||
transition: height .1s;
|
||||
overflow: hidden;
|
||||
z-index: 1;
|
||||
|
||||
.FilePath {
|
||||
border-bottom: 1px solid #ebedf0;
|
||||
font-size: 11px;
|
||||
padding: 1px 5px;
|
||||
}
|
||||
}
|
||||
|
||||
@CcCodeContainerExpandedHeight: 300px;
|
||||
@@ -47,4 +53,4 @@
|
||||
height: (@CcCodeContainerExpandedHeight + @ccUnderlayPaddingV);
|
||||
transition: height, background-color .1s;
|
||||
z-index: 2;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,10 @@ export default props => {
|
||||
selectedCcFlowEdgeNodes
|
||||
} = props;
|
||||
|
||||
if (!involvedNsData || !involvedNsData[namespace]) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { codecrumbsLayoutMap } = involvedNsData[namespace];
|
||||
const ccNamespacesKeys = Object.keys(involvedNsData || {});
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import { isCodeCrumbSelected, getCcPosition } from './helpers';
|
||||
|
||||
const Tree = props => {
|
||||
const {
|
||||
sourceDiagramOn,
|
||||
ccAlightPoint,
|
||||
ccShiftIndexMap,
|
||||
shiftToCenterPoint,
|
||||
@@ -19,6 +20,10 @@ const Tree = props => {
|
||||
selectedCcFlowEdgeNodes
|
||||
} = props;
|
||||
|
||||
if (!ccShiftIndexMap) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{Object.keys(codecrumbsLayoutMap).map(key => {
|
||||
@@ -58,21 +63,23 @@ const Tree = props => {
|
||||
<React.Fragment
|
||||
key={`code-crumb-edge-${crumbData.name}-${crumbPosition.x}-${crumbPosition.y}`}
|
||||
>
|
||||
{!i && (
|
||||
<PartEdge
|
||||
sourcePosition={position}
|
||||
parentName={file.name}
|
||||
ccAlightPoint={crumbPosition.x}
|
||||
singleCrumb={singleCrumb}
|
||||
/>
|
||||
)}
|
||||
{!singleCrumb && (
|
||||
<CodeCrumbMultiEdge
|
||||
sourcePosition={position}
|
||||
targetPosition={crumbPosition}
|
||||
ccAlightPoint={i && firstCrumbXPoint}
|
||||
/>
|
||||
)}
|
||||
{!i &&
|
||||
sourceDiagramOn && (
|
||||
<PartEdge
|
||||
sourcePosition={position}
|
||||
parentName={file.name}
|
||||
ccAlightPoint={crumbPosition.x}
|
||||
singleCrumb={singleCrumb}
|
||||
/>
|
||||
)}
|
||||
{!singleCrumb &&
|
||||
sourceDiagramOn && (
|
||||
<CodeCrumbMultiEdge
|
||||
sourcePosition={position}
|
||||
targetPosition={crumbPosition}
|
||||
ccAlightPoint={i && firstCrumbXPoint}
|
||||
/>
|
||||
)}
|
||||
<CodeCrumbName
|
||||
position={crumbPosition}
|
||||
loc={codeCrumbsLineNumbers ? crumbData.displayLoc : ''}
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
import { getCodeCrumbsUserChoice, getSourceLayout } from '../../../../../core/dataBus/selectors';
|
||||
import { NO_TRAIL_FLOW } from '../../../../../core/constants';
|
||||
|
||||
export const isCodeCrumbsEqual = (cc, currentCc) =>
|
||||
cc.name === currentCc.name && cc.flowStep === currentCc.flowStep;
|
||||
|
||||
@@ -13,100 +10,4 @@ export const isCodeCrumbSelected = (selectedCcFlowEdgeNodes, cc) => {
|
||||
);
|
||||
};
|
||||
|
||||
const stepSorter = (a, b) => a.step - b.step;
|
||||
|
||||
const getFlowSteps = ({
|
||||
namespace,
|
||||
codeCrumbedFlowsMap,
|
||||
selectedCrumbedFlowKey,
|
||||
codecrumbsLayoutMap
|
||||
}) => {
|
||||
const currentFlow = codeCrumbedFlowsMap[selectedCrumbedFlowKey] || {};
|
||||
|
||||
return Object.keys(currentFlow).reduce((flowSteps, filePath) => {
|
||||
const steps = ((codecrumbsLayoutMap[filePath] && codecrumbsLayoutMap[filePath].children) || [])
|
||||
.filter(({ data }) => data.params.flow === selectedCrumbedFlowKey)
|
||||
.map(({ data, x, y }) => ({
|
||||
...data,
|
||||
namespace,
|
||||
filePath,
|
||||
step: data.params.flowStep,
|
||||
flow: selectedCrumbedFlowKey,
|
||||
x,
|
||||
y
|
||||
}));
|
||||
|
||||
return [...flowSteps, ...steps];
|
||||
}, []);
|
||||
};
|
||||
|
||||
const createCcShiftIndexMap = sortedFlowSteps => {
|
||||
const ccMap = {};
|
||||
let shiftOrderIndex = 0;
|
||||
|
||||
sortedFlowSteps.forEach((crumb, i, list) => {
|
||||
if (!i) {
|
||||
ccMap[crumb.id] = shiftOrderIndex;
|
||||
return;
|
||||
}
|
||||
|
||||
if (crumb.x < list[i - 1].x) {
|
||||
shiftOrderIndex++;
|
||||
}
|
||||
|
||||
ccMap[crumb.id] = shiftOrderIndex;
|
||||
});
|
||||
|
||||
return ccMap;
|
||||
};
|
||||
|
||||
export const gatherFlowStepsData = (state, { namespacesList, currentSelectedCrumbedFlowKey }) => {
|
||||
const flowStepsData = namespacesList.reduce(
|
||||
(acc, ns) => {
|
||||
const namespaceProps = { namespace: ns };
|
||||
const { selectedCrumbedFlowKey, codeCrumbedFlowsMap } = getCodeCrumbsUserChoice(
|
||||
state,
|
||||
namespaceProps
|
||||
);
|
||||
|
||||
if (currentSelectedCrumbedFlowKey !== selectedCrumbedFlowKey) {
|
||||
return acc;
|
||||
}
|
||||
|
||||
const { codecrumbsLayoutMap, ccAlightPoint } = getSourceLayout(state, namespaceProps);
|
||||
|
||||
const flowSteps = getFlowSteps({
|
||||
namespace: ns,
|
||||
codeCrumbedFlowsMap,
|
||||
selectedCrumbedFlowKey,
|
||||
codecrumbsLayoutMap
|
||||
});
|
||||
|
||||
return {
|
||||
flowSteps,
|
||||
sortedFlowSteps:
|
||||
selectedCrumbedFlowKey !== NO_TRAIL_FLOW
|
||||
? [...acc.sortedFlowSteps, ...flowSteps].sort(stepSorter)
|
||||
: [],
|
||||
involvedNsData: {
|
||||
...acc.involvedNsData,
|
||||
[ns]: { codecrumbsLayoutMap, ccAlightPoint }
|
||||
}
|
||||
};
|
||||
},
|
||||
{ involvedNsData: {}, flowSteps: [], sortedFlowSteps: [] }
|
||||
);
|
||||
|
||||
return {
|
||||
...flowStepsData,
|
||||
ccShiftIndexMap: createCcShiftIndexMap(flowStepsData.sortedFlowSteps)
|
||||
};
|
||||
};
|
||||
|
||||
export const getCcPosition = (x, index = 0) => x + 70 * index;
|
||||
|
||||
export const getMaxWidthForNs = (state, { namespacesList }) =>
|
||||
namespacesList.reduce((maxWidth, namespace) => {
|
||||
const { layoutSize } = getSourceLayout(state, { namespace });
|
||||
return layoutSize && layoutSize.width > maxWidth ? layoutSize.width : maxWidth;
|
||||
}, 0);
|
||||
|
||||
@@ -18,7 +18,7 @@ import Tree from './Tree';
|
||||
import FlowEdges from './FlowEdge';
|
||||
|
||||
const mapStateToProps = (state, props) => {
|
||||
const { codeCrumbsMinimize, codeCrumbsLineNumbers } = getCheckedState(state);
|
||||
const { codeCrumbsMinimize, codeCrumbsLineNumbers, sourceDiagramOn } = getCheckedState(state);
|
||||
|
||||
const namespacesList = getNamespacesList(state);
|
||||
|
||||
@@ -35,6 +35,7 @@ const mapStateToProps = (state, props) => {
|
||||
});
|
||||
|
||||
return {
|
||||
sourceDiagramOn,
|
||||
codecrumbsLayoutMap,
|
||||
ccAlightPoint,
|
||||
filesMap,
|
||||
|
||||
@@ -2,24 +2,20 @@ import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { DependenciesEdge, DependenciesArrow } from '../../Edge/DepenenciesEdge';
|
||||
import { FileName } from '../../../component/Node/File';
|
||||
import { selectDependencyEdge } from '../../../../../core/dataBus/actions';
|
||||
import {
|
||||
getSourceLayout,
|
||||
getSourceUserChoice,
|
||||
getDependenciesUserChoice
|
||||
} from '../../../../../core/dataBus/selectors';
|
||||
import { getCheckedState } from '../../../../../core/controlsBus/selectors';
|
||||
|
||||
import { getGroupsAroundNode, checkIsEdgeSelected } from './utils';
|
||||
|
||||
const DependenciesTree = props => {
|
||||
const {
|
||||
language,
|
||||
selectedNode,
|
||||
filesLayoutMap,
|
||||
shiftToCenterPoint,
|
||||
sourceDiagramOn,
|
||||
onDependencyEdgeClick,
|
||||
selectedDependencyEdgeNodes
|
||||
} = props;
|
||||
@@ -33,28 +29,19 @@ const DependenciesTree = props => {
|
||||
<React.Fragment>
|
||||
{selectedNodeDependencies &&
|
||||
[selectedNodeDependencies].map(({ moduleName, importedModuleNames }) => {
|
||||
const moduleNode = filesLayoutMap[moduleName];
|
||||
const moduleNode = filesLayoutMap[selectedNode.path];
|
||||
if (!moduleNode) return null;
|
||||
|
||||
const { name, path } = moduleNode.data;
|
||||
const [mX, mY] = [moduleNode.y, moduleNode.x];
|
||||
const targetPosition = shiftToCenterPoint(mX, mY);
|
||||
const sourceNodes = [];
|
||||
if (!sourceDiagramOn) {
|
||||
// TODO: un sync with FileName in SourceTree, duplication
|
||||
sourceNodes.push(
|
||||
<FileName
|
||||
language={language}
|
||||
key={path}
|
||||
position={targetPosition}
|
||||
name={name}
|
||||
dependency={true}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const importedNodes = importedModuleNames
|
||||
.map(name => filesLayoutMap[name])
|
||||
.map(moduleName => {
|
||||
const key = Object.keys(selectedNode.dependencies).find(key => {
|
||||
return selectedNode.dependencies[key].moduleName === moduleName
|
||||
})
|
||||
return filesLayoutMap[key]
|
||||
})
|
||||
.filter(node => !!node);
|
||||
|
||||
const edges = [];
|
||||
@@ -72,11 +59,11 @@ const DependenciesTree = props => {
|
||||
groupNodes.forEach((importedNode, i) => {
|
||||
const [iX, iY] = [importedNode.y, importedNode.x];
|
||||
const sourcePosition = shiftToCenterPoint(iX, iY);
|
||||
const { path: importedNodePath, name } = importedNode.data;
|
||||
const { path: importedNodePath } = importedNode.data;
|
||||
|
||||
const selected =
|
||||
selectedDependencyEdgeNodes &&
|
||||
checkIsEdgeSelected(selectedDependencyEdgeNodes, moduleName, importedNodePath);
|
||||
checkIsEdgeSelected(selectedDependencyEdgeNodes, selectedNode.path, importedNodePath);
|
||||
|
||||
const edge = (
|
||||
<DependenciesEdge
|
||||
@@ -89,7 +76,7 @@ const DependenciesTree = props => {
|
||||
firstSourcePosition={i ? firstSourcePosition : null}
|
||||
onClick={() =>
|
||||
onDependencyEdgeClick({
|
||||
target: moduleName,
|
||||
target: selectedNode.path,
|
||||
sources: [importedNodePath],
|
||||
groupName
|
||||
})
|
||||
@@ -114,18 +101,6 @@ const DependenciesTree = props => {
|
||||
);
|
||||
arrowSelected ? selectedEdges.push(arrow) : edges.push(arrow);
|
||||
}
|
||||
|
||||
if (!sourceDiagramOn) {
|
||||
sourceNodes.push(
|
||||
<FileName
|
||||
key={importedNodePath}
|
||||
language={language}
|
||||
position={sourcePosition}
|
||||
name={name}
|
||||
dependency={true}
|
||||
/>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -134,7 +109,6 @@ const DependenciesTree = props => {
|
||||
<React.Fragment key={moduleName}>
|
||||
{edges}
|
||||
{selectedEdges}
|
||||
{sourceNodes}
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
@@ -143,8 +117,6 @@ const DependenciesTree = props => {
|
||||
};
|
||||
|
||||
const mapStateToProps = (state, props) => {
|
||||
const { sourceDiagramOn } = getCheckedState(state);
|
||||
|
||||
const { namespace } = props;
|
||||
const namespaceProps = { namespace };
|
||||
const { filesLayoutMap } = getSourceLayout(state, namespaceProps);
|
||||
@@ -152,7 +124,6 @@ const mapStateToProps = (state, props) => {
|
||||
const { selectedDependencyEdgeNodes } = getDependenciesUserChoice(state, namespaceProps);
|
||||
|
||||
return {
|
||||
sourceDiagramOn,
|
||||
filesLayoutMap,
|
||||
selectedNode,
|
||||
selectedDependencyEdgeNodes
|
||||
|
||||
@@ -114,7 +114,7 @@ const SourceTree = props => {
|
||||
selectedNodeDependencies[path] &&
|
||||
!selectedNodeDependencies[path].importedModuleNames.length
|
||||
}
|
||||
onNodeClick={e => onFileNodeClick(e, fileNode)}
|
||||
onNodeClick={e => selectedNode !== fileNode && onFileNodeClick(e, fileNode)}
|
||||
/>
|
||||
);
|
||||
} else if (type === DIR_NODE_TYPE) {
|
||||
|
||||
@@ -8,12 +8,7 @@ import { UnderLayer } from './UnderLayer';
|
||||
import './TreeDiagram.less';
|
||||
|
||||
import { buildShiftToPoint } from '../../../core/dataBus/utils/geometry';
|
||||
import {
|
||||
getProjectMetadata,
|
||||
getSourceLayout,
|
||||
getCodeCrumbsUserChoice,
|
||||
getNamespacesList
|
||||
} from '../../../core/dataBus/selectors';
|
||||
import { getProjectMetadata, getSourceLayout } from '../../../core/dataBus/selectors';
|
||||
import { getCheckedState, getValuesState } from '../../../core/controlsBus/selectors';
|
||||
import {
|
||||
selectDependencyEdge,
|
||||
@@ -21,9 +16,9 @@ import {
|
||||
saveTreeDiagramContentId
|
||||
} from '../../../core/dataBus/actions';
|
||||
import { setActiveNamespace } from '../../../core/namespaceIntegration/actions';
|
||||
import { gatherFlowStepsData, getMaxWidthForNs } from './Tree/CodeCrumbs/helpers';
|
||||
import { getSharedFlowStepsData } from '../../../core/namespaceIntegration/selectors';
|
||||
|
||||
class TreeDiagram extends React.Component {
|
||||
class TreeDiagram extends React.PureComponent {
|
||||
componentDidUpdate(prevProps) {
|
||||
if (!prevProps.layoutSize && this.props.layoutSize) {
|
||||
this.props.saveContentId(this.treeDiagramContent.getAttribute('id'));
|
||||
@@ -121,31 +116,7 @@ const mapStateToProps = (state, props) => {
|
||||
const { diagramZoom } = getValuesState(state);
|
||||
const { codeCrumbsDiagramOn } = getCheckedState(state);
|
||||
|
||||
let extendedCcProps = {};
|
||||
if (codeCrumbsDiagramOn) {
|
||||
const codeCrumbsUserChoice = getCodeCrumbsUserChoice(state, {
|
||||
namespace
|
||||
});
|
||||
|
||||
const namespacesList = getNamespacesList(state);
|
||||
const { flowSteps, sortedFlowSteps, involvedNsData, ccShiftIndexMap } = gatherFlowStepsData(
|
||||
state,
|
||||
{
|
||||
currentSelectedCrumbedFlowKey: codeCrumbsUserChoice.selectedCrumbedFlowKey,
|
||||
namespacesList
|
||||
}
|
||||
);
|
||||
|
||||
const maxWidth = getMaxWidthForNs(state, { namespacesList });
|
||||
|
||||
extendedCcProps = {
|
||||
flowSteps,
|
||||
sortedFlowSteps,
|
||||
involvedNsData,
|
||||
ccShiftIndexMap,
|
||||
maxWidth
|
||||
};
|
||||
}
|
||||
const extendedCcProps = codeCrumbsDiagramOn ? getSharedFlowStepsData(state) : {};
|
||||
|
||||
return {
|
||||
namespace,
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
left: 0;
|
||||
z-index: 2;
|
||||
border-left: 1px solid #ebedf0;
|
||||
background: rgba(255,255,255,0.8);
|
||||
font-size: 11px;
|
||||
|
||||
&.activeTreeDiagram {
|
||||
|
||||
@@ -7,8 +7,11 @@ import { getFoldersForPaths, downloadObjectAsJsonFile, uploadFileAsObject } from
|
||||
import {
|
||||
getTreeLayout,
|
||||
getFilesForCurrentCcFlow,
|
||||
getCodeCrumbsMapForCurrentCcFlow
|
||||
getCodeCrumbsMapForCurrentCcFlow,
|
||||
getFileNodesMap,
|
||||
getCodecrumbNodesMap
|
||||
} from './utils/treeLayout';
|
||||
import { calculateLayoutProps } from './utils/geometry';
|
||||
|
||||
import { ACTIONS } from './constants';
|
||||
import {
|
||||
@@ -155,6 +158,7 @@ export const calcFilesTreeLayoutNodes = namespace => (dispatch, getState) => {
|
||||
);
|
||||
|
||||
const {
|
||||
sourceDiagramOn,
|
||||
codeCrumbsDiagramOn,
|
||||
codeCrumbsMinimize,
|
||||
codeCrumbsDetails,
|
||||
@@ -172,16 +176,30 @@ export const calcFilesTreeLayoutNodes = namespace => (dispatch, getState) => {
|
||||
});
|
||||
}
|
||||
|
||||
const sourceLayoutTree = getTreeLayout(sourceTree, {
|
||||
includeFileChildren: codeCrumbsDiagramOn && !codeCrumbsMinimize,
|
||||
extraSpaceForDetails: codeCrumbsDetails,
|
||||
extraSpaceForCodePreview: codeCrumbsCodePreview,
|
||||
openedFolders,
|
||||
activeItemsMap,
|
||||
activeCodeCrumbs
|
||||
});
|
||||
|
||||
const filesLayoutMap = getFileNodesMap(sourceLayoutTree);
|
||||
const codecrumbsLayoutMap = getCodecrumbNodesMap(filesLayoutMap);
|
||||
const { ccAlightPoint, ...layoutSize } = calculateLayoutProps(sourceLayoutTree, {
|
||||
sourceDiagramOn
|
||||
});
|
||||
|
||||
return dispatch({
|
||||
type: ACTIONS.UPDATE_FILES_TREE_LAYOUT_NODES,
|
||||
payload: getTreeLayout(sourceTree, {
|
||||
includeFileChildren: codeCrumbsDiagramOn && !codeCrumbsMinimize,
|
||||
extraSpaceForDetails: codeCrumbsDetails,
|
||||
extraSpaceForCodePreview: codeCrumbsCodePreview,
|
||||
openedFolders,
|
||||
activeItemsMap,
|
||||
activeCodeCrumbs
|
||||
}),
|
||||
payload: {
|
||||
sourceLayoutTree,
|
||||
filesLayoutMap,
|
||||
codecrumbsLayoutMap,
|
||||
ccAlightPoint,
|
||||
layoutSize
|
||||
},
|
||||
namespace
|
||||
});
|
||||
};
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
updateFiles
|
||||
} from './actions';
|
||||
|
||||
class DataBusContainer extends React.Component {
|
||||
class DataBusContainer extends React.PureComponent {
|
||||
componentDidMount() {
|
||||
const { standalone } = this.props;
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { FOLDER_OPEN_STATE } from '../constants';
|
||||
|
||||
import { ACTIONS } from './constants';
|
||||
import { getFileNodesMap, getCodecrumbNodesMap } from './utils/treeLayout';
|
||||
import { calculateLayoutProps } from './utils/geometry';
|
||||
|
||||
const DefaultState = {};
|
||||
|
||||
@@ -36,7 +34,7 @@ export const getMergeState = (state, namespace) => namespaceStateUpdate => ({
|
||||
}
|
||||
});
|
||||
|
||||
const FULL_FEATURES_LANG_LIST = ['javascript', 'typescript'];
|
||||
const FULL_FEATURES_LANG_LIST = ['javascript', 'typescript', 'php'];
|
||||
|
||||
export default (state = DefaultState, action) => {
|
||||
const namespace = action.namespace;
|
||||
@@ -65,18 +63,9 @@ export default (state = DefaultState, action) => {
|
||||
return mergeState(action.payload);
|
||||
|
||||
case ACTIONS.UPDATE_FILES_TREE_LAYOUT_NODES: {
|
||||
const { payload: sourceLayoutTree } = action;
|
||||
|
||||
const { ccAlightPoint, ...layoutSize } = calculateLayoutProps(sourceLayoutTree);
|
||||
const filesLayoutMap = getFileNodesMap(sourceLayoutTree);
|
||||
const codecrumbsLayoutMap = getCodecrumbNodesMap(filesLayoutMap);
|
||||
|
||||
const { payload } = action;
|
||||
return mergeState({
|
||||
sourceLayoutTree,
|
||||
layoutSize,
|
||||
filesLayoutMap,
|
||||
codecrumbsLayoutMap,
|
||||
ccAlightPoint
|
||||
...payload
|
||||
});
|
||||
}
|
||||
|
||||
@@ -161,10 +150,9 @@ export default (state = DefaultState, action) => {
|
||||
const { fileNode } = action.payload;
|
||||
const dependenciesEntryName = fileNode ? fileNode.path : namespaceState.dependenciesEntryName;
|
||||
|
||||
// do some reducerr
|
||||
// do some reducer
|
||||
return mergeState({
|
||||
dependenciesEntryName,
|
||||
selectedDependencyEdgeNodes: null
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { CC_NODE_TYPE } from '../../constants';
|
||||
|
||||
export const calculateLayoutProps = (list, padding = 120) => {
|
||||
export const calculateLayoutProps = (list, { sourceDiagramOn }) => {
|
||||
if (!list) {
|
||||
return {
|
||||
width: 0,
|
||||
@@ -50,14 +50,14 @@ export const calculateLayoutProps = (list, padding = 120) => {
|
||||
|
||||
const yShift = Math.abs(Math.round(minY)) + 20;
|
||||
const height = Math.round(Math.abs(maxY) + Math.abs(yShift)) + 5;
|
||||
const width = Math.round(Math.abs(maxX + maxCcWidth) + Math.abs(minX) + 2 * padding);
|
||||
const width = Math.round(Math.abs(maxX + maxCcWidth) + Math.abs(minX) + 2 * 20);
|
||||
|
||||
return {
|
||||
xShift: padding / 4,
|
||||
xShift: 5,
|
||||
width,
|
||||
height,
|
||||
yShift,
|
||||
ccAlightPoint
|
||||
ccAlightPoint: sourceDiagramOn ? ccAlightPoint : 50
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -1,10 +1,31 @@
|
||||
import { ACTIONS } from './constants';
|
||||
import { gatherFlowStepsData } from './utils/sharedCcFlows';
|
||||
import { getCodeCrumbsUserChoice, getNamespacesList } from '../dataBus/selectors';
|
||||
import { getActiveNamespace } from './selectors';
|
||||
|
||||
export const setActiveNamespace = payload => ({
|
||||
type: ACTIONS.SET_ACTIVE_NAMESPACE,
|
||||
payload
|
||||
});
|
||||
|
||||
export const calcSharedFlowStepsData = () => (dispatch, getState) => {
|
||||
const state = getState();
|
||||
|
||||
const namespace = getActiveNamespace(state);
|
||||
const namespacesList = getNamespacesList(state);
|
||||
const { selectedCrumbedFlowKey: currentSelectedCrumbedFlowKey } = getCodeCrumbsUserChoice(state, {
|
||||
namespace
|
||||
});
|
||||
|
||||
dispatch({
|
||||
type: ACTIONS.CALC_SHARED_FLOW_STEPS_DATA,
|
||||
payload: gatherFlowStepsData(state, {
|
||||
currentSelectedCrumbedFlowKey,
|
||||
namespacesList
|
||||
})
|
||||
});
|
||||
};
|
||||
|
||||
export const setFullState = payload => ({
|
||||
type: ACTIONS.SET_FULL_STATE,
|
||||
payload
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export const ACTIONS = {
|
||||
SET_ACTIVE_NAMESPACE: 'NAMESPACE_INTEGRATION.SET_ACTIVE_NAMESPACE',
|
||||
CALC_SHARED_FLOW_STEPS_DATA: 'NAMESPACE_INTEGRATION.CALC_SHARED_FLOW_STEPS_DATA',
|
||||
SET_FULL_STATE: 'NAMESPACE_INTEGRATION.SET_FULL_STATE',
|
||||
RESET_ALL: 'NAMESPACE_INTEGRATION.RESET_ALL'
|
||||
};
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { ACTIONS } from './constants';
|
||||
|
||||
const DefaultState = {
|
||||
activeNamespace: undefined
|
||||
activeNamespace: undefined,
|
||||
sharedFlowStepsData: null
|
||||
};
|
||||
|
||||
export default (state = DefaultState, action) => {
|
||||
@@ -12,6 +13,12 @@ export default (state = DefaultState, action) => {
|
||||
activeNamespace: action.payload
|
||||
};
|
||||
|
||||
case ACTIONS.CALC_SHARED_FLOW_STEPS_DATA:
|
||||
return {
|
||||
...state,
|
||||
sharedFlowStepsData: action.payload
|
||||
};
|
||||
|
||||
case ACTIONS.SET_FULL_STATE:
|
||||
return {
|
||||
...state,
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
export const getActiveNamespace = state => state.namespaceIntegration.activeNamespace;
|
||||
export const getSharedFlowStepsData = state => state.namespaceIntegration.sharedFlowStepsData;
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
import { getCodeCrumbsUserChoice, getSourceLayout } from '../../dataBus/selectors';
|
||||
import { NO_TRAIL_FLOW } from '../../../shared-constants';
|
||||
|
||||
export const gatherFlowStepsData = (state, { namespacesList, currentSelectedCrumbedFlowKey }) => {
|
||||
const flowStepsData = namespacesList.reduce(
|
||||
(acc, ns) => {
|
||||
const namespaceProps = { namespace: ns };
|
||||
const { selectedCrumbedFlowKey, codeCrumbedFlowsMap } = getCodeCrumbsUserChoice(
|
||||
state,
|
||||
namespaceProps
|
||||
);
|
||||
|
||||
if (currentSelectedCrumbedFlowKey !== selectedCrumbedFlowKey) {
|
||||
return acc;
|
||||
}
|
||||
|
||||
const { codecrumbsLayoutMap, ccAlightPoint } = getSourceLayout(state, namespaceProps);
|
||||
|
||||
const flowSteps = getFlowSteps({
|
||||
namespace: ns,
|
||||
codeCrumbedFlowsMap,
|
||||
selectedCrumbedFlowKey,
|
||||
codecrumbsLayoutMap
|
||||
});
|
||||
|
||||
return {
|
||||
flowSteps,
|
||||
sortedFlowSteps:
|
||||
selectedCrumbedFlowKey !== NO_TRAIL_FLOW
|
||||
? [...acc.sortedFlowSteps, ...flowSteps].sort(stepSorter)
|
||||
: [],
|
||||
involvedNsData: {
|
||||
...acc.involvedNsData,
|
||||
[ns]: { codecrumbsLayoutMap, ccAlightPoint }
|
||||
}
|
||||
};
|
||||
},
|
||||
{ involvedNsData: {}, flowSteps: [], sortedFlowSteps: [] }
|
||||
);
|
||||
|
||||
return {
|
||||
...flowStepsData,
|
||||
ccShiftIndexMap: createCcShiftIndexMap(flowStepsData.sortedFlowSteps),
|
||||
maxWidth: getMaxWidthForNs(state, { namespacesList })
|
||||
};
|
||||
};
|
||||
|
||||
const stepSorter = (a, b) => a.step - b.step;
|
||||
|
||||
const getFlowSteps = ({
|
||||
namespace,
|
||||
codeCrumbedFlowsMap,
|
||||
selectedCrumbedFlowKey,
|
||||
codecrumbsLayoutMap
|
||||
}) => {
|
||||
const currentFlow = codeCrumbedFlowsMap[selectedCrumbedFlowKey] || {};
|
||||
|
||||
return Object.keys(currentFlow).reduce((flowSteps, filePath) => {
|
||||
const steps = ((codecrumbsLayoutMap[filePath] && codecrumbsLayoutMap[filePath].children) || [])
|
||||
.filter(({ data }) => data.params.flow === selectedCrumbedFlowKey)
|
||||
.map(({ data, x, y }) => ({
|
||||
...data,
|
||||
namespace,
|
||||
filePath,
|
||||
step: data.params.flowStep,
|
||||
flow: selectedCrumbedFlowKey,
|
||||
x,
|
||||
y
|
||||
}));
|
||||
|
||||
return [...flowSteps, ...steps];
|
||||
}, []);
|
||||
};
|
||||
|
||||
const createCcShiftIndexMap = sortedFlowSteps => {
|
||||
const ccMap = {};
|
||||
let shiftOrderIndex = 0;
|
||||
|
||||
sortedFlowSteps.forEach((crumb, i, list) => {
|
||||
if (!i) {
|
||||
ccMap[crumb.id] = shiftOrderIndex;
|
||||
return;
|
||||
}
|
||||
|
||||
if (crumb.x < list[i - 1].x) {
|
||||
shiftOrderIndex++;
|
||||
}
|
||||
|
||||
ccMap[crumb.id] = shiftOrderIndex;
|
||||
});
|
||||
|
||||
return ccMap;
|
||||
};
|
||||
|
||||
const getMaxWidthForNs = (state, { namespacesList }) =>
|
||||
namespacesList.reduce((maxWidth, namespace) => {
|
||||
const { layoutSize } = getSourceLayout(state, { namespace }); // TODO: double select for getSourceLayout, use from above
|
||||
return layoutSize && layoutSize.width > maxWidth ? layoutSize.width : maxWidth;
|
||||
}, 0);
|
||||
@@ -9,13 +9,13 @@ import dataBus from '../dataBus/reducer';
|
||||
import namespaceIntegration from '../namespaceIntegration/reducer';
|
||||
import rootSaga from './sagas';
|
||||
|
||||
export default ({ extraReducers = {}, extraPersistWhiteList = [] } = {}) => {
|
||||
export default () => {
|
||||
const sagaMiddleware = createSagaMiddleware();
|
||||
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
|
||||
|
||||
const persistConfig = {
|
||||
key: 'codecrumbs-config-storage',
|
||||
whitelist: ['controlsBus', ...extraPersistWhiteList],
|
||||
whitelist: ['controlsBus'],
|
||||
storage
|
||||
};
|
||||
|
||||
@@ -24,8 +24,7 @@ export default ({ extraReducers = {}, extraPersistWhiteList = [] } = {}) => {
|
||||
combineReducers({
|
||||
controlsBus,
|
||||
dataBus,
|
||||
namespaceIntegration,
|
||||
...extraReducers
|
||||
namespaceIntegration
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
@@ -17,7 +17,8 @@ import { ACTIONS as SWITCHES_ACTIONS, CONTROLS_KEYS } from '../controlsBus/const
|
||||
import { setDisabledControl, toggleSwitch } from '../controlsBus/actions';
|
||||
import { getCheckedState } from '../controlsBus/selectors';
|
||||
|
||||
import { setActiveNamespace } from '../namespaceIntegration/actions';
|
||||
import { getActiveNamespace } from '../namespaceIntegration/selectors';
|
||||
import { setActiveNamespace, calcSharedFlowStepsData } from '../namespaceIntegration/actions';
|
||||
|
||||
function* reactOnSwitchToggle(action) {
|
||||
const namespacesList = yield select(getNamespacesList);
|
||||
@@ -29,6 +30,20 @@ function* reactOnSwitchToggle(action) {
|
||||
}
|
||||
|
||||
function* applyReactionOnSwitchToggleToNamespace({ switchKey, checked, namespace }) {
|
||||
if (switchKey === CONTROLS_KEYS.SOURCE_DIAGRAM_ON) {
|
||||
if (!checked) {
|
||||
yield all([
|
||||
put(toggleSwitch(CONTROLS_KEYS.DEPENDENCIES_DIAGRAM_ON, false)),
|
||||
put(setDisabledControl(CONTROLS_KEYS.DEPENDENCIES_DIAGRAM_ON, true))
|
||||
]);
|
||||
} else {
|
||||
yield all([
|
||||
put(setDisabledControl(CONTROLS_KEYS.DEPENDENCIES_DIAGRAM_ON, false)),
|
||||
reactByUpdatingFoldersState({ namespace })
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
if (switchKey === CONTROLS_KEYS.SOURCE_KEEP_ONLY_ACTIVE_ITEMS) {
|
||||
if (checked) {
|
||||
yield reactByUpdatingFoldersState({ namespace });
|
||||
@@ -102,14 +117,24 @@ function* applyReactionOnButtonActionToNamespace({ buttonKey, namespace }) {
|
||||
}
|
||||
}
|
||||
|
||||
function* reactOnUpdateFiles({namespace}) {
|
||||
const {dependenciesShowDirectOnly} = yield select(getCheckedState)
|
||||
if (dependenciesShowDirectOnly) {
|
||||
yield put(setDependenciesEntryPoint(undefined, namespace))
|
||||
}
|
||||
}
|
||||
|
||||
function* reactOnToggledFolder({ namespace }) {
|
||||
yield put(calcFilesTreeLayoutNodes(namespace));
|
||||
}
|
||||
|
||||
function* reactOnSourceSet({ namespace }) {
|
||||
const { dependenciesDiagramOn, codeCrumbsDiagramOn } = yield select(getCheckedState);
|
||||
const activeNamespace = yield select(getActiveNamespace);
|
||||
|
||||
yield put(setActiveNamespace(namespace));
|
||||
if (activeNamespace !== namespace) {
|
||||
yield put(setActiveNamespace(namespace));
|
||||
}
|
||||
|
||||
if (!dependenciesDiagramOn && !codeCrumbsDiagramOn) {
|
||||
yield reactByUpdatingFoldersState({ namespace });
|
||||
@@ -129,6 +154,13 @@ function* reactByUpdatingFoldersState({ namespace }) {
|
||||
yield put(calcFilesTreeLayoutNodes(namespace));
|
||||
}
|
||||
|
||||
function* reactByCalcSharedFlowStepsData() {
|
||||
const { codeCrumbsDiagramOn } = yield select(getCheckedState);
|
||||
if (codeCrumbsDiagramOn) {
|
||||
yield put(calcSharedFlowStepsData());
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: will work if switches per namespace as well
|
||||
/*function* reactByDisablingFeatures({ payload }) {
|
||||
const { fullFeaturesSupport } = yield select(state =>
|
||||
@@ -147,8 +179,11 @@ function* reactOnCcFlowEdgeSelect({ payload, namespace }) {
|
||||
}
|
||||
}
|
||||
|
||||
function* reactBySettingActiveNamespace({ payload, namespace }) {
|
||||
yield put(setActiveNamespace(namespace));
|
||||
function* reactBySettingActiveNamespace({ namespace }) {
|
||||
const activeNamespace = yield select(getActiveNamespace);
|
||||
if (activeNamespace !== namespace) {
|
||||
yield put(setActiveNamespace(namespace));
|
||||
}
|
||||
}
|
||||
|
||||
export default function* rootSaga() {
|
||||
@@ -160,9 +195,10 @@ export default function* rootSaga() {
|
||||
takeLatest(DATA_BUS_ACTIONS.SET_CHANGED_SOURCE_DATA, reactOnSourceSet),
|
||||
takeLatest(DATA_BUS_ACTIONS.SET_DEPENDENCIES_ENTRY_POINT, reactByUpdatingFoldersState),
|
||||
takeLatest(DATA_BUS_ACTIONS.SELECT_CODE_CRUMBED_FLOW, reactByUpdatingFoldersState),
|
||||
takeLatest(DATA_BUS_ACTIONS.UPDATE_FILES, reactByUpdatingFoldersState),
|
||||
takeLatest(DATA_BUS_ACTIONS.SELECT_CC_FLOW_EDGE, reactOnCcFlowEdgeSelect),
|
||||
takeLatest(DATA_BUS_ACTIONS.SELECT_NODE, reactBySettingActiveNamespace),
|
||||
takeLatest(DATA_BUS_ACTIONS.SELECT_CODE_CRUMB, reactBySettingActiveNamespace)
|
||||
takeLatest(DATA_BUS_ACTIONS.UPDATE_FILES, reactOnUpdateFiles),
|
||||
takeLatest(DATA_BUS_ACTIONS.SELECT_CODE_CRUMB, reactBySettingActiveNamespace),
|
||||
takeLatest(DATA_BUS_ACTIONS.UPDATE_FILES_TREE_LAYOUT_NODES, reactByCalcSharedFlowStepsData)
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -6,14 +6,13 @@ import { PersistGate } from 'redux-persist/integration/react';
|
||||
import App from './App';
|
||||
import getStore from './core/store';
|
||||
|
||||
const extraLayout = {};
|
||||
export default (options, mountNodeId) => {
|
||||
const { store, persistor } = getStore();
|
||||
|
||||
ReactDOM.render(
|
||||
<Provider store={store}>
|
||||
<PersistGate loading={null} persistor={persistor}>
|
||||
<App extraLayout={extraLayout} {...options} />
|
||||
<App {...options} />
|
||||
</PersistGate>
|
||||
</Provider>,
|
||||
document.getElementById(mountNodeId)
|
||||
|
||||
@@ -12,7 +12,8 @@ module.exports = merge(common, {
|
||||
openAnalyzer: false
|
||||
}),
|
||||
new webpack.DefinePlugin({
|
||||
'process.env.LOCAL': JSON.stringify(true)
|
||||
'process.env.LOCAL': JSON.stringify(true),
|
||||
'process.env.DEV': JSON.stringify(true)
|
||||
})
|
||||
]
|
||||
});
|
||||
|
||||
@@ -1,7 +1,70 @@
|
||||
const defaultDependencies = require('../default/dependencies');
|
||||
const path = require('path');
|
||||
const namespaces = require('./namespaces');
|
||||
const parser = require('./parser');
|
||||
|
||||
const getDependencies = async (entryPoint, projectDir) => {
|
||||
const phpNamespaces = await namespaces.parse(projectDir);
|
||||
|
||||
const dependencies = {
|
||||
[entryPoint]: phpNamespaces[entryPoint]
|
||||
};
|
||||
|
||||
if (phpNamespaces[entryPoint]) {
|
||||
phpNamespaces[entryPoint].importedModuleNames.forEach(moduleName => {
|
||||
Object.keys(phpNamespaces).forEach(itemPath => {
|
||||
if (phpNamespaces[itemPath].moduleName !== moduleName) {
|
||||
return;
|
||||
}
|
||||
|
||||
dependencies[itemPath] = phpNamespaces[itemPath];
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return dependencies;
|
||||
};
|
||||
|
||||
const getImports = (fileCode, itemPath) => {
|
||||
const phpNamespaces = namespaces.getCache();
|
||||
const parsed = parser.parseCode(fileCode, path.basename(itemPath));
|
||||
const dependencies = [];
|
||||
|
||||
const findSourceFile = phpNamespace => {
|
||||
return Object.keys(phpNamespaces).find(key => {
|
||||
return phpNamespaces[key].moduleName === phpNamespace;
|
||||
});
|
||||
};
|
||||
|
||||
(parsed.children || []).forEach(parsedChild => {
|
||||
(parsedChild.children || []).forEach(c => {
|
||||
if (c.kind !== 'usegroup') {
|
||||
return;
|
||||
}
|
||||
const dependence = {
|
||||
importNodeLines: [c.loc.start.line, c.loc.end.line],
|
||||
sourceFile: null,
|
||||
specifiers: []
|
||||
};
|
||||
c.items.forEach(item => {
|
||||
dependence.sourceFile = findSourceFile(item.name);
|
||||
const importSpecifierName =
|
||||
dependence && dependence.sourceFile && dependence.sourceFile.split('/').pop();
|
||||
|
||||
if (importSpecifierName) {
|
||||
dependence.specifiers.push({
|
||||
type: 'ImportSpecifier',
|
||||
name: importSpecifierName
|
||||
});
|
||||
}
|
||||
});
|
||||
dependencies.push(dependence);
|
||||
});
|
||||
});
|
||||
return dependencies.filter(({ sourceFile }) => !!sourceFile);
|
||||
};
|
||||
|
||||
// replace with own implementation if needed
|
||||
module.exports = {
|
||||
getImports: defaultDependencies.getImports,
|
||||
getDependencies: defaultDependencies.getDependencies
|
||||
getImports: getImports,
|
||||
getDependencies: getDependencies
|
||||
};
|
||||
|
||||
54
src/server/code-parse/language/php/namespaces.js
Normal file
54
src/server/code-parse/language/php/namespaces.js
Normal file
@@ -0,0 +1,54 @@
|
||||
const readdirRecursive = require('fs-readdir-recursive');
|
||||
const file = require('../../../utils/file');
|
||||
const extensions = require('./extensions');
|
||||
const path = require('path');
|
||||
const parser = require('./parser');
|
||||
|
||||
let cache = {};
|
||||
|
||||
const addNamespaces = (namespaces, itemPath, parsed) =>
|
||||
(parsed.children || []).filter(parsedChild => parsedChild.name).forEach(parsedChild => {
|
||||
const namespace = {
|
||||
moduleName: parsedChild.name,
|
||||
importedModuleNames: []
|
||||
};
|
||||
|
||||
(parsedChild.children || []).forEach(c => {
|
||||
switch (c.kind) {
|
||||
case 'usegroup':
|
||||
c.items.forEach(item => {
|
||||
namespace.importedModuleNames.push(item.name);
|
||||
});
|
||||
break;
|
||||
case 'class':
|
||||
case 'trait':
|
||||
case 'interface':
|
||||
namespace.moduleName = `${parsedChild.name}\\${c.name.name}`;
|
||||
}
|
||||
});
|
||||
|
||||
namespaces[itemPath] = namespace;
|
||||
});
|
||||
|
||||
const parse = async projectDir => {
|
||||
const namespaces = {};
|
||||
const tasks = readdirRecursive(projectDir)
|
||||
.filter(f => extensions.test('.' + f.split('.').pop()))
|
||||
.map(async f => {
|
||||
const separator = path.sep;
|
||||
const itemPath = `${projectDir}${separator}${f.replace(/\//g, separator)}`;
|
||||
const fileCode = await file.read(itemPath, 'utf8');
|
||||
addNamespaces(namespaces, itemPath, parser.parseCode(fileCode, path.basename(f)));
|
||||
});
|
||||
|
||||
await Promise.all(tasks);
|
||||
cache = namespaces;
|
||||
|
||||
return namespaces;
|
||||
};
|
||||
const getCache = () => cache;
|
||||
|
||||
module.exports = {
|
||||
parse,
|
||||
getCache
|
||||
};
|
||||
12
src/server/code-parse/language/php/parser.js
Normal file
12
src/server/code-parse/language/php/parser.js
Normal file
@@ -0,0 +1,12 @@
|
||||
const Engine = require('php-parser');
|
||||
|
||||
const parser = new Engine({
|
||||
parser: {
|
||||
extractDoc: true,
|
||||
php7: true
|
||||
},
|
||||
ast: {
|
||||
withPositions: true
|
||||
}
|
||||
});
|
||||
module.exports = parser
|
||||
@@ -28,7 +28,7 @@ const setup = (options, devOptions) => {
|
||||
logger.info(`> started with options: ${JSON.stringify(options)}`);
|
||||
|
||||
const PORT_IN_USE = 'open';
|
||||
const HOST = '127.0.0.1';
|
||||
const HOST = '0.0.0.0';
|
||||
|
||||
validateProjectPath(projectDir, entryPoint);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user