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