geonetwork-ui 2.10.0-dev.bf2e85a24 → 2.10.0-dev.bfea85a63

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,40 +7,20 @@ import {
7
7
  inject,
8
8
  } from '@angular/core'
9
9
  import { PlatformServiceInterface } from '../../../../../../../libs/common/domain/src/lib/platform.service.interface'
10
- import { AutocompleteComponent, BadgeComponent } from '../../../../../../../libs/ui/inputs/src'
10
+ import { AutocompleteComponent } from '../../../../../../../libs/ui/inputs/src'
11
+ import { KeywordBadgeComponent } from '../../../../../../../libs/ui/elements/src'
11
12
  import { map } from 'rxjs'
12
13
  import { Keyword } from '../../../../../../../libs/common/domain/src/lib/model/record'
13
14
  import { KeywordType } from '../../../../../../../libs/common/domain/src/lib/model/thesaurus'
14
- import { TranslatePipe } from '@ngx-translate/core'
15
- import {
16
- NgIconComponent,
17
- provideIcons,
18
- provideNgIconsConfig,
19
- } from '@ng-icons/core'
20
- import { matWarningAmberOutline } from '@ng-icons/material-icons/outline'
21
15
 
22
16
  type AutocompleteItem = { title: string; value: Keyword }
23
17
 
24
18
  @Component({
25
19
  selector: 'gn-ui-generic-keywords',
26
20
  templateUrl: './generic-keywords.component.html',
27
- styleUrls: ['./generic-keywords.component.css'],
28
21
  changeDetection: ChangeDetectionStrategy.OnPush,
29
22
  standalone: true,
30
- imports: [
31
- AutocompleteComponent,
32
- NgIconComponent,
33
- BadgeComponent,
34
- TranslatePipe,
35
- ],
36
- providers: [
37
- provideIcons({
38
- matWarningAmberOutline,
39
- }),
40
- provideNgIconsConfig({
41
- size: '1.5em',
42
- }),
43
- ],
23
+ imports: [AutocompleteComponent, KeywordBadgeComponent],
44
24
  })
45
25
  export class GenericKeywordsComponent {
46
26
  private platformService = inject(PlatformServiceInterface)
@@ -87,9 +67,4 @@ export class GenericKeywordsComponent {
87
67
  this.changedKeywords.emit(this.keywords)
88
68
  this.deletedKeyword.emit(keyword)
89
69
  }
90
-
91
- isPlaceWithoutExtent(keyword: Keyword): boolean {
92
- if (keyword.type !== 'place') return false
93
- return !keyword.bbox
94
- }
95
70
  }
@@ -138,6 +138,7 @@ export class FormFieldConstraintsShortcutsComponent implements OnDestroy {
138
138
  this.editorFacade.setFieldVisibility({ model }, visible)
139
139
  })
140
140
  }
141
+ hideEmptyConstraints(this.legalConstraints$, 'legalConstraints')
141
142
  hideEmptyConstraints(this.securityConstraints$, 'securityConstraints')
142
143
  hideEmptyConstraints(this.otherConstraints$, 'otherConstraints')
143
144
  })
@@ -11,6 +11,7 @@ export * from './lib/image-overlay-preview/image-overlay-preview.component'
11
11
  export * from './lib/kind-badge/kind-badge.component'
12
12
  export * from './lib/external-link-card/external-link-card.component'
13
13
  export * from './lib/markdown-editor/markdown-editor.component'
14
+ export * from './lib/keyword-badge/keyword-badge.component'
14
15
  export * from './lib/markdown-parser/markdown-parser.component'
15
16
  export * from './lib/metadata-catalog/metadata-catalog.component'
16
17
  export * from './lib/metadata-contact/metadata-contact.component'
