Initial commit
This commit is contained in:
148
landing/src/app/ui/project-card.ts
Normal file
148
landing/src/app/ui/project-card.ts
Normal file
@@ -0,0 +1,148 @@
|
||||
import { ChangeDetectionStrategy, Component, computed, input } from '@angular/core';
|
||||
import {
|
||||
PiCardBottomContentDirective,
|
||||
PiCardCenterContentDirective,
|
||||
PiCardComponent,
|
||||
PiCardTopContentDirective,
|
||||
PiChipComponent,
|
||||
PiChipStatusComponent,
|
||||
PiTextColorDirective,
|
||||
PiTextSmallDirective,
|
||||
PiTextViewerLinkComponent,
|
||||
PiTextWeightBoldDirective,
|
||||
} from '@pi/ui';
|
||||
|
||||
import type { Copy } from '../core/copy';
|
||||
import type { PublicProject } from '../core/api.types';
|
||||
import { ApiImage } from './api-image';
|
||||
|
||||
@Component({
|
||||
selector: 'app-project-card',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [
|
||||
ApiImage,
|
||||
PiCardComponent,
|
||||
PiCardTopContentDirective,
|
||||
PiCardCenterContentDirective,
|
||||
PiCardBottomContentDirective,
|
||||
PiChipComponent,
|
||||
PiChipStatusComponent,
|
||||
PiTextViewerLinkComponent,
|
||||
PiTextSmallDirective,
|
||||
PiTextColorDirective,
|
||||
PiTextWeightBoldDirective,
|
||||
],
|
||||
template: `
|
||||
<pi-card
|
||||
class="landing-card"
|
||||
[hasShadow]="true"
|
||||
[isStroked]="true"
|
||||
gap="m"
|
||||
padding="m"
|
||||
innerPadding="none"
|
||||
alignment="stretch"
|
||||
context="public"
|
||||
>
|
||||
<pi-card-top-content>
|
||||
@if (project().cover_image; as cover) {
|
||||
<div class="landing-card__media">
|
||||
<app-api-image [image]="cover" />
|
||||
</div>
|
||||
}
|
||||
<div class="landing-card__chips">
|
||||
<pi-chip-status
|
||||
size="small"
|
||||
[status]="statusTone()"
|
||||
[label]="copy().projectStatus[project().status]"
|
||||
/>
|
||||
@if (project().needs_help) {
|
||||
<pi-chip
|
||||
size="small"
|
||||
appearance="outline"
|
||||
color="accent"
|
||||
[label]="copy().labels.needsHelp"
|
||||
/>
|
||||
}
|
||||
@if (stars() > 0) {
|
||||
<pi-chip size="small" icon="star-fill" [label]="stars().toString()" />
|
||||
}
|
||||
</div>
|
||||
</pi-card-top-content>
|
||||
|
||||
<pi-card-center-content>
|
||||
<h3 bold class="landing-card__title">{{ project().title }}</h3>
|
||||
@if (project().tagline; as tagline) {
|
||||
<p s pi-color color="grey">{{ tagline }}</p>
|
||||
}
|
||||
@if (technologies().length > 0) {
|
||||
<ul class="landing-card__chips">
|
||||
@for (technology of technologies(); track technology.id) {
|
||||
<li>
|
||||
<pi-chip size="small" appearance="outline" [label]="technology.name" />
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
</pi-card-center-content>
|
||||
|
||||
<pi-card-bottom-content>
|
||||
<div class="landing-card__links">
|
||||
@if (project().demo_url; as demo) {
|
||||
<pi-text-viewer-link
|
||||
icon="play-arrow"
|
||||
iconSize="small"
|
||||
[value]="copy().labels.demo"
|
||||
[link]="demo"
|
||||
target="_blank"
|
||||
/>
|
||||
}
|
||||
@if (repositoryUrl(); as repository) {
|
||||
<pi-text-viewer-link
|
||||
icon="link"
|
||||
iconSize="small"
|
||||
[value]="copy().labels.repository"
|
||||
[link]="repository"
|
||||
target="_blank"
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
</pi-card-bottom-content>
|
||||
</pi-card>
|
||||
`,
|
||||
})
|
||||
export class ProjectCard {
|
||||
readonly project = input.required<PublicProject>();
|
||||
readonly copy = input.required<Copy>();
|
||||
|
||||
/** Enough to read the stack at a glance; the rest is noise on a card. */
|
||||
protected readonly technologies = computed(() =>
|
||||
(this.project().technologies ?? []).slice(0, 5)
|
||||
);
|
||||
|
||||
protected readonly stars = computed(() =>
|
||||
(this.project().repositories ?? []).reduce(
|
||||
(total, repository) => total + (repository.stars ?? 0),
|
||||
0
|
||||
)
|
||||
);
|
||||
|
||||
protected readonly repositoryUrl = computed(
|
||||
() => (this.project().repositories ?? []).find((repo) => repo.url)?.url ?? null
|
||||
);
|
||||
|
||||
/** The API's five project states onto the chip's five tones. */
|
||||
protected readonly statusTone = computed(() => {
|
||||
const tones: Record<
|
||||
PublicProject['status'],
|
||||
'success' | 'error' | 'pending' | 'info' | 'disabled'
|
||||
> = {
|
||||
idea: 'info',
|
||||
active: 'success',
|
||||
paused: 'pending',
|
||||
completed: 'info',
|
||||
archived: 'disabled',
|
||||
};
|
||||
|
||||
return tones[this.project().status];
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user