Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/3213/add notes to a custom view #3234

Merged
merged 23 commits into from Mar 24, 2023
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
177e38c
Enable safari compatibility and add babel class-static-blcok package
Feb 10, 2023
d825ea3
Add Custom Config Dialog Component, Add note to custom config model, …
Feb 16, 2023
c515573
Fix addCustomButton tests
Feb 16, 2023
9c13eb6
Move note edit below title, change button behaviour, display note pre…
Feb 16, 2023
785f26b
Merge branch 'main' into feature/3213/add-notes-to-a-custom-view
Feb 16, 2023
4833bf7
Update old tests
Feb 17, 2023
f9716f9
Add and fix remaining tests
Feb 22, 2023
5c9c2b3
Merge branch 'main' into feature/3213/add-notes-to-a-custom-view
RomanenkoVladimir Feb 22, 2023
9afb37d
Update Changelog
Feb 22, 2023
c69449f
Apply suggested scss changes
Feb 23, 2023
b6b63fd
Fix bottmo maring for note preview
Feb 23, 2023
2f79c04
Re-Enable previous click-on-title-to-apply-custom-config behaviour an…
Feb 23, 2023
c293eb6
Merge branch 'main' into feature/3213/add-notes-to-a-custom-view
RomanenkoVladimir Feb 23, 2023
4d41a05
Merge branch 'main' into feature/3213/add-notes-to-a-custom-view
Mar 17, 2023
cd4112a
Remove angular legacy dialogs #3213
Mar 17, 2023
71a4b56
Fix view of custom config list, make pipe for truncate text more reus…
Hall-Ma Mar 20, 2023
9e880cc
Adjust styling for editing custom config notes in dialog, restructure…
Hall-Ma Mar 20, 2023
9218c48
Adjust tests
Hall-Ma Mar 22, 2023
ef79d79
Update changelog
Hall-Ma Mar 22, 2023
3d3ebb6
Custom config note property is now optional, adjust tests and data mocks
Hall-Ma Mar 22, 2023
58b0f0e
Update tests
Hall-Ma Mar 24, 2023
b8ec50f
Merge branch 'main' into feature/3213/add-notes-to-a-custom-view
Hall-Ma Mar 24, 2023
d7e0be3
refactor: remove not needed mock
Mar 24, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/)

## [unreleased] (Added 🚀 | Changed | Removed 🗑 | Fixed 🐞 | Chore 👨‍💻 👩‍💻)

### Added 🚀