@@ -0,0 +1,14 @@
1
+ <gn-ui-badge
2
+ [clickable]="!editable"
3
+ [removable]="editable"
4
+ (badgeClicked)="keywordClick.emit(keyword)"
5
+ (badgeRemoveClicked)="keywordRemove.emit(keyword)"
6
+ ><ng-container>{{ keyword.label }}</ng-container>
7
+ @if (editable && isPlaceWithoutExtent) {
8
+ <ng-icon
9
+ [title]="'editor.record.form.keywords.place.withoutExtent' | translate"
10
+ class="!text-[1.4em] ml-1"
11
+ name="matWarningAmberOutline"
12
+ ></ng-icon>
13
+ }
14
+ </gn-ui-badge>
@@ -0,0 +1,85 @@
1
+ import {
2
+ AfterViewInit,
3
+ ChangeDetectionStrategy,
4
+ Component,
5
+ ElementRef,
6
+ EventEmitter,
7
+ inject,
8
+ Input,
9
+ Output,
10
+ ViewChild,
11
+ } from '@angular/core'
12
+ import { Keyword } from '../../../../../../libs/common/domain/src/lib/model/record'
13
+ import { BadgeComponent } from '../../../../../../libs/ui/inputs/src'
14
+ import { TranslatePipe, TranslateService } from '@ngx-translate/core'
15
+ import { marker } from '@biesbjerg/ngx-translate-extract-marker'
16
+ import {
17
+ NgIconComponent,
18
+ provideIcons,
19
+ provideNgIconsConfig,
20
+ } from '@ng-icons/core'
21
+ import { matWarningAmberOutline } from '@ng-icons/material-icons/outline'
22
+ import tippy from 'tippy.js'
23
+
24
+ marker('domain.record.keywordType.theme')
25
+ marker('domain.record.keywordType.place')
26
+ marker('domain.record.keywordType.temporal')
27
+ marker('domain.record.keywordType.other')
28
+
29
+ @Component({
30
+ selector: 'gn-ui-keyword-badge',
31
+ templateUrl: './keyword-badge.component.html',
32
+ changeDetection: ChangeDetectionStrategy.OnPush,
33
+ standalone: true,
34
+ imports: [BadgeComponent, NgIconComponent, TranslatePipe],
35
+ providers: [
36
+ provideIcons({
37
+ matWarningAmberOutline,
38
+ }),
39
+ provideNgIconsConfig({
40
+ size: '1.5em',
41
+ }),
42
+ ],
43
+ })
44
+ export class KeywordBadgeComponent implements AfterViewInit {
45
+ private translateService = inject(TranslateService)
46
+
47
+ @Input() keyword: Keyword
48
+ @Input() editable = false // if false, keyword is only clickable
49
+ @Output() keywordClick = new EventEmitter<Keyword>()
50
+ @Output() keywordRemove = new EventEmitter<Keyword>()
51
+ @ViewChild(BadgeComponent, { read: ElementRef }) badgeComponent: ElementRef
52
+
53
+ get segments(): string[] {
54
+ if (this.keyword.hierarchyPath?.length) {
55
+ return this.keyword.hierarchyPath
56
+ }
57
+ if (this.keyword.thesaurus?.name) {
58
+ return [this.keyword.thesaurus.name, this.keyword.label]
59
+ }
60
+ return [
61
+ this.translateService.instant(
62
+ `domain.record.keywordType.${this.keyword.type}`
63
+ ),
64
+ this.keyword.label,
65
+ ]
66
+ }
67
+
68
+ get isPlaceWithoutExtent(): boolean {
69
+ return this.keyword.type === 'place' && !this.keyword.bbox
70
+ }
71
+
72
+ ngAfterViewInit() {
73
+ const content = this.segments
74
+ .map(
75
+ (segment, i, arr) =>
76
+ `<span ${i === arr.length - 1 ? 'class="font-bold"' : ''}>${segment}</span>`
77
+ )
78
+ .join(' &gt; ')
79
+ tippy(this.badgeComponent.nativeElement, {
80
+ appendTo: () => document.body,
81
+ content,
82
+ allowHTML: true,
83
+ })
84
+ }
85
+ }
@@ -16,3 +16,8 @@
16
16
  --gn-ui-badge-background-color: var(--color-primary-white);
17
17
  --gn-ui-badge-text-color: var(--color-primary-darkest);
18
18
  }
19
+
20
+ .metadata-info-keywords {
21
+ --gn-ui-badge-padding: 0.25rem 0.5rem;
22
+ text-transform: lowercase;
23
+ }
@@ -28,23 +28,10 @@
28
28
  <gn-ui-max-lines [maxLines]="7">
29
29
  <div class="metadata-info-keywords sm:pb-4 flex flex-wrap gap-2">
30
30
  @for (keyword of metadata.keywords; track keyword) {
31
- <ng-template #keywordTooltip>
32
- @for (segment of keywordTooltipSegments(keyword); track $index) {
33
- <span [class.font-bold]="$last">{{ segment }}</span>
34
- @if (!$last) {
35
- <span> &gt; </span>
36
- }
37
- }
38
- </ng-template>
39
- <gn-ui-popover [content]="keywordTooltip" theme="dark">
40
- <gn-ui-button
41
- type="primary-light"
42
- class="inline-block opacity-70"
43
- extraClass="lowercase text-sm py-1 px-2"
44
- (buttonClick)="onKeywordClick(keyword)"
45
- >{{ keyword.label }}</gn-ui-button
46
- >
47
- </gn-ui-popover>
31
+ <gn-ui-keyword-badge
32
+ [keyword]="keyword"
33
+ (keywordClick)="onKeywordClick($event)"
34
+ ></gn-ui-keyword-badge>
48
35
  }
