Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
0961ce9
chore(signage-manager): add initial app
MrYuion Feb 26, 2026
e073826
feat(signage-manager): add media listing view
MrYuion Feb 26, 2026
b8ec368
chore: add footer nav for mobile
MrYuion Feb 26, 2026
61d079f
feat(signage-manager): add playlist sidebar and item actions to media…
MrYuion Feb 26, 2026
5a30da7
feat(signage-manager): add media preview modal
MrYuion Feb 26, 2026
317a8cf
feat(signage-manager): add ability to add and edit media items
MrYuion Feb 26, 2026
cd7a621
chore(signage-manager): show banner on expired media items
MrYuion Feb 26, 2026
a736fda
feat(signage-manager): add playlist view
MrYuion Feb 26, 2026
2874758
chore(signage-manager): add some mobile and tablet styles for playlists
MrYuion Feb 26, 2026
604bec2
feat(signage-manager): add zones view
MrYuion Feb 26, 2026
cc3e81f
feat(signage-manager): add displays view
MrYuion Feb 27, 2026
66864c1
chore(signage-manager): added filter for playlists on media view
MrYuion Feb 27, 2026
5404f44
chore(signage-manager): add counts to some tabs
MrYuion Feb 27, 2026
6d6e2b7
chore(signage-manager): remove details section from displays
MrYuion Feb 27, 2026
578b997
feat(signage-manager): add schedule block to displays view
MrYuion Feb 28, 2026
3d6bad2
chore(signage-manager): link listed item to their details page
MrYuion Feb 28, 2026
7f2bb49
chore(signage-manager): tweaks
MrYuion Mar 1, 2026
7d8cab8
chore(signage-manager): add link to display panel UI
MrYuion Mar 3, 2026
56dade4
chore(signage-manager): mobile style tweaks
MrYuion Mar 3, 2026
7130e7d
chore(signage-manager): tweak sidebar max width
MrYuion Mar 3, 2026
1042a6f
feat(signage-manager): add file format validation
MrYuion Mar 4, 2026
deb9012
chore(signage-manager): add drag-n-drop uploads to media section
MrYuion Mar 4, 2026
4446ecb
chore(signage-manager): add mov support
MrYuion Mar 4, 2026
a03af1d
chore(signage-manager): add setting to enable support for AV1 and H265
MrYuion Mar 4, 2026
9391bfc
chore(signage-manager): tweaks to previous
MrYuion Mar 4, 2026
5a3852f
feat(signage-manager): show preview stack for playlist list items
MrYuion Mar 4, 2026
b2bb317
chore(signage-manager): update playlist listings on display and zone …
MrYuion Mar 4, 2026
230059f
feat(signage-manager): add playlist approvals flow
MrYuion Mar 4, 2026
a2ae7b0
chore(signage-manager): add logic to keep selected tab when item changes
MrYuion Mar 4, 2026
f41d5fc
chore(signage-manager): link playlists to item view
MrYuion Mar 5, 2026
54941f1
chore(signage-manager): tweaks for WCAG 2.1 AA
MrYuion Mar 5, 2026
2a9a99f
feat(signage-manager): add schedule view for displays and zones
MrYuion Mar 6, 2026
8a09dd8
chore(signage-manager): remove border from playlist thumbnails
MrYuion Mar 19, 2026
5d47f35
Merge branch 'develop' into feat/signage-manager
MrYuion Mar 19, 2026
cb5867e
chore(signage-manager): remove date pipe from schedule timeline
MrYuion Mar 19, 2026
94010f6
Merge branch 'develop' into feat/signage-manager
MrYuion Mar 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions apps/signage-manager/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"extends": ["../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts"],
"extends": [
"plugin:@nx/angular",
"plugin:@angular-eslint/template/process-inline-templates"
],
"rules": {
"@angular-eslint/directive-selector": [
"error",
{
"type": "attribute",
"prefix": "app",
"style": "camelCase"
}
],
"@angular-eslint/component-selector": [
"error",
{
"type": "element",
"prefix": "app",
"style": "kebab-case"
}
]
}
},
{
"files": ["*.html"],
"extends": ["plugin:@nx/angular-template"],
"rules": {}
}
]
}
21 changes: 21 additions & 0 deletions apps/signage-manager/jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
export default {
displayName: 'signage-manager',
preset: '../../jest.preset.js',
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
coverageDirectory: '../../coverage/apps/signage-manager',
transform: {
'^.+\\.(ts|mjs|js|html)$': [
'jest-preset-angular',
{
tsconfig: '<rootDir>/tsconfig.spec.json',
stringifyContentPathRegex: '\\.(html|svg)$',
},
],
},
transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'],
snapshotSerializers: [
'jest-preset-angular/build/serializers/no-ng-attributes',
'jest-preset-angular/build/serializers/ng-snapshot',
'jest-preset-angular/build/serializers/html-comment',
],
};
31 changes: 31 additions & 0 deletions apps/signage-manager/ngsw-config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"$schema": "../../node_modules/@angular/service-worker/config/schema.json",
"index": "/index.html",
"assetGroups": [
{
"name": "app",
"installMode": "prefetch",
"resources": {
"files": [
"/favicon.ico",
"/index.html",
"/manifest.webmanifest",
"/*.css",
"/*.js"
]
}
},
{
"name": "assets",
"installMode": "lazy",
"updateMode": "prefetch",
"resources": {
"files": [
"/assets/**",
"/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)",
"https://*.amazonaws.com/**/*.*"
]
}
}
]
}
136 changes: 136 additions & 0 deletions apps/signage-manager/project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
{
"name": "signage-manager",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"projectType": "application",
"sourceRoot": "apps/signage-manager/src",
"prefix": "placeos",
"tags": [],
"targets": {
"build": {
"executor": "@angular-devkit/build-angular:application",
"options": {
"baseHref": "./",
"outputPath": "dist/apps/signage-manager",
"index": "apps/signage-manager/src/index.html",
"polyfills": [],
"tsConfig": "apps/signage-manager/tsconfig.app.json",
"fileReplacements": [
{
"replace": "libs/common/src/lib/settings.ts",
"with": "apps/signage-manager/src/environments/settings.ts"
}
],
"assets": [
"apps/signage-manager/src/favicon.ico",
"apps/signage-manager/src/assets",
{
"glob": "**",
"input": "shared/assets",
"output": "/assets/"
},
{
"glob": "*.woff2",
"input": "node_modules/material-symbols",
"output": "/assets/"
},
{
"glob": "oauth-resp.html",
"input": "node_modules/@placeos/ts-client/dist/",
"output": "/"
},
{
"input": "node_modules/ts-md5/dist/",
"output": "/assets",
"glob": "*worker.js"
},
"apps/signage-manager/src/manifest.webmanifest"
],
"styles": [
"./node_modules/@angular/material/prebuilt-themes/indigo-pink.css",
"apps/signage-manager/src/styles.css",
"shared/styles/application.css"
],
"scripts": [],
"extractLicenses": false,
"sourceMap": true,
"optimization": false,
"namedChunks": true,
"browser": "apps/signage-manager/src/main.ts"
},
"configurations": {
"staging": {
"optimization": true,
"fileReplacements": [
{
"replace": "apps/signage-manager/src/environments/environment.ts",
"with": "apps/signage-manager/src/environments/environment.prod.ts"
},
{
"replace": "libs/common/src/lib/settings.ts",
"with": "apps/signage-manager/src/environments/settings.ts"
}
],
"serviceWorker": "apps/signage-manager/ngsw-config.json"
},
"production": {
"fileReplacements": [
{
"replace": "libs/mocks/src/index.ts",
"with": "libs/mocks/src/empty.ts"
},
{
"replace": "apps/signage-manager/src/environments/environment.ts",
"with": "apps/signage-manager/src/environments/environment.prod.ts"
},
{
"replace": "libs/common/src/lib/settings.ts",
"with": "apps/signage-manager/src/environments/settings.ts"
}
],
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"namedChunks": false,
"extractLicenses": true,
"budgets": [
{
"type": "initial",
"maximumWarning": "1.5mb",
"maximumError": "2mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "6kb",
"maximumError": "10kb"
}
],
"serviceWorker": "apps/signage-manager/ngsw-config.json"
}
}
},
"serve": {
"executor": "@angular-devkit/build-angular:dev-server",
"options": {
"proxyConfig": "config/proxy.conf.js",
"buildTarget": "signage-manager:build"
},
"configurations": {
"production": {
"buildTarget": "signage-manager:build:production"
}
},
"continuous": true
},
"lint": {
"executor": "@nx/eslint:lint",
"outputs": ["{options.outputFile}"]
},
"test": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/apps/signage-manager"],
"options": {
"jestConfig": "apps/signage-manager/jest.config.ts"
}
}
}
}
Binary file added apps/signage-manager/public/favicon.ico
Binary file not shown.
42 changes: 42 additions & 0 deletions apps/signage-manager/src/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Component, inject, OnInit } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { PlaceOS_Service, setMocks, UploadsService } from '@placeos/common';
import {
GlobalBannerComponent,
GlobalLoadingComponent,
} from '@placeos/components';
import { mocksInit } from '@placeos/mocks';

