Skip to content
This repository has been archived by the owner on Jan 19, 2023. It is now read-only.

Commit

Permalink
Add property CustomSvg to icons and navigation (#2786)
Browse files Browse the repository at this point in the history
Signed-off-by: ftovaro <[email protected]>
Signed-off-by: Vikram Yadav <[email protected]>
  • Loading branch information
ftovaro committed Sep 8, 2021
1 parent 458baed commit d4f47ac
Show file tree
Hide file tree
Showing 22 changed files with 562 additions and 158 deletions.
1 change: 1 addition & 0 deletions changelogs/unreleased/1422-ftovaro
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added support of SVGs for Icons and Navigation
2 changes: 2 additions & 0 deletions pkg/navigation/navigation.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ type Navigation struct {
Children []Navigation `json:"children,omitempty"`
IconName string `json:"iconName,omitempty"`
Loading bool `json:"isLoading"`
CustomSvg string `json:"customSvg,omitempty"`
}

// New creates a Navigation.
Expand Down Expand Up @@ -215,6 +216,7 @@ type navConfig struct {
suffix string
iconName string
isLoading bool
customSvg string
}

// EntriesHelper generates navigation entries.
Expand Down
14 changes: 8 additions & 6 deletions pkg/plugin/conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,10 @@ func convertToNavigation(in *dashboard.NavigationResponse_Navigation) navigation
}

out := navigation.Navigation{
Title: in.Title,
Path: in.Path,
IconName: in.IconName,
Title: in.Title,
Path: in.Path,
IconName: in.IconName,
CustomSvg: in.CustomSvg,
}

for _, child := range in.Children {
Expand All @@ -104,9 +105,10 @@ func convertToNavigation(in *dashboard.NavigationResponse_Navigation) navigation

func convertFromNavigation(in navigation.Navigation) *dashboard.NavigationResponse_Navigation {
out := dashboard.NavigationResponse_Navigation{
Title: in.Title,
Path: in.Path,
IconName: in.IconName,
Title: in.Title,
Path: in.Path,
IconName: in.IconName,
CustomSvg: in.CustomSvg,
}

for _, child := range in.Children {
Expand Down
286 changes: 148 additions & 138 deletions pkg/plugin/dashboard/dashboard.pb.go

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions pkg/plugin/dashboard/dashboard.proto
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ message NavigationResponse {
repeated Navigation children = 3;
string icon_name = 4;
string icon_source = 5;
string custom_svg = 6;
}

Navigation navigation = 1;
Expand Down
6 changes: 6 additions & 0 deletions pkg/view/component/icon.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type IconConfig struct {
BadgeColor string `json:"badgeColor"`
Label string `json:"label"`
Tooltip *TooltipConfig `json:"tooltip,omitempty"`
CustomSvg string `json:"customSvg"`
}

type Direction string
Expand Down Expand Up @@ -146,3 +147,8 @@ func (i *Icon) SetBadgeColor(color string) {
func (i *Icon) SetSize(size string) {
i.Config.Size = size
}

// SetCustomSvg sets an SVG for an Icon.
func (i *Icon) SetCustomSvg(customSvg string) {
i.Config.CustomSvg = customSvg
}
45 changes: 45 additions & 0 deletions pkg/view/component/icon_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,51 @@ func Test_Icon_Marshal(t *testing.T) {
}
}

func Test_Icon_With_SVG(t *testing.T) {
test := []struct {
name string
input *Icon
expectedPath string
isErr bool
}{
{
name: "in general with svg",
input: &Icon{
Base: newBase(TypeIcon, nil),
Config: IconConfig{
Shape: "user",
Size: "16",
Direction: DirectionDown,
Flip: FlipHorizontal,
Solid: true,
Status: StatusDanger,
Inverse: false,
Badge: BadgeDanger,
Color: "#add8e6",
BadgeColor: "purple",
Label: "example icon",
CustomSvg: "<svg width=\"9px\" height=\"9px\" viewBox=\"0 0 9 9\"><g stroke-width=\"1\"><g transform=\"translate(-496.000000, -443.000000)\" stroke=\"#111111\"></g></g></svg>",
},
},
expectedPath: "icon_with_svg.json",
isErr: false,
},
}
for _, tc := range test {
t.Run(tc.name, func(t *testing.T) {
actual, err := json.Marshal(tc.input)
isErr := err != nil
if isErr != tc.isErr {
t.Fatalf("UnExpected error: %v", err)
}

expected, err := ioutil.ReadFile(path.Join("testdata", tc.expectedPath))
require.NoError(t, err)
assert.JSONEq(t, string(expected), string(actual))
})
}
}

func Test_Icon_Options(t *testing.T) {
test := []struct {
name string
Expand Down
3 changes: 2 additions & 1 deletion pkg/view/component/testdata/icon.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"badge": "danger",
"color": "#add8e6",
"badgeColor": "purple",
"label": "example icon"
"label": "example icon",
"customSvg": ""
}
}
19 changes: 19 additions & 0 deletions pkg/view/component/testdata/icon_with_svg.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"metadata": {
"type": "icon"
},
"config": {
"shape": "user",
"size": "16",
"direction": "down",
"flip": "horizontal",
"solid": true,
"status": "danger",
"inverse": false,
"badge": "danger",
"color": "#add8e6",
"badgeColor": "purple",
"label": "example icon",
"customSvg": "<svg width=\"9px\" height=\"9px\" viewBox=\"0 0 9 9\"><g stroke-width=\"1\"><g transform=\"translate(-496.000000, -443.000000)\" stroke=\"#111111\"></g></g></svg>"
}
}
3 changes: 2 additions & 1 deletion pkg/view/component/testdata/signpost.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
"badge": "",
"color": "",
"badgeColor": "",
"label": ""
"label": "",
"customSvg": ""
}
},
"message": "Message",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { IconView } from '../../../models/content';