49
36
  </div>
50
37
  </gn-ui-max-lines>
@@ -19,19 +19,13 @@ import {
19
19
  ExpandablePanelComponent,
20
20
  MaxLinesComponent,
21
21
  } from '../../../../../../libs/ui/layout/src'
22
- import {
23
- TranslateDirective,
24
- TranslatePipe,
25
- TranslateService,
26
- } from '@ngx-translate/core'
27
- import { marker } from '@biesbjerg/ngx-translate-extract-marker'
22
+ import { TranslateDirective, TranslatePipe } from '@ngx-translate/core'
28
23
  import {
29
24
  BadgeComponent,
30
- ButtonComponent,
31
25
  CopyTextButtonComponent,
32
26
  } from '../../../../../../libs/ui/inputs/src'
33
- import { PopoverComponent } from '../../../../../../libs/ui/widgets/src'
34
27
  import { ContentGhostComponent } from '../content-ghost/content-ghost.component'
28
+ import { KeywordBadgeComponent } from '../keyword-badge/keyword-badge.component'
35
29
  import { NgIcon, provideIcons } from '@ng-icons/core'
36
30
  import { matOpenInNew } from '@ng-icons/material-icons/baseline'
37
31
  import { matMailOutline } from '@ng-icons/material-icons/outline'
@@ -41,11 +35,6 @@ import { GnUiHumanizeDateDirective } from '../../../../../../libs/util/shared/sr
41
35
  import { SpatialExtentComponent } from '../../../../../../libs/ui/map/src'
42
36
  import { ContactPillComponent } from '../contact-pill/contact-pill.component'
43
37
 
