Skip to content

Commit

Permalink
Integrate entity features (#181)
Browse files Browse the repository at this point in the history
* Generalize/rename list stuff to entity, for a more generic naming convention
* Abstract out entity view from home into entity-page into dynamic components per entity
* Add entity tree to sidebar
* Add task and tasklist models, add entities store
* Abstact out entity services into a generic API, put interfaces into more suiting files

* Split user.service into auth and account service, rename state files from .model to .state

* Extract out main pane from sidebar into own component
* Combine page entity icon and status icon into one component, add entity page label
* Hide sidebar scroll bar when unused
* Add breadcrumbs component

* Add entity-page placeholder => dashboard
* Add reactive rename feature
* Integrate description feature
* Display children on entity page

* Update app title strategy
* Add error logging to actionLogger meta reducer
* Use OnPush strategy for task.component as well

* Write unit tests for store utils
* Write component tests for breadcrumbs
* Write component tests for TasklistViewComponent
* Comment out component tests, comment out var process dummy
* Write home e2e tests
  • Loading branch information
floydnant authored Jan 5, 2023
1 parent dacfd0c commit 2e4cf1c
Show file tree
Hide file tree
Showing 106 changed files with 3,154 additions and 368 deletions.
7 changes: 7 additions & 0 deletions client-v2/angular.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"schematics": {
"@schematics/angular": {
"component": {
"changeDetection": "OnPush"
}
}
},
"version": 1,
"newProjectRoot": "projects",
"projects": {
Expand Down
90 changes: 90 additions & 0 deletions client-v2/cypress/e2e/workspace.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { signup } from 'cypress/support/auth-helpers'
import { testName } from 'cypress/support/helpers'

beforeEach(() => {
cy.clearDb()
signup()

cy.intercept('POST', '/list').as('createEntity')
})

describe('Workspace', () => {
it('opens dashboard', () => {
cy.get(testName('dashboard-page')).should('exist')
cy.get(testName('entity-page')).should('not.exist')
})

describe('Sidebar entity tree', () => {
it.skip('should display the tree')

it('can add entities', () => {
cy.get(testName('entity-tree-node')).should('have.length', 0)
cy.get(testName('sidebar-create-new-list')).click()
cy.wait('@createEntity').its('response.statusCode').should('equal', 201)

cy.get(testName('dashboard-page')).should('not.exist')
cy.get(testName('entity-page')).should('exist')
cy.get(testName('entity-tree-node')).should('have.length', 1)
cy.get(testName('entity-tree-node')).should('have.attr', 'data-level', 0)
})

describe('Tree nodes', () => {
it('can add children', () => {
cy.get(testName('sidebar-create-new-list')).click()

cy.get(testName('entity-tree-node'))
.first()
.focus()
.within(() => {
cy.get(testName('create-child')).click()
})
cy.get(testName('entity-tree-node')).should('have.length', 2)
cy.get(testName('entity-tree-node')).last().should('have.attr', 'data-level', 1)
})

it('can open the options menu', () => {
cy.get(testName('sidebar-create-new-list')).click()

cy.get(testName('entity-tree-node'))
.first()
.focus()
.within(() => {
cy.get(testName('open-menu')).click()
})

cy.get(testName('drop-down-menu')).should('exist')
})
})
})

describe('Entity page', () => {
describe('Tasklist view', () => {
it('can edit the entity name', () => {
cy.get(testName('sidebar-create-new-list')).click()

const entityName = 'The testing entity'
cy.get(testName('editable-entity-name')).focus().type(entityName)
cy.get(testName('entity-tree-node')).should('contain.text', entityName)
})

it('can edit the description', () => {
cy.get(testName('sidebar-create-new-list')).click()

cy.intercept('PATCH', '/list/*').as('updateEntity')
const description = 'The testing entity description'

cy.get(testName('add-description')).click()
cy.get(testName('description-editor')).type(description).blur()
cy.wait('@updateEntity').its('response.statusCode').should('equal', 200) // we currently don't have any other way to verify if updating the description has succeeded
})

it('can add children', () => {
cy.get(testName('sidebar-create-new-list')).click()

cy.get(testName('create-children')).click()
cy.get(testName('entity-tree-node')).should('have.length', 2)
cy.get(testName('entity-tree-node')).last().should('have.attr', 'data-level', 1)
})
})
})
})
21 changes: 21 additions & 0 deletions client-v2/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion client-v2/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"version": "2.0.0",
"scripts": {
"dev": "ng serve",
"dev:lan": "ng serve --host 0.0.0.0",
"dev:lan": "echo \"Listening on http://$(ipconfig getifaddr en0):4200\" && NG_APP_SERVER_BASE_URL=http://$(ipconfig getifaddr en0):3000 ng serve --host 0.0.0.0",
"build": "ng build",
"watch": "ng build --watch --configuration development",
"update-colors": "ts-node ./tools/update-colors.cts",
Expand Down Expand Up @@ -34,6 +34,7 @@
"@angular/router": "^14.2.12",
"@ngneat/hot-toast": "^4.1.0",
"@ngneat/overview": "^3.0.0",
"@ngneat/until-destroy": "^9.2.2",
"@ngrx/effects": "^14.3.2",
"@ngrx/store": "^14.3.2",
"@ngrx/store-devtools": "^14.3.2",
Expand Down
41 changes: 37 additions & 4 deletions client-v2/src/app/app-routing.module.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,35 @@
import { NgModule } from '@angular/core'
import { inject, NgModule } from '@angular/core'
import { RouterModule, Routes } from '@angular/router'
import { Store } from '@ngrx/store'
import { environment } from '../environments/environment'
import { AuthGuard } from './guards/auth.guard'
import { AuthComponent } from './pages/auth/auth.component'
import { LoginLoadingComponent } from './pages/auth/login-loading/login-loading.component'
import { LoginComponent } from './pages/auth/login/login.component'
import { SignupComponent } from './pages/auth/signup/signup.component'
import { ComponentPlaygroundComponent } from './pages/component-playground/component-playground.component'
import { DashboardComponent } from './pages/home/dashboard/dashboard.component'
import { EntityPageComponent } from './pages/home/entity-page/entity-page.component'
import { HomeComponent } from './pages/home/home.component'
import { LandingPageComponent } from './pages/landing-page/landing-page.component'
import { NotFoundPageComponent } from './pages/not-found-page/not-found-page.component'
import { SettingsAccountComponent } from './pages/settings/account/account.component'
import { SettingsAppearanceComponent } from './pages/settings/appearance/appearance.component'
import { SettingsGeneralComponent } from './pages/settings/general/general.component'
import { SettingsComponent } from './pages/settings/settings.component'
import { AppState } from './store'
import { getEntityById } from './store/entities/utils'

const ENVIRONMENT_HINT = `[${environment.REVIEW_ID ? environment.REVIEW_ID + '-' : ''}${environment.CONTEXT}]`
const APP_TITLE = `Rockket ${environment.CONTEXT == 'Production' ? '' : ENVIRONMENT_HINT}`
const APP_TITLE_SUFFIX = `- ${APP_TITLE}`

const routes: Routes = [
{
path: '',
component: LandingPageComponent,
pathMatch: 'full',
title: APP_TITLE,
},
{
path: 'auth',
Expand All @@ -27,14 +38,17 @@ const routes: Routes = [
{
path: 'login',
component: LoginComponent,
title: `Login ${APP_TITLE_SUFFIX}`,
},
{
path: 'signup',
component: SignupComponent,
title: `Signup ${APP_TITLE_SUFFIX}`,
},
{
path: 'login-loading',
component: LoginLoadingComponent,
title: `Confirming Login... ${APP_TITLE_SUFFIX}`,
},
{
path: '',
Expand All @@ -47,6 +61,19 @@ const routes: Routes = [
path: 'home',
component: HomeComponent,
canActivate: [AuthGuard],
children: [
{
path: ':id',
component: EntityPageComponent,
title: route =>
inject(Store).select((state: AppState) => {
const activeEntityId = route.paramMap.get('id') as string
const activeEntity = getEntityById(state.entities.entityTree || [], activeEntityId)
return activeEntity ? `${activeEntity.name} ${APP_TITLE_SUFFIX}` : APP_TITLE
}),
},
{ path: '', component: DashboardComponent, title: `Dashboard ${APP_TITLE_SUFFIX}` },
],
},
{
path: 'settings',
Expand All @@ -58,18 +85,24 @@ const routes: Routes = [
pathMatch: 'full',
redirectTo: 'general',
},
{ path: 'general', component: SettingsGeneralComponent },
{ path: 'account', component: SettingsAccountComponent },
{ path: 'appearance', component: SettingsAppearanceComponent },
{ path: 'general', component: SettingsGeneralComponent, title: `General Settings ${APP_TITLE_SUFFIX}` },
{ path: 'account', component: SettingsAccountComponent, title: `Account Settings ${APP_TITLE_SUFFIX}` },
{
path: 'appearance',
component: SettingsAppearanceComponent,
title: `Appearance Settings ${APP_TITLE_SUFFIX}`,
},
],
},
{
path: 'playground',
component: ComponentPlaygroundComponent,
title: `Component Playground ${APP_TITLE_SUFFIX}`,
},
{
path: '**',
component: NotFoundPageComponent,
title: `404 Not Found ${APP_TITLE_SUFFIX}`,
},
]

Expand Down
22 changes: 22 additions & 0 deletions client-v2/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,17 @@ import { IconsModule } from './components/atoms/icons/icons.module'
import { OverlayModule } from '@angular/cdk/overlay'
import { TooltipDirective } from './directives/tooltip.directive'
import { TooltipComponent } from './components/atoms/tooltip/tooltip.component'
import { CdkTreeModule } from '@angular/cdk/tree'
import { EntityPageLabelComponent } from './components/atoms/entity-page-label/entity-page-label.component'
import { EntityPageComponent } from './pages/home/entity-page/entity-page.component'
import { BreadcrumbsComponent } from './components/molecules/breadcrumbs/breadcrumbs.component'
import { MainPaneComponent } from './components/templates/main-pane/main-pane.component'
import { MutationDirective } from './directives/mutation.directive'
import { MenuToggleComponent } from './components/templates/sidebar-layout/menu-toggle/menu-toggle.component'
import { DashboardComponent } from './pages/home/dashboard/dashboard.component'
import { EntityViewComponent } from './components/organisms/entity-view/entity-view.component'
import { TasklistViewComponent } from './components/organisms/entity-view/views/tasklist-view/tasklist-view.component'
import { EditableEntityNameComponent } from './components/molecules/editable-entity-heading/editable-entity-name.component'

@NgModule({
declarations: [
Expand Down Expand Up @@ -67,6 +78,16 @@ import { TooltipComponent } from './components/atoms/tooltip/tooltip.component'
DropDownComponent,
TooltipDirective,
TooltipComponent,
EntityPageLabelComponent,
EntityPageComponent,
BreadcrumbsComponent,
MainPaneComponent,
MutationDirective,
MenuToggleComponent,
DashboardComponent,
EntityViewComponent,
TasklistViewComponent,
EditableEntityNameComponent,
],
imports: [
BrowserModule,
Expand Down Expand Up @@ -104,6 +125,7 @@ import { TooltipComponent } from './components/atoms/tooltip/tooltip.component'
IconsModule,
ModalModule,
OverlayModule,
CdkTreeModule,
],
providers: [],
bootstrap: [AppComponent],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
import { PageEntityIconKey } from '../icons/page-entity-icon/page-entity-icon.component'

@Component({
selector: 'app-entity-page-label',
template: `
<span class="inline-flex justify-center">
<page-entity-icon class="icon | mr-1 inline-block w-4 text-md" [icon]="pageIcon"></page-entity-icon>
</span>
<span class="title | truncate">{{ title }}</span>
`,
styles: [
`
:host {
@apply truncate;
}
`,
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class EntityPageLabelComponent {
@Input() title!: string
@Input() pageIcon!: PageEntityIconKey
}
3 changes: 0 additions & 3 deletions client-v2/src/app/components/atoms/icons/icons.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { IconComponent } from './icon/icon.component'
import { LoadingSpinnerComponent } from './loading-spinner/loading-spinner.component'
import { PageEntityIconComponent } from './page-entity-icon/page-entity-icon.component'
import { PriorityIconComponent } from './priority-icon/priority-icon.component'
import { StatusIconComponent } from './status-icon/status-icon.component'

@NgModule({
declarations: [
Expand All @@ -14,7 +13,6 @@ import { StatusIconComponent } from './status-icon/status-icon.component'
LoadingSpinnerComponent,
PageEntityIconComponent,
PriorityIconComponent,
StatusIconComponent,
],
imports: [CommonModule],
exports: [
Expand All @@ -23,7 +21,6 @@ import { StatusIconComponent } from './status-icon/status-icon.component'
LoadingSpinnerComponent,
PageEntityIconComponent,
PriorityIconComponent,
StatusIconComponent,
],
})
export class IconsModule {}
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
<status-icon *ngIf="icon != 'tasklist'" [status]="icon"> </status-icon>

<app-icon *ngIf="icon == 'tasklist'" iconClass="far fa-tasks text-tinted-300"></app-icon>
<app-icon [iconClass]="entityIconClassMap[icon]"></app-icon>
Loading

0 comments on commit 2e4cf1c

Please sign in to comment.