import { IconComponent } from './icon.component';

Expand All @@ -21,4 +22,38 @@ describe('IconComponent', () => {
it('should create', () => {
expect(component).toBeTruthy();
});

it('uses custom icon when customIcon svg is valid', () => {
component.view = {
metadata: {
type: 'icon',
},
config: {
shape: 'custom',
solid: true,
customSvg: '<svg></svg>',
},
} as IconView;
fixture.detectChanges();

let view = component.view as IconView;
expect(view.config.shape).toEqual('custom');
});

it('uses placeholder icon when customIcon svg is invalid', () => {
component.view = {
metadata: {
type: 'icon',
},
config: {
shape: 'custom',
solid: true,
customSvg: '<svg><circle><foo>',
},
} as IconView;
fixture.detectChanges();

let view = component.view as IconView;
expect(view.config.shape).toEqual('times');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ import {
loadTextEditIconSet,
loadTechnologyIconSet,
loadChartIconSet,
ClarityIcons,
} from '@cds/core/icon';
import { IconView, Tooltip } from '../../../models/content';
import { isSvg } from '../../../../../util/isSvg';

@Component({
selector: 'app-view-icon',
Expand Down Expand Up @@ -58,6 +60,15 @@ export class IconComponent extends AbstractViewComponent<IconView> {
this.generateTooltipClassStyles();
}

if (view.config.customSvg) {
if (isSvg(view.config.customSvg)) {
ClarityIcons.addIcons([view.config.shape, view.config.customSvg]);
} else {
console.error(`Invalid SVG for icon '${view.config.shape}'`);
view.config.shape = 'times';
}
}

this.shape = view.config.shape;
this.flip = view.config.flip;
this.size = view.config.size;
Expand Down
1 change: 1 addition & 0 deletions web/src/app/modules/shared/models/content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -625,6 +625,7 @@ export interface IconView extends View {
badgeColor?: string;
label?: string;
tooltip?: Tooltip;
customSvg?: string;
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import { ContentService } from '../content/content.service';
import {
expectedSelection,
NAVIGATION_MOCK_DATA,
NAVIGATION_WITH_INVALID_CUSTOM_SVG,
NAVIGATION_WITH_VALID_CUSTOM_SVG,
} from './navigation.test.data';
import { BehaviorSubject } from 'rxjs';
import { take } from 'rxjs/operators';
Expand Down Expand Up @@ -196,4 +198,48 @@ describe('NavigationService', () => {
));
});
});

describe('custom icon svgs', () => {
describe('with valid custom svg', () => {
it('uses the given icon svg', inject(
[NavigationService, WebsocketService],
(svc: NavigationService, backendService: BackendService) => {
const currentNavigation: Navigation = {
sections: NAVIGATION_WITH_VALID_CUSTOM_SVG,
defaultPath: '',
};

backendService.triggerHandler(
'event.octant.dev/navigation',
currentNavigation
);

svc.current.subscribe(_ => {
expect(svc.modules.value[0].icon).toEqual('custom');
});
}
));
});

describe('with invalid custom svg', () => {
it('uses the default icon', inject(
[NavigationService, WebsocketService],
(svc: NavigationService, backendService: BackendService) => {
const currentNavigation: Navigation = {
sections: NAVIGATION_WITH_INVALID_CUSTOM_SVG,
defaultPath: '',
};

backendService.triggerHandler(
'event.octant.dev/navigation',
currentNavigation
);

svc.current.subscribe(_ => {
expect(svc.modules.value[0].icon).toEqual('times');
});
}
));
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,16 @@
import { Injectable } from '@angular/core';
import { WebsocketService } from '../../../../data/services/websocket/websocket.service';
import { BehaviorSubject } from 'rxjs';
import { Navigation } from '../../../sugarloaf/models/navigation';
import {
Navigation,
NavigationChild,
} from '../../../sugarloaf/models/navigation';
import { ContentService } from '../content/content.service';
import { NavigationEnd, Router, RouterEvent } from '@angular/router';
import { filter } from 'rxjs/operators';
import { LoadingService } from '../loading/loading.service';
import { ClarityIcons } from '@cds/core/icon';
import { isSvg } from '../../../../util/isSvg';

export type Selection = {
module: number;
Expand All @@ -25,7 +30,8 @@ export type Module = {
startIndex: number;
endIndex?: number;
icon: string;
children?: any[];
children?: NavigationChild[];
customSvg?: string;
};

const emptyNavigation: Navigation = {
Expand Down Expand Up @@ -159,13 +165,18 @@ export class NavigationService {

sections.forEach((section, index) => {
if (section.module && section.module.length > 0) {
if (section.customSvg) {
this.registerCustomSvg(section);
}

modules.push({
startIndex: index,
name: section.module,
icon: section.iconName,
description: section.description,
path: section.path,
title: section.title,
customSvg: section.customSvg,
});
}
});
Expand All @@ -186,6 +197,7 @@ export class NavigationService {
path: module.path,
icon: module.icon,
title: module.title,
customSvg: module.customSvg,
};
module.children = [
...[first],
Expand All @@ -199,13 +211,39 @@ export class NavigationService {
module.children.push(sections[i]);
}
}
if (module.children) {
this.findCustomSvg(module.children, true);
}
});
if (modules.length > 0) {
modules.push(modules.splice(pluginsIndex, 1)[0]);
}
this.modules.next(modules);
}

findCustomSvg(navigationChildren: NavigationChild[], skip: boolean): void {
navigationChildren.forEach(navChild => {
if (navChild.children) {
this.findCustomSvg(navChild.children, false);
}
if (!skip && navChild.customSvg) {
// do not show custom icon for parent of submenu
this.registerCustomSvg(navChild);
}
});
}

registerCustomSvg(nc: NavigationChild): void {
if (isSvg(nc.customSvg)) {
ClarityIcons.addIcons([nc.iconName, nc.customSvg]);
} else {
console.error(
`Invalid SVG for module: '${nc.title}'. Using default icon shape...`
);
nc.iconName = 'times';
}
}

redirect(namespace: string): string {
let routerLink = '';
const paths = this.activeUrl.value.split('/');
Expand Down
Loading

0 comments on commit d4f47ac

Please sign in to comment.