44
- marker('domain.record.keywordType.theme')
45
- marker('domain.record.keywordType.place')
46
- marker('domain.record.keywordType.temporal')
47
- marker('domain.record.keywordType.other')
48
-
49
38
  @Component({
50
39
  selector: 'gn-ui-metadata-info',
51
40
  templateUrl: './metadata-info.component.html',
@@ -57,7 +46,6 @@ marker('domain.record.keywordType.other')
57
46
  TranslatePipe,
58
47
  MarkdownParserComponent,
59
48
  ExpandablePanelComponent,
60
- ButtonComponent,
61
49
  BadgeComponent,
62
50
  ContentGhostComponent,
63
51
  MaxLinesComponent,
@@ -67,7 +55,7 @@ marker('domain.record.keywordType.other')
67
55
  GnUiHumanizeDateDirective,
68
56
  SpatialExtentComponent,
69
57
  ContactPillComponent,
70
- PopoverComponent,
58
+ KeywordBadgeComponent,
71
59
  ],
72
60
  viewProviders: [
73
61
  provideIcons({
@@ -78,7 +66,6 @@ marker('domain.record.keywordType.other')
78
66
  })
79
67
  export class MetadataInfoComponent {
80
68
  private dateService = inject(DateService)
81
- private translateService = inject(TranslateService)
82
69
 
83
70
  @Input() metadata: Partial<CatalogRecord>
84
71
  @Input() incomplete: boolean
@@ -180,19 +167,4 @@ export class MetadataInfoComponent {
180
167
  onKeywordClick(keyword: Keyword) {
181
168
  this.keyword.emit(keyword)
182
169
  }
183
-
184
- keywordTooltipSegments(keyword: Keyword): string[] {
185
- if (keyword.hierarchyPath?.length) {
186
- return keyword.hierarchyPath
187
- }
188
- if (keyword.thesaurus?.name) {
189
- return [keyword.thesaurus.name, keyword.label]
190
- }
191
- return [
192
- this.translateService.instant(
193
- `domain.record.keywordType.${keyword.type}`
194
- ),
195
- keyword.label,
196
- ]
197
- }
198
170
  }
@@ -1,27 +1,34 @@
1
- <div
2
- class="gn-ui-badge flex items-center gap-1.5"
3
- [ngClass]="
4
- clickable
5
- ? 'hover:bg-primary cursor-pointer transition-colors duration-100'
6
- : ''
7
- "
8
- >
1
+ <ng-template #badgeContent>
9
2
  <span><ng-content></ng-content></span>
10
- @if (removable) {
11
- <gn-ui-button
12
- type="light"
13
- (buttonClick)="removeBadge()"
14
- extraClass="text-xs border-0 px-0 py-0"
15
- style="
16
- --gn-ui-button-padding: 0;
17
- --gn-ui-button-font-size: 0.8em;
18
- --gn-ui-button-width: 1.4em;
19
- --gn-ui-button-height: 1.4em;
20
- --gn-ui-button-rounded: 1.4em;
21
- --gn-ui-button-background: var(--gn-ui-badge-background-color, white);
22
- "
23
- >
24
- <ng-icon name="matClose"></ng-icon>
25
- </gn-ui-button>
26
- }
27
- </div>
3
+ </ng-template>
4
+
5
+ @if (clickable) {
6
+ <button
7
+ type="button"
8
+ class="gn-ui-badge-clickable"
9
+ (click)="handleClick($event)"
10
+ >
11
+ <ng-container [ngTemplateOutlet]="badgeContent"></ng-container>
12
+ </button>
13
+ } @else {
14
+ <span class="gn-ui-badge gap-1.5">
15
+ <ng-container [ngTemplateOutlet]="badgeContent"></ng-container>
16
+ @if (removable) {
17
+ <gn-ui-button
18
+ type="light"
19
+ (buttonClick)="removeBadge()"
20
+ extraClass="text-xs border-0 px-0 py-0"
21
+ style="
22
+ --gn-ui-button-padding: 0;
23
+ --gn-ui-button-font-size: 0.8em;
24
+ --gn-ui-button-width: 1.4em;
25
+ --gn-ui-button-height: 1.4em;
26
+ --gn-ui-button-rounded: 1.4em;
27
+ --gn-ui-button-background: var(--gn-ui-badge-background-color, white);
28
+ "
29
+ >
30
+ <ng-icon name="matClose"></ng-icon>
31
+ </gn-ui-button>
32
+ }
33
+ </span>
34
+ }
@@ -13,6 +13,7 @@ import {
13
13
  provideNgIconsConfig,
14
14
  } from '@ng-icons/core'
15
15
  import { matClose } from '@ng-icons/material-icons/baseline'
16
+ import { propagateToDocumentOnly } from '../../../../../../libs/util/shared/src'
16
17
 
17
18
  @Component({
18
19
  selector: 'gn-ui-badge',
@@ -34,8 +35,16 @@ export class BadgeComponent {
34
35
  @Input() clickable? = false
35
36
  @Input() removable? = false
36
37
  @Output() badgeRemoveClicked = new EventEmitter<void>()
38
+ @Output() badgeClicked = new EventEmitter<void>()
37
39
 
38
40
  removeBadge() {
39
41
  this.badgeRemoveClicked.emit()
40
42
  }
43
+
44
+ handleClick(event: MouseEvent) {
45
+ this.badgeClicked.emit()
46
+ event.preventDefault()
47
+ ;(event.currentTarget as HTMLElement).blur()
48
+ propagateToDocumentOnly(event)
49
+ }
41
50
  }
package/tailwind.base.css CHANGED
@@ -1,5 +1,3 @@
1
- @import './material-styles.css';
2
-
3
1
  @tailwind base;
4
2
  @tailwind components;
5
3
  @tailwind utilities;
@@ -186,9 +184,18 @@
186
184
  --text-color: var(--gn-ui-badge-text-color, var(--color-gray-50));
187
185
  --background-color: var(--gn-ui-badge-background-color, black);
188
186
  --opacity: var(--gn-ui-badge-opacity, 0.7);
187
+ text-transform: inherit;
189
188
  @apply opacity-[--opacity] p-[--padding] rounded-[--rounded]
190
189
  font-[--font-weight] text-[length:--text-size] text-[color:--text-color] bg-[color:--background-color] flex justify-center items-center content-center;
191
190
  }
191
+ .gn-ui-badge-clickable {
192
+ @apply gn-ui-badge hover:bg-primary focus:bg-primary active:bg-primary-darker
193
+ hover:text-white active:text-white focus:text-white
194
+ transition-all duration-100
195
+ outline-none
196
+ relative
197
+ focus:z-10 active:z-10 hover:z-10;
198
+ }
192
199
  /* makes sure icons will not make the badges grow vertically; also make size proportional */
193
200
  .gn-ui-badge ng-icon {
194
201
  margin-top: -0.325em;