- Supports adding note to a custom view, provides a preview and an edit option for notes from the selection menu [#3234](https://github.com/MaibornWolff/codecharta/pull/3234)
Hall-Ma marked this conversation as resolved.
Show resolved Hide resolved

### Fixed 🐞

- Unselecting a folder in Presentation Mode leads to console error [#3215](https://github.com/MaibornWolff/codecharta/pull/3215)
Expand Down
Expand Up @@ -25,6 +25,7 @@ export interface CustomConfig {
camera: Vector3
cameraTarget: Vector3
}
note: string
}

export type ExportCustomConfig = CustomConfig
Expand Down
Expand Up @@ -23,12 +23,16 @@ describe("addCustomConfigButtonComponent", () => {
const addCustomConfigSpy = jest.spyOn(CustomConfigHelper, "addCustomConfig")
await render(AddCustomConfigButtonComponent, { excludeComponentDeclaration: true })

const configName = "myCustomConfig"
const configNote = "My Custom Note"

const button = screen.getByRole("button")
fireEvent.click(button)

await screen.findByText("Add Custom View")

await userEvent.type(screen.getByRole("textbox"), "myCustomConfig")
await userEvent.type(screen.getAllByRole("textbox")[0], configName)
await userEvent.type(screen.getAllByRole("textbox")[1], configNote)
fireEvent.click(screen.getByRole("button", { name: "ADD" }))

await waitForElementToBeRemoved(screen.queryByText("Add Custom View"))
Expand Down
Expand Up @@ -5,9 +5,10 @@ import { AddCustomConfigDialogComponent } from "./addCustomConfigDialog/addCusto
import { FormsModule, ReactiveFormsModule } from "@angular/forms"
import { CommonModule } from "@angular/common"
import { DownloadAndPurgeConfigsComponent } from "./addCustomConfigDialog/downloadAndPurgeConfigs/downloadAndPurgeConfigs.component"
import { CustomConfigNoteDialogModule } from "../customConfigNoteDialog/customConfigNoteDialog.module"

@NgModule({
imports: [MaterialModule, ReactiveFormsModule, FormsModule, CommonModule],
imports: [MaterialModule, ReactiveFormsModule, FormsModule, CommonModule, CustomConfigNoteDialogModule],
declarations: [AddCustomConfigButtonComponent, AddCustomConfigDialogComponent, DownloadAndPurgeConfigsComponent],
exports: [AddCustomConfigButtonComponent]
})
Expand Down
@@ -1,4 +1,4 @@
<mat-toolbar class="header">
<mat-toolbar class="header" xmlns="http://www.w3.org/1999/html">
<h2>Add Custom View</h2>
</mat-toolbar>

Expand All @@ -7,9 +7,22 @@ <h2>Add Custom View</h2>
<mat-form-field>
<label>Save individual configurations for your map.</label>
<input matInput class="custom-config-input" [formControl]="customConfigName" required />
<mat-hint align="start">View Name</mat-hint>
<mat-hint align="start">Config View Name</mat-hint>
<mat-error *ngIf="customConfigName.invalid">{{ getErrorMessage() }}</mat-error>
</mat-form-field>
<br />
<mat-form-field>
<label>Provide custom note for configuration (optional)</label>
<textarea
cdkAutosizeMinRows="8"
cdkTextareaAutosize
matInput
class="custom-config-input"
[(ngModel)]="customConfigNote"
required
Hall-Ma marked this conversation as resolved.
Show resolved Hide resolved
></textarea>
<mat-hint align="start">Config Note</mat-hint>
</mat-form-field>
</div>
</mat-dialog-content>

Expand Down
Expand Up @@ -14,6 +14,7 @@ import { VisibleFilesBySelectionMode, visibleFilesBySelectionModeSelector } from
})
export class AddCustomConfigDialogComponent implements OnInit {
customConfigName: UntypedFormControl
customConfigNote: string

constructor(
private state: State,
Expand All @@ -38,10 +39,15 @@ export class AddCustomConfigDialogComponent implements OnInit {
}

addCustomConfig() {
const newCustomConfig = buildCustomConfigFromState(this.customConfigName.value, this.state.getValue(), {
camera: this.threeCameraService.camera.position,
cameraTarget: this.threeOrbitControlsService.controls.target
})
const newCustomConfig = buildCustomConfigFromState(
this.customConfigName.value,
this.state.getValue(),
{
camera: this.threeCameraService.camera.position,
cameraTarget: this.threeOrbitControlsService.controls.target
},
this.customConfigNote
)
CustomConfigHelper.addCustomConfig(newCustomConfig)
}
}
Expand Down
Expand Up @@ -27,18 +27,30 @@ describe("addCustomConfigDialogComponent", () => {
it("should suggest a valid custom view name and 'add' button is enabled", async () => {
await render(AddCustomConfigDialogComponent, { excludeComponentDeclaration: true })

const input = screen.getByRole("textbox") as HTMLInputElement
const input = screen.getAllByRole("textbox") as HTMLInputElement[]
const nameTextField = input[0]

expect(input.value).toBe("new custom view name")
expect(nameTextField.value).toBe("new custom view name")
expect((screen.getByRole("button") as HTMLButtonElement).disabled).toBe(false)
})

it("should show an empty comment text field, with optional entry", async () => {
await render(AddCustomConfigDialogComponent, { excludeComponentDeclaration: true })

const input = screen.getAllByRole("textbox") as HTMLInputElement[]
const commentTextField = input[1]

expect(commentTextField.value).toBe("")
expect((screen.getByRole("button") as HTMLButtonElement).disabled).toBe(false)
})

it("should show error message when input field is empty and disable 'add' button", async () => {
await render(AddCustomConfigDialogComponent, { excludeComponentDeclaration: true })

const input = screen.getByRole("textbox") as HTMLInputElement
await userEvent.clear(input)
input.blur()
const input = screen.getAllByRole("textbox") as HTMLInputElement[]
const nameTextField = input[0]
await userEvent.clear(nameTextField)
nameTextField.blur()

expect(await screen.findByText("Please enter a view name.")).not.toBeNull()
expect((screen.getByRole("button") as HTMLButtonElement).disabled).toBe(true)
Expand All @@ -48,9 +60,11 @@ describe("addCustomConfigDialogComponent", () => {
await render(AddCustomConfigDialogComponent, { excludeComponentDeclaration: true })
jest.spyOn(CustomConfigHelper, "hasCustomConfigByName").mockReturnValue(true)

const input = screen.getByRole("textbox") as HTMLInputElement
await userEvent.type(input, "file name already exists")
input.blur()
const input = screen.getAllByRole("textbox") as HTMLInputElement[]
const nameTextField = input[0]

await userEvent.type(nameTextField, "file name already exists")
nameTextField.blur()

expect(await screen.findByText("A Custom View with this name already exists.")).not.toBeNull()
expect((screen.getByRole("button") as HTMLButtonElement).disabled).toBe(true)
Expand Down
@@ -0,0 +1,12 @@
import { Pipe, PipeTransform } from "@angular/core"

@Pipe({ name: "customConfigTransformNote" })
export class CustomConfigTransformNote implements PipeTransform {
transform(stringToTransfrom: string, limit, replacerString = "...") {
if (!stringToTransfrom) {
return "Add note"
}

return stringToTransfrom.length > limit ? stringToTransfrom.slice(0, limit) + replacerString : stringToTransfrom
}
}
Expand Up @@ -3,10 +3,8 @@
(click)="applyCustomConfig()"
[disabled]="!customConfigItem.isApplicable"
[style.color]="customConfigItem | customConfig2ApplicableColor"
title="{{ customConfigItem.name }}"
>
<p class="config-name" title="{{ customConfigItem.name }}">
<b>{{ customConfigItem.name }}</b>
</p>
<p class="config-metric"><i class="fa fa-arrows-alt"></i> {{ customConfigItem.metrics.areaMetric }}</p>
<p class="config-metric"><i class="fa fa-arrows-v"></i> {{ customConfigItem.metrics.heightMetric }}</p>
<p class="config-metric"><i class="fa fa-paint-brush"></i> {{ customConfigItem.metrics.colorMetric }}</p>
Expand Down
Expand Up @@ -27,7 +27,7 @@ cc-apply-custom-config-button {
}

&.config-metric {
flex: 0 0 8em;
flex: 1 1 auto;
}
}

Expand Down
Expand Up @@ -39,7 +39,6 @@ describe("applyCustomConfigButtonComponent", () => {
expect(getComputedStyle(colorSwatchElements[2]).backgroundColor).toBe("rgb(130, 14, 14)")
expect(getComputedStyle(colorSwatchElements[3]).backgroundColor).toBe("rgb(235, 131, 25)")

expect(screen.getByText("SampleMap View #1")).not.toBeNull()
expect(screen.getByText("rloc")).not.toBeNull()
expect(screen.getByText("mcc")).not.toBeNull()
expect(screen.getByText("functions")).not.toBeNull()
Expand All @@ -63,7 +62,6 @@ describe("applyCustomConfigButtonComponent", () => {
const applyCustomConfigButton = screen.getByRole("button") as HTMLButtonElement

expect(getComputedStyle(applyCustomConfigButton).color).toBe("rgb(204, 204, 204)")
expect(screen.getByText("SampleMap View #1")).not.toBeNull()
expect(screen.getByText("rloc")).not.toBeNull()
expect(screen.getByText("mcc")).not.toBeNull()
expect(screen.getByText("functions")).not.toBeNull()
Expand Down
@@ -0,0 +1,19 @@
import { CustomConfigTransformNote } from "./CustomConfigTransformNote.pipe"

describe("customConfigTruncateNotePipe", () => {
it("should return empty placeholder sting if no string given", () => {
expect(new CustomConfigTransformNote().transform("", 0)).toBe("Add note")
})

it("should return a shortened string when given string above limit", () => {
expect(new CustomConfigTransformNote().transform("Shorten This", 5)).toBe("Short...")
Hall-Ma marked this conversation as resolved.
Show resolved Hide resolved
})

it("should not modify string if below limit", () => {
expect(new CustomConfigTransformNote().transform("Too short", 9)).toBe("Too short")
})

it("should allow shortening with a custom string", () => {
expect(new CustomConfigTransformNote().transform("Too short", 4, "replaced")).toBe("Too replaced")
})
})
Expand Up @@ -14,6 +14,23 @@
</mat-expansion-panel-header>
<mat-list *ngFor="let customConfig of customConfigItemGroup.value.customConfigItems">
<mat-list-item title="{{ customConfig | customConfig2ApplicableMessage }}">
<div class="metrics-box">
<p class="config-item-name" title="{{ customConfig.name }}">
<b
><span (click)="applyCustomConfig(customConfig.id)" mat-dialog-close>
{{ customConfig.name | customConfigTransformNote: 33 }}
</span></b
>
</p>
<div class="configNoteAndButton">
<p class="config-item-note">
<span (click)="applyCustomConfig(customConfig.id)" mat-dialog-close>
{{ customConfig.note | customConfigTransformNote: 47 }}
</span>
</p>
<cc-custom-config-note-dialog [customConfigItem]="customConfig"></cc-custom-config-note-dialog>
</div>
</div>
<cc-apply-custom-config-button [customConfigItem]="customConfig"></cc-apply-custom-config-button>
<div>
<button class="remove-button" title="Remove Custom View" (click)="removeCustomConfig(customConfig.id)">
Expand Down
@@ -0,0 +1,40 @@
cc-custom-config-item-group {
div {
.configNoteAndButton {
display: flex;
align-items: center;
position: absolute;
top: 15px;
}

.metrics-box {
margin-right: 30px;
}
}

p {
&.config-item-name {
font-size: 15px;
margin-top: 10px;
margin-bottom: 30px;
text-align: left;
flex: 0 0 22em;
&:hover {
cursor: pointer;
}
}

&.config-item-note {
white-space: nowrap;
min-width: auto;
font-size: 10px;
margin: 5px 0;
text-align: left;
flex: 0 0 22em;
color: grey;
&:hover {
cursor: pointer;
}
}
}
}
Expand Up @@ -53,11 +53,10 @@ describe("customConfigItemGroupComponent", () => {
excludeComponentDeclaration: true,
componentProperties: { customConfigItemGroups }
})
const applyCustomConfigButton = screen.getByText("SampleMap View #1").closest("button") as HTMLButtonElement
const applyCustomConfigButton = screen.getAllByText("mcc")[0].closest("button") as HTMLButtonElement

await userEvent.click(applyCustomConfigButton)

expect(screen.getAllByTitle("Apply Custom View").length).toBe(2)
expect(applyCustomConfigButton.disabled).toBe(false)
expect(CustomConfigHelper.applyCustomConfig).toHaveBeenCalledTimes(1)
expect(mockedDialogReference.close).toHaveBeenCalledTimes(1)
Expand Down Expand Up @@ -87,6 +86,31 @@ describe("customConfigItemGroupComponent", () => {
expect(mockedDialogReference.close).toHaveBeenCalledTimes(0)
})

it("should apply a custom config and close custom config dialog when clicking on config name", async () => {
mockedVisibleFilesBySelectionModeSelector.mockImplementation(() => {
return {
mapSelectionMode: CustomConfigMapSelectionMode.MULTIPLE,
assignedMaps: new Map([
["md5_fileB", "fileB"],
["md5_fileC", "fileC"]
])
}
})
const customConfigItemGroups = new Map([["File_B_File_C_STANDARD", CUSTOM_CONFIG_ITEM_GROUPS.get("File_B_File_C_STANDARD")]])
await render(CustomConfigItemGroupComponent, {
excludeComponentDeclaration: true,
componentProperties: { customConfigItemGroups }
})

CustomConfigHelper.applyCustomConfig = jest.fn()
const applyCustomConfigButton = screen.getByText("SampleMap View #1").closest("span") as HTMLElement

await userEvent.click(applyCustomConfigButton)

expect(CustomConfigHelper.applyCustomConfig).toHaveBeenCalledTimes(1)
expect(mockedDialogReference.close).toHaveBeenCalledTimes(1)
})

it("should show tooltip with missing maps and correct selection mode if selected custom config is not fully applicable", async () => {
mockedVisibleFilesBySelectionModeSelector.mockImplementation(() => {
return {
Expand All @@ -100,7 +124,7 @@ describe("customConfigItemGroupComponent", () => {
componentProperties: { customConfigItemGroups }
})

const applyCustomConfigButton = screen.getByText("SampleMap View #1").closest("button")
const applyCustomConfigButton = screen.getAllByText("mcc")[0].closest("button")

expect(
screen.getAllByTitle(
Expand All @@ -127,9 +151,11 @@ describe("customConfigItemGroupComponent", () => {
excludeComponentDeclaration: true,
componentProperties: { customConfigItemGroups }
})
const applyCustomConfigButton = screen.getByText("SampleMap View #1").closest("button") as HTMLButtonElement
const editNoteArea = screen.getAllByText("Add note")[0]
const applyCustomConfigButton = screen.getAllByText("mcc")[0].closest("button") as HTMLButtonElement

expect(applyCustomConfigButton.disabled).toBe(true)
expect(editNoteArea).toBeTruthy()
expect(getComputedStyle(applyCustomConfigButton).color).toBe("rgb(204, 204, 204)")
})
})
@@ -1,17 +1,31 @@
import { Component, Input, ViewEncapsulation } from "@angular/core"
import { CustomConfigHelper } from "../../../../util/customConfigHelper"
import { CustomConfigItemGroup } from "../../customConfigs.component"
import { Store } from "../../../../state/angular-redux/store"
import { ThreeCameraService } from "../../../codeMap/threeViewer/threeCamera.service"
import { ThreeOrbitControlsService } from "../../../codeMap/threeViewer/threeOrbitControls.service"

@Component({
selector: "cc-custom-config-item-group",
templateUrl: "./customConfigItemGroup.component.html",
styleUrls: ["./customConfigItemGroup.component.scss"],
encapsulation: ViewEncapsulation.None
})
export class CustomConfigItemGroupComponent {
@Input() customConfigItemGroups: Map<string, CustomConfigItemGroup>
isExpanded = false

constructor(
private store: Store,
private threeCameraService: ThreeCameraService,
private threeOrbitControlsService: ThreeOrbitControlsService
) {}

removeCustomConfig(configId: string) {
CustomConfigHelper.deleteCustomConfig(configId)
}

applyCustomConfig(configId: string) {
CustomConfigHelper.applyCustomConfig(configId, this.store, this.threeCameraService, this.threeOrbitControlsService)
}
}