diff --git a/ui/angular.json b/ui/angular.json index 02a3fde..d9afffa 100644 --- a/ui/angular.json +++ b/ui/angular.json @@ -26,19 +26,13 @@ "src/favicon.ico", "src/assets", "src/manifest.json", - "src/robots.txt", - { - "glob": "**/*", - "input": "src/app/modules/eqmac-components/assets", - "output": "assets/" - } - + "src/robots.txt" ], "styles": [ "src/styles.scss" ], "scripts": [], - "aot": false, + "aot": true, "vendorChunk": true, "extractLicenses": false, "buildOptimizer": false, @@ -58,12 +52,11 @@ "optimization": true, "outputHashing": "all", "sourceMap": false, - "extractCss": true, "namedChunks": false, "aot": true, "extractLicenses": true, "vendorChunk": false, - "buildOptimizer": true, + "buildOptimizer": false, "budgets": [ { "type": "initial", diff --git a/ui/package.json b/ui/package.json index 2a9cd0e..a03cc85 100644 --- a/ui/package.json +++ b/ui/package.json @@ -3,8 +3,8 @@ "version": "3.0.0", "scripts": { "lint": "npx eslint .", - "start": "ng serve --port 8080 --host 0.0.0.0 --disable-host-check", - "build": "rm -rf dist/ && ng build --configuration production && node -e \"console.log(require('./package.json').version)\" > dist/version.txt && cd dist/ && zip -r -D ui.zip * -x '*.DS_Store' && cp ui.zip ../../native/app/Embedded" + "start": "../node_modules/.bin/ng serve --port 8080 --host 0.0.0.0 --disable-host-check", + "build": "rm -rf dist/ && ../node_modules/.bin/ng build --configuration production && node -e \"console.log(require('./package.json').version)\" > dist/version.txt && cd dist/ && zip -r -D ui.zip * -x '*.DS_Store' && cp ui.zip ../../native/app/Embedded" }, "private": true, "dependencies": { diff --git a/ui/src/app/app.component.html b/ui/src/app/app.component.html index 6c82356..5a43f91 100644 --- a/ui/src/app/app.component.html +++ b/ui/src/app/app.component.html @@ -23,9 +23,9 @@ - + - + diff --git a/ui/src/app/app.component.scss b/ui/src/app/app.component.scss index 2faf513..75114fd 100644 --- a/ui/src/app/app.component.scss +++ b/ui/src/app/app.component.scss @@ -1,25 +1,35 @@ @import "./styles/colors"; ::ng-deep *:not(input) { - user-select: none; - -moz-user-select: none; - -khtml-user-select: none; - -webkit-user-select: none; - -o-user-select: none; - cursor: default; + -webkit-touch-callout: none !important; /* iOS Safari */ + -webkit-user-select: none !important; /* Safari */ + -khtml-user-select: none !important; /* Konqueror HTML */ + -moz-user-select: none !important; /* Old versions of Firefox */ + -ms-user-select: none !important; /* Internet Explorer/Edge */ + user-select: none !important; /* Non-prefixed version, currently + supported by Chrome, Edge, Opera and Firefox */ &::-webkit-scrollbar { display: none; } -} +} -// ::ng-deep * : +::ng-deep .pointer { + cursor: pointer !important; + + & * { + cursor: pointer !important; + } +} $noise: url('data:image/png;base64,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'); ::ng-deep html, body { - width: 100vw; - height: 100vh; + width: 100vw !important; + height: 100vh !important; + max-width: 100vw !important; + max-height: 100vh !important; + margin: 0; overflow: hidden; background-image: $noise; @@ -31,6 +41,9 @@ $noise: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyA text-rendering: optimizeLegibility; text-shadow: rgba(0, 0, 0, .01) 0 0 1px; -webkit-font-smoothing: subpixel-antialiased; + &::-webkit-scrollbar { + display: none; + } transform: perspective(1px) translateZ(0); backface-visibility: hidden; } @@ -84,6 +97,11 @@ $noise: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyA .dropdown-section { width: 100%; + background-color: $gradient-end; + box-shadow: 0px 8px 77px 2px rgba(0,0,0,0.56); + padding: 10px; + $border: 1px solid black; + border-bottom: $border; max-width: 400px; box-sizing: border-box; position: absolute; diff --git a/ui/src/app/app.component.ts b/ui/src/app/app.component.ts index 80446c0..850659b 100644 --- a/ui/src/app/app.component.ts +++ b/ui/src/app/app.component.ts @@ -12,6 +12,7 @@ import { TransitionService } from './services/transitions.service' import { AnalyticsService } from './services/analytics.service' import { ApplicationService } from './services/app.service' import { SettingsService, IconMode } from './sections/settings/settings.service' +import { ToastService } from './services/toast.service' import { OptionsDialogComponent } from './components/options-dialog/options-dialog.component' import { Option, Options } from './components/options/options.component' import { HeaderComponent } from './sections/header/header.component' @@ -59,7 +60,8 @@ export class AppComponent implements OnInit, AfterContentInit { public transitions: TransitionService, public analytics: AnalyticsService, public app: ApplicationService, - public settings: SettingsService + public settings: SettingsService, + public toast: ToastService ) { this.app.ref = this } @@ -88,10 +90,38 @@ export class AppComponent implements OnInit, AfterContentInit { return minHeight } + get minWidth () { + return 400 + } + + get maxHeight () { + const divider = 3 + + const { + volumeFeatureEnabled, balanceFeatureEnabled, + equalizersFeatureEnabled, + outputFeatureEnabled + } = this.ui.settings + let maxHeight = this.header.height + divider + + ((volumeFeatureEnabled || balanceFeatureEnabled) ? (this.volumeBoosterBalance.height + divider) : 0) + + (equalizersFeatureEnabled ? (this.equalizers.maxHeight + divider) : 0) + + (outputFeatureEnabled ? this.outputs.height : 0) + + const dropdownSection = document.getElementById('dropdown-section') + if (dropdownSection) { + const dropdownHeight = dropdownSection.offsetHeight + this.header.height + divider + if (dropdownHeight > maxHeight) { + maxHeight = dropdownHeight + } + } + + return maxHeight + } + async ngOnInit () { await this.sync() - this.startHeightSync() await this.fixUIMode() + this.startDimensionsSync() await this.setupPrivacy() } @@ -201,6 +231,7 @@ This data would help us improve and grow the product.` async ngAfterContentInit () { await this.utils.delay(this.animationDuration) this.loaded = true + await this.utils.delay(1000) this.ui.loaded() } @@ -210,23 +241,39 @@ This data would help us improve and grow the product.` ]) } - async startHeightSync () { - this.syncHeight() + async startDimensionsSync () { + this.previousMinHeight = this.minHeight + this.previousMaxHeight = this.maxHeight setInterval(() => { - this.syncHeight() + this.syncMinHeight() + this.syncMaxHeight() }, 1000) } - private previousMinHeight - async syncHeight () { + private previousMinHeight: number + async syncMinHeight () { const diff = this.minHeight - this.previousMinHeight this.previousMinHeight = this.minHeight - await this.ui.setMinHeight({ minHeight: this.minHeight }) + if (diff !== 0) { + this.ui.onMinHeightChanged.emit() + await this.ui.setMinHeight({ minHeight: this.minHeight }) + } + if (diff < 0) { this.ui.changeHeight({ diff }) } } + private previousMaxHeight + async syncMaxHeight () { + const diff = this.maxHeight - this.previousMaxHeight + this.previousMaxHeight = this.maxHeight + await this.ui.setMaxHeight({ maxHeight: this.maxHeight }) + if (diff > 0) { + // this.ui.changeHeight({ diff }) + } + } + async getTransitionSettings () { const settings = await this.transitions.getSettings() this.animationDuration = settings.duration @@ -239,6 +286,12 @@ This data would help us improve and grow the product.` } } + openDropdownSection (section: string) { + for (const key in this.showDropdownSections) { + this.showDropdownSections[key] = key === section + } + } + async fixUIMode () { const [ mode, iconMode ] = await Promise.all([ this.ui.getMode(), @@ -250,7 +303,7 @@ This data would help us improve and grow the product.` } } - closeDropdownSection (section: string, event?: any) { + closeDropdownSection (section: string, event?: MouseEvent) { // if (event && event.target && ['backdrop', 'mat-dialog'].some(e => event.target.className.includes(e))) return if (this.dialog.openDialogs.length > 0) return if (section in this.showDropdownSections) { diff --git a/ui/src/app/services/app.service.ts b/ui/src/app/services/app.service.ts index 179271d..d964981 100644 --- a/ui/src/app/services/app.service.ts +++ b/ui/src/app/services/app.service.ts @@ -12,6 +12,25 @@ export interface Info { isOpenSource: boolean driverVersion?: string } + +export const SystemSounds = [ + 'Basso', + 'Blow', + 'Bottle', + 'From', + 'Funk', + 'Glass', + 'Hero', + 'Morse', + 'Ping', + 'Pop', + 'Purr', + 'Sosumi', + 'Submarine', + 'Tink' +] as const +export type SystemSound = typeof SystemSounds[number] + @Injectable({ providedIn: 'root' }) @@ -85,4 +104,17 @@ export class ApplicationService extends DataService { this.enabled = enabled return this.request({ method: 'POST', endpoint: '/enabled', data: { enabled } }) } + + async getBundleIcon (bundleId: string): Promise { + const resp = await this.request({ method: 'GET', endpoint: '/bundle-icon', data: { bundleId } }) + return resp?.base64 + } + + playAlertSound () { + return this.request({ method: 'GET', endpoint: '/alert-sound' }) + } + + playSystemSound (name: SystemSound) { + return this.request({ method: 'POST', endpoint: '/system-sound', data: { name } }) + } } diff --git a/ui/src/app/services/constants.service.ts b/ui/src/app/services/constants.service.ts index 4996028..b94975a 100644 --- a/ui/src/app/services/constants.service.ts +++ b/ui/src/app/services/constants.service.ts @@ -5,8 +5,8 @@ import { Injectable } from '@angular/core' }) export class ConstantsService { readonly DOMAIN = 'eqmac.app' - readonly FAQ_URL = new URL(`https://${this.DOMAIN}/faq`) + readonly FAQ_URL = new URL(`https://${this.DOMAIN}#faq`) + readonly FEATURES_URL = new URL(`https://${this.DOMAIN}#features`) + readonly ACCOUNT_URL = new URL(`https://${this.DOMAIN}/account`) readonly BUG_REPORT_URL = new URL(`https://${this.DOMAIN}/report-bug`) - readonly LOCAL_API_URL = 'https://127.0.0.1' - readonly REMOTE_API_URL = `https://api.${this.DOMAIN}` } diff --git a/ui/src/app/services/context.service.ts b/ui/src/app/services/context.service.ts index 4f524a7..7a9a6a1 100644 --- a/ui/src/app/services/context.service.ts +++ b/ui/src/app/services/context.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@angular/core' -export type Context = 'EQ_TYPE_EXPERT' +export type Context = never @Injectable({ providedIn: 'root' diff --git a/ui/src/app/services/toast.service.ts b/ui/src/app/services/toast.service.ts index 1308da6..c4cad7a 100644 --- a/ui/src/app/services/toast.service.ts +++ b/ui/src/app/services/toast.service.ts @@ -29,7 +29,6 @@ export class ToastService { const toast = this.snackBar.open(message, action, { horizontalPosition: 'center', verticalPosition: 'top', - duration, panelClass: [ bgClass, textClass ] }) setTimeout(() => { diff --git a/ui/src/app/services/types.service.ts b/ui/src/app/services/types.service.ts new file mode 100644 index 0000000..e0eb1f5 --- /dev/null +++ b/ui/src/app/services/types.service.ts @@ -0,0 +1,6 @@ + +export class Types { + static unreachable (type: never) { + console.error(`Should not have reached type: ${type}`) + } +} diff --git a/ui/src/app/services/ui.service.ts b/ui/src/app/services/ui.service.ts index 45f63e0..065fef6 100644 --- a/ui/src/app/services/ui.service.ts +++ b/ui/src/app/services/ui.service.ts @@ -15,7 +15,6 @@ export interface UISettings { equalizersFeatureEnabled?: boolean outputFeatureEnabled?: boolean - showReverbs?: boolean showEqualizers?: boolean reverbsShownBefore?: boolean @@ -116,6 +115,12 @@ export class UIService extends DataService { return this.request({ method: 'POST', endpoint: '/width', data: { width } }) } + async changeWidth ({ diff }: { diff: number }) { + const currentWidth = await this.getWidth() + const width = currentWidth + diff + await this.setWidth(width) + } + async getHeight (): Promise { const { height } = await this.request({ method: 'GET', endpoint: '/height' }) return height @@ -203,6 +208,7 @@ export class UIService extends DataService { ]) } + onMinHeightChanged = new EventEmitter() async getMinHeight (): Promise { const { minHeight } = await this.request({ method: 'GET', endpoint: '/min-height' }) return minHeight @@ -212,6 +218,33 @@ export class UIService extends DataService { return this.request({ method: 'POST', endpoint: '/min-height', data: { minHeight } }) } + async getMinWidth (): Promise { + const { minWidth } = await this.request({ method: 'GET', endpoint: '/min-width' }) + return minWidth + } + + async setMinWidth ({ minWidth }: { minWidth: number }) { + return this.request({ method: 'POST', endpoint: '/min-width', data: { minWidth } }) + } + + async getMaxHeight (): Promise { + const { maxHeight } = await this.request({ method: 'GET', endpoint: '/max-height' }) + return maxHeight + } + + async setMaxHeight ({ maxHeight }: { maxHeight?: number }) { + return this.request({ method: 'POST', endpoint: '/max-height', data: { maxHeight } }) + } + + async getMaxWidth (): Promise { + const { maxWidth } = await this.request({ method: 'GET', endpoint: '/max-width' }) + return maxWidth + } + + async setMaxWidth ({ maxWidth }: { maxWidth?: number }) { + return this.request({ method: 'POST', endpoint: '/max-width', data: { maxWidth } }) + } + onShownChanged (cb: UIShownChangedEventCallback) { this.on('/shown', cb) } diff --git a/ui/src/app/services/utilities.service.ts b/ui/src/app/services/utilities.service.ts index 54d9616..db2cc86 100644 --- a/ui/src/app/services/utilities.service.ts +++ b/ui/src/app/services/utilities.service.ts @@ -12,6 +12,15 @@ export class UtilitiesService { return (value - inMin) * (outMax - outMin) / (inMax - inMin) + outMin } + clampValue ({ value, min, max }: { value: number, min: number, max: number }) { + if (value < min) { + value = min + } else if (value > max) { + value = max + } + return value + } + getTimestampFromDurationAndProgress (duration, progress = 1) { const currentSecond = Math.floor(duration * progress) let minutes = Math.floor(currentSecond / 60).toString() @@ -55,6 +64,16 @@ export class UtilitiesService { }) } + getCoordinatesInsideElementFromEvent (event: MouseEvent, element?: HTMLElement) { + const el = element || event.target as HTMLElement + const rect = el.getBoundingClientRect() + const scale = rect.width / el.clientWidth + return { + x: (event.clientX - rect.left) / scale, + y: (event.clientY - rect.top) / scale + } + } + static async injectScript ({ src, id }: { src: string, id?: string }) { return new Promise((resolve, reject) => { const script = document.createElement('script') @@ -74,4 +93,17 @@ export class UtilitiesService { head.appendChild(script) }) } + + quickHash (str: string) { return UtilitiesService.quickHash(str) } + static quickHash (str: string) { + let hash = 0 + let chr: number + if (str.length === 0) return hash + for (let i = 0; i < str.length; i++) { + chr = str.charCodeAt(i) + hash = ((hash << 5) - hash) + chr + hash |= 0 // Convert to 32bit integer + } + return hash + } } diff --git a/ui/src/polyfills.ts b/ui/src/polyfills.ts index 86b3024..d7e311b 100644 --- a/ui/src/polyfills.ts +++ b/ui/src/polyfills.ts @@ -61,14 +61,13 @@ import 'web-animations-js' // Run `npm install --save web-animations-js`. // (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame // (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick -// (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames /* * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js * with the following flag, it will bypass `zone.js` patch for IE/Edge */ // (window as any).__Zone_enable_cross_context_check = true; - +import './zone-flags' /*************************************************************************************************** * Zone JS is required by default for Angular itself. */ diff --git a/ui/src/styles.scss b/ui/src/styles.scss index 91ee7ee..3f4c0ce 100644 --- a/ui/src/styles.scss +++ b/ui/src/styles.scss @@ -2,14 +2,40 @@ @import '~@angular/cdk/overlay-prebuilt.css'; @import "~@angular/material/prebuilt-themes/pink-bluegrey.css"; -.mat-dialog-container { - padding: 12px !important; -} - .w-100 { width: 100%; } .h-100 { height: 100%; +} + +.mat-dialog-container { + padding: 12px !important; +} + +.mat-menu-panel { + min-height: 0px !important; +} + +.underline { + text-decoration: underline; +} + +.clickable { + cursor: pointer !important; + + * { + cursor: pointer !important; + } +} + +.not-available-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.5); + backdrop-filter: blur(10px); } \ No newline at end of file diff --git a/ui/src/tsconfig.app.json b/ui/src/tsconfig.app.json index 1853b0e..1e452e9 100644 --- a/ui/src/tsconfig.app.json +++ b/ui/src/tsconfig.app.json @@ -7,7 +7,7 @@ }, "angularCompilerOptions": { "enableIvy": true, - "strictTemplates": true + "strictTemplates": true, }, "exclude": [], "paths": { "@angular/*": [ "./node_modules/@angular/*" ] } diff --git a/ui/src/zone-flags.ts b/ui/src/zone-flags.ts new file mode 100644 index 0000000..86f499b --- /dev/null +++ b/ui/src/zone-flags.ts @@ -0,0 +1,2 @@ +;(window as any).__Zone_disable_requestAnimationFrame = true +;(window as any).__zone_symbol__BLACK_LISTED_EVENTS = [ 'mousewheel', 'wheel', 'mousemove', 'scroll' ] // disable patch specified eventNames