Skip to content

Commit

Permalink
Merge pull request #204 from e-picsa/feat/admin-resource-upload
Browse files Browse the repository at this point in the history
Feat(dashboard): resource editor
  • Loading branch information
chrismclarke authored Dec 14, 2023
2 parents 4cd93ea + 0c4a06b commit 12c539a
Show file tree
Hide file tree
Showing 70 changed files with 3,373 additions and 1,337 deletions.
2 changes: 1 addition & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {
"@nx/enforce-module-boundaries": [
"error",
"warn",
{
"enforceBuildableLibDependency": true,
"allow": [],
Expand Down
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@
"apps/picsa-server/_deprecated/scripts"
],
"deno.unstable": true,
"deno.config": "apps/picsa-server/supabase/functions/deno.jsonc"
"deno.config": "apps/picsa-server/supabase/functions/deno.jsonc",
"angular.enable-strict-mode-prompt": false
}
2 changes: 2 additions & 0 deletions apps/picsa-apps/dashboard/.eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
"files": ["*.ts"],
"extends": ["plugin:@nx/angular", "plugin:@angular-eslint/template/process-inline-templates"],
"rules": {
"@typescript-eslint/no-unused-vars": ["warn"],
"@typescript-eslint/no-explicit-any": ["warn"],
"@angular-eslint/directive-selector": [
"error",
{
Expand Down
30 changes: 25 additions & 5 deletions apps/picsa-apps/dashboard/src/app/app.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
<mat-icon>menu</mat-icon>
</button>
<span style="flex: 1">PICSA Dashboard</span>
@if(supabaseService.authUser(); as user){
@if(supabaseService.auth.authUser(); as user){
<div style="position: relative">
<button mat-button (click)="supabaseService.signOut()" style="margin-top: 16px">
<button mat-button (click)="supabaseService.auth.signOut()" style="margin-top: 16px">
<mat-icon>person</mat-icon>
Sign Out
</button>
Expand All @@ -16,18 +16,38 @@
</div>

} @else {
<button mat-button (click)="supabaseService.signInPrompt()" style="margin: 8px 0">
<button mat-button (click)="supabaseService.auth.signInPrompt()" style="margin: 8px 0">
<mat-icon>person</mat-icon>
Sign In
</button>
}
</mat-toolbar>

<mat-sidenav-container style="flex: 1">
<mat-sidenav #sidenav mode="side" opened [fixedInViewport]="true" fixedTopGap="48">
<mat-sidenav #sidenav mode="side" opened [fixedInViewport]="true" fixedTopGap="64">
<mat-nav-list>
@for (link of navLinks; track link.href) {
<a mat-list-item [routerLink]="link.href" routerLinkActive="mdc-list-item--activated">{{ link.label }}</a>
<a
mat-list-item
[routerLink]="link.href"
routerLinkActive="mdc-list-item--activated active-link"
[routerLinkActiveOptions]="{ exact: true }"
>
{{ link.label }}
</a>
}
<mat-divider style="margin-top: auto"></mat-divider>
<div mat-subheader>Global Admin</div>
<mat-divider></mat-divider>
@for (link of globalLinks; track link.href) {
<a
mat-list-item
[routerLink]="link.href"
routerLinkActive="mdc-list-item--activated active-link"
[routerLinkActiveOptions]="{ exact: true }"
>
{{ link.label }}
</a>
}
</mat-nav-list>
</mat-sidenav>
Expand Down
7 changes: 7 additions & 0 deletions apps/picsa-apps/dashboard/src/app/app.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ mat-sidenav {
}
mat-nav-list {
--mdc-list-list-item-one-line-container-height: 48px;

height: calc(100% - 16px);
display: flex;
flex-direction: column;
}
mat-toolbar {
z-index: 2;
Expand All @@ -11,3 +15,6 @@ mat-sidenav-content {
display: flex;
flex-direction: column;
}
.active-link {
text-decoration: underline;
}
38 changes: 32 additions & 6 deletions apps/picsa-apps/dashboard/src/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { CommonModule } from '@angular/common';
import { AfterViewInit, Component } from '@angular/core';
import { RouterModule } from '@angular/router';
import { PicsaNotificationService } from '@picsa/shared/services/core/notification.service';
import { SupabaseService } from '@picsa/shared/services/core/supabase';

import { DashboardMaterialModule } from './material.module';
Expand All @@ -27,16 +26,43 @@ export class AppComponent implements AfterViewInit {
label: 'Home',
href: '/',
},
{
label: 'Resources',
href: '/resources',
},
// {
// label: 'Climate Data',
// href: '/climate-data',
// },
// {
// label: 'Crop Information',
// href: '/crop-information',
// },
// {
// label: 'Monitoring Forms',
// href: '/monitoring-forms',
// },
// {
// label: 'Translations',
// href: '/translations',
// },
];

globalLinks: INavLink[] = [
// {
// label: 'Deployments',
// href: '/deployments',
// },
// {
// label: 'Resources',
// href: '/resources',
// label: 'Users',
// href: '/users',
// },
];

constructor(public supabaseService: SupabaseService, private notificationService: PicsaNotificationService) {}
constructor(public supabaseService: SupabaseService) {}

async ngAfterViewInit() {
await this.supabaseService.init();
await this.supabaseService.signInDefaultUser();
await this.supabaseService.ready();
await this.supabaseService.auth.signInDefaultUser();
}
}
2 changes: 1 addition & 1 deletion apps/picsa-apps/dashboard/src/app/app.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ import { Route } from '@angular/router';
export const appRoutes: Route[] = [
{
path: 'resources',
loadChildren: () => import('./pages/resources/resources.module').then((m) => m.ResourcesPageModule),
loadChildren: () => import('./modules/resources/resources.module').then((m) => m.ResourcesPageModule),
},
];
22 changes: 21 additions & 1 deletion apps/picsa-apps/dashboard/src/app/material.module.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,31 @@
import { NgModule } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatChipsModule } from '@angular/material/chips';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatListModule } from '@angular/material/list';
import { MatRadioModule } from '@angular/material/radio';
import { MatSidenavModule } from '@angular/material/sidenav';
import { MatStepperModule } from '@angular/material/stepper';
import { MatTableModule } from '@angular/material/table';
import { MatTabsModule } from '@angular/material/tabs';
import { MatToolbarModule } from '@angular/material/toolbar';

const matModules = [MatButtonModule, MatIconModule, MatListModule, MatSidenavModule, MatToolbarModule];
const matModules = [
MatButtonModule,
MatChipsModule,
MatFormFieldModule,
MatIconModule,
MatInputModule,
MatListModule,
MatRadioModule,
MatSidenavModule,
MatStepperModule,
MatTableModule,
MatTabsModule,
MatToolbarModule,
];

@NgModule({
imports: matModules,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './storage-link/storage-link.component';
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
@if(entry){ @switch (displayStyle) {
<!-- chip style -->
@case ('button') {
<button
class="chip-button filetype-icon"
mat-button
[attr.data-icon]="fileTypeIcon"
(click)="handleLinkClick(entry.publicUrl)"
>
<mat-icon class="chip-icon filetype-icon inverted" [attr.data-icon]="fileTypeIcon">{{ fileTypeIcon }}</mat-icon>
<div class="chip-text filename">{{ entry.name?.split('/')?.pop() }}</div>
</button>

}
<!-- link style -->
@case ('link') {
<a [href]="entry.publicUrl" target="_blank" rel="noopener">
<div class="link-text">{{ entry.name?.split('/')?.pop() }}</div>
</a>
}
<!-- default style -->
@default {
<div style="display: flex; align-items: center">
<mat-icon class="filetype-icon default-icon" [attr.data-icon]="fileTypeIcon">{{ fileTypeIcon }}</mat-icon>
<div class="filename" style="margin-left: 8px">{{ entry.name?.split('/')?.pop() }}</div>
</div>
} } } @if(notFound) {
<p class="error-message">Storage file not found</p>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
:host {
display: block;
max-width: 200px;
}
.error-message {
color: var(--color-warn);
}
mat-icon {
overflow: visible;
}

a {
text-decoration: none;
color: var(--color-primary);
}

button.chip-button {
padding: 0;
margin-bottom: 4px;
border: 1px solid;
height: auto;

.chip-icon {
color: white;
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 32px;
display: flex;
align-items: center;
justify-content: center;
}
.chip-text {
margin-left: 32px;
padding: 8px;
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
}
}
.filename {
word-break: break-word;
}

// Colors used for different filetypes
$color-file-document: hsl(230, 40%, 40%);
$color-file-video: hsl(0, 70%, 60%);
$color-file-image: hsl(150, 60%, 40%);
$color-file-audio: hsl(300, 60%, 40%);

.filetype-icon {
color: var(--color-primary);
&[data-icon='description'] {
color: $color-file-document;
}
&[data-icon='smart_display'] {
color: $color-file-video;
}
&[data-icon='image'] {
color: $color-file-image;
}
&[data-icon='music_note'] {
color: $color-file-audio;
}
}

.filetype-icon.inverted {
color: white;
background: var(--color-primary);
&[data-icon='description'] {
background: $color-file-document;
}
&[data-icon='smart_display'] {
background: $color-file-video;
}
&[data-icon='image'] {
background: $color-file-image;
}
&[data-icon='music_note'] {
background: $color-file-audio;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { DashboardResourcesStorageLinkComponent } from './storage-link.component';

describe('StorageLinkComponent', () => {
let component: DashboardResourcesStorageLinkComponent;
let fixture: ComponentFixture<DashboardResourcesStorageLinkComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [DashboardResourcesStorageLinkComponent],
}).compileComponents();

fixture = TestBed.createComponent(DashboardResourcesStorageLinkComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { CommonModule } from '@angular/common';
import { Component, Input, OnInit } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';

import { IResourceStorageEntry, ResourcesDashboardService } from '../../resources.service';

/**
* Mat icons used to represent various filetype extensions
* https://fonts.google.com/icons
*/
const filetypeIconMapping = {
pdf: 'description',
jpeg: 'image',
jpg: 'image',
png: 'image',
svg: 'image',
mp4: 'smart_display',
mp3: 'music_note',
};

@Component({
selector: 'dashboard-resources-storage-link',
standalone: true,
imports: [CommonModule, MatButtonModule, MatIconModule],
templateUrl: './storage-link.component.html',
styleUrls: ['./storage-link.component.scss'],
})
/**
* Minimal component that takes a storage file id input and returns a link
* to the public url of the file, as populated from resources store cache
*/
export class DashboardResourcesStorageLinkComponent implements OnInit {
/** Resource storage id */
@Input() id: string;

@Input() displayStyle: 'button' | 'link' | 'default' = 'default';

constructor(private service: ResourcesDashboardService) {}

public entry?: IResourceStorageEntry;

public notFound = false;

public fileTypeIcon = 'description';

async ngOnInit() {
const entry = await this.service.getStorageFileById(this.id);
this.entry = entry;
this.notFound = entry ? false : true;
if (entry) {
this.fileTypeIcon = this.getFileTypeIcon(entry);
}
}

public handleLinkClick(url: string) {
window.open(url, '_blank', 'noopener');
}

private getFileTypeIcon(entry: IResourceStorageEntry) {
const extension = entry.name?.split('.').pop();
if (!extension) return 'document';
return filetypeIconMapping[extension] || 'document';
}
}
Loading

0 comments on commit 12c539a

Please sign in to comment.