@Component({
selector: 'app-root',
template: `
<a class="skip-link" href="#main-content">Skip to main content</a>
<global-banner />
<main id="main-content" tabindex="-1" class="relative h-1/2 w-full flex-1">
<router-outlet></router-outlet>
</main>
<global-loading />
<!-- <debug-console *ngIf="debug"></debug-console> -->
`,
styles: [
`
:host {
display: flex;
flex-direction: column;
height: 100%;
width: 100%;
}
`,
],
imports: [GlobalBannerComponent, RouterOutlet, GlobalLoadingComponent],
})
export class AppComponent implements OnInit {
private _placeos = inject(PlaceOS_Service);
private _uploads = inject(UploadsService);

public async ngOnInit() {
setMocks(mocksInit);
await this._placeos.init();
this._uploads.init();
}
}
103 changes: 103 additions & 0 deletions apps/signage-manager/src/app/app.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import {
ApplicationConfig,
LOCALE_ID,
provideBrowserGlobalErrorListeners,
provideZonelessChangeDetection,
} from '@angular/core';
import { provideRouter, Routes, withHashLocation } from '@angular/router';
import { provideServiceWorker } from '@angular/service-worker';
import { LocaleService } from '@placeos/common';
import { UnauthorisedComponent } from '@placeos/components';
import { environment } from '../environments/environment';

