├── .editorconfig
├── .env.dist
├── .eslintrc.json
├── .gitattributes
├── .github
└── workflows
│ ├── coding_standards.yml
│ └── frontend.yml
├── .gitignore
├── .php-cs-fixer.php
├── LICENSE
├── README.md
├── bundle
├── DependencyInjection
│ └── NetgenContentBrowserUIExtension.php
├── NetgenContentBrowserUIBundle.php
└── Resources
│ ├── config
│ └── framework
│ │ └── assets.yaml
│ └── public
│ ├── css
│ └── main.css
│ ├── js
│ └── main.js
│ └── media
│ ├── Roboto-300.eot
│ ├── Roboto-300.svg
│ ├── Roboto-300.ttf
│ ├── Roboto-300.woff
│ ├── Roboto-300.woff2
│ ├── Roboto-500.eot
│ ├── Roboto-500.svg
│ ├── Roboto-500.ttf
│ ├── Roboto-500.woff
│ ├── Roboto-500.woff2
│ ├── Roboto-700.eot
│ ├── Roboto-700.svg
│ ├── Roboto-700.ttf
│ ├── Roboto-700.woff
│ ├── Roboto-700.woff2
│ ├── Roboto-regular.eot
│ ├── Roboto-regular.svg
│ ├── Roboto-regular.ttf
│ ├── Roboto-regular.woff
│ └── Roboto-regular.woff2
├── composer.json
├── config
├── env.js
├── paths.js
├── webpack.config.js
└── webpackDevServer.config.js
├── cypress.json
├── cypress
├── integration
│ └── browser_test.js
├── plugins
│ └── index.js
├── support
│ ├── commands.js
│ └── index.js
└── utils.js
├── express
├── data
│ ├── config.json
│ └── items.json
├── helpers.js
├── public
│ └── index.html
└── server.js
├── package.json
├── public
└── index.html
├── scripts
├── build.js
└── start.js
└── src
├── App.js
├── App.module.css
├── assets
└── fonts
│ └── Roboto
│ ├── Roboto-300.eot
│ ├── Roboto-300.svg
│ ├── Roboto-300.ttf
│ ├── Roboto-300.woff
│ ├── Roboto-300.woff2
│ ├── Roboto-500.eot
│ ├── Roboto-500.svg
│ ├── Roboto-500.ttf
│ ├── Roboto-500.woff
│ ├── Roboto-500.woff2
│ ├── Roboto-700.eot
│ ├── Roboto-700.svg
│ ├── Roboto-700.ttf
│ ├── Roboto-700.woff
│ ├── Roboto-700.woff2
│ ├── Roboto-regular.eot
│ ├── Roboto-regular.svg
│ ├── Roboto-regular.ttf
│ ├── Roboto-regular.woff
│ └── Roboto-regular.woff2
├── components
├── Breadcrumbs.js
├── Breadcrumbs.module.css
├── Browser.js
├── Browser.module.css
├── Footer.js
├── Footer.module.css
├── FooterItem.js
├── Item.js
├── Items.js
├── Items.module.css
├── ItemsTable.js
├── ItemsTable.module.css
├── Preview.js
├── Preview.module.css
├── Search.js
├── Search.module.css
├── Tab.js
├── TableSettings.js
├── Tabs.js
├── Tabs.module.css
├── TogglePreview.js
├── Tree.js
├── Tree.module.css
├── TreeItem.js
├── TreeItems.js
├── Variables.module.css
└── utils
│ ├── Button.js
│ ├── Button.module.css
│ ├── Checkbox.js
│ ├── Checkbox.module.css
│ ├── Dropdown.js
│ ├── Dropdown.module.css
│ ├── Input.js
│ ├── Input.module.css
│ ├── Loader.js
│ ├── Loader.module.css
│ ├── Pager.js
│ ├── Pager.module.css
│ ├── Select.js
│ ├── Select.module.css
│ ├── Toggle.js
│ └── Toggle.module.css
├── containers
├── Browser.js
├── Items.js
├── Search.js
├── SearchItems.js
└── Tree.js
├── fonts.css
├── helpers
└── index.js
├── index.css
├── index.js
├── plugins
├── Browser.js
├── InputBrowse.js
└── MultipleBrowse.js
├── setupProxy.js
└── store
├── actionTypes.js
├── actions
├── app.js
├── items.js
└── search.js
├── reducers
├── app.js
├── index.js
├── items.js
└── search.js
└── store.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 2
6 | end_of_line = lf
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
11 | [*.md]
12 | trim_trailing_whitespace = false
13 |
--------------------------------------------------------------------------------
/.env.dist:
--------------------------------------------------------------------------------
1 | SITE_URL=http://localhost:8282
2 | PORT=8181
3 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "extends": "react-app"
4 | }
5 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | /bundle/Resources/public/js/*.js binary
2 | /bundle/Resources/public/css/*.css binary
3 |
--------------------------------------------------------------------------------
/.github/workflows/coding_standards.yml:
--------------------------------------------------------------------------------
1 | name: Coding standards
2 |
3 | on:
4 | push:
5 | branches:
6 | - 'master'
7 | - '[0-9].[0-9]+'
8 | pull_request: ~
9 |
10 | jobs:
11 | php-cs-fixer:
12 | name: PHP CS Fixer
13 | runs-on: ubuntu-latest
14 |
15 | steps:
16 | - uses: actions/checkout@v3
17 | - uses: actions/checkout@v3
18 | with:
19 | repository: netgen-layouts/layouts-coding-standard
20 | path: vendor/netgen/layouts-coding-standard
21 | - uses: docker://oskarstark/php-cs-fixer-ga
22 | with:
23 | args: --diff --dry-run
24 |
--------------------------------------------------------------------------------
/.github/workflows/frontend.yml:
--------------------------------------------------------------------------------
1 | name: Frontend
2 |
3 | on:
4 | push:
5 | branches:
6 | - 'master'
7 | - '[0-9].[0-9]+'
8 | pull_request: ~
9 |
10 | jobs:
11 | frontend:
12 | name: ${{ matrix.script }}
13 | runs-on: ubuntu-latest
14 |
15 | strategy:
16 | fail-fast: false
17 | matrix:
18 | script: ['build', 'ci']
19 |
20 | steps:
21 | - uses: actions/checkout@v3
22 | - uses: actions/setup-node@v3
23 | with:
24 | node-version: '14'
25 | - uses: shivammathur/setup-php@v2
26 | with:
27 | php-version: '8.1'
28 | coverage: none
29 |
30 | # Install Flex as a global dependency to enable usage of extra.symfony.require
31 | # while keeping Flex recipes from applying
32 | - run: composer global config --no-plugins allow-plugins.symfony/flex true
33 | - run: composer global require --no-scripts symfony/flex
34 |
35 | - run: composer config extra.symfony.require ~6.4.0
36 |
37 | - run: composer update --prefer-dist
38 |
39 | - run: yarn install
40 | - run: cp .env.dist .env
41 |
42 | - run: yarn run ${{ matrix.script }}
43 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | .env
3 | cypress/screenshots/
4 | cypress/fixtures/
5 | node_modules/
6 | vendor/
7 | .php-cs-fixer.cache
8 | composer.lock
9 | composer.phar
10 | package-lock.json
11 | studio.json
12 | yarn.lock
13 | yarn-error.log
14 | *.sublime-workspace
15 |
--------------------------------------------------------------------------------
/.php-cs-fixer.php:
--------------------------------------------------------------------------------
1 | setFinder(
8 | PhpCsFixer\Finder::create()
9 | ->exclude(['vendor', 'node_modules'])
10 | ->in(__DIR__)
11 | )
12 | ;
13 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2015-2019 Netgen
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is furnished
8 | to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Netgen Content Browser user interface
2 |
3 | This repository contains the user interface for Netgen Content Browser.
4 |
5 | ## Requirements
6 |
7 | - [Node.js](https://nodejs.org)
8 | - [Yarn](https://yarnpkg.com)
9 |
10 | ## Development
11 |
12 | After cloning the repository, install the dependencies with:
13 |
14 | ```bash
15 | $ yarn install
16 | ```
17 |
18 | ## First time build configuration
19 |
20 | Before building the project for the first time, you need to copy `.env.dist` file to `.env`. This file specifies
21 | basic configuration for development and running the tests.
22 |
23 | ## Starting the development server
24 |
25 | The app uses a mock API by default, provided by the included Express server, which you need to start before starting
26 | the Webpack development server:
27 |
28 | ```bash
29 | # Starts the Express server
30 | $ yarn express
31 |
32 | # Starts the Webpack development server
33 | $ yarn start
34 | ```
35 |
36 | You can now access the app at `http://localhost:8181`. Webpack watches for changes in files and automatically
37 | refreshes the app.
38 |
39 | If you want to use real data from your backend CMS for development of Content Browser, you need to change
40 | the `SITE_URL` parameter inside `.env` file to proxy all API requests to your site.
41 |
42 | In that case, you don't need to start the Express server. Run only the following:
43 |
44 | ```bash
45 | # Starts the Webpack development server
46 | $ yarn start
47 | ```
48 |
49 | ## Build
50 |
51 | To build the production assets run the following:
52 |
53 | ```bash
54 | $ yarn build
55 | ```
56 |
57 | This will build the app and place all generated assets into `bundle/Resources/public` folder.
58 |
59 | ## Tests
60 |
61 | For end-to-end testing this repo uses [Cypress](https://www.cypress.io). Tests are mostly written
62 | for test data so Express server needs to be started before running them. To run the tests continuously
63 | in Google Chrome while developing, start Cypress with:
64 |
65 | ```bash
66 | $ yarn cypress
67 | ```
68 |
69 | This opens a window where you can click on `browser_test.js` which opens its own Google Chrome window
70 | and runs the tests. Tests are automatically ran whenever the app updates (on every file change).
71 |
72 | When ran standalone, tests use the production build of the app:
73 |
74 | ```bash
75 | $ yarn ci
76 | ```
77 |
78 | This starts the Express server and runs the tests in a headless browser.
79 |
--------------------------------------------------------------------------------
/bundle/DependencyInjection/NetgenContentBrowserUIExtension.php:
--------------------------------------------------------------------------------
1 | setParameter(
23 | 'netgen_content_browser.asset.version',
24 | PrettyVersions::getVersion('netgen/content-browser-ui')->getShortReference(),
25 | );
26 |
27 | $prependConfigs = [
28 | 'framework/assets.yaml' => 'framework',
29 | ];
30 |
31 | foreach ($prependConfigs as $configFile => $prependConfig) {
32 | $configFile = __DIR__ . '/../Resources/config/' . $configFile;
33 | $config = Yaml::parse((string) file_get_contents($configFile));
34 | $container->prependExtensionConfig($prependConfig, $config);
35 | $container->addResource(new FileResource($configFile));
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/bundle/NetgenContentBrowserUIBundle.php:
--------------------------------------------------------------------------------
1 | *{display:flex;align-items:center}.js-multiple-browse .js-remove{padding:0 3px;margin:0 1px 0 0;text-decoration:none}.js-multiple-browse .js-remove i{font-size:1.1666666667em;float:left;line-height:inherit}.js-multiple-browse .item .name{padding:4px 6px}.js-multiple-browse .no-items{padding:4px 0;display:none}.js-multiple-browse.items-empty .no-items{display:block}@font-face{font-family:Roboto;font-weight:300;font-style:normal;src:url(../media/Roboto-300.eot);src:url(../media/Roboto-300.eot?#iefix) format("embedded-opentype"),local("Roboto Light"),local("Roboto-300"),url(../media/Roboto-300.woff2) format("woff2"),url(../media/Roboto-300.woff) format("woff"),url(../media/Roboto-300.ttf) format("truetype"),url(../media/Roboto-300.svg#Roboto) format("svg")}@font-face{font-family:Roboto;font-weight:400;font-style:normal;src:url(../media/Roboto-regular.eot);src:url(../media/Roboto-regular.eot?#iefix) format("embedded-opentype"),local("Roboto"),local("Roboto-regular"),url(../media/Roboto-regular.woff2) format("woff2"),url(../media/Roboto-regular.woff) format("woff"),url(../media/Roboto-regular.ttf) format("truetype"),url(../media/Roboto-regular.svg#Roboto) format("svg")}@font-face{font-family:Roboto;font-weight:500;font-style:normal;src:url(../media/Roboto-500.eot);src:url(../media/Roboto-500.eot?#iefix) format("embedded-opentype"),local("Roboto Medium"),local("Roboto-500"),url(../media/Roboto-500.woff2) format("woff2"),url(../media/Roboto-500.woff) format("woff"),url(../media/Roboto-500.ttf) format("truetype"),url(../media/Roboto-500.svg#Roboto) format("svg")}@font-face{font-family:Roboto;font-weight:700;font-style:normal;src:url(../media/Roboto-700.eot);src:url(../media/Roboto-700.eot?#iefix) format("embedded-opentype"),local("Roboto Bold"),local("Roboto-700"),url(../media/Roboto-700.woff2) format("woff2"),url(../media/Roboto-700.woff) format("woff"),url(../media/Roboto-700.ttf) format("truetype"),url(../media/Roboto-700.svg#Roboto) format("svg")}.Button_button__1pfPt{cursor:pointer;font-family:Roboto,Helvetica Neue,sans-serif;font-weight:400;display:inline-flex;align-items:center;background-image:none;text-align:center;white-space:nowrap;vertical-align:middle;touch-action:manipulation;border:1px solid transparent;padding:.5em 1em;font-size:.875em;line-height:1.5;border-radius:2px;-webkit-user-select:none;user-select:none;transition:background-color .25s,color .25s;color:#333;height:2.6428571429em}.Button_button__1pfPt:focus{outline:none}.Button_prefixed__jvfYb{border-radius:0 2px 2px 0}.Button_sufixed__3QLiS{border-radius:2px 0 0 2px}.Button_button__1pfPt[disabled]{opacity:.65;cursor:not-allowed}.Button_link__1EdEA{border:0;padding:0;white-space:normal;background:transparent;font-size:inherit;line-height:inherit;border-radius:0;color:#2970ef;transition:color .25s;height:auto}.Button_link__1EdEA:hover{text-decoration:underline;color:#1057d5}.Button_primary__2rvzA{background:#2970ef;color:#fff}.Button_primary__2rvzA:hover:not(:disabled){background:#1057d5}.Button_transparent__1wOyR{background-color:initial}.Button_transparent__1wOyR:hover:not(:disabled){background:#dedede}.Button_cancel__6onep{background:transparent;color:#fff}.Button_cancel__6onep:hover{text-decoration:underline}.Tree_wrapper__3FCAV{background:#ededed;flex:1 1;padding:1em;position:absolute;left:1em;right:1em;top:2.75em;bottom:0;overflow-y:auto}.Tree_tree__1kXto{list-style-type:none;margin:0 0 1em;padding:0}.Tree_tree__1kXto .Tree_tree__1kXto{margin-left:1.5em;margin-bottom:0}.Tree_item__3qPn2{margin:0;padding:0;position:relative}.Tree_tree__1kXto .Tree_tree__1kXto>.Tree_item__3qPn2:before{content:"";position:absolute;left:-1em;height:100%;top:0;width:1px;background:#c7c7c7}.Tree_tree__1kXto .Tree_tree__1kXto>.Tree_item__3qPn2:last-child:before{height:50%}.Tree_tree__1kXto .Tree_tree__1kXto>.Tree_item__3qPn2:after{content:"";position:absolute;left:-1em;height:1px;top:.75em;width:.5em;background:#c7c7c7}.Tree_button__1pwmr{background:transparent;cursor:pointer;font-family:Roboto,Helvetica Neue,sans-serif;border:0;padding:.25em 0;font-size:.875em;display:flex;align-items:center;transition:color .2s}.Tree_active__1oqj2{color:#2970ef;font-weight:700}.Tree_button__1pwmr:focus{outline:none}.Tree_icon__10abK{color:#999;display:flex;align-items:center;margin-right:.5em;font-size:1.1428571429em}.Tree_hasItems__1R79E{color:#333;font-size:1.0714285714em;margin-left:1px}.Tree_rotate__1H3of{-webkit-animation:Tree_rotate__1H3of 1.25s linear infinite;animation:Tree_rotate__1H3of 1.25s linear infinite}@-webkit-keyframes Tree_rotate__1H3of{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@keyframes Tree_rotate__1H3of{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}.Loader_loader__bugWu{position:absolute;top:0;left:0;width:100%;height:100%;display:flex;justify-content:center;align-items:center;z-index:99999;margin:0}.Loader_loader__bugWu span{font-size:.75em;position:relative;top:1em;display:block;text-transform:uppercase;opacity:.8}.Loader_content__ylrK7{color:hsl(0,0,50);text-align:center}.Loader_icon__18aWx{display:inline-block;position:relative;font-size:1em;width:1.375em;height:2.375em;margin:0 1.5em -.3em;-webkit-transform:rotate(-48deg);transform:rotate(-48deg);-webkit-animation:Loader_loadRotate__2o6ua 1.5s cubic-bezier(.45,.05,.55,.95) infinite;animation:Loader_loadRotate__2o6ua 1.5s cubic-bezier(.45,.05,.55,.95) infinite}.Loader_icon__18aWx:after,.Loader_icon__18aWx:before{content:"";display:block;background:currentColor;border-radius:50%;position:absolute;left:50%}.Loader_icon__18aWx:before{width:1em;height:1em;margin-left:-.5em;bottom:1.375em;-webkit-animation:Loader_loadBounceTopSquash__2k92q .75s ease infinite alternate,Loader_loadBounceTopFlow__1xxdW .75s ease infinite alternate;animation:Loader_loadBounceTopSquash__2k92q .75s ease infinite alternate,Loader_loadBounceTopFlow__1xxdW .75s ease infinite alternate}.Loader_icon__18aWx:after{width:1.375em;height:1.375em;margin-left:-.6875em;bottom:0;-webkit-animation:Loader_loadBounceBottomSquash__1Fxre .75s ease infinite alternate,Loader_loadBounceBottomFlow__1dwsV .75s ease infinite alternate;animation:Loader_loadBounceBottomSquash__1Fxre .75s ease infinite alternate,Loader_loadBounceBottomFlow__1dwsV .75s ease infinite alternate}.Loader_fadeEnter__3pX7d{opacity:.01}.Loader_fadeActiveEnter__D0iWY{opacity:1;transition:opacity .25s}.Loader_fadeExit__xQVJO{opacity:1}.Loader_fadeActiveExit__ZNfOf{opacity:.01;transition:opacity .25s}@-webkit-keyframes Loader_loadBounceTopSquash__2k92q{0%{height:.375em;border-radius:3.75em 3.75em 1.25em 1.25em;-webkit-transform:scaleX(2);transform:scaleX(2)}15%{height:1em;border-radius:50%;-webkit-transform:scaleX(1);transform:scaleX(1)}to{height:1em;border-radius:50%;-webkit-transform:scaleX(1);transform:scaleX(1)}}@keyframes Loader_loadBounceTopSquash__2k92q{0%{height:.375em;border-radius:3.75em 3.75em 1.25em 1.25em;-webkit-transform:scaleX(2);transform:scaleX(2)}15%{height:1em;border-radius:50%;-webkit-transform:scaleX(1);transform:scaleX(1)}to{height:1em;border-radius:50%;-webkit-transform:scaleX(1);transform:scaleX(1)}}@-webkit-keyframes Loader_loadBounceBottomSquash__1Fxre{0%{height:1em;border-radius:1.25em 1.25em 3.75em 3.75em;-webkit-transform:scaleX(1.5);transform:scaleX(1.5)}15%{height:1.375em;border-radius:50%;-webkit-transform:scaleX(1);transform:scaleX(1)}to{height:1.375em;border-radius:50%;-webkit-transform:scaleX(1);transform:scaleX(1)}}@keyframes Loader_loadBounceBottomSquash__1Fxre{0%{height:1em;border-radius:1.25em 1.25em 3.75em 3.75em;-webkit-transform:scaleX(1.5);transform:scaleX(1.5)}15%{height:1.375em;border-radius:50%;-webkit-transform:scaleX(1);transform:scaleX(1)}to{height:1.375em;border-radius:50%;-webkit-transform:scaleX(1);transform:scaleX(1)}}@-webkit-keyframes Loader_loadBounceTopFlow__1xxdW{0%{bottom:1.125em}50%{bottom:2.25em;-webkit-animation-timing-function:cubic-bezier(.55,.06,.68,.19);animation-timing-function:cubic-bezier(.55,.06,.68,.19)}90%{bottom:1.75em}to{bottom:1.75em}}@keyframes Loader_loadBounceTopFlow__1xxdW{0%{bottom:1.125em}50%{bottom:2.25em;-webkit-animation-timing-function:cubic-bezier(.55,.06,.68,.19);animation-timing-function:cubic-bezier(.55,.06,.68,.19)}90%{bottom:1.75em}to{bottom:1.75em}}@-webkit-keyframes Loader_loadBounceBottomFlow__1dwsV{0%{bottom:.1875em}50%{bottom:-.9375em;-webkit-animation-timing-function:cubic-bezier(.55,.06,.68,.19);animation-timing-function:cubic-bezier(.55,.06,.68,.19)}90%{bottom:0}to{bottom:0}}@keyframes Loader_loadBounceBottomFlow__1dwsV{0%{bottom:.1875em}50%{bottom:-.9375em;-webkit-animation-timing-function:cubic-bezier(.55,.06,.68,.19);animation-timing-function:cubic-bezier(.55,.06,.68,.19)}90%{bottom:0}to{bottom:0}}@-webkit-keyframes Loader_loadRotate__2o6ua{0%{-webkit-transform:rotate(-228deg);transform:rotate(-228deg)}49%{-webkit-transform:rotate(-48deg);transform:rotate(-48deg)}51%{-webkit-transform:rotate(-48deg);transform:rotate(-48deg)}92%{-webkit-transform:rotate(132deg);transform:rotate(132deg)}to{-webkit-transform:rotate(132deg);transform:rotate(132deg)}}@keyframes Loader_loadRotate__2o6ua{0%{-webkit-transform:rotate(-228deg);transform:rotate(-228deg)}49%{-webkit-transform:rotate(-48deg);transform:rotate(-48deg)}51%{-webkit-transform:rotate(-48deg);transform:rotate(-48deg)}92%{-webkit-transform:rotate(132deg);transform:rotate(132deg)}to{-webkit-transform:rotate(132deg);transform:rotate(132deg)}}.Select_select__11lyk{font-family:Roboto,Helvetica Neue,sans-serif;-webkit-appearance:none;appearance:none;border:none;border-radius:2px;font-size:.75em;height:3em;padding:0 2.25em 0 1em;background-color:#dedede;background-image:url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTIiIGhlaWdodD0iOCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLW1pdGVybGltaXQ9IjEuNDEiPjxwYXRoIGQ9Ik01LjUzIDcuNDlMLjIgMi4xNWEuNjYuNjYgMCAwMTAtLjkzTC44Mi41OWEuNjYuNjYgMCAwMS45MyAwTDYgNC44MiAxMC4yNS42YS42Ni42NiAwIDAxLjkzIDBsLjYzLjYzYy4yNS4yNS4yNS42NyAwIC45M0w2LjQ3IDcuNDlhLjY2LjY2IDAgMDEtLjk0IDB6IiBmaWxsLXJ1bGU9Im5vbnplcm8iLz48L3N2Zz4=");background-repeat:no-repeat;background-position:right .75em center;background-size:.75em auto;cursor:pointer}.Checkbox_checkbox__yoCzk{position:absolute;opacity:0;left:-9999em;pointer-events:all}.Checkbox_label__2Wa0C{line-height:1.2;cursor:pointer;display:inline-flex;margin:0;padding:0;font-weight:400}.Checkbox_disabledLabel__3XU3p{opacity:.5;cursor:not-allowed}.Checkbox_icon__eqMXr{color:grey;font-size:1.1428571429em;margin-right:.35em;display:inline-flex}.Checkbox_iconActive__2qtld{color:#2970ef}.ItemsTable_table__11mZ6{width:100%;border-collapse:collapse;border-spacing:0;border-bottom:1px solid #ccc}.ItemsTable_table__11mZ6 td,.ItemsTable_table__11mZ6 th{padding:.5em .5em .5em 0;font-size:.875em;line-height:1.3;vertical-align:middle;transition:background-color .25s}.ItemsTable_table__11mZ6 th{background:#999;color:#fff;font-size:.75em;text-transform:uppercase;font-weight:400;text-align:left}.ItemsTable_table__11mZ6 thead td{border-bottom:1px solid #ccc}.ItemsTable_table__11mZ6 thead td,.ItemsTable_table__11mZ6 thead th{padding-top:.8333333333em;padding-bottom:.8333333333em}.ItemsTable_table__11mZ6 tr{cursor:default;background:transparent}.ItemsTable_table__11mZ6 tr>td:first-child,.ItemsTable_table__11mZ6 tr>th:first-child{padding-left:1em}.ItemsTable_table__11mZ6 tbody tr:hover td{background:#f2f2f2}.ItemsTable_table__11mZ6 tbody tr.ItemsTable_activeRow__1MDO8 td{background:#e0e0e0}.ItemsTable_table__11mZ6.ItemsTable_indent__2zNAu tbody td:first-child{padding-left:3em}.ItemsTable_checkbox__3mqo2{font-size:1.125em;display:inline-flex;vertical-align:middle;margin-right:.25em}.ItemsTable_table__11mZ6 td img{display:block;max-width:4.2857142857em;max-height:4.2857142857em;padding:2px;background-color:#fff}.ItemsTable_invisibleIcon__1x6hs{font-size:1.1428571429em;display:inline-flex;vertical-align:middle;margin-right:.5em}.Pager_pager__3ZmyX{padding:2em 1em 1em;display:flex}.Pager_pagination__3oXxT{flex:1 1;text-align:center;list-style-type:none;margin:0;padding:0}.Pager_paginatorItem__1muPB{display:inline-block;margin-right:1px}.Breadcrumbs_breadcrumbs__3q5Gt{list-style-type:none;margin:0;padding:0;display:flex;align-items:flex-start;flex-wrap:wrap}.Breadcrumbs_breadcrumbs__3q5Gt li{display:inline-block;font-size:.75em;margin:0;padding:0}.Breadcrumbs_breadcrumbs__3q5Gt li+li{position:relative;padding-left:1.5em}.Breadcrumbs_breadcrumbs__3q5Gt li+li:before{content:"/";display:inline-block;margin:0 .5em;position:absolute;left:0;top:.125em}.Dropdown_dropdown__-SNI4{position:relative;display:inline-block}.Dropdown_toggle__206aM{color:#999;text-decoration:none;font-size:.6875em;font-weight:500;text-transform:uppercase;display:flex;align-items:center}.Dropdown_toggle__206aM:active,.Dropdown_toggle__206aM:focus,.Dropdown_toggle__206aM:hover{color:#2970ef;text-decoration:none}.Dropdown_toggle__206aM svg{margin-left:.35em}.Dropdown_menu__3juzx{position:absolute;right:0;top:100%;background:#fff;box-shadow:0 0 .5em rgba(0,0,0,.5);border-radius:2px;padding:.5em 0;margin:1em 0 0;list-style-type:none;z-index:999;font-size:.874em;min-width:160px;background-clip:padding-box;text-align:left}.Dropdown_menu__3juzx li{padding:.35em .5em;margin:0}.Preview_preview__11IQq{width:18em;position:relative;overflow-x:hidden}.Preview_content__hzQ7j{padding:0 1em;width:18em;position:absolute;left:0;right:0;top:0;bottom:0;overflow-y:auto}.Preview_preview__11IQq a{color:#2970ef;text-decoration:none}.Preview_preview__11IQq a:focus,.Preview_preview__11IQq a:hover{text-decoration:underline}.Preview_preview__11IQq figure{margin:0}.Preview_preview__11IQq img{max-width:100%}.Preview_preview__11IQq p{margin:0 0 1em}.Preview_preview__11IQq .layout-icon{width:90%;height:0;padding-bottom:130%;margin:1em auto 0;border:2px solid #a1a1a1;background-size:95%;border-radius:3px}.Preview_slideEnter__2Ol_c{width:0;opacity:.01}.Preview_slideActiveEnter__3dkZu{width:18em;opacity:1;transition:width .25s ease-out,opacity .15s}.Preview_slideExit__WxkLB{width:18em;opacity:1}.Preview_slideActiveExit__265PC{width:0;opacity:.01;transition:width .25s ease-out,opacity .15s .1s}.Items_items__3Y0zI{flex:1 1;background:#ededed;position:relative;max-height:100%}.Items_header__2cOTb{display:flex;padding:.75em 1em;justify-content:space-between;align-items:center}.Items_fadeEnter__1HvTy{opacity:.01}.Items_fadeActiveEnter__1G_6N{opacity:1;transition:opacity .2s}.Items_fadeExit__-5rK9{opacity:1}.Items_fadeActiveExit__3aLFE{opacity:.01;transition:opacity .2s}.Items_wrapper__2Rw5j{position:absolute;left:0;top:0;right:0;bottom:0;overflow-y:auto}.Items_settings__38PJy{flex:1 1;text-align:right}.Input_input__2FuG5{border:none;box-shadow:none;border-radius:2px;font-size:.875em;padding:.7142857143em .875em;line-height:1}.Input_prefixed__3WO7g{border-radius:0 2px 2px 0}.Input_sufixed__BTVq5{border-radius:2px 0 0 2px}.Search_searchPanel__1e2Sf{flex:0 0 25%;padding:0 1em;display:flex;flex-direction:column;position:relative;max-height:100%;overflow-y:auto}.Search_resultsPanel__tL9jM{flex:1 1;background:#ededed;position:relative;max-height:100%;overflow-y:auto;padding-bottom:1em}.Search_searchWrapper__2B9Bt{flex:1 1;background:#ededed;margin-top:.5em;padding:1em}.Search_search__kehRK{display:flex;width:100%;flex-wrap:wrap}.Search_search__kehRK input{flex:1 1}.Tabs_tabs__33r_k{list-style-type:none;margin:0;display:flex;width:25%;padding:1em 1em .5em}.Tabs_tab__1XZ6E{flex:1 1;text-align:center;font-size:.75em;display:flex;align-items:center;justify-content:center;border:1px solid #2970ef;color:#2970ef;cursor:pointer;padding:.5em .125em;margin:0}.Tabs_tab__1XZ6E+.Tabs_tab__1XZ6E{border-left:0}.Tabs_tab__1XZ6E:first-child{border-radius:2px 0 0 2px}.Tabs_tab__1XZ6E:last-child{border-radius:0 2px 2px 0}.Tabs_tab__1XZ6E svg{margin-right:.125em}.Tabs_active__FvpRL{background:#2970ef;color:#fff;cursor:default}.Tabs_tabsHeader__3FcqX{display:flex;justify-content:space-between;align-items:center}.Tabs_headerContent__1T0AU{padding:1em 1em .5em;flex:1 1;text-align:right}.Toggle_checkbox__rhGcW{position:absolute;opacity:0;left:-9999em;pointer-events:all}.Toggle_label__3lr_v{line-height:1.2;cursor:pointer;display:inline-flex;font-size:.6875em;text-transform:uppercase;font-weight:500;align-items:center;padding:1.0909090909em 0;margin:0}.Toggle_disabledLabel__dWisf{opacity:.5;cursor:not-allowed}.Toggle_icon__1v23t{display:inline-block;margin-left:.75em;width:2.5454545455em;height:1.0909090909em;border-radius:50em;position:relative;background:#c7c7c7;transition:background-color .3s}.Toggle_icon__1v23t:before{content:"";display:inline-block;position:absolute;width:1.4545454545em;height:1.4545454545em;border-radius:50em;left:-2px;top:50%;-webkit-transform:translate3d(0,-50%,0);transform:translate3d(0,-50%,0);transition:left .3s cubic-bezier(.4,0,.2,1),background-color .1s;background:#fff;box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12)}.Toggle_iconActive__2ahte{background:#7ea9f5}.Toggle_iconActive__2ahte:before{left:1.2727272727em;background:#2970ef;box-shadow:0 3px 4px 0 rgba(0,0,0,.14),0 3px 3px -2px rgba(0,0,0,.2),0 1px 8px 0 rgba(0,0,0,.12)}.Footer_footer__2NNgL{background:#383838;color:#fff;padding:.75em;display:flex;justify-content:space-between}.Footer_actions__3DKAf{min-width:11em;text-align:right}.Footer_footer__2NNgL button{margin:.25em}.Footer_itemIcon__2SY-W{margin-left:.5em;color:#999;font-size:1.25em;display:inline-flex}.Browser_browser__1ZZaQ{position:fixed;left:0;top:0;width:100%;height:100%;z-index:1000;background:rgba(0,0,0,.5);color:#333;font-family:Roboto,Helvetica Neue,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;overflow-x:hidden;font-size:16px;line-height:1.2}.Browser_browser__1ZZaQ *{box-sizing:border-box}.Browser_dialog__MW-Vw{padding:1em;height:100%;display:flex;flex-direction:column}.Browser_content__38RPK{background:#fff;box-shadow:0 .5em 1em rgba(0,0,0,.5);flex:1 1;display:flex;flex-direction:column;position:relative}.Browser_panels__2fOkv{display:flex;flex:1 1}.Browser_treePanel__3xYgJ{flex:0 0 25%;padding:0 1em;display:flex;flex-direction:column;position:relative;max-height:100%;overflow-y:auto}.Browser_loading__2FF_l{background:#333;color:#999;flex:1 1;box-shadow:0 .5em 1em rgba(0,0,0,.5)}.Browser_slideEnter__1vdKy{opacity:.01;-webkit-transform:translate3d(0,-10%,0);transform:translate3d(0,-10%,0)}.Browser_slideActiveEnter__bEByv{opacity:1;-webkit-transform:translateZ(0);transform:translateZ(0);transition:opacity .25s,-webkit-transform .5s;transition:opacity .25s,transform .5s;transition:opacity .25s,transform .5s,-webkit-transform .5s}
--------------------------------------------------------------------------------
/bundle/Resources/public/media/Roboto-300.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netgen-layouts/content-browser-ui/df625ae599d9c71aac2493ca7fbd03cef4d781f1/bundle/Resources/public/media/Roboto-300.eot
--------------------------------------------------------------------------------
/bundle/Resources/public/media/Roboto-300.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netgen-layouts/content-browser-ui/df625ae599d9c71aac2493ca7fbd03cef4d781f1/bundle/Resources/public/media/Roboto-300.ttf
--------------------------------------------------------------------------------
/bundle/Resources/public/media/Roboto-300.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netgen-layouts/content-browser-ui/df625ae599d9c71aac2493ca7fbd03cef4d781f1/bundle/Resources/public/media/Roboto-300.woff
--------------------------------------------------------------------------------
/bundle/Resources/public/media/Roboto-300.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netgen-layouts/content-browser-ui/df625ae599d9c71aac2493ca7fbd03cef4d781f1/bundle/Resources/public/media/Roboto-300.woff2
--------------------------------------------------------------------------------
/bundle/Resources/public/media/Roboto-500.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netgen-layouts/content-browser-ui/df625ae599d9c71aac2493ca7fbd03cef4d781f1/bundle/Resources/public/media/Roboto-500.eot
--------------------------------------------------------------------------------
/bundle/Resources/public/media/Roboto-500.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netgen-layouts/content-browser-ui/df625ae599d9c71aac2493ca7fbd03cef4d781f1/bundle/Resources/public/media/Roboto-500.ttf
--------------------------------------------------------------------------------
/bundle/Resources/public/media/Roboto-500.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netgen-layouts/content-browser-ui/df625ae599d9c71aac2493ca7fbd03cef4d781f1/bundle/Resources/public/media/Roboto-500.woff
--------------------------------------------------------------------------------
/bundle/Resources/public/media/Roboto-500.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netgen-layouts/content-browser-ui/df625ae599d9c71aac2493ca7fbd03cef4d781f1/bundle/Resources/public/media/Roboto-500.woff2
--------------------------------------------------------------------------------
/bundle/Resources/public/media/Roboto-700.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netgen-layouts/content-browser-ui/df625ae599d9c71aac2493ca7fbd03cef4d781f1/bundle/Resources/public/media/Roboto-700.eot
--------------------------------------------------------------------------------
/bundle/Resources/public/media/Roboto-700.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netgen-layouts/content-browser-ui/df625ae599d9c71aac2493ca7fbd03cef4d781f1/bundle/Resources/public/media/Roboto-700.ttf
--------------------------------------------------------------------------------
/bundle/Resources/public/media/Roboto-700.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netgen-layouts/content-browser-ui/df625ae599d9c71aac2493ca7fbd03cef4d781f1/bundle/Resources/public/media/Roboto-700.woff
--------------------------------------------------------------------------------
/bundle/Resources/public/media/Roboto-700.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netgen-layouts/content-browser-ui/df625ae599d9c71aac2493ca7fbd03cef4d781f1/bundle/Resources/public/media/Roboto-700.woff2
--------------------------------------------------------------------------------
/bundle/Resources/public/media/Roboto-regular.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netgen-layouts/content-browser-ui/df625ae599d9c71aac2493ca7fbd03cef4d781f1/bundle/Resources/public/media/Roboto-regular.eot
--------------------------------------------------------------------------------
/bundle/Resources/public/media/Roboto-regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netgen-layouts/content-browser-ui/df625ae599d9c71aac2493ca7fbd03cef4d781f1/bundle/Resources/public/media/Roboto-regular.ttf
--------------------------------------------------------------------------------
/bundle/Resources/public/media/Roboto-regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netgen-layouts/content-browser-ui/df625ae599d9c71aac2493ca7fbd03cef4d781f1/bundle/Resources/public/media/Roboto-regular.woff
--------------------------------------------------------------------------------
/bundle/Resources/public/media/Roboto-regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netgen-layouts/content-browser-ui/df625ae599d9c71aac2493ca7fbd03cef4d781f1/bundle/Resources/public/media/Roboto-regular.woff2
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "netgen/content-browser-ui",
3 | "description": "Netgen Content Browser user interface",
4 | "license": "MIT",
5 | "type": "symfony-bundle",
6 | "authors": [
7 | {
8 | "name": "Netgen",
9 | "homepage": "https://netgen.io"
10 | }
11 | ],
12 | "require-dev": {
13 | "netgen/layouts-coding-standard": "^2.0"
14 | },
15 | "config": {
16 | "allow-plugins": false
17 | },
18 | "autoload": {
19 | "psr-4": {
20 | "Netgen\\Bundle\\ContentBrowserUIBundle\\": "bundle/"
21 | }
22 | },
23 | "minimum-stability": "dev",
24 | "prefer-stable": true,
25 | "extra": {
26 | "branch-alias": {
27 | "dev-master": "1.4.x-dev"
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/config/env.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const paths = require('./paths');
3 |
4 | // Make sure that including paths.js after env.js will read .env variables.
5 | delete require.cache[require.resolve('./paths')];
6 |
7 | const NODE_ENV = process.env.NODE_ENV;
8 | if (!NODE_ENV) {
9 | throw new Error(
10 | 'The NODE_ENV environment variable is required but was not specified.'
11 | );
12 | }
13 |
14 | var dotenvFiles = [
15 | paths.dotenv,
16 | ].filter(Boolean);
17 |
18 | // Load environment variables from .env* files. Suppress warnings using silent
19 | // if this file is missing. dotenv will never modify any environment variables
20 | // that have already been set. Variable expansion is supported in .env files.
21 | // https://github.com/motdotla/dotenv
22 | // https://github.com/motdotla/dotenv-expand
23 | dotenvFiles.forEach(dotenvFile => {
24 | if (fs.existsSync(dotenvFile)) {
25 | require('dotenv-expand')(
26 | require('dotenv').config({
27 | path: dotenvFile,
28 | })
29 | );
30 | }
31 | });
32 |
33 | process.env.NODE_PATH = (process.env.NODE_PATH || '');
34 |
35 | function getClientEnvironment() {
36 | const raw = Object.keys(process.env)
37 | .reduce(
38 | (env, key) => {
39 | env[key] = process.env[key];
40 | return env;
41 | },
42 | {
43 | // Useful for determining whether we’re running in production mode.
44 | // Most importantly, it switches React into the correct mode.
45 | NODE_ENV: process.env.NODE_ENV || 'development',
46 | }
47 | );
48 | // Stringify all values so we can feed into Webpack DefinePlugin
49 | const stringified = {
50 | 'process.env': Object.keys(raw).reduce((env, key) => {
51 | env[key] = JSON.stringify(raw[key]);
52 | return env;
53 | }, {}),
54 | };
55 |
56 | return { raw, stringified };
57 | }
58 |
59 | module.exports = getClientEnvironment;
60 |
--------------------------------------------------------------------------------
/config/paths.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const fs = require('fs');
3 | const url = require('url');
4 |
5 | const appDirectory = fs.realpathSync(process.cwd());
6 | const resolveApp = relativePath => path.resolve(appDirectory, relativePath);
7 |
8 | const envPublicUrl = process.env.PUBLIC_URL;
9 | const getPublicUrl = appPackageJson =>
10 | envPublicUrl || require(appPackageJson).homepage;
11 |
12 | function ensureSlash(inputPath, needsSlash) {
13 | const hasSlash = inputPath.endsWith('/');
14 | if (hasSlash && !needsSlash) {
15 | return inputPath.substr(0, inputPath.length - 1);
16 | } else if (!hasSlash && needsSlash) {
17 | return `${inputPath}/`;
18 | } else {
19 | return inputPath;
20 | }
21 | }
22 |
23 | function getServedPath(appPackageJson) {
24 | const publicUrl = getPublicUrl(appPackageJson);
25 | const servedUrl =
26 | envPublicUrl || (publicUrl ? url.parse(publicUrl).pathname : '/');
27 | return ensureSlash(servedUrl, true);
28 | }
29 |
30 | module.exports = {
31 | dotenv: path.resolve('.env'),
32 | appBuild: path.resolve('bundle/Resources/public'),
33 | appPublic: path.resolve('public'),
34 | appHtml: path.resolve('public/index.html'),
35 | appIndexJs: path.resolve('src/index.js'),
36 | appPackageJson: path.resolve('package.json'),
37 | appSrc: path.resolve('src'),
38 | proxySetup: path.resolve('src/setupProxy.js'),
39 | publicUrl: getPublicUrl(resolveApp('package.json')),
40 | servedPath: getServedPath(resolveApp('package.json')),
41 | };
42 |
--------------------------------------------------------------------------------
/config/webpackDevServer.config.js:
--------------------------------------------------------------------------------
1 | const errorOverlayMiddleware = require('react-dev-utils/errorOverlayMiddleware');
2 | const evalSourceMapMiddleware = require('react-dev-utils/evalSourceMapMiddleware');
3 | const ignoredFiles = require('react-dev-utils/ignoredFiles');
4 | const paths = require('./paths');
5 | const fs = require('fs');
6 |
7 | const protocol = process.env.HTTPS === 'true' ? 'https' : 'http';
8 | const host = process.env.HOST || '0.0.0.0';
9 |
10 | module.exports = function(proxy, allowedHost) {
11 | return {
12 | // WebpackDevServer 2.4.3 introduced a security fix that prevents remote
13 | // websites from potentially accessing local content through DNS rebinding:
14 | // https://github.com/webpack/webpack-dev-server/issues/887
15 | // https://medium.com/webpack/webpack-dev-server-middleware-security-issues-1489d950874a
16 | // However, it made several existing use cases such as development in cloud
17 | // environment or subdomains in development significantly more complicated:
18 | // https://github.com/facebook/create-react-app/issues/2271
19 | // https://github.com/facebook/create-react-app/issues/2233
20 | // While we're investigating better solutions, for now we will take a
21 | // compromise. Since our WDS configuration only serves files in the `public`
22 | // folder we won't consider accessing them a vulnerability. However, if you
23 | // use the `proxy` feature, it gets more dangerous because it can expose
24 | // remote code execution vulnerabilities in backends like Django and Rails.
25 | // So we will disable the host check normally, but enable it if you have
26 | // specified the `proxy` setting. Finally, we let you override it if you
27 | // really know what you're doing with a special environment variable.
28 | disableHostCheck:
29 | !proxy || process.env.DANGEROUSLY_DISABLE_HOST_CHECK === 'true',
30 | // Enable gzip compression of generated files.
31 | compress: true,
32 | // Silence WebpackDevServer's own logs since they're generally not useful.
33 | // It will still show compile warnings and errors with this setting.
34 | clientLogLevel: 'none',
35 | // By default WebpackDevServer serves physical files from current directory
36 | // in addition to all the virtual build products that it serves from memory.
37 | // This is confusing because those files won’t automatically be available in
38 | // production build folder unless we copy them. However, copying the whole
39 | // project directory is dangerous because we may expose sensitive files.
40 | // Instead, we establish a convention that only files in `public` directory
41 | // get served. Our build script will copy `public` into the `build` folder.
42 | // In `index.html`, you can get URL of `public` folder with %PUBLIC_URL%:
43 | //
44 | // In JavaScript code, you can access it with `process.env.PUBLIC_URL`.
45 | // Note that we only recommend to use `public` folder as an escape hatch
46 | // for files like `favicon.ico`, `manifest.json`, and libraries that are
47 | // for some reason broken when imported through Webpack. If you just want to
48 | // use an image, put it in `src` and `import` it from JavaScript instead.
49 | contentBase: paths.appPublic,
50 | // By default files from `contentBase` will not trigger a page reload.
51 | watchContentBase: true,
52 | // Enable hot reloading server. It will provide /sockjs-node/ endpoint
53 | // for the WebpackDevServer client so it can learn when the files were
54 | // updated. The WebpackDevServer client is included as an entry point
55 | // in the Webpack development configuration. Note that only changes
56 | // to CSS are currently hot reloaded. JS changes will refresh the browser.
57 | hot: true,
58 | // It is important to tell WebpackDevServer to use the same "root" path
59 | // as we specified in the config. In development, we always serve from /.
60 | publicPath: '/',
61 | // WebpackDevServer is noisy by default so we emit custom message instead
62 | // by listening to the compiler events with `compiler.hooks[...].tap` calls above.
63 | quiet: true,
64 | // Reportedly, this avoids CPU overload on some systems.
65 | // https://github.com/facebook/create-react-app/issues/293
66 | // src/node_modules is not ignored to support absolute imports
67 | // https://github.com/facebook/create-react-app/issues/1065
68 | watchOptions: {
69 | ignored: ignoredFiles(paths.appSrc),
70 | },
71 | // Enable HTTPS if the HTTPS environment variable is set to 'true'
72 | https: protocol === 'https',
73 | host,
74 | overlay: false,
75 | historyApiFallback: {
76 | // Paths with dots should still use the history fallback.
77 | // See https://github.com/facebook/create-react-app/issues/387.
78 | disableDotRule: true,
79 | },
80 | public: allowedHost,
81 | proxy,
82 | before(app, server) {
83 | if (fs.existsSync(paths.proxySetup)) {
84 | // This registers user provided middleware for proxy reasons
85 | require(paths.proxySetup)(app);
86 | }
87 |
88 | // This lets us fetch source contents from webpack for the error overlay
89 | app.use(evalSourceMapMiddleware(server));
90 | // This lets us open files from the runtime error overlay.
91 | app.use(errorOverlayMiddleware());
92 | },
93 | };
94 | };
95 |
--------------------------------------------------------------------------------
/cypress.json:
--------------------------------------------------------------------------------
1 | {
2 | "baseUrl": "http://localhost:8181",
3 | "video": false
4 | }
5 |
--------------------------------------------------------------------------------
/cypress/integration/browser_test.js:
--------------------------------------------------------------------------------
1 | ///
2 | /* global Cypress, cy */
3 | import { withCyTag } from '../utils';
4 |
5 | describe('Multiple browse test', function() {
6 | Cypress.Commands.add('openContentBrowser', (nr) => {
7 | cy.get(withCyTag('multiple-add')).click();
8 | cy.get(withCyTag('browser')).should('be.visible');
9 | });
10 |
11 | Cypress.Commands.add('checkSelectedItemsLength', (nr) => {
12 | cy.window().its('store').invoke('getState').its('app').its('selectedItems').should('have.length', nr);
13 | });
14 |
15 | Cypress.Commands.add('checkBreadcrumbsAndTableForText', (text) => {
16 | cy.get(withCyTag('breadcrumbs')).contains(text);
17 | cy.get(withCyTag('items-table-head')).contains(text);
18 | });
19 |
20 | describe('Open index page', () => {
21 | it('Loads index page', () => {
22 | cy.visit('/');
23 | cy.get(withCyTag('multiple-browse')).should('be.visible');
24 | });
25 | describe('Open Browser', () => {
26 | it('open content browser on button click', () => {
27 | cy.openContentBrowser();
28 | });
29 | it('has no selected items in store on load', () => {
30 | cy.checkSelectedItemsLength(0);
31 | });
32 | });
33 | describe('Test loaders', () => {
34 | it('shows loader while config loading', () => {
35 | cy.window().its('store').invoke('dispatch', { type: 'CONFIG_LOADED', isLoaded: false });
36 | cy.get(withCyTag('browser-loading')).should('be.visible');
37 | cy.window().its('store').invoke('dispatch', { type: 'CONFIG_LOADED', isLoaded: true });
38 | cy.get(withCyTag('browser-loading')).should('not.be.visible');
39 | });
40 | it('shows tree loader while loading', () => {
41 | cy.window().its('store').invoke('dispatch', { type: 'START_TREE_LOAD' });
42 | cy.get(withCyTag('tree-wrapper')).find(withCyTag('loader')).should('be.visible');
43 | cy.window().its('store').invoke('dispatch', { type: 'STOP_TREE_LOAD' });
44 | cy.get(withCyTag('tree-wrapper')).find(withCyTag('loader')).should('not.be.visible');
45 | });
46 | it('shows items loader while loading', () => {
47 | cy.window().its('store').invoke('dispatch', { type: 'START_LOCATION_LOAD' });
48 | cy.get(withCyTag('items')).find(withCyTag('loader')).should('be.visible');
49 | cy.window().its('store').invoke('dispatch', { type: 'STOP_LOCATION_LOAD' });
50 | cy.get(withCyTag('items')).find(withCyTag('loader')).should('not.be.visible');
51 | });
52 | })
53 | describe('Test content tree', () => {
54 | it('changes section in select dropdown', () => {
55 | cy.get(withCyTag('tree-panel')).find('select').as('sectionSelect').children().its('length').should('be.greaterThan', 1);
56 | cy.get('@sectionSelect').children().eq(1).then((el) => {
57 | cy.get('@sectionSelect').select(el.val());
58 | cy.wrap(el).invoke('text').then((text) => {
59 | cy.checkBreadcrumbsAndTableForText(text);
60 | });
61 | });
62 | cy.get('@sectionSelect').children().eq(0).then((el) => {
63 | cy.get('@sectionSelect').select(el.val());
64 | cy.wrap(el).invoke('text').then((text) => {
65 | cy.checkBreadcrumbsAndTableForText(text);
66 | });
67 | });
68 | });
69 | it('opens second level in tree item', () => {
70 | cy.get(withCyTag('tree')).as('tree');
71 | cy.get('@tree').find(withCyTag('item-icon-has-sub')).as('toggle').click();
72 | cy.get('@toggle').closest(withCyTag('tree-item')).as('item');
73 | cy.get('@item').find(withCyTag('tree')).should('be.visible').children().its('length').should('be.greaterThan', 1);
74 | cy.get('@toggle').click();
75 | cy.get('@item').find(withCyTag('tree')).should('not.be.visible');
76 | });
77 | it('clicks item in content tree', () => {
78 | cy.get(withCyTag('tree')).as('tree');
79 | cy.get('@tree').children().eq(0).as('firstItem').find('button').click();
80 | cy.get('@firstItem').invoke('text').then(cy.checkBreadcrumbsAndTableForText);
81 | cy.get('@tree').children().eq(2).as('secondItem').find('button').click();
82 | cy.get('@secondItem').invoke('text').then(cy.checkBreadcrumbsAndTableForText);
83 | });
84 | });
85 | describe('Test breadcrumbs', () => {
86 | it('tests three levels of content', () => {
87 | cy.get(withCyTag('breadcrumbs')).as('breadcrumbs').children().eq(0).click();
88 | cy.get('@breadcrumbs').children().should('have.length', 1);
89 | cy.get(withCyTag('items-table-body')).as('tablebody').children().eq(2).children().eq(0).find('button').click();
90 | cy.get('@breadcrumbs').children().should('have.length', 2);
91 | cy.get('@tablebody').children().eq(0).children().eq(0).find('button').click();
92 | cy.get('@breadcrumbs').children().should('have.length', 3);
93 | cy.get('@breadcrumbs').children().eq(2).find('button').should('not.exist');
94 | cy.get('@breadcrumbs').children().eq(0).find('button').click();
95 | cy.get('@breadcrumbs').children().eq(0).invoke('text').then((text) => {
96 | cy.get(withCyTag('items-table-head')).contains(text);
97 | });
98 | cy.get('@breadcrumbs').children().should('have.length', 1);
99 | cy.get('@breadcrumbs').children().eq(0).find('button').should('not.exist');
100 | });
101 | });
102 | describe('Test pagination', () => {
103 | it('changes table items limit', () => {
104 | cy.get(withCyTag('pager')).find('select').as('limiter').select('25');
105 | cy.window().its('store').invoke('getState').its('app').its('itemsLimit').should('eq', '25');
106 | cy.get(withCyTag('items-table-body')).as('tablebody').children().should('have.length.greaterThan', 10);
107 | cy.get('@limiter').select('5');
108 | cy.window().its('store').invoke('getState').its('app').its('itemsLimit').should('eq', '5');
109 | cy.get('@tablebody').children().should('have.length', 5);
110 | });
111 | it('tests pagination', () => {
112 | cy.get(withCyTag('pager')).find(withCyTag('pagination')).as('pagination').children().should('have.length.greaterThan', 3);
113 | cy.window().its('store').invoke('getState').its('items').its('currentPage').as('statePage').should('eq', 1);
114 | cy.get('@pagination').children().first().find('button').should('be.disabled');
115 | cy.get('@pagination').children().last().find('button').should('not.be.disabled');
116 | cy.get('@pagination').children().last().click();
117 | cy.window().its('store').invoke('getState').its('items').its('currentPage').as('statePage').should('eq', 2);
118 | cy.get('@pagination').children().first().find('button').should('not.be.disabled');
119 | cy.get('@pagination').children().last().prev().click();
120 | cy.get('@pagination').children().first().find('button').should('not.be.disabled');
121 | cy.get('@pagination').children().last().find('button').should('be.disabled');
122 | cy.get('@pagination').children().first().click();
123 | cy.get('@pagination').children().first().find('button').should('not.be.disabled');
124 | cy.get('@pagination').children().last().find('button').should('not.be.disabled');
125 | });
126 | });
127 | describe('Test table options', () => {
128 | it('opens table options dropdown and checks if it\'s visible', () => {
129 | cy.get(withCyTag('items-header')).find(withCyTag('dropdown')).as('dropdown').find(withCyTag('dropdown-toggle')).as('toggle').click();
130 | cy.get('@dropdown').find(withCyTag('dropdown-menu')).as('menu').should('be.visible');
131 | });
132 | it('checks if selected table columns are visible', () => {
133 | cy.get(withCyTag('items-table-head')).children().eq(0).as('tableHead');
134 | cy.get(withCyTag('items-header')).find(withCyTag('dropdown-menu')).find('input[type="checkbox"]').each(($checkbox) => {
135 | cy.wrap($checkbox).parent().find('label').invoke('text').then((text) => {
136 | if ($checkbox[0].checked) {
137 | cy.get('@tableHead').should('contain', text);
138 | } else {
139 | cy.get('@tableHead').should('not.contain', text);
140 | }
141 | });
142 | });
143 | });
144 | it('selects first not checked table column and checks if it\'s visible', () => {
145 | cy.get(withCyTag('items-header')).find(withCyTag('dropdown-menu')).find('input[type="checkbox"]:not(:checked)').first().parent().find('label').then($label => {
146 | cy.wrap($label).click().invoke('text').then((text) => {
147 | cy.get(withCyTag('items-table-head')).children().eq(0).should('contain', text);
148 | });
149 | });
150 | });
151 | it('deselects last checked table column and checks if it\'s not visible', () => {
152 | cy.get(withCyTag('items-header')).find(withCyTag('dropdown-menu')).find('input[type="checkbox"]:checked').last().parent().find('label').then($label => {
153 | cy.wrap($label).click().invoke('text').then((text) => {
154 | cy.get(withCyTag('items-table-head')).children().eq(0).should('not.contain', text);
155 | });
156 | });
157 | });
158 | it('closes table options dropdown and checks if it\'s not visible', () => {
159 | cy.get(withCyTag('items-header')).find(withCyTag('dropdown')).as('dropdown').find(withCyTag('dropdown-toggle')).as('toggle').click();
160 | cy.get('@dropdown').find(withCyTag('dropdown-menu')).as('menu').should('not.be.visible');
161 | });
162 | });
163 | describe('Test preview panel', () => {
164 | it('opens preview on toggle and checks if it\'s visible', () => {
165 | cy.window().its('store').invoke('dispatch', { type: 'TOGGLE_PREVIEW', toggle: false });
166 | cy.get('#togglePreview').parent().find('label').click();
167 | cy.get(withCyTag('preview')).should('be.visible');
168 | cy.window().its('store').invoke('getState').its('app').its('showPreview').should('eq', true);
169 | });
170 | it('shows preview loader while preview is loading', () => {
171 | cy.window().its('store').invoke('dispatch', { type: 'START_PREVIEW_LOAD' });
172 | cy.get(withCyTag('preview')).find(withCyTag('loader')).should('be.visible');
173 | cy.window().its('store').invoke('dispatch', { type: 'STOP_PREVIEW_LOAD' });
174 | cy.get(withCyTag('preview')).find(withCyTag('loader')).should('not.be.visible');
175 | });
176 | it('checks if preview shows clicked item', () => {
177 | cy.get(withCyTag('items-table-head')).children().eq(1).children().eq(0).then(($cell) => {
178 | cy.wrap($cell).click().invoke('text').then((text) => {
179 | cy.get(withCyTag('preview')).should('contain', text);
180 | });
181 | });
182 | cy.get(withCyTag('items-table-body')).children().eq(0).children().eq(0).then(($cell) => {
183 | cy.wrap($cell).click().invoke('text').then((text) => {
184 | cy.get(withCyTag('preview')).should('contain', text);
185 | });
186 | });
187 | });
188 | it('closes preview on toggle and checks if it\'s invisible', () => {
189 | cy.get('#togglePreview').parent().find('label').click();
190 | cy.get('#togglePreview').should('not.be.checked');
191 | cy.get(withCyTag('preview')).should('not.be.visible');
192 | cy.window().its('store').invoke('getState').its('app').its('showPreview').should('eq', false);
193 | });
194 | });
195 | describe('Test search', () => {
196 | it('tests if search returns results when inputs string', () => {
197 | if (cy.window().its('store').invoke('getState').its('app').its('config').its('has_search')) {
198 | cy.get(withCyTag('tabs')).contains('Search').should('be.visible').click();
199 | cy.get(withCyTag('tree-panel')).should('not.be.visible');
200 | cy.get(withCyTag('items-table')).should('not.be.visible');
201 | cy.get(withCyTag('search-form')).as('form').find('input').as('input').type('test');
202 | cy.get('@form').submit();
203 | cy.get(withCyTag('items-table')).should('be.visible').find('tbody').children().its('length').should('be.greaterThan', 3);
204 | cy.get('@input').clear().type('a{enter}');
205 | cy.get(withCyTag('items-table')).should('be.visible').find('tbody').children().should('not.be.visible');
206 | cy.get('@input').clear();
207 | cy.get('@form').find('button[type="submit"]').click();
208 | cy.get(withCyTag('items-table')).should('not.be.visible');
209 | }
210 | });
211 | });
212 | describe('Test category select', () => {
213 | it('saves category to store when changing it on tree panel', () => {
214 | cy.visit('/');
215 | cy.openContentBrowser();
216 | cy.get(withCyTag('tabs')).contains('Browse').should('be.visible').click();
217 | cy.get(withCyTag('tree-panel')).find('select').find(':selected').contains("Home");
218 | cy.window().its('store').invoke('getState').its('app').its('sectionId').should('eq', 2);
219 | cy.get(withCyTag('tree-panel')).find('select').select('Users');
220 | cy.get(withCyTag('tree-panel')).find('select').find(':selected').contains("Users");
221 | cy.window().its('store').invoke('getState').its('app').its('sectionId').should('eq', 1);
222 | });
223 | it('saves category to store when changing it on search panel', () => {
224 | cy.visit('/');
225 | cy.openContentBrowser();
226 | cy.get(withCyTag('tabs')).contains('Search').should('be.visible').click();
227 | cy.get(withCyTag('search-panel')).find('select').find(':selected').contains("Home");
228 | cy.window().its('store').invoke('getState').its('app').its('sectionId').should('eq', 2);
229 | cy.get(withCyTag('search-panel')).find('select').select('Users');
230 | cy.get(withCyTag('search-panel')).find('select').find(':selected').contains("Users");
231 | cy.window().its('store').invoke('getState').its('app').its('sectionId').should('eq', 1);
232 | });
233 | it('perfoms search when category is changed', () => {
234 | cy.visit('/');
235 | cy.openContentBrowser();
236 | cy.get(withCyTag('tabs')).contains('Search').should('be.visible').click();
237 |
238 | cy.get(withCyTag('search-form')).as('form').find('input').as('input').type('test');
239 | cy.get('@form').submit();
240 |
241 | cy.get(withCyTag('items-table')).should('be.visible').find('tbody').children().first().contains('Test');
242 |
243 | cy.get(withCyTag('search-panel')).find('select').first().select('Users');
244 | cy.get(withCyTag('items-table')).should('be.visible').find('tbody').children().first().contains('User');
245 |
246 | cy.get(withCyTag('search-form')).as('form').find('input').as('input').clear();
247 | cy.get(withCyTag('search-panel')).find('select').first().select('Home');
248 | cy.get(withCyTag('items-table')).should('be.visible').find('tbody').children().first().contains('User');
249 |
250 | });
251 | });
252 | describe('Closes content browser', () => {
253 | it('clicks on Cancel in footer and closes content browser', () => {
254 | cy.get(withCyTag('footer-actions')).contains('Cancel').click();
255 | cy.get(withCyTag('browser')).should('not.be.visible');
256 | });
257 | });
258 | describe('Open Browser and select items', () => {
259 | it('open content browser on button click', () => {
260 | cy.openContentBrowser();
261 | });
262 | it('selects first three items from items list', () => {
263 | cy.get(withCyTag('footer-actions')).contains('Confirm').should('be.disabled');
264 | Cypress._.times(3, (i) => {
265 | cy.get(withCyTag('items-table-body')).children().eq(i).children().eq(0).find('label').click();
266 | })
267 | cy.get(withCyTag('footer-actions')).contains('Confirm').should('not.be.disabled');
268 | });
269 | it('has three selected items in store', () => {
270 | cy.checkSelectedItemsLength(3);
271 | });
272 | describe('Remove items', () => {
273 | it('removes one selected item', () => {
274 | cy.get(withCyTag('footer-items')).find('button').last().click();
275 | cy.checkSelectedItemsLength(2);
276 | });
277 | });
278 | describe('Close browser', () => {
279 | it('closes browser with two selected items', () => {
280 | cy.get(withCyTag('footer-actions')).contains('Confirm').should('not.be.disabled').click();
281 | });
282 | it('checks if two selected items are displayed', () => {
283 | cy.get(withCyTag('multiple-items')).as('items').find('.item').its('length').should('be.eq', 2);
284 | cy.window().its('store').invoke('getState').its('app').its('selectedItems').each((item) => {
285 | cy.get('@items').contains(item.name);
286 | }).then((items) => {
287 | cy.wrap(items).its('length').should('be.eq', 2);
288 | });
289 | })
290 | });
291 | });
292 | describe('Open browser with disabled items', () => {
293 | it('opens content browser and checks if two items disabled', () => {
294 | cy.openContentBrowser();
295 | cy.window().its('store').invoke('getState').its('app').its('disabledItems').its('length').should('be.eq', 2);
296 | Cypress._.times(2, (i) => {
297 | cy.get(withCyTag('items-table-body')).children().eq(i).children().eq(0).find('input[type="checkbox"]').should('be.disabled');
298 | })
299 | });
300 | it('selects first available item from items', () => {
301 | cy.get(withCyTag('items-table-body')).find('input[type="checkbox"]:not(:disabled)').first().parent().find('label').click().parents('td').invoke('text').then((text) => {
302 | cy.get(withCyTag('footer-items')).should('contain', text);
303 | });
304 | });
305 | })
306 | describe('Close browser', () => {
307 | it('closes browser with three selected items', () => {
308 | cy.get(withCyTag('footer-actions')).contains('Confirm').should('not.be.disabled').click();
309 | });
310 | it('checks if three selected items are displayed', () => {
311 | cy.get(withCyTag('multiple-items')).as('items').find('.item').its('length').should('be.eq', 3);
312 | })
313 | });
314 | });
315 | });
316 |
--------------------------------------------------------------------------------
/cypress/plugins/index.js:
--------------------------------------------------------------------------------
1 | // ***********************************************************
2 | // This example plugins/index.js can be used to load plugins
3 | //
4 | // You can change the location of this file or turn off loading
5 | // the plugins file with the 'pluginsFile' configuration option.
6 | //
7 | // You can read more here:
8 | // https://on.cypress.io/plugins-guide
9 | // ***********************************************************
10 |
11 | // This function is called when a project is opened or re-opened (e.g. due to
12 | // the project's config changing)
13 |
14 | module.exports = (on, config) => {
15 | // `on` is used to hook into various events Cypress emits
16 | // `config` is the resolved Cypress config
17 | }
18 |
--------------------------------------------------------------------------------
/cypress/support/commands.js:
--------------------------------------------------------------------------------
1 | // ***********************************************
2 | // This example commands.js shows you how to
3 | // create various custom commands and overwrite
4 | // existing commands.
5 | //
6 | // For more comprehensive examples of custom
7 | // commands please read more here:
8 | // https://on.cypress.io/custom-commands
9 | // ***********************************************
10 | //
11 | //
12 | // -- This is a parent command --
13 | // Cypress.Commands.add("login", (email, password) => { ... })
14 | //
15 | //
16 | // -- This is a child command --
17 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
18 | //
19 | //
20 | // -- This is a dual command --
21 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
22 | //
23 | //
24 | // -- This is will overwrite an existing command --
25 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
26 |
--------------------------------------------------------------------------------
/cypress/support/index.js:
--------------------------------------------------------------------------------
1 | // ***********************************************************
2 | // This example support/index.js is processed and
3 | // loaded automatically before your test files.
4 | //
5 | // This is a great place to put global configuration and
6 | // behavior that modifies Cypress.
7 | //
8 | // You can change the location of this file or turn off
9 | // automatically serving support files with the
10 | // 'supportFile' configuration option.
11 | //
12 | // You can read more here:
13 | // https://on.cypress.io/configuration
14 | // ***********************************************************
15 |
16 | // Import commands.js using ES2015 syntax:
17 | import './commands'
18 |
19 | // Alternatively you can use CommonJS syntax:
20 | // require('./commands')
21 |
--------------------------------------------------------------------------------
/cypress/utils.js:
--------------------------------------------------------------------------------
1 | /* global cy */
2 |
3 | export const loginToAdmin = (test) => {
4 | const options = {
5 | method: 'POST',
6 | url: '/admin/login_check',
7 | form: true,
8 | body: {
9 | _username: 'admin',
10 | _password: 'publish',
11 | }
12 | }
13 | cy.request(options).then(() => {
14 | cy.getCookie('eZSESSID').then(cookie => test.eZSESSID = cookie.value);
15 | });
16 | };
17 |
18 | export const withCyTag = value => `[data-cy=${value}]`;
19 |
--------------------------------------------------------------------------------
/express/data/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "item_type":"ezlocation",
3 | "min_selected":1,
4 | "max_selected":0,
5 | "has_tree":true,
6 | "has_search":true,
7 | "has_preview":true,
8 | "default_columns":["name","location_id","visible"],
9 | "available_columns":[
10 | {"id":"name","name":"Name"},
11 | {"id":"location_id","name":"Location ID"},
12 | {"id":"content_id","name":"Content ID"},
13 | {"id":"visible","name":"Visible"},
14 | {"id":"priority","name":"Priority"}
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/express/data/items.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "location_id":2,
4 | "id":2,
5 | "value":2,
6 | "name":"Home",
7 | "visible":true,
8 | "selectable":true,
9 | "has_sub_items":true,
10 | "has_sub_locations": false,
11 | "parent_id": null,
12 | "preview": "
",
13 | "columns":{
14 | "name":"Home",
15 | "location_id":"2",
16 | "id":2,
17 | "content_id":"11",
18 | "visible":"Yes",
19 | "priority":"0"
20 | }
21 | },
22 | {
23 | "location_id":1,
24 | "id":1,
25 | "value":1,
26 | "name":"Users",
27 | "visible":true,
28 | "selectable":true,
29 | "has_sub_items":true,
30 | "has_sub_locations": false,
31 | "parent_id": null,
32 | "preview": "",
33 | "columns":{
34 | "name":"Users",
35 | "location_id":"1",
36 | "id":1,
37 | "content_id":"11",
38 | "visible":"Yes",
39 | "priority":"0"
40 | }
41 | },
42 | {
43 | "location_id":100,
44 | "id":100,
45 | "value":100,
46 | "name":"Test item 100",
47 | "visible":true,
48 | "selectable":true,
49 | "has_sub_items":false,
50 | "has_sub_locations": false,
51 | "parent_id": 2,
52 | "preview": "",
53 | "columns":{
54 | "name":"Test item 100",
55 | "location_id":"100",
56 | "id":1,
57 | "content_id":"200",
58 | "visible":"Yes",
59 | "priority":"0"
60 | }
61 | },
62 | {
63 | "location_id":101,
64 | "id":101,
65 | "value":101,
66 | "name":"Test item 101",
67 | "visible":true,
68 | "selectable":true,
69 | "has_sub_items":true,
70 | "has_sub_locations": true,
71 | "parent_id": 2,
72 | "preview": "",
73 | "columns":{
74 | "name":"Test item 101",
75 | "location_id":"101",
76 | "id":1,
77 | "content_id":"200",
78 | "visible":"Yes",
79 | "priority":"0"
80 | }
81 | },
82 | {
83 | "location_id":102,
84 | "id":102,
85 | "value":102,
86 | "name":"Test item 102",
87 | "visible":true,
88 | "selectable":true,
89 | "has_sub_items":true,
90 | "has_sub_locations": false,
91 | "parent_id": 2,
92 | "preview": "",
93 | "columns":{
94 | "name":"Test item 102",
95 | "location_id":"102",
96 | "id":1,
97 | "content_id":"202",
98 | "visible":"Yes",
99 | "priority":"0"
100 | }
101 | },
102 | {
103 | "location_id":103,
104 | "id":103,
105 | "value":103,
106 | "name":"Test item 103",
107 | "visible":true,
108 | "selectable":true,
109 | "has_sub_items":false,
110 | "has_sub_locations": false,
111 | "parent_id": 2,
112 | "preview": "",
113 | "columns":{
114 | "name":"Test item 103",
115 | "location_id":"103",
116 | "id":1,
117 | "content_id":"203",
118 | "visible":"Yes",
119 | "priority":"0"
120 | }
121 | },
122 | {
123 | "location_id":104,
124 | "id":104,
125 | "value":104,
126 | "name":"Test item 104",
127 | "visible":true,
128 | "selectable":true,
129 | "has_sub_items":false,
130 | "has_sub_locations": false,
131 | "parent_id": 2,
132 | "preview": "",
133 | "columns":{
134 | "name":"Test item 104",
135 | "location_id":"104",
136 | "id":1,
137 | "content_id":"204",
138 | "visible":"Yes",
139 | "priority":"0"
140 | }
141 | },
142 | {
143 | "location_id":105,
144 | "id":105,
145 | "value":105,
146 | "name":"Test item 105",
147 | "visible":true,
148 | "selectable":true,
149 | "has_sub_items":false,
150 | "has_sub_locations": false,
151 | "parent_id": 2,
152 | "preview": "",
153 | "columns":{
154 | "name":"Test item 105",
155 | "location_id":"105",
156 | "id":1,
157 | "content_id":"205",
158 | "visible":"Yes",
159 | "priority":"0"
160 | }
161 | },
162 | {
163 | "location_id":106,
164 | "id":106,
165 | "value":106,
166 | "name":"Test item 106",
167 | "visible":true,
168 | "selectable":true,
169 | "has_sub_items":false,
170 | "has_sub_locations": false,
171 | "parent_id": 2,
172 | "preview": "",
173 | "columns":{
174 | "name":"Test item 106",
175 | "location_id":"106",
176 | "id":1,
177 | "content_id":"206",
178 | "visible":"Yes",
179 | "priority":"0"
180 | }
181 | },
182 | {
183 | "location_id":107,
184 | "id":107,
185 | "value":107,
186 | "name":"Test item 107",
187 | "visible":true,
188 | "selectable":true,
189 | "has_sub_items":false,
190 | "has_sub_locations": false,
191 | "parent_id": 2,
192 | "preview": "",
193 | "columns":{
194 | "name":"Test item 107",
195 | "location_id":"107",
196 | "id":1,
197 | "content_id":"207",
198 | "visible":"Yes",
199 | "priority":"0"
200 | }
201 | },
202 | {
203 | "location_id":108,
204 | "id":108,
205 | "value":108,
206 | "name":"Test item 108",
207 | "visible":true,
208 | "selectable":true,
209 | "has_sub_items":false,
210 | "has_sub_locations": false,
211 | "parent_id": 2,
212 | "preview": "",
213 | "columns":{
214 | "name":"Test item 108",
215 | "location_id":"108",
216 | "id":1,
217 | "content_id":"208",
218 | "visible":"Yes",
219 | "priority":"0"
220 | }
221 | },
222 | {
223 | "location_id":109,
224 | "id":109,
225 | "value":109,
226 | "name":"Test item 109",
227 | "visible":true,
228 | "selectable":true,
229 | "has_sub_items":false,
230 | "has_sub_locations": false,
231 | "parent_id": 2,
232 | "preview": "",
233 | "columns":{
234 | "name":"Test item 109",
235 | "location_id":"109",
236 | "id":1,
237 | "content_id":"209",
238 | "visible":"Yes",
239 | "priority":"0"
240 | }
241 | },
242 | {
243 | "location_id":110,
244 | "id":110,
245 | "value":110,
246 | "name":"Test item 110",
247 | "visible":true,
248 | "selectable":true,
249 | "has_sub_items":false,
250 | "has_sub_locations": false,
251 | "parent_id": 2,
252 | "preview": "",
253 | "columns":{
254 | "name":"Test item 110",
255 | "location_id":"110",
256 | "id":1,
257 | "content_id":"210",
258 | "visible":"Yes",
259 | "priority":"0"
260 | }
261 | },
262 | {
263 | "location_id":111,
264 | "id":111,
265 | "value":111,
266 | "name":"Test item 111",
267 | "visible":true,
268 | "selectable":true,
269 | "has_sub_items":false,
270 | "has_sub_locations": false,
271 | "parent_id": 2,
272 | "preview": "",
273 | "columns":{
274 | "name":"Test item 111",
275 | "location_id":"111",
276 | "id":1,
277 | "content_id":"211",
278 | "visible":"Yes",
279 | "priority":"0"
280 | }
281 | },
282 | {
283 | "location_id":112,
284 | "id":112,
285 | "value":112,
286 | "name":"Test item 112",
287 | "visible":true,
288 | "selectable":true,
289 | "has_sub_items":false,
290 | "has_sub_locations": false,
291 | "parent_id": 2,
292 | "preview": "",
293 | "columns":{
294 | "name":"Test item 112",
295 | "location_id":"112",
296 | "id":1,
297 | "content_id":"212",
298 | "visible":"Yes",
299 | "priority":"0"
300 | }
301 | },
302 | {
303 | "location_id":113,
304 | "id":113,
305 | "value":113,
306 | "name":"Test item 113",
307 | "visible":true,
308 | "selectable":true,
309 | "has_sub_items":false,
310 | "has_sub_locations": false,
311 | "parent_id": 101,
312 | "preview": "",
313 | "columns":{
314 | "name":"Test item 113",
315 | "location_id":"113",
316 | "id":1,
317 | "content_id":"212",
318 | "visible":"Yes",
319 | "priority":"0"
320 | }
321 | },
322 | {
323 | "location_id":114,
324 | "id":114,
325 | "value":114,
326 | "name":"Test item 114",
327 | "visible":true,
328 | "selectable":true,
329 | "has_sub_items":false,
330 | "has_sub_locations": false,
331 | "parent_id": 101,
332 | "preview": "",
333 | "columns":{
334 | "name":"Test item 114",
335 | "location_id":"114",
336 | "id":1,
337 | "content_id":"212",
338 | "visible":"Yes",
339 | "priority":"0"
340 | }
341 | },
342 | {
343 | "location_id":115,
344 | "id":115,
345 | "value":115,
346 | "name":"Test item 115",
347 | "visible":true,
348 | "selectable":true,
349 | "has_sub_items":false,
350 | "has_sub_locations": false,
351 | "parent_id": 101,
352 | "preview": "",
353 | "columns":{
354 | "name":"Test item 115",
355 | "location_id":"115",
356 | "id":1,
357 | "content_id":"212",
358 | "visible":"Yes",
359 | "priority":"0"
360 | }
361 | },
362 | {
363 | "location_id":116,
364 | "id":116,
365 | "value":116,
366 | "name":"Test item 116",
367 | "visible":true,
368 | "selectable":true,
369 | "has_sub_items":true,
370 | "has_sub_locations": false,
371 | "parent_id": 102,
372 | "preview": "",
373 | "columns":{
374 | "name":"Test item 116",
375 | "location_id":"116",
376 | "id":1,
377 | "content_id":"212",
378 | "visible":"Yes",
379 | "priority":"0"
380 | }
381 | },
382 | {
383 | "location_id":117,
384 | "id":117,
385 | "value":117,
386 | "name":"Test item 117",
387 | "visible":true,
388 | "selectable":true,
389 | "has_sub_items":false,
390 | "has_sub_locations": false,
391 | "parent_id": 116,
392 | "preview": "",
393 | "columns":{
394 | "name":"Test item 117",
395 | "location_id":"117",
396 | "id":1,
397 | "content_id":"212",
398 | "visible":"Yes",
399 | "priority":"0"
400 | }
401 | },
402 | {
403 | "location_id":118,
404 | "id":118,
405 | "value":118,
406 | "name":"Test item 118",
407 | "visible":true,
408 | "selectable":true,
409 | "has_sub_items":false,
410 | "has_sub_locations": false,
411 | "parent_id": 102,
412 | "preview": "",
413 | "columns":{
414 | "name":"Test item 118",
415 | "location_id":"118",
416 | "id":1,
417 | "content_id":"212",
418 | "visible":"Yes",
419 | "priority":"0"
420 | }
421 | },
422 | {
423 | "location_id":200,
424 | "id":200,
425 | "value":200,
426 | "name":"User item 200",
427 | "visible":true,
428 | "selectable":true,
429 | "has_sub_items":true,
430 | "has_sub_locations": false,
431 | "parent_id":1,
432 | "preview": "",
433 | "columns":{
434 | "name":"User item 200",
435 | "location_id":"200",
436 | "id":1,
437 | "content_id":"200",
438 | "visible":"Yes",
439 | "priority":"0"
440 | }
441 | },
442 | {
443 | "location_id":202,
444 | "id":202,
445 | "value":202,
446 | "name":"User item 202",
447 | "visible":true,
448 | "selectable":true,
449 | "has_sub_items":true,
450 | "has_sub_locations": false,
451 | "parent_id":1,
452 | "preview": "",
453 | "columns":{
454 | "name":"User item 202",
455 | "location_id":"202",
456 | "id":1,
457 | "content_id":"202",
458 | "visible":"Yes",
459 | "priority":"0"
460 | }
461 | },
462 | {
463 | "location_id":203,
464 | "id":203,
465 | "value":203,
466 | "name":"User item 203",
467 | "visible":true,
468 | "selectable":true,
469 | "has_sub_items":false,
470 | "has_sub_locations": false,
471 | "parent_id":1,
472 | "preview": "",
473 | "columns":{
474 | "name":"User item 203",
475 | "location_id":"203",
476 | "id":1,
477 | "content_id":"203",
478 | "visible":"Yes",
479 | "priority":"0"
480 | }
481 | },
482 | {
483 | "location_id":204,
484 | "id":204,
485 | "value":204,
486 | "name":"User item 204",
487 | "visible":true,
488 | "selectable":true,
489 | "has_sub_items":false,
490 | "has_sub_locations": false,
491 | "parent_id":1,
492 | "preview": "",
493 | "columns":{
494 | "name":"User item 204",
495 | "location_id":"204",
496 | "id":1,
497 | "content_id":"204",
498 | "visible":"Yes",
499 | "priority":"0"
500 | }
501 | },
502 | {
503 | "location_id":205,
504 | "id":205,
505 | "value":205,
506 | "name":"User item 205",
507 | "visible":true,
508 | "selectable":true,
509 | "has_sub_items":false,
510 | "has_sub_locations": false,
511 | "parent_id":1,
512 | "preview": "",
513 | "columns":{
514 | "name":"User item 205",
515 | "location_id":"205",
516 | "id":1,
517 | "content_id":"205",
518 | "visible":"Yes",
519 | "priority":"0"
520 | }
521 | },
522 | {
523 | "location_id":206,
524 | "id":206,
525 | "value":206,
526 | "name":"User item 206",
527 | "visible":true,
528 | "selectable":true,
529 | "has_sub_items":false,
530 | "has_sub_locations": false,
531 | "parent_id":1,
532 | "preview": "",
533 | "columns":{
534 | "name":"User item 206",
535 | "location_id":"206",
536 | "id":1,
537 | "content_id":"206",
538 | "visible":"Yes",
539 | "priority":"0"
540 | }
541 | },
542 | {
543 | "location_id":207,
544 | "id":207,
545 | "value":207,
546 | "name":"User item 207",
547 | "visible":true,
548 | "selectable":true,
549 | "has_sub_items":false,
550 | "has_sub_locations": false,
551 | "parent_id":1,
552 | "preview": "",
553 | "columns":{
554 | "name":"User item 207",
555 | "location_id":"207",
556 | "id":1,
557 | "content_id":"207",
558 | "visible":"Yes",
559 | "priority":"0"
560 | }
561 | },
562 | {
563 | "location_id":208,
564 | "id":208,
565 | "value":208,
566 | "name":"User item 208",
567 | "visible":true,
568 | "selectable":true,
569 | "has_sub_items":false,
570 | "has_sub_locations": false,
571 | "parent_id":1,
572 | "preview": "",
573 | "columns":{
574 | "name":"User item 208",
575 | "location_id":"208",
576 | "id":1,
577 | "content_id":"208",
578 | "visible":"Yes",
579 | "priority":"0"
580 | }
581 | },
582 | {
583 | "location_id":209,
584 | "id":209,
585 | "value":209,
586 | "name":"User item 209",
587 | "visible":true,
588 | "selectable":true,
589 | "has_sub_items":false,
590 | "has_sub_locations": false,
591 | "parent_id":1,
592 | "preview": "",
593 | "columns":{
594 | "name":"User item 209",
595 | "location_id":"209",
596 | "id":1,
597 | "content_id":"209",
598 | "visible":"Yes",
599 | "priority":"0"
600 | }
601 | },
602 | {
603 | "location_id":210,
604 | "id":210,
605 | "value":210,
606 | "name":"User item 210",
607 | "visible":true,
608 | "selectable":true,
609 | "has_sub_items":false,
610 | "has_sub_locations": false,
611 | "parent_id":1,
612 | "preview": "",
613 | "columns":{
614 | "name":"User item 210",
615 | "location_id":"210",
616 | "id":1,
617 | "content_id":"210",
618 | "visible":"Yes",
619 | "priority":"0"
620 | }
621 | },
622 | {
623 | "location_id":211,
624 | "id":211,
625 | "value":211,
626 | "name":"User item 211",
627 | "visible":true,
628 | "selectable":true,
629 | "has_sub_items":false,
630 | "has_sub_locations": false,
631 | "parent_id":1,
632 | "preview": "",
633 | "columns":{
634 | "name":"User item 211",
635 | "location_id":"211",
636 | "id":1,
637 | "content_id":"211",
638 | "visible":"Yes",
639 | "priority":"0"
640 | }
641 | },
642 | {
643 | "location_id":212,
644 | "id":212,
645 | "value":212,
646 | "name":"User item 212",
647 | "visible":true,
648 | "selectable":true,
649 | "has_sub_items":false,
650 | "has_sub_locations": false,
651 | "parent_id":1,
652 | "preview": "",
653 | "columns":{
654 | "name":"User item 212",
655 | "location_id":"212",
656 | "id":1,
657 | "content_id":"212",
658 | "visible":"Yes",
659 | "priority":"0"
660 | }
661 | }
662 | ]
663 |
--------------------------------------------------------------------------------
/express/helpers.js:
--------------------------------------------------------------------------------
1 | const items = require('./data/items.json');
2 | const config = require('./data/config.json');
3 |
4 | const getItem = (id) => {
5 | return items.find(item => item.location_id === id);
6 | }
7 |
8 | const getChildrenItems = (req) => {
9 | const parentLocation = parseInt(req.params.id, 10);
10 | let children = [];
11 | items.forEach(item => {
12 | if (item.parent_id === parentLocation) {
13 | const newItem = {...item};
14 | delete newItem.id;
15 | delete newItem.parent_id;
16 | delete newItem.has_sub_locations;
17 | delete newItem.preview;
18 | children.push(newItem);
19 | }
20 | });
21 | const children_count = children.length;
22 | const parent = getItem(parentLocation);
23 | const path = getPath(parent, []);
24 | const page = req.query.page || 1;
25 | const limit = req.query.limit || children_count;
26 | children = children.slice((page - 1) * limit, page * limit);
27 | const data = {
28 | path,
29 | parent,
30 | children,
31 | children_count,
32 | };
33 | return data;
34 | }
35 |
36 | const getLocationItems = (req) => {
37 | const parentLocation = parseInt(req.params.id, 10);
38 | const children = [];
39 | items.forEach(item => {
40 | if (item.parent_id === parentLocation) {
41 | const newItem = {...item};
42 | delete newItem.selectable;
43 | delete newItem.value;
44 | delete newItem.location_id;
45 | delete newItem.columns;
46 | delete newItem.preview;
47 | newItem.columns = {
48 | "name": item.columns.name,
49 | };
50 | children.push(newItem);
51 | }
52 | });
53 | return {
54 | children,
55 | };
56 | }
57 |
58 | const getSections = () => {
59 | const sections = [];
60 | items.forEach((item) => {
61 | if (item.parent_id === null) {
62 | sections.push({
63 | "id":item.id,
64 | "parent_id":item.parent_id,
65 | "name":item.name,
66 | "has_sub_items":item.has_sub_items,
67 | "has_sub_locations":item.has_sub_locations,
68 | "visible":item.visible,
69 | "columns":{
70 | "name":item.columns.name,
71 | },
72 | });
73 | }
74 | });
75 | return sections;
76 | }
77 |
78 | const getPath = (item, path) => {
79 | path.unshift({
80 | "id":item.id,
81 | "name": item.name,
82 | });
83 | if (item.parent_id) {
84 | getPath(getItem(item.parent_id), path);
85 | }
86 | return path;
87 | }
88 |
89 | const getConfig = () => {
90 | const sections = getSections();
91 | return {
92 | ...config,
93 | sections,
94 | };
95 | }
96 |
97 | const getPreview = (req) => {
98 | const id = parseInt(req.params.id, 10);
99 | const item = getItem(id);
100 | return item.preview;
101 | }
102 |
103 | const getSearchResults = (req) => {
104 | const parentLocation = parseInt(req.query.sectionId, 10);
105 | let children = [];
106 | if (req.query.searchText.length > 2) items.forEach(item => {
107 | if (children.length < 12 && item.parent_id === parentLocation) {
108 | const newItem = {...item};
109 | delete newItem.id;
110 | delete newItem.parent_id;
111 | delete newItem.has_sub_locations;
112 | delete newItem.preview;
113 | children.push(newItem);
114 | }
115 | });
116 | const children_count = children.length;
117 | const page = req.query.page || 1;
118 | const limit = req.query.limit || children_count;
119 | children = children.slice((page - 1) * limit, page * limit);
120 | const data = {
121 | children,
122 | children_count,
123 | };
124 | return data;
125 | }
126 |
127 | module.exports = {
128 | getChildrenItems,
129 | getLocationItems,
130 | getPath,
131 | getConfig,
132 | getItem,
133 | getPreview,
134 | getSearchResults,
135 | }
136 |
--------------------------------------------------------------------------------
/express/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Netgen Content Browser
9 |
15 |
16 |
17 |
18 |
19 |
20 |
47 |
48 |
69 |
70 |
71 |
76 |
77 |
78 | (NO ITEMS SELECTED)
79 |
80 |
81 |
82 |
Add items
83 |
84 |
85 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/express/server.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const express = require('express');
3 | const model = require('./helpers');
4 |
5 | const app = express();
6 |
7 | app.get('/cb/api/:name/config', function (req, res) {
8 | const data = model.getConfig(req);
9 | res.send(data);
10 | });
11 |
12 | app.get('/cb/api/:name/browse/:id/items', function (req, res) {
13 | const data = model.getChildrenItems(req);
14 | res.send(data);
15 | });
16 |
17 | app.get('/cb/api/:name/browse/:id/locations', function (req, res) {
18 | const data = model.getLocationItems(req);
19 | res.send(data);
20 | });
21 |
22 | app.get('/cb/api/:name/render/:id', function (req, res) {
23 | const data = model.getPreview(req);
24 | res.send(data);
25 | });
26 |
27 | app.get('/cb/api/:name/search', function (req, res) {
28 | const data = model.getSearchResults(req);
29 | res.send(data);
30 | });
31 |
32 | app.use(express.static(path.join(__dirname, '../bundle/Resources/public'), {
33 | index: false
34 | }));
35 |
36 | app.get('*', function(req, res) {
37 | res.sendFile(path.join(__dirname, 'public', 'index.html'));
38 | });
39 |
40 | app.listen('8282');
41 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@netgen/content-browser-ui",
3 | "description": "Netgen Content Browser user interface",
4 | "version": "1.4.0",
5 | "repository": {
6 | "type": "git",
7 | "url": "https://github.com/netgen-layouts/content-browser-ui.git"
8 | },
9 | "private": true,
10 | "main": "bundle/Resources/public/js/main.js",
11 | "scripts": {
12 | "start": "nodemon --watch .env --watch config --watch scripts node scripts/start.js",
13 | "build": "node scripts/build.js",
14 | "cypress": "cypress open",
15 | "express": "nodemon express/server.js -V -w express",
16 | "test": "CYPRESS_baseUrl=http://localhost:8282 cypress run",
17 | "ci": "start-test 'node express/server.js' 8282 test"
18 | },
19 | "eslintConfig": {
20 | "extends": "react-app"
21 | },
22 | "browserslist": [
23 | ">0.2%",
24 | "not dead",
25 | "not ie <= 10",
26 | "not op_mini all"
27 | ],
28 | "babel": {
29 | "presets": [
30 | "@babel/react",
31 | "@babel/env"
32 | ]
33 | },
34 | "devDependencies": {
35 | "@babel/core": "^7.7.5",
36 | "@svgr/webpack": "^4.3.3",
37 | "babel-core": "^6.26.3",
38 | "babel-eslint": "^10.0.3",
39 | "babel-loader": "^8.0.6",
40 | "babel-plugin-named-asset-import": "^0.3.5",
41 | "babel-preset-react-app": "^9.1.0",
42 | "css-loader": "^3.4.0",
43 | "cypress": "^3.8.0",
44 | "dotenv": "^8.2.0",
45 | "dotenv-expand": "^5.1.0",
46 | "eslint": "^6.7.2",
47 | "eslint-config-react-app": "^5.1.0",
48 | "eslint-loader": "^3.0.3",
49 | "eslint-plugin-flowtype": "^4.5.2",
50 | "eslint-plugin-import": "^2.19.1",
51 | "eslint-plugin-jsx-a11y": "^6.2.3",
52 | "eslint-plugin-react": "^7.17.0",
53 | "eslint-plugin-react-hooks": "^2.3.0",
54 | "express": "^4.17.1",
55 | "file-loader": "^5.0.2",
56 | "html-webpack-plugin": "^3.2.0",
57 | "http-proxy-middleware": "^0.20.0",
58 | "mini-css-extract-plugin": "^0.8.1",
59 | "nodemon": "^2.0.2",
60 | "optimize-css-assets-webpack-plugin": "^5.0.3",
61 | "postcss-flexbugs-fixes": "^4.1.0",
62 | "postcss-loader": "^3.0.0",
63 | "postcss-preset-env": "^6.7.0",
64 | "postcss-safe-parser": "^4.0.1",
65 | "sass-loader": "^8.0.0",
66 | "start-server-and-test": "^1.10.6",
67 | "style-loader": "^1.0.2",
68 | "terser-webpack-plugin": "^2.3.1",
69 | "url-loader": "^3.0.0",
70 | "webpack": "^4.41.3",
71 | "webpack-dev-server": "^3.9.0"
72 | },
73 | "dependencies": {
74 | "@material-ui/core": "^4.8.0",
75 | "@material-ui/icons": "^4.5.1",
76 | "cross-fetch": "^3.0.4",
77 | "react": "^16.12.0",
78 | "react-addons-css-transition-group": "^15.6.2",
79 | "react-app-polyfill": "^1.0.5",
80 | "react-dev-utils": "^10.0.0",
81 | "react-dom": "^16.12.0",
82 | "react-redux": "^7.1.3",
83 | "react-transition-group": "^4.3.0",
84 | "redux": "^4.0.4",
85 | "redux-devtools-extension": "^2.13.8",
86 | "redux-thunk": "^2.3.0"
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Netgen Content Browser
9 |
15 |
16 |
17 |
18 |
19 |
46 |
47 |
68 |
69 |
70 |
75 |
76 |
77 | (NO ITEMS SELECTED)
78 |
79 |
80 |
81 |
Add items
82 |
83 |
84 |
85 |
86 |
87 |
--------------------------------------------------------------------------------
/scripts/build.js:
--------------------------------------------------------------------------------
1 | // Do this as the first thing so that any code reading it knows the right env.
2 | process.env.BABEL_ENV = 'production';
3 | process.env.NODE_ENV = 'production';
4 |
5 | process.on('unhandledRejection', err => {
6 | throw err;
7 | });
8 |
9 | // Ensure environment variables are read.
10 | require('../config/env');
11 |
12 | const chalk = require('react-dev-utils/chalk');
13 | const webpack = require('webpack');
14 | const configFactory = require('../config/webpack.config');
15 | const paths = require('../config/paths');
16 | const formatWebpackMessages = require('react-dev-utils/formatWebpackMessages');
17 | const FileSizeReporter = require('react-dev-utils/FileSizeReporter');
18 | const printBuildError = require('react-dev-utils/printBuildError');
19 |
20 | const measureFileSizesBeforeBuild =
21 | FileSizeReporter.measureFileSizesBeforeBuild;
22 | const printFileSizesAfterBuild = FileSizeReporter.printFileSizesAfterBuild;
23 |
24 | // These sizes are pretty large. We'll warn for bundles exceeding them.
25 | const WARN_AFTER_BUNDLE_GZIP_SIZE = 512 * 1024;
26 | const WARN_AFTER_CHUNK_GZIP_SIZE = 1024 * 1024;
27 |
28 | // Generate configuration
29 | const config = configFactory('production');
30 |
31 | measureFileSizesBeforeBuild(paths.appBuild)
32 | .then(previousFileSizes => {
33 | // Start the webpack build
34 | return build(previousFileSizes);
35 | })
36 | .then(
37 | ({ stats, previousFileSizes, warnings }) => {
38 | if (warnings.length) {
39 | console.log(chalk.yellow('Compiled with warnings.\n'));
40 | console.log(warnings.join('\n\n'));
41 | console.log(
42 | '\nSearch for the ' +
43 | chalk.underline(chalk.yellow('keywords')) +
44 | ' to learn more about each warning.'
45 | );
46 | console.log(
47 | 'To ignore, add ' +
48 | chalk.cyan('// eslint-disable-next-line') +
49 | ' to the line before.\n'
50 | );
51 | } else {
52 | console.log(chalk.green('Compiled successfully.\n'));
53 | }
54 |
55 | console.log('File sizes after gzip:\n');
56 | printFileSizesAfterBuild(
57 | stats,
58 | previousFileSizes,
59 | paths.appBuild,
60 | WARN_AFTER_BUNDLE_GZIP_SIZE,
61 | WARN_AFTER_CHUNK_GZIP_SIZE
62 | );
63 | console.log();
64 | },
65 | err => {
66 | console.log(chalk.red('Failed to compile.\n'));
67 | printBuildError(err);
68 | process.exit(1);
69 | }
70 | )
71 | .catch(err => {
72 | if (err && err.message) {
73 | console.log(err.message);
74 | }
75 | process.exit(1);
76 | });
77 |
78 | // Create the production build and print the deployment instructions.
79 | function build(previousFileSizes) {
80 | console.log('Creating an optimized production build...');
81 |
82 | let compiler = webpack(config);
83 | return new Promise((resolve, reject) => {
84 | compiler.run((err, stats) => {
85 | let messages;
86 | if (err) {
87 | if (!err.message) {
88 | return reject(err);
89 | }
90 | messages = formatWebpackMessages({
91 | errors: [err.message],
92 | warnings: [],
93 | });
94 | } else {
95 | messages = formatWebpackMessages(
96 | stats.toJson({ all: false, warnings: true, errors: true })
97 | );
98 | }
99 | if (messages.errors.length) {
100 | // Only keep the first error. Others are often indicative
101 | // of the same problem, but confuse the reader with noise.
102 | if (messages.errors.length > 1) {
103 | messages.errors.length = 1;
104 | }
105 | return reject(new Error(messages.errors.join('\n\n')));
106 | }
107 | if (
108 | process.env.CI &&
109 | (typeof process.env.CI !== 'string' ||
110 | process.env.CI.toLowerCase() !== 'false') &&
111 | messages.warnings.length
112 | ) {
113 | console.log(
114 | chalk.yellow(
115 | '\nTreating warnings as errors because process.env.CI = true.\n' +
116 | 'Most CI servers set it automatically.\n'
117 | )
118 | );
119 | return reject(new Error(messages.warnings.join('\n\n')));
120 | }
121 |
122 | const resolveArgs = {
123 | stats,
124 | previousFileSizes,
125 | warnings: messages.warnings,
126 | };
127 |
128 | return resolve(resolveArgs);
129 | });
130 | });
131 | }
132 |
--------------------------------------------------------------------------------
/scripts/start.js:
--------------------------------------------------------------------------------
1 | // Do this as the first thing so that any code reading it knows the right env.
2 | process.env.BABEL_ENV = 'development';
3 | process.env.NODE_ENV = 'development';
4 |
5 | // Makes the script crash on unhandled rejections instead of silently
6 | // ignoring them. In the future, promise rejections that are not handled will
7 | // terminate the Node.js process with a non-zero exit code.
8 | process.on('unhandledRejection', err => {
9 | throw err;
10 | });
11 |
12 | // Ensure environment variables are read.
13 | require('../config/env');
14 |
15 | const chalk = require('react-dev-utils/chalk');
16 | const webpack = require('webpack');
17 | const WebpackDevServer = require('webpack-dev-server');
18 | const {
19 | choosePort,
20 | createCompiler,
21 | prepareProxy,
22 | prepareUrls,
23 | } = require('react-dev-utils/WebpackDevServerUtils');
24 | const openBrowser = require('react-dev-utils/openBrowser');
25 | const paths = require('../config/paths');
26 | const configFactory = require('../config/webpack.config');
27 | const createDevServerConfig = require('../config/webpackDevServer.config');
28 |
29 | const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 3000;
30 | const HOST = process.env.HOST || '0.0.0.0';
31 |
32 | choosePort(HOST, DEFAULT_PORT)
33 | .then(port => {
34 | if (port == null) {
35 | // We have not found a port.
36 | return;
37 | }
38 | const config = configFactory('development');
39 | const protocol = process.env.HTTPS === 'true' ? 'https' : 'http';
40 | const appName = require(paths.appPackageJson).name;
41 | const urls = prepareUrls(protocol, HOST, port);
42 | const devSocket = {
43 | warnings: warnings =>
44 | devServer.sockWrite(devServer.sockets, 'warnings', warnings),
45 | errors: errors =>
46 | devServer.sockWrite(devServer.sockets, 'errors', errors),
47 | };
48 | // Create a webpack compiler that is configured with custom messages.
49 | const compiler = createCompiler({
50 | appName,
51 | config,
52 | devSocket,
53 | urls,
54 | webpack,
55 | });
56 | // Load proxy config
57 | const proxySetting = require(paths.appPackageJson).proxy;
58 | const proxyConfig = prepareProxy(proxySetting, paths.appPublic);
59 | // Serve webpack assets generated by the compiler over a web server.
60 | const serverConfig = createDevServerConfig(
61 | proxyConfig,
62 | urls.lanUrlForConfig
63 | );
64 | const devServer = new WebpackDevServer(compiler, serverConfig);
65 | // Launch WebpackDevServer.
66 | devServer.listen(port, HOST, err => {
67 | if (err) {
68 | return console.log(err);
69 | }
70 | console.log(chalk.cyan('Starting the development server...\n'));
71 | openBrowser(urls.localUrlForBrowser);
72 | });
73 |
74 | ['SIGINT', 'SIGTERM'].forEach(function(sig) {
75 | process.on(sig, function() {
76 | devServer.close();
77 | process.exit();
78 | });
79 | });
80 | })
81 | .catch(err => {
82 | if (err && err.message) {
83 | console.log(err.message);
84 | }
85 | process.exit(1);
86 | });
87 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Browser from './containers/Browser';
3 | import { Provider as ReduxProvider } from 'react-redux';
4 | import configureStore from './store/store';
5 | import { initialSetup } from './store/actions/app';
6 |
7 | function App({ overrides = {}, onCancel, onConfirm, itemType, disabledItems = [] }) {
8 | const reduxStore = configureStore();
9 | reduxStore.dispatch(initialSetup({
10 | itemType,
11 | onCancel,
12 | onConfirm,
13 | config: overrides,
14 | disabledItems,
15 | itemsLimit: localStorage.getItem('cb_itemsLimit') || 10,
16 | showPreview: localStorage.getItem('cb_showPreview') ? JSON.parse(localStorage.getItem('cb_showPreview')) : false,
17 | }))
18 |
19 | return (
20 |
21 |
22 |
23 | );
24 | }
25 |
26 | export default App;
27 |
--------------------------------------------------------------------------------
/src/App.module.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netgen-layouts/content-browser-ui/df625ae599d9c71aac2493ca7fbd03cef4d781f1/src/App.module.css
--------------------------------------------------------------------------------
/src/assets/fonts/Roboto/Roboto-300.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netgen-layouts/content-browser-ui/df625ae599d9c71aac2493ca7fbd03cef4d781f1/src/assets/fonts/Roboto/Roboto-300.eot
--------------------------------------------------------------------------------
/src/assets/fonts/Roboto/Roboto-300.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netgen-layouts/content-browser-ui/df625ae599d9c71aac2493ca7fbd03cef4d781f1/src/assets/fonts/Roboto/Roboto-300.ttf
--------------------------------------------------------------------------------
/src/assets/fonts/Roboto/Roboto-300.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netgen-layouts/content-browser-ui/df625ae599d9c71aac2493ca7fbd03cef4d781f1/src/assets/fonts/Roboto/Roboto-300.woff
--------------------------------------------------------------------------------
/src/assets/fonts/Roboto/Roboto-300.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netgen-layouts/content-browser-ui/df625ae599d9c71aac2493ca7fbd03cef4d781f1/src/assets/fonts/Roboto/Roboto-300.woff2
--------------------------------------------------------------------------------
/src/assets/fonts/Roboto/Roboto-500.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netgen-layouts/content-browser-ui/df625ae599d9c71aac2493ca7fbd03cef4d781f1/src/assets/fonts/Roboto/Roboto-500.eot
--------------------------------------------------------------------------------
/src/assets/fonts/Roboto/Roboto-500.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netgen-layouts/content-browser-ui/df625ae599d9c71aac2493ca7fbd03cef4d781f1/src/assets/fonts/Roboto/Roboto-500.ttf
--------------------------------------------------------------------------------
/src/assets/fonts/Roboto/Roboto-500.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netgen-layouts/content-browser-ui/df625ae599d9c71aac2493ca7fbd03cef4d781f1/src/assets/fonts/Roboto/Roboto-500.woff
--------------------------------------------------------------------------------
/src/assets/fonts/Roboto/Roboto-500.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netgen-layouts/content-browser-ui/df625ae599d9c71aac2493ca7fbd03cef4d781f1/src/assets/fonts/Roboto/Roboto-500.woff2
--------------------------------------------------------------------------------
/src/assets/fonts/Roboto/Roboto-700.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netgen-layouts/content-browser-ui/df625ae599d9c71aac2493ca7fbd03cef4d781f1/src/assets/fonts/Roboto/Roboto-700.eot
--------------------------------------------------------------------------------
/src/assets/fonts/Roboto/Roboto-700.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netgen-layouts/content-browser-ui/df625ae599d9c71aac2493ca7fbd03cef4d781f1/src/assets/fonts/Roboto/Roboto-700.ttf
--------------------------------------------------------------------------------
/src/assets/fonts/Roboto/Roboto-700.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netgen-layouts/content-browser-ui/df625ae599d9c71aac2493ca7fbd03cef4d781f1/src/assets/fonts/Roboto/Roboto-700.woff
--------------------------------------------------------------------------------
/src/assets/fonts/Roboto/Roboto-700.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netgen-layouts/content-browser-ui/df625ae599d9c71aac2493ca7fbd03cef4d781f1/src/assets/fonts/Roboto/Roboto-700.woff2
--------------------------------------------------------------------------------
/src/assets/fonts/Roboto/Roboto-regular.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netgen-layouts/content-browser-ui/df625ae599d9c71aac2493ca7fbd03cef4d781f1/src/assets/fonts/Roboto/Roboto-regular.eot
--------------------------------------------------------------------------------
/src/assets/fonts/Roboto/Roboto-regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netgen-layouts/content-browser-ui/df625ae599d9c71aac2493ca7fbd03cef4d781f1/src/assets/fonts/Roboto/Roboto-regular.ttf
--------------------------------------------------------------------------------
/src/assets/fonts/Roboto/Roboto-regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netgen-layouts/content-browser-ui/df625ae599d9c71aac2493ca7fbd03cef4d781f1/src/assets/fonts/Roboto/Roboto-regular.woff
--------------------------------------------------------------------------------
/src/assets/fonts/Roboto/Roboto-regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netgen-layouts/content-browser-ui/df625ae599d9c71aac2493ca7fbd03cef4d781f1/src/assets/fonts/Roboto/Roboto-regular.woff2
--------------------------------------------------------------------------------
/src/components/Breadcrumbs.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Button from './utils/Button';
3 | import S from './Breadcrumbs.module.css';
4 |
5 | function Breadcrumbs({ items, setId }) {
6 | if (!items) {
7 | return '';
8 | } else {
9 | return (
10 |
11 | {items.map((item, i) => (
12 | -
13 | {i < items.length - 1 ?
14 |
15 | :
16 | {item.name}
17 | }
18 |
19 | ))}
20 |
21 | );
22 | }
23 | }
24 |
25 | export default Breadcrumbs;
26 |
--------------------------------------------------------------------------------
/src/components/Breadcrumbs.module.css:
--------------------------------------------------------------------------------
1 | .breadcrumbs {
2 | list-style-type: none;
3 | margin: 0;
4 | padding: 0;
5 | display: flex;
6 | align-items: flex-start;
7 | flex-wrap: wrap;
8 | }
9 |
10 | .breadcrumbs li {
11 | display: inline-block;
12 | font-size: .75em;
13 | margin: 0;
14 | padding: 0;
15 | }
16 |
17 | .breadcrumbs li + li {
18 | position: relative;
19 | padding-left: 1.5em;
20 | }
21 |
22 | .breadcrumbs li + li::before {
23 | content: '/';
24 | display: inline-block;
25 | margin: 0 .5em;
26 | position: absolute;
27 | left: 0;
28 | top: .125em;
29 | }
30 |
--------------------------------------------------------------------------------
/src/components/Browser.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react';
2 | import { CSSTransition } from 'react-transition-group';
3 | import Tree from '../containers/Tree';
4 | import Items from '../containers/Items';
5 | import Search from '../containers/Search';
6 | import Tabs from './Tabs';
7 | import TogglePreview from './TogglePreview';
8 | import Footer from './Footer';
9 | import ListIcon from '@material-ui/icons/List';
10 | import SearchIcon from '@material-ui/icons/Search';
11 | import Loader from './utils/Loader';
12 | import S from './Browser.module.css';
13 |
14 | function BrowserContent(props) {
15 | return (
16 |
17 |
: ''}>
18 |
}>
19 |
20 | {props.config.has_tree ?
21 |
22 |
23 |
24 | : null
25 | }
26 |
27 |
28 |
29 | {props.config.has_search &&
30 | }>
31 |
32 |
33 |
34 |
35 | }
36 |
37 |
38 |
39 | );
40 | }
41 |
42 | function Browser(props) {
43 | useEffect(() => {
44 | window.addEventListener('keydown', handleKeypress);
45 | });
46 | useEffect(() => {
47 | return () => {
48 | window.removeEventListener('keydown', handleKeypress);
49 | }
50 | });
51 |
52 | const handleKeypress = (e) => {
53 | e.keyCode === 27 && props.onCancel();
54 | }
55 |
56 | return (
57 |
58 |
71 |
72 | {props.error ?
73 |
Error: {props.error.message}
74 | :
75 | !props.isLoaded ?
76 |
77 | :
78 |
79 | }
80 |
81 |
82 |
83 | );
84 | }
85 |
86 | export default Browser;
87 |
--------------------------------------------------------------------------------
/src/components/Browser.module.css:
--------------------------------------------------------------------------------
1 | @value variables: './Variables.module.css';
2 | @value gray60, gray20 from variables;
3 |
4 | .browser {
5 | position: fixed;
6 | left: 0;
7 | top: 0;
8 | width: 100%;
9 | height: 100%;
10 | z-index: 1000;
11 | background: rgba(0, 0, 0, .5);
12 | color: gray20;
13 | font-family: 'Roboto', 'Helvetica Neue', sans-serif;
14 | -webkit-font-smoothing: antialiased;
15 | -moz-osx-font-smoothing: grayscale;
16 | overflow-x: hidden;
17 | font-size: 16px;
18 | line-height: 1.2;
19 | }
20 |
21 | .browser * {
22 | box-sizing: border-box;
23 | }
24 |
25 | .dialog {
26 | padding: 1em;
27 | height: 100%;
28 | display: flex;
29 | flex-direction: column;
30 | }
31 |
32 | .content {
33 | background: #fff;
34 | box-shadow: 0 .5em 1em rgba(0, 0, 0, .5);
35 | flex: 1;
36 | display: flex;
37 | flex-direction: column;
38 | position: relative;
39 | }
40 |
41 | .panels {
42 | display: flex;
43 | flex: 1;
44 | }
45 |
46 | .treePanel {
47 | flex: 0 0 25%;
48 | padding: 0 1em;
49 | display: flex;
50 | flex-direction: column;
51 | position: relative;
52 | max-height: 100%;
53 | overflow-y: auto;
54 | }
55 |
56 | .loading {
57 | background: gray20;
58 | color: gray60;
59 | flex: 1;
60 | box-shadow: 0 .5em 1em rgba(0, 0, 0, .5);
61 | }
62 |
63 | .slideEnter {
64 | opacity: .01;
65 | transform: translate3d(0, -10%, 0);
66 | }
67 |
68 | .slideActiveEnter {
69 | opacity: 1;
70 | transform: translate3d(0, 0, 0);
71 | transition: opacity .25s, transform .5s;
72 | }
73 |
--------------------------------------------------------------------------------
/src/components/Footer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Button from './utils/Button';
3 | import FooterItem from './FooterItem';
4 | import S from './Footer.module.css';
5 | import { connect } from 'react-redux';
6 | import { setSelectedItem } from '../store/actions/app';
7 |
8 | const mapsStateToProps = state => ({
9 | selectedItems: state.app.selectedItems,
10 | min_selected: parseInt(state.app.config.min_selected, 10),
11 | onCancel: state.app.onCancel,
12 | onConfirm: state.app.onConfirm,
13 | });
14 |
15 | const mapDispatchToProps = dispatch => ({
16 | setSelectedItem: (id, toggle) => {
17 | dispatch(setSelectedItem(id, toggle));
18 | },
19 | });
20 |
21 | function Footer(props) {
22 | return (
23 |
24 |
25 | {props.selectedItems.map(item => props.setSelectedItem(item, false)} />)}
26 |
27 |
28 |
29 |
30 |
31 |
32 | );
33 | }
34 |
35 | export default connect(
36 | mapsStateToProps,
37 | mapDispatchToProps
38 | )(Footer);
39 |
--------------------------------------------------------------------------------
/src/components/Footer.module.css:
--------------------------------------------------------------------------------
1 | @value variables: './Variables.module.css';
2 | @value gray22, gray60 from variables;
3 |
4 | .footer {
5 | background: gray22;
6 | color: #fff;
7 | padding: .75em;
8 | display: flex;
9 | justify-content: space-between;
10 | }
11 |
12 | .actions {
13 | min-width: 11em;
14 | text-align: right;
15 | }
16 |
17 | .footer button {
18 | margin: .25em;
19 | }
20 |
21 | .itemIcon {
22 | margin-left: .5em;
23 | color: gray60;
24 | font-size: 1.25em;
25 | display: inline-flex;
26 | }
27 |
--------------------------------------------------------------------------------
/src/components/FooterItem.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Button from './utils/Button';
3 | import ClearIcon from '@material-ui/icons/Clear';
4 | import S from './Footer.module.css';
5 |
6 | function FooterItem({ item, onClick }) {
7 | return (
8 |
14 | );
15 | }
16 |
17 | export default FooterItem;
18 |
--------------------------------------------------------------------------------
/src/components/Item.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Button from './utils/Button';
3 | import Checkbox from './utils/Checkbox';
4 | import VisibilityOffIcon from '@material-ui/icons/VisibilityOff';
5 | import S from './ItemsTable.module.css';
6 | import { connect } from 'react-redux';
7 | import { setSelectedItem } from '../store/actions/app';
8 |
9 | const mapsStateToProps = state => ({
10 | selectedItems: state.app.selectedItems,
11 | max_selected: parseInt(state.app.config.max_selected, 10),
12 | disabledItems: state.app.disabledItems,
13 | });
14 |
15 | const mapDispatchToProps = dispatch => ({
16 | setSelectedItem: (id, checked) => {
17 | dispatch(setSelectedItem(id, checked));
18 | },
19 | });
20 |
21 | function Item(props) {
22 | const { item } = props;
23 | const isItemDisabled = props.disabledItems.indexOf(item.value) > -1;
24 | const isChecked = isItemDisabled || props.selectedItems.findIndex(selectedItem => selectedItem.value === item.value) > -1;
25 | const isDisabled = !item.selectable || isItemDisabled || (!isChecked && props.max_selected !== 0 && props.max_selected > 1 && props.selectedItems.length >= props.max_selected);
26 | return (
27 | {if (item.value) props.setPreviewItem(item.value)}} className={props.previewItem === item.value ? S.activeRow : undefined}>
28 |
29 |
30 | props.setSelectedItem(item, e.target.checked)}
34 | checked={isChecked}
35 | disabled={isDisabled}
36 | />
37 |
38 | {!item.visible && }
39 | {item.has_sub_items && props.setId ? : {item.name}}
40 | |
41 | {props.columns.map((column) => {
42 | if (column.id === 'name') return false;
43 | return | ;
44 | })}
45 |
46 | );
47 | }
48 |
49 | export default connect(
50 | mapsStateToProps,
51 | mapDispatchToProps
52 | )(Item);
53 |
--------------------------------------------------------------------------------
/src/components/Items.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { CSSTransition } from 'react-transition-group';
3 | import ItemsTable from './ItemsTable';
4 | import Breadcrumbs from './Breadcrumbs';
5 | import TableSettings from './TableSettings';
6 | import Preview from './Preview';
7 | import Loader from './utils/Loader';
8 | import S from './Items.module.css';
9 |
10 | function ItemsContent(props) {
11 | if (!props.items) {
12 | return '';
13 | } else if (props.isLoading) {
14 | return ;
15 | } else {
16 | return (
17 |
30 |
39 |
40 | );
41 | }
42 | }
43 |
44 | function Items(props) {
45 | return (
46 | <>
47 |
48 |
49 |
50 |
51 | >
52 | );
53 |
54 | }
55 |
56 | export default Items;
57 |
--------------------------------------------------------------------------------
/src/components/Items.module.css:
--------------------------------------------------------------------------------
1 | @value variables: './Variables.module.css';
2 | @value gray93 from variables;
3 |
4 | .items {
5 | flex: 1;
6 | background: gray93;
7 | position: relative;
8 | max-height: 100%;
9 | }
10 |
11 | .header {
12 | display: flex;
13 | padding: .75em 1em;
14 | justify-content: space-between;
15 | align-items: center;
16 | }
17 |
18 | .fadeEnter {
19 | opacity: .01;
20 | }
21 |
22 | .fadeActiveEnter {
23 | opacity: 1;
24 | transition: opacity .2s;
25 | }
26 |
27 | .fadeExit {
28 | opacity: 1;
29 | }
30 |
31 | .fadeActiveExit {
32 | opacity: .01;
33 | transition: opacity .2s;
34 | }
35 |
36 | .wrapper {
37 | position: absolute;
38 | left: 0;
39 | top: 0;
40 | right: 0;
41 | bottom: 0;
42 | overflow-y: auto;
43 | }
44 |
45 | .settings {
46 | flex: 1;
47 | text-align: right;
48 | }
49 |
--------------------------------------------------------------------------------
/src/components/ItemsTable.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { connect } from 'react-redux';
3 | import Item from './Item';
4 | import Pager from './utils/Pager';
5 | import S from './ItemsTable.module.css';
6 |
7 | const mapsStateToProps = state => ({
8 | availableColumns: state.app.config.available_columns,
9 | activeColumns: state.app.activeColumns,
10 | });
11 |
12 | function ItemsTable(props) {
13 | const visibleColumns = props.availableColumns.filter(column => props.activeColumns.includes(column.id));
14 | const showParent = props.showParentItem && props.items.parent && !!props.items.parent.value;
15 |
16 | let className = S.table;
17 | if (showParent) className += ` ${S.indent}`;
18 |
19 | if (!props.items.children) {
20 | return '';
21 | } else {
22 | return (
23 | <>
24 |
25 |
26 |
27 | {visibleColumns.map((column) => {column.name} | )}
28 |
29 | {showParent &&
35 | }
36 |
37 |
38 | {props.items.children.map(child => (
39 |
47 | ))}
48 |
49 |
50 |
55 | >
56 | );
57 | }
58 | }
59 |
60 | export default connect(
61 | mapsStateToProps,
62 | )(ItemsTable);
63 |
--------------------------------------------------------------------------------
/src/components/ItemsTable.module.css:
--------------------------------------------------------------------------------
1 | .table {
2 | width: 100%;
3 | border-collapse: collapse;
4 | border-spacing: 0;
5 | border-bottom: 1px solid #cccccc;
6 | }
7 |
8 | .table th,
9 | .table td {
10 | padding: .5em .5em .5em 0;
11 | font-size: .875em;
12 | line-height: 1.3;
13 | vertical-align: middle;
14 | transition: background-color .25s;
15 | }
16 |
17 | .table th {
18 | background: #999999;
19 | color: #fff;
20 | font-size: .75em;
21 | text-transform: uppercase;
22 | font-weight: 400;
23 | text-align: left;
24 | }
25 |
26 | .table thead td {
27 | border-bottom: 1px solid #cccccc;
28 | }
29 |
30 | .table thead th,
31 | .table thead td {
32 | padding-top: .8333333333em;
33 | padding-bottom: .8333333333em;
34 | }
35 |
36 | .table tr {
37 | cursor: default;
38 | background: transparent;
39 | }
40 |
41 | .table tr > th:first-child,
42 | .table tr > td:first-child {
43 | padding-left: 1em;
44 | }
45 |
46 | .table tbody tr:hover td {
47 | background: #f2f2f2;
48 | }
49 |
50 | .table tbody tr.activeRow td {
51 | background: #e0e0e0;
52 | }
53 |
54 | .table.indent tbody td:first-child {
55 | padding-left: 3em;
56 | }
57 |
58 | .checkbox {
59 | font-size: 1.125em;
60 | display: inline-flex;
61 | vertical-align: middle;
62 | margin-right: .25em;
63 | }
64 |
65 | .table td img {
66 | display: block;
67 | max-width: 4.2857142857em;
68 | max-height: 4.2857142857em;
69 | padding: 2px;
70 | background-color: #fff;
71 | }
72 |
73 | .invisibleIcon {
74 | font-size: 1.1428571429em;
75 | display: inline-flex;
76 | vertical-align: middle;
77 | margin-right: .5em;
78 | }
79 |
--------------------------------------------------------------------------------
/src/components/Preview.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react';
2 | import Loader from './utils/Loader';
3 | import { CSSTransition } from 'react-transition-group';
4 | import S from './Preview.module.css';
5 | import { connect } from 'react-redux';
6 | import { fetchPreview } from '../store/actions/app';
7 |
8 | const mapsStateToProps = state => ({
9 | previews: state.app.previews,
10 | showPreview: state.app.showPreview,
11 | isLoading: state.app.isPreviewLoading,
12 | has_preview: state.app.config.has_preview,
13 | });
14 |
15 | const mapDispatchToProps = dispatch => ({
16 | fetchPreview: (id) => {
17 | dispatch(fetchPreview(id));
18 | },
19 | });
20 |
21 | function PreviewContent(props) {
22 | if (props.isLoading) {
23 | return
24 | } else if (props.previewItem === null || !props.previews[props.previewItem]) {
25 | return (
26 | This item does not have a preview.
27 | );
28 | } else {
29 | return (
30 |
31 | );
32 | }
33 | }
34 |
35 | function Preview(props) {
36 | const {showPreview, previewItem, fetchPreview} = props;
37 | useEffect(() => {
38 | if (showPreview && previewItem) fetchPreview(previewItem);
39 | }, [previewItem, showPreview, fetchPreview]);
40 |
41 | if (!props.has_preview) return null;
42 | return (
43 |
54 |
57 |
58 | )
59 | }
60 |
61 | export default connect(
62 | mapsStateToProps,
63 | mapDispatchToProps,
64 | )(Preview);
65 |
--------------------------------------------------------------------------------
/src/components/Preview.module.css:
--------------------------------------------------------------------------------
1 | @value variables: './Variables.module.css';
2 | @value linkColor from variables;
3 |
4 | .preview {
5 | width: 18em;
6 | position: relative;
7 | overflow-x: hidden;
8 | }
9 |
10 | .content {
11 | padding: 0 1em;
12 | width: 18em;
13 | position: absolute;
14 | left: 0;
15 | right: 0;
16 | top: 0;
17 | bottom: 0;
18 | overflow-y: auto;
19 | }
20 |
21 | .preview a {
22 | color: linkColor;
23 | text-decoration: none;
24 | }
25 |
26 | .preview a:hover,
27 | .preview a:focus {
28 | text-decoration: underline;
29 | }
30 |
31 | .preview figure {
32 | margin: 0;
33 | }
34 |
35 | .preview img {
36 | max-width: 100%;
37 | }
38 |
39 | .preview p {
40 | margin: 0 0 1em;
41 | }
42 |
43 | .preview :global .layout-icon {
44 | width: 90%;
45 | height: 0;
46 | padding-bottom: 130%;
47 | margin: 1em auto 0;
48 | border: 2px solid #a1a1a1;
49 | background-size: 95%;
50 | border-radius: 3px;
51 | }
52 |
53 | .slideEnter {
54 | width: 0;
55 | opacity: .01;
56 | }
57 |
58 | .slideActiveEnter {
59 | width: 18em;
60 | opacity: 1;
61 | transition: width .25s ease-out, opacity .15s;
62 | }
63 |
64 | .slideExit {
65 | width: 18em;
66 | opacity: 1;
67 | }
68 |
69 | .slideActiveExit {
70 | width: 0;
71 | opacity: .01;
72 | transition: width .25s ease-out, opacity .15s .1s;
73 | }
74 |
--------------------------------------------------------------------------------
/src/components/Search.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import SearchItems from '../containers/SearchItems';
3 | import Select from './utils/Select';
4 | import Input from './utils/Input';
5 | import Button from './utils/Button';
6 | import S from './Search.module.css';
7 |
8 | function Search(props) {
9 | const handleSearchSubmit = (e) => {
10 | e.preventDefault();
11 | props.fetchItems();
12 | };
13 |
14 | const handleSectionChange = id => {
15 | props.setSectionId(id);
16 |
17 | if (props.searchTerm){
18 | props.fetchItems()
19 | }
20 | }
21 |
22 | return (
23 | <>
24 |
25 |
42 |
43 | >
44 | );
45 | }
46 |
47 | export default Search;
48 |
--------------------------------------------------------------------------------
/src/components/Search.module.css:
--------------------------------------------------------------------------------
1 | @value variables: './Variables.module.css';
2 | @value gray93 from variables;
3 |
4 | .searchPanel {
5 | flex: 0 0 25%;
6 | padding: 0 1em;
7 | display: flex;
8 | flex-direction: column;
9 | position: relative;
10 | max-height: 100%;
11 | overflow-y: auto;
12 | }
13 |
14 | .resultsPanel {
15 | flex: 1;
16 | background: gray93;
17 | position: relative;
18 | max-height: 100%;
19 | overflow-y: auto;
20 | padding-bottom: 1em;
21 | }
22 |
23 | .searchWrapper {
24 | flex: 1;
25 | background: gray93;
26 | margin-top: .5em;
27 | padding: 1em;
28 | }
29 |
30 | .search {
31 | display: flex;
32 | width: 100%;
33 | flex-wrap: wrap;
34 | }
35 |
36 | .search input {
37 | flex: 1;
38 | }
39 |
--------------------------------------------------------------------------------
/src/components/Tab.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import S from './Tabs.module.css';
3 |
4 | function Tab(props) {
5 | let className = S.tab;
6 | if (props.isActive) className += ` ${S.active}`;
7 | return (
8 |
9 | {props.icon ? props.icon : ''}{props.label}
10 |
11 | );
12 | }
13 |
14 | export default Tab;
15 |
--------------------------------------------------------------------------------
/src/components/TableSettings.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Dropdown from './utils/Dropdown';
3 | import Checkbox from './utils/Checkbox';
4 | import SettingsIcon from '@material-ui/icons/Settings';
5 | import { connect } from 'react-redux';
6 | import { toggleColumn } from '../store/actions/app';
7 |
8 | const mapsStateToProps = state => ({
9 | availableColumns: state.app.config.available_columns,
10 | activeColumns: state.app.activeColumns,
11 | });
12 |
13 | const mapDispatchToProps = dispatch => ({
14 | toggleColumn: (id, toggle) => {
15 | dispatch(toggleColumn(id, toggle));
16 | },
17 | });
18 |
19 | function TableSettings(props) {
20 | return (
21 | }>
22 | {props.availableColumns.map(column => {
23 | if (column.id === 'name') return false;
24 | return (
25 |
26 | props.toggleColumn(column.id, e.target.checked)}
31 | checked={props.activeColumns.includes(column.id)}
32 | />
33 |
34 | );
35 | })}
36 |
37 | );
38 | }
39 |
40 | export default connect(
41 | mapsStateToProps,
42 | mapDispatchToProps
43 | )(TableSettings);
44 |
--------------------------------------------------------------------------------
/src/components/Tabs.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import Tab from './Tab';
3 | import S from './Tabs.module.css';
4 |
5 | function Tabs(props) {
6 | const [activeTab, setActiveTab] = useState(props.initialTab || props.children[0].props.id);
7 | const children = props.children.filter(child => !!child);
8 |
9 | return (
10 | <>
11 |
12 | {children.length > 1 &&
13 |
14 | {children.map((child) => {
15 | if (!child) return false;
16 | return (
17 | setActiveTab(child.props.id)}
22 | icon={child.props.icon}
23 | />
24 | );
25 | })}
26 |
27 | }
28 | {props.headerContent &&
29 | {props.headerContent}
30 |
31 | }
32 |
33 | <>
34 | {children.map((child) => {
35 | if (!child) return false;
36 | if (child.props.id !== activeTab) return undefined;
37 | return child.props.children;
38 | })}
39 | >
40 | >
41 | );
42 | }
43 |
44 | export default Tabs;
45 |
--------------------------------------------------------------------------------
/src/components/Tabs.module.css:
--------------------------------------------------------------------------------
1 | @value variables: './Variables.module.css';
2 | @value linkColor from variables;
3 |
4 | .tabs {
5 | list-style-type: none;
6 | margin: 0;
7 | padding: 0;
8 | display: flex;
9 | width: 25%;
10 | padding: 1em 1em .5em;
11 | }
12 |
13 | .tab {
14 | flex: 1;
15 | text-align: center;
16 | font-size: .75em;
17 | display: flex;
18 | align-items: center;
19 | justify-content: center;
20 | border: 1px solid linkColor;
21 | color: linkColor;
22 | cursor: pointer;
23 | padding: .5em .125em;
24 | margin: 0;
25 | }
26 |
27 | .tab + .tab {
28 | border-left: 0;
29 | }
30 |
31 | .tab:first-child {
32 | border-radius: 2px 0 0 2px;
33 | }
34 |
35 | .tab:last-child {
36 | border-radius: 0 2px 2px 0;
37 | }
38 |
39 | .tab svg {
40 | margin-right: .125em;
41 | }
42 |
43 | .active {
44 | background: linkColor;
45 | color: #fff;
46 | cursor: default;
47 | }
48 |
49 | .tabsHeader {
50 | display: flex;
51 | justify-content: space-between;
52 | align-items: center;
53 | }
54 |
55 | .headerContent {
56 | padding: 1em 1em .5em;
57 | flex: 1;
58 | text-align: right;
59 | }
60 |
--------------------------------------------------------------------------------
/src/components/TogglePreview.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Toggle from './utils/Toggle';
3 | import { connect } from 'react-redux';
4 | import { togglePreview } from '../store/actions/app';
5 |
6 | const mapsStateToProps = state => ({
7 | showPreview: state.app.showPreview,
8 | });
9 |
10 | const mapDispatchToProps = dispatch => ({
11 | togglePreview: (id, toggle) => {
12 | dispatch(togglePreview(id, toggle));
13 | },
14 | });
15 |
16 | function TogglePreview(props) {
17 | return (
18 | props.togglePreview(e.target.checked)}
22 | checked={props.showPreview}
23 | label="Preview"
24 | />
25 | );
26 | }
27 |
28 | export default connect(
29 | mapsStateToProps,
30 | mapDispatchToProps
31 | )(TogglePreview);
32 |
--------------------------------------------------------------------------------
/src/components/Tree.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import TreeItems from './TreeItems';
3 | import Loader from './utils/Loader';
4 | import Select from './utils/Select';
5 | import S from './Tree.module.css';
6 |
7 | function Tree(props) {
8 | return (
9 | <>
10 |