diff --git a/addons/rose/addon/styles/rose/components/layout/_global.scss b/addons/rose/addon/styles/rose/components/layout/_global.scss index cfc218e4e..894f83410 100644 --- a/addons/rose/addon/styles/rose/components/layout/_global.scss +++ b/addons/rose/addon/styles/rose/components/layout/_global.scss @@ -13,7 +13,7 @@ .rose-layout-global-body { grid-area: body; - margin: 0 sizing.rems(m); + padding: sizing.rems(l); } .rose-layout-global-header { diff --git a/addons/rose/addon/styles/rose/components/nav/_breadcrumbs.scss b/addons/rose/addon/styles/rose/components/nav/_breadcrumbs.scss index c07156279..e7ac5572e 100644 --- a/addons/rose/addon/styles/rose/components/nav/_breadcrumbs.scss +++ b/addons/rose/addon/styles/rose/components/nav/_breadcrumbs.scss @@ -11,6 +11,7 @@ color: var(--color); text-decoration: none; cursor: pointer; + display: inline-flex; &:hover { --color: var(--action); diff --git a/ui/core/app/components/.gitkeep b/ui/core/app/components/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/ui/core/app/components/bread-crumbs.hbs b/ui/core/app/components/bread-crumbs.hbs new file mode 100644 index 000000000..f969028c6 --- /dev/null +++ b/ui/core/app/components/bread-crumbs.hbs @@ -0,0 +1,15 @@ +{{#if this.breadCrumbs}} + + {{#each this.breadCrumbs as |crumb|}} + {{#if crumb.model}} + + {{crumb.label}} + + {{else}} + + {{crumb.label}} + + {{/if}} + {{/each}} + +{{/if}} diff --git a/ui/core/app/controllers/.gitkeep b/ui/core/app/controllers/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/ui/core/app/controllers/orgs/org/projects.js b/ui/core/app/controllers/orgs/org/projects.js new file mode 100644 index 000000000..e1199f8e8 --- /dev/null +++ b/ui/core/app/controllers/orgs/org/projects.js @@ -0,0 +1,19 @@ +import Controller from '@ember/controller'; +import { inject as service } from '@ember/service'; + +export default class OrgsOrgProjectsController extends Controller { + + // =services + + @service intl; + + // =attributes + + /** + * @type {string} + */ + get breadCrumb() { + return this.intl.t('resources.projects'); + } + +} diff --git a/ui/core/app/controllers/orgs/org/projects/new.js b/ui/core/app/controllers/orgs/org/projects/new.js new file mode 100644 index 000000000..5f9540189 --- /dev/null +++ b/ui/core/app/controllers/orgs/org/projects/new.js @@ -0,0 +1,19 @@ +import Controller from '@ember/controller'; +import { inject as service } from '@ember/service'; + +export default class OrgsOrgProjectsNewController extends Controller { + + // =services + + @service intl; + + // =attributes + + /** + * @type {string} + */ + get breadCrumb() { + return this.intl.t('titles.new'); + } + +} diff --git a/ui/core/app/controllers/orgs/org/projects/project.js b/ui/core/app/controllers/orgs/org/projects/project.js new file mode 100644 index 000000000..15f6255cf --- /dev/null +++ b/ui/core/app/controllers/orgs/org/projects/project.js @@ -0,0 +1,13 @@ +import Controller from '@ember/controller'; +import { alias } from '@ember/object/computed'; + +export default class OrgsOrgProjectsProjectController extends Controller { + + // =attributes + + /** + * @type {string} + */ + @alias('model.name') breadCrumb; + +} diff --git a/ui/core/app/controllers/orgs/org/projects/project/host-catalogs.js b/ui/core/app/controllers/orgs/org/projects/project/host-catalogs.js new file mode 100644 index 000000000..80dde2c39 --- /dev/null +++ b/ui/core/app/controllers/orgs/org/projects/project/host-catalogs.js @@ -0,0 +1,19 @@ +import Controller from '@ember/controller'; +import { inject as service } from '@ember/service'; + +export default class OrgsOrgProjectsProjectHostCatalogsController extends Controller { + + // =services + + @service intl; + + // =attributes + + /** + * @type {string} + */ + get breadCrumb() { + return this.intl.t('resources.host-catalogs'); + } + +} diff --git a/ui/core/app/controllers/orgs/org/projects/project/host-catalogs/host-catalog.js b/ui/core/app/controllers/orgs/org/projects/project/host-catalogs/host-catalog.js new file mode 100644 index 000000000..067facc1d --- /dev/null +++ b/ui/core/app/controllers/orgs/org/projects/project/host-catalogs/host-catalog.js @@ -0,0 +1,13 @@ +import Controller from '@ember/controller'; +import { alias } from '@ember/object/computed'; + +export default class OrgsOrgProjectsProjectHostCatalogsHostCatalogController extends Controller { + + // =attributes + + /** + * @type {string} + */ + @alias('model.name') breadCrumb; + +} diff --git a/ui/core/app/controllers/orgs/org/projects/project/host-catalogs/new.js b/ui/core/app/controllers/orgs/org/projects/project/host-catalogs/new.js new file mode 100644 index 000000000..4fb2aed63 --- /dev/null +++ b/ui/core/app/controllers/orgs/org/projects/project/host-catalogs/new.js @@ -0,0 +1,19 @@ +import Controller from '@ember/controller'; +import { inject as service } from '@ember/service'; + +export default class OrgsOrgProjectsProjectHostCatalogsNewController extends Controller { + + // =services + + @service intl; + + // =attributes + + /** + * @type {string} + */ + get breadCrumb() { + return this.intl.t('titles.new'); + } + +} diff --git a/ui/core/app/styles/app.scss b/ui/core/app/styles/app.scss index 20dbbe53f..56d7f26df 100644 --- a/ui/core/app/styles/app.scss +++ b/ui/core/app/styles/app.scss @@ -18,31 +18,3 @@ } } } - -.project-overview, -.project-new { - .breadcrumbs, - .breadcrumbs a { - color: #6f7682; - text-decoration: none; - } - - .project-new-form { - .form-actions { - display: flex; - flex-direction: row; - position: relative; - float: right; - - button { - margin-right: 10px; - } - } - - input, - textarea { - box-sizing: border-box; - width: 100%; - } - } -} diff --git a/ui/core/app/templates/orgs/org/projects/index.hbs b/ui/core/app/templates/orgs/org/projects/index.hbs index f141c2649..0ddef022c 100644 --- a/ui/core/app/templates/orgs/org/projects/index.hbs +++ b/ui/core/app/templates/orgs/org/projects/index.hbs @@ -1,4 +1,8 @@ - + + + + +

{{t "resources.projects"}}

diff --git a/ui/core/app/templates/orgs/org/projects/new.hbs b/ui/core/app/templates/orgs/org/projects/new.hbs index a4066d4f5..5289636f7 100644 --- a/ui/core/app/templates/orgs/org/projects/new.hbs +++ b/ui/core/app/templates/orgs/org/projects/new.hbs @@ -1,10 +1,6 @@ - + - + @@ -16,7 +12,6 @@ @model={{@model}} @submit={{route-action "save" @model}} @cancel={{route-action "cancel" @model}} - class="project-new-form" /> diff --git a/ui/core/app/templates/orgs/org/projects/project/host-catalogs/host-catalog/index.hbs b/ui/core/app/templates/orgs/org/projects/project/host-catalogs/host-catalog/index.hbs index de04351ae..b80d3f9b0 100644 --- a/ui/core/app/templates/orgs/org/projects/project/host-catalogs/host-catalog/index.hbs +++ b/ui/core/app/templates/orgs/org/projects/project/host-catalogs/host-catalog/index.hbs @@ -1,18 +1,27 @@ -
+ -

{{t "resources.host-catalog"}}

+ + + - + +

{{t "resources.host-catalog"}}

+
- - {{t 'actions.delete'}} - -
+ + + + + {{t 'actions.delete'}} + + + +
diff --git a/ui/core/app/templates/orgs/org/projects/project/host-catalogs/index.hbs b/ui/core/app/templates/orgs/org/projects/project/host-catalogs/index.hbs index 006513fa8..d164728eb 100644 --- a/ui/core/app/templates/orgs/org/projects/project/host-catalogs/index.hbs +++ b/ui/core/app/templates/orgs/org/projects/project/host-catalogs/index.hbs @@ -1,4 +1,9 @@ - + + + + + +

{{t 'resources.host-catalogs'}}

diff --git a/ui/core/app/templates/orgs/org/projects/project/host-catalogs/new.hbs b/ui/core/app/templates/orgs/org/projects/project/host-catalogs/new.hbs index 2f5cdf96a..1ac7bf86f 100644 --- a/ui/core/app/templates/orgs/org/projects/project/host-catalogs/new.hbs +++ b/ui/core/app/templates/orgs/org/projects/project/host-catalogs/new.hbs @@ -1,5 +1,9 @@ + + + +

{{t "titles.new"}}

diff --git a/ui/core/app/templates/orgs/org/projects/project/index.hbs b/ui/core/app/templates/orgs/org/projects/project/index.hbs index 1b255c4e1..5c297e624 100644 --- a/ui/core/app/templates/orgs/org/projects/project/index.hbs +++ b/ui/core/app/templates/orgs/org/projects/project/index.hbs @@ -1,30 +1,35 @@ -
- + {{t 'actions.delete'}} + + -

{{t "resources.project"}}

- - - - {{t 'titles.settings'}} - - - - - - - {{t 'actions.delete'}} - -
+
diff --git a/ui/core/package.json b/ui/core/package.json index cd4e42ab9..a03037825 100644 --- a/ui/core/package.json +++ b/ui/core/package.json @@ -38,6 +38,7 @@ "doctoc": "^1.4.0", "ember-a11y-testing": "^3.0.0", "ember-auto-import": "^1.5.3", + "ember-breadcrumbs": "^0.2.3", "ember-cli": "~3.16.1", "ember-cli-app-version": "^3.2.0", "ember-cli-babel": "^7.17.2", diff --git a/ui/core/tests/acceptance/bread-crumbs-test.js b/ui/core/tests/acceptance/bread-crumbs-test.js new file mode 100644 index 000000000..acdd9e736 --- /dev/null +++ b/ui/core/tests/acceptance/bread-crumbs-test.js @@ -0,0 +1,82 @@ +import { module, test } from 'qunit'; +import { visit, currentURL, click, find, findAll } from '@ember/test-helpers'; +import { setupApplicationTest } from 'ember-qunit'; +import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; + +module('Acceptance | breadcrumbs', function (hooks) { + setupApplicationTest(hooks); + setupMirage(hooks); + + let project; + let hostCatalog; + let projectsURL; + let newProjectURL; + let projectURL; + let hostCatalogsURL; + let newHostCatalogURL; + let hostCatalogURL; + + let crumbPath; + + hooks.beforeEach(function () { + project = this.server.create('project'); + hostCatalog = this.server.create('host-catalog'); + projectsURL = `/orgs/1/projects`; + newProjectURL = `${projectsURL}/new`; + projectURL = `${projectsURL}/${project.id}`; + hostCatalogsURL = `${projectURL}/host-catalogs`; + newHostCatalogURL = `${hostCatalogsURL}/new`; + hostCatalogURL = `${hostCatalogsURL}/${hostCatalog.id}`; + }); + + test('can navigate via breadcrumbs to projects from project', async function (assert) { + assert.expect(3); + await visit(projectURL); + assert.equal(currentURL(), projectURL, 'We begin on th expected page.'); + crumbPath = new URL(find('.rose-nav-breadcrumbs-link:last-child').href).pathname; + assert.equal(crumbPath, projectURL, 'Last crumb has expected URL.'); + await click(find('.rose-nav-breadcrumbs-link:first-child')); + assert.equal(currentURL(), '/orgs/1/projects', 'After navigating via breadcrumbs, we are one level up.'); + }); + + test('can navigate via breadcrumbs to projects from new project', async function (assert) { + assert.expect(3); + await visit(newProjectURL); + assert.equal(currentURL(), newProjectURL, 'We begin on th expected page.'); + crumbPath = new URL(find('.rose-nav-breadcrumbs-link:last-child').href).pathname; + assert.equal(crumbPath, newProjectURL, 'Last crumb has expected URL.'); + await click(find('.rose-nav-breadcrumbs-link:first-child')); + assert.equal(currentURL(), '/orgs/1/projects', 'After navigating via breadcrumbs, we are one level up.'); + }); + + test('can navigate via breadcrumbs to project from host catalogs', async function (assert) { + assert.expect(3); + await visit(hostCatalogsURL); + assert.equal(currentURL(), hostCatalogsURL, 'We begin on th expected page.'); + crumbPath = new URL(find('.rose-nav-breadcrumbs-link:last-child').href).pathname; + assert.equal(crumbPath, hostCatalogsURL, 'Last crumb has expected URL.'); + await click(findAll('.rose-nav-breadcrumbs-link')[1]); + assert.equal(currentURL(), projectURL, 'After navigating via breadcrumbs, we are one level up.'); + }); + + test('can navigate via breadcrumbs to host catalogs from host catalog', async function (assert) { + assert.expect(3); + await visit(hostCatalogURL); + assert.equal(currentURL(), hostCatalogURL, 'We begin on th expected page.'); + crumbPath = new URL(find('.rose-nav-breadcrumbs-link:last-child').href).pathname; + assert.equal(crumbPath, hostCatalogURL, 'Last crumb has expected URL.'); + await click(findAll('.rose-nav-breadcrumbs-link')[2]); + assert.equal(currentURL(), hostCatalogsURL, 'After navigating via breadcrumbs, we are one level up.'); + }); + + test('can navigate via breadcrumbs to host catalogs from new host catalog', async function (assert) { + assert.expect(3); + await visit(newHostCatalogURL); + assert.equal(currentURL(), newHostCatalogURL, 'We begin on th expected page.'); + crumbPath = new URL(find('.rose-nav-breadcrumbs-link:last-child').href).pathname; + assert.equal(crumbPath, newHostCatalogURL, 'Last crumb has expected URL.'); + await click(findAll('.rose-nav-breadcrumbs-link')[2]); + assert.equal(currentURL(), hostCatalogsURL, 'After navigating via breadcrumbs, we are one level up.'); + }); + +}); diff --git a/ui/core/tests/integration/components/bread-crumbs-test.js b/ui/core/tests/integration/components/bread-crumbs-test.js new file mode 100644 index 000000000..a6f3579c8 --- /dev/null +++ b/ui/core/tests/integration/components/bread-crumbs-test.js @@ -0,0 +1,47 @@ +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'ember-qunit'; +import { render, find, findAll } from '@ember/test-helpers'; +import { hbs } from 'ember-cli-htmlbars'; + +module('Integration | Component | bread-crumbs', function(hooks) { + setupRenderingTest(hooks); + + test('it renders', async function(assert) { + this.set('breadCrumbs', [{label: 'Level 1'}]); + + await render(hbs``); + // Default index route + assert.ok(find('.rose-nav-breadcrumbs a').href.match(/#$/)); + assert.equal(find('.rose-nav-breadcrumbs').textContent.trim(), 'Level 1'); + }); + + test('it renders with model', async function(assert) { + let store = this.owner.lookup('service:store'); + let project = store.createRecord('project', {id: '1234'}); + + this.set('breadCrumbs', [{ + label: 'Level 2', + path: 'orgs.org.projects.project', + model: project + }]); + + await render(hbs``); + assert.equal(find('.rose-nav-breadcrumbs').textContent.trim(), 'Level 2'); + }); + + test('it renders multiple breadCrumbs', async function(assert) { + this.set('breadCrumbs', [{ + label: 'Level 1' + },{ + label: 'Level 2' + }, { + label: 'Level 3' + }]); + + await render(hbs``); + assert.equal(findAll('a').length, 3); + assert.equal(findAll('a')[0].textContent.trim(), 'Level 1'); + assert.equal(findAll('a')[1].textContent.trim(), 'Level 2'); + assert.equal(findAll('a')[2].textContent.trim(), 'Level 3'); + }); +}); diff --git a/ui/core/tests/unit/controllers/orgs/org/projects-test.js b/ui/core/tests/unit/controllers/orgs/org/projects-test.js new file mode 100644 index 000000000..6176a173c --- /dev/null +++ b/ui/core/tests/unit/controllers/orgs/org/projects-test.js @@ -0,0 +1,12 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Controller | orgs/org/projects', function(hooks) { + setupTest(hooks); + + // Replace this with your real tests. + test('it exists', function(assert) { + let controller = this.owner.lookup('controller:orgs/org/projects'); + assert.ok(controller); + }); +}); diff --git a/ui/core/tests/unit/controllers/orgs/org/projects/new-test.js b/ui/core/tests/unit/controllers/orgs/org/projects/new-test.js new file mode 100644 index 000000000..9e3731313 --- /dev/null +++ b/ui/core/tests/unit/controllers/orgs/org/projects/new-test.js @@ -0,0 +1,12 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Controller | orgs/org/projects/new', function(hooks) { + setupTest(hooks); + + // Replace this with your real tests. + test('it exists', function(assert) { + let controller = this.owner.lookup('controller:orgs/org/projects/new'); + assert.ok(controller); + }); +}); diff --git a/ui/core/tests/unit/controllers/orgs/org/projects/project-test.js b/ui/core/tests/unit/controllers/orgs/org/projects/project-test.js new file mode 100644 index 000000000..36a2c2482 --- /dev/null +++ b/ui/core/tests/unit/controllers/orgs/org/projects/project-test.js @@ -0,0 +1,12 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Controller | orgs/org/projects/project', function(hooks) { + setupTest(hooks); + + // Replace this with your real tests. + test('it exists', function(assert) { + let controller = this.owner.lookup('controller:orgs/org/projects/project'); + assert.ok(controller); + }); +}); diff --git a/ui/core/tests/unit/controllers/orgs/org/projects/project/host-catalogs-test.js b/ui/core/tests/unit/controllers/orgs/org/projects/project/host-catalogs-test.js new file mode 100644 index 000000000..808f14138 --- /dev/null +++ b/ui/core/tests/unit/controllers/orgs/org/projects/project/host-catalogs-test.js @@ -0,0 +1,12 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Controller | orgs/org/projects/project/host-catalogs', function(hooks) { + setupTest(hooks); + + // Replace this with your real tests. + test('it exists', function(assert) { + let controller = this.owner.lookup('controller:orgs/org/projects/project/host-catalogs'); + assert.ok(controller); + }); +}); diff --git a/ui/core/tests/unit/controllers/orgs/org/projects/project/host-catalogs/host-catalog-test.js b/ui/core/tests/unit/controllers/orgs/org/projects/project/host-catalogs/host-catalog-test.js new file mode 100644 index 000000000..4efc4b85c --- /dev/null +++ b/ui/core/tests/unit/controllers/orgs/org/projects/project/host-catalogs/host-catalog-test.js @@ -0,0 +1,12 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Controller | orgs/org/projects/project/host-catalogs/host-catalog', function(hooks) { + setupTest(hooks); + + // Replace this with your real tests. + test('it exists', function(assert) { + let controller = this.owner.lookup('controller:orgs/org/projects/project/host-catalogs/host-catalog'); + assert.ok(controller); + }); +}); diff --git a/ui/core/tests/unit/controllers/orgs/org/projects/project/host-catalogs/new-test.js b/ui/core/tests/unit/controllers/orgs/org/projects/project/host-catalogs/new-test.js new file mode 100644 index 000000000..1981e1857 --- /dev/null +++ b/ui/core/tests/unit/controllers/orgs/org/projects/project/host-catalogs/new-test.js @@ -0,0 +1,12 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Controller | orgs/org/projects/project/host-catalogs/new', function(hooks) { + setupTest(hooks); + + // Replace this with your real tests. + test('it exists', function(assert) { + let controller = this.owner.lookup('controller:orgs/org/projects/project/host-catalogs/new'); + assert.ok(controller); + }); +}); diff --git a/yarn.lock b/yarn.lock index b5270240f..90f435da4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7058,6 +7058,13 @@ ember-auto-import@^1.2.15, ember-auto-import@^1.2.19, ember-auto-import@^1.5.3: walk-sync "^0.3.3" webpack "~4.28" +ember-breadcrumbs@^0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/ember-breadcrumbs/-/ember-breadcrumbs-0.2.3.tgz#a95e02b73fdc2150f3d552e84a479d6dc96a8b31" + integrity sha512-QUiPXwAlTeiXykF5RnyilEgcj9NSFGciAawktImv6V6Lq9sNfmJHZAYZXAg8U/wA1tw7CG0fQl991K+jtuBTKQ== + dependencies: + ember-cli-babel "^7.10.0" + ember-cli-addon-docs-yuidoc@^0.2.3: version "0.2.4" resolved "https://registry.yarnpkg.com/ember-cli-addon-docs-yuidoc/-/ember-cli-addon-docs-yuidoc-0.2.4.tgz#dd8f5cf4982e8492b255fb677e9d771925e27dce"