const APP_ROUTES: Routes = [
{
path: 'unauthorised',
component: UnauthorisedComponent,
},
{
path: 'media',
loadComponent: () =>
import('./media/media.component').then(
(m) => m.MediaSectionComponent,
),
},
{
path: 'playlists/:id',
loadComponent: () =>
import('./playlists/playlists.component').then(
(m) => m.PlaylistsSectionComponent,
),
},
{
path: 'playlists',
loadComponent: () =>
import('./playlists/playlists.component').then(
(m) => m.PlaylistsSectionComponent,
),
},
{
path: 'schedules',
loadComponent: () =>
import('./schedules/schedules.component').then(
(m) => m.SchedulesSectionComponent,
),
},
{
path: 'displays/:id',
loadComponent: () =>
import('./displays/displays.component').then(
(m) => m.DisplaysSectionComponent,
),
},
{
path: 'displays',
loadComponent: () =>
import('./displays/displays.component').then(
(m) => m.DisplaysSectionComponent,
),
},
{
path: 'zones/:id',
loadComponent: () =>
import('./zones/zones.component').then(
(m) => m.ZonesSectionComponent,
),
},
{
path: 'zones',
loadComponent: () =>
import('./zones/zones.component').then(
(m) => m.ZonesSectionComponent,
),
},

{ path: '**', redirectTo: 'media' },
];

export const APP_CONFIG: ApplicationConfig = {
providers: [
provideBrowserGlobalErrorListeners(),
provideServiceWorker('ngsw-worker.js', {
enabled: environment.production,
}),
provideZonelessChangeDetection(),
provideRouter(APP_ROUTES, withHashLocation()),
// {
// provide: ErrorHandler,
// useValue: Sentry.createErrorHandler({
// showDialog: false,
// }),
// },
// {
// provide: Sentry.TraceService,
// deps: [Router],
// },

{
provide: LOCALE_ID,
deps: [LocaleService],
useFactory: (localeService: LocaleService) => localeService.locale,
},
],
};
Loading
Loading