Skip to content

Commit

Permalink
feat: add uirouter-layout component
Browse files Browse the repository at this point in the history
* add `@ovh-ux/ng-ovh-uirouter-layout` package
  • Loading branch information
Cyrille Bourgois authored and jleveugle committed Mar 27, 2019
1 parent 2fba937 commit e86c972
Show file tree
Hide file tree
Showing 9 changed files with 3,919 additions and 0 deletions.
15 changes: 15 additions & 0 deletions README.md

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"description": "OVH Control Panel also known as Manager",
"license": "BSD-3-Clause",
"workspaces": [
"packages/components/*",
"packages/manager/apps/*",
"packages/manager/modules/*"
],
Expand Down
Empty file.
29 changes: 29 additions & 0 deletions packages/components/ng-ovh-uirouter-layout/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
BSD 3-Clause License

Copyright (c) 2013-present, OVH SAS
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.

* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
36 changes: 36 additions & 0 deletions packages/components/ng-ovh-uirouter-layout/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# ng-ovh-uirouter-layout

> Support layout:modal when using ui-router
[![Downloads](https://badgen.net/npm/dt/@ovh-ux/ng-ovh-uirouter-layout)](https://npmjs.com/package/@ovh-ux/ng-ovh-uirouter-layout) [![Dependencies](https://badgen.net/david/dep/ovh-ux/manager/packages/manager/modules/ng-ovh-uirouter-layout)](https://npmjs.com/package/@ovh-ux/ng-ovh-uirouter-layout?activeTab=dependencies) [![Dev Dependencies](https://badgen.net/david/dev/ovh-ux/manager/packages/manager/modules/ng-ovh-uirouter-layout)](https://npmjs.com/package/@ovh-ux/ng-ovh-uirouter-layout?activeTab=dependencies) [![Gitter](https://badgen.net/badge/gitter/ovh-ux/blue?icon=gitter)](https://gitter.im/ovh/ux)

## Install

```sh
yarn add @ovh-ux/ng-ovh-uirouter-layout
```
## Usage

```js
import angular from 'angular';
import ngOvhUiRouterLayout from '@ovh-ux/ng-ovh-uirouter-layout';

angular
.module('myApp', [
ngOvhUiRouterLayout,
]);
```

## Test

```sh
yarn test
```

## Contributing

Always feel free to help out! Whether it's [filing bugs and feature requests](https://github.com/ovh-ux/manager/issues/new) or working on some of the [open issues](https://github.com/ovh-ux/manager/issues), our [contributing guide](CONTRIBUTING.md) will help get you started.

## License

[BSD-3-Clause](LICENSE) © OVH SAS
43 changes: 43 additions & 0 deletions packages/components/ng-ovh-uirouter-layout/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"name": "@ovh-ux/ng-ovh-uirouter-layout",
"version": "0.0.0",
"description": "UiRouter Layout support",
"keywords": [
"uirouter",
"layout",
"ovh"
],
"repository": {
"type": "git",
"url": "git+https://github.com/ovh-ux/manager.git",
"directory": "packages/components/ng-ovh-uirouter-layout"
},
"license": "BSD-3-Clause",
"author": "OVH SAS",
"files": [
"dist"
],
"main": "./dist/cjs/index.js",
"module": "./dist/esm/index.js",
"browser": "./dist/umd/ng-ovh-uirouter-layout.js",
"scripts": {
"build": "rollup -c --environment BUILD:production",
"dev": "rollup -c --environment BUILD:development",
"dev:watch": "yarn run dev --watch",
"prepare": "yarn run build",
"start": "lerna exec --stream --scope='@ovh-ux/ng-ovh-uirouter-layout' --include-filtered-dependencies -- yarn run build",
"start:dev": "lerna exec --stream --scope='@ovh-ux/ng-ovh-uirouter-layout' --include-filtered-dependencies -- yarn run dev",
"start:watch": "lerna exec --stream --parallel --scope='@ovh-ux/ng-ovh-uirouter-layout' --include-filtered-dependencies -- yarn run dev:watch"
},
"dependencies": {
"lodash": "^4.17.11"
},
"devDependencies": {
"@ovh-ux/component-rollup-config": "^5.0.0-beta.9"
},
"peerDependencies": {
"@uirouter/angularjs": "^1.0.22",
"angular": "^1.5.0",
"angular-ui-bootstrap": "~1.3.3"
}
}
25 changes: 25 additions & 0 deletions packages/components/ng-ovh-uirouter-layout/rollup.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import path from 'path';
import rollupConfig from '@ovh-ux/component-rollup-config';

const config = rollupConfig({
input: './src/index.js',
}, {
lessTildeImporter: {
paths: [
path.resolve(__dirname, 'node_modules'),
path.resolve(__dirname, '../../../../node_modules'),
],
},
});

export default [
config.cjs(),
config.es(),
config.umd({
output: {
globals: {
angular: 'angular',
},
},
}),
];
196 changes: 196 additions & 0 deletions packages/components/ng-ovh-uirouter-layout/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
import angular from 'angular';
import '@uirouter/angularjs';
import 'angular-ui-bootstrap';

import forEach from 'lodash/forEach';
import filter from 'lodash/filter';
import get from 'lodash/get';
import intersection from 'lodash/intersection';
import isString from 'lodash/isString';
import isObject from 'lodash/isObject';
import kebabCase from 'lodash/kebabCase';
import last from 'lodash/last';
import map from 'lodash/map';
import reduce from 'lodash/reduce';
import set from 'lodash/set';
import size from 'lodash/size';
import startsWith from 'lodash/startsWith';
import xor from 'lodash/xor';

const moduleName = 'ngOvhUiRouterLayout';

angular
.module(moduleName, [
'ui.bootstrap',
'ui.router',
])
.config(/* @ngInject */($stateProvider, $transitionsProvider, $injector) => {
let modalInstance = null;

/**
* Create a decorator for our new state attribute 'layout'.
* For modal layout, the attribute can be a string or an object:
* - if string - value must be 'modal'
* - if object - avaiable attributes are:
* - name: the value must be 'modal'.
* - toChilds (default value: 'false'):
* - can be a boolean: 'true' to declare the modal state to all direct parent childs.
* - or an array of string that contains the childs of the direct parents where the modal
* needs to be displayed.
* - ignoreChilds: an array of string that contains states names where the modal state
* doesn't need to be displayed.
*/
$stateProvider.decorator('layout', (state) => {
let modalLayout;
const layout = get(state, 'self.layout');
if ((isString(layout) && layout === 'modal')
|| (isObject(layout) && get(layout, 'name') === 'modal')) {
modalLayout = {
name: 'modal',
toChilds: state.self.layout.toChilds || false,
ignoreChilds: state.self.layout.ignoreChilds || [],
redirectTo: state.self.layout.redirectTo || '^',
};
}

return modalLayout;
});

/**
* Use onSuccess hook to manage the modal display.
*/
$transitionsProvider.onSuccess({}, (transition) => {
transition.promise.finally(() => {
const state = transition.$to();

// close previous modal
if (modalInstance) {
modalInstance.close();
}

if (get(state, 'layout.name') === 'modal') {
const $state = transition.injector().get('$state');
const $uibModal = transition.injector().get('$uibModal');

const componentName = get(state, state.views.modal ? 'views.modal.component' : 'component');
if (componentName && isString(componentName)) {
const directives = $injector.get(`${componentName}DirectiveProvider`).$get();
// look for those directives that are components
const candidateDirectives = directives.filter(
directiveInfo => directiveInfo.controller
&& directiveInfo.controllerAs
&& directiveInfo.restrict === 'E',
);

if (candidateDirectives.length === 0) {
throw new Error('No component found');
}
if (candidateDirectives.length > 1) {
throw new Error('Too many components found');
}
// get the info of the component
const [directiveInfo] = candidateDirectives;

// create controller
const resolves = reduce(
transition.getResolveTokens(),
(acc, resolveKey) => ({
...acc,
[resolveKey]: transition.injector().get(resolveKey),
}),
{},
);
const controller = () => resolves;

// get resolveKeys compatibles with component bindings
const resolveKeys = intersection(
transition.getResolveTokens(),
Object.keys(directiveInfo.bindToController),
);

// create template
const div = document.createElement('div');
const elmt = document.createElement(kebabCase(directiveInfo.name));
forEach(resolveKeys, key => elmt.setAttribute(kebabCase(key), `$ctrl.${key}`));
div.appendChild(elmt);
const template = div.innerHTML;

modalInstance = $uibModal.open({
template,
controller,
controllerAs: '$ctrl',
});
} else {
modalInstance = $uibModal.open({
templateUrl: get(state, state.views.modal ? 'views.modal.templateUrl' : 'templateUrl'),
template: get(state, state.views.modal ? 'views.modal.template' : 'template'),
controller: get(state, state.views.modal ? 'views.modal.controller' : 'controller'),
controllerAs: get(state, state.views.modal ? 'views.modal.controllerAs' : 'controllerAs', '$ctrl'),
});
}
// if backdrop is clicked - be sure to close the modal
modalInstance.result.catch(() => $state.go(get(state, 'layout.redirectTo')));
}
});
});
})
.run(/* @ngInject */($stateRegistry) => {
/**
* As initial URL synchronization is delayed we can check all modal layout states and check
* if toChilds attribute is setted.
* For these states we will need to create new states as follow:
* We will take the direct parent of the modal layout state and create new states with the same
* layout configuration to all of its child states.
*/
const layoutStates = filter(
$stateRegistry.states,
({ layout }) => get(layout, 'toChilds') === true || size(get(layout, 'toChilds', [])),
);

const getChildStates = parentStateName => filter(
$stateRegistry.states,
({ name }) => startsWith(name, `${parentStateName}.`),
);
const getChildStatesNames = parentStateName => map(getChildStates(parentStateName), 'name');

layoutStates.forEach((layoutState) => {
let childStates;

// build child states that need modal layout applied
if (angular.isArray(layoutState.layout.toChilds)) {
childStates = layoutState.layout.toChilds;
} else {
childStates = getChildStatesNames(layoutState.parent.name);
}

// build child states that need to be ignored
// 1st: all child states of each states that need to be ignored
// 2nd: current layout state doesn't need to have itself as modal child
layoutState.layout.ignoreChilds.forEach((childState) => {
set(layoutState, 'layout.ignoreChilds', layoutState.layout.ignoreChilds.concat(getChildStatesNames(childState)));
});
layoutState.layout.ignoreChilds.push(layoutState.name);

// remove child states that need to be ignored
childStates = xor(childStates, layoutState.layout.ignoreChilds);

// create child state with layout settings applied
const modalSateSuffix = last(layoutState.name.split('.'));
childStates.forEach((childState) => {
$stateRegistry.register({
name: `${childState}.${modalSateSuffix}`,
url: layoutState.self.url,
templateUrl: layoutState.self.templateUrl,
controller: layoutState.self.controller,
component: layoutState.self.component,
layout: { // don't know why full config must be defined???
name: 'modal',
toChilds: false,
ignoreChilds: [],
},
});
});
});
});

export default moduleName;
Loading

0 comments on commit e86c972

Please sign in to comment.