Night theme (#2137)

* feat: add themes

* fix: remove obvious issues with light theme

* chore: improve light theme a bit

* comment out theme swticher

* chore: make login dark

* add theme and widgets to seeds

* add theme and widgets to migration

---------

Co-authored-by: Matt Hill <matthewonthemoon@gmail.com>
This commit is contained in:
Alex Inkin
2023-03-04 02:31:19 +08:00
committed by Aiden McClelland
parent e867f31c31
commit 3c0a82293c
53 changed files with 598 additions and 237 deletions

View File

@@ -1,6 +1,6 @@
use async_trait::async_trait; use async_trait::async_trait;
use emver::VersionRange; use emver::VersionRange;
use serde_json::json; use serde_json::{json, Value};
use super::v0_3_0::V0_3_0_COMPAT; use super::v0_3_0::V0_3_0_COMPAT;
use super::*; use super::*;
@@ -61,6 +61,8 @@ impl VersionT for Version {
.put(db, &parsed_url) .put(db, &parsed_url)
.await?; .await?;
} }
ui["theme"] = json!("Dark".to_string());
ui["widgets"] = json!([]);
Ok(()) Ok(())
} }
async fn down<Db: DbHandle>(&self, db: &mut Db) -> Result<(), Error> { async fn down<Db: DbHandle>(&self, db: &mut Db) -> Result<(), Error> {
@@ -87,6 +89,11 @@ impl VersionT for Version {
.await?; .await?;
} }
if let Value::Object(ref mut obj) = *ui {
obj.remove("theme");
obj.remove("widgets");
}
ui["marketplace"]["known-hosts"][COMMUNITY_URL].take(); ui["marketplace"]["known-hosts"][COMMUNITY_URL].take();
ui.save(db).await?; ui.save(db).await?;
Ok(()) Ok(())

View File

@@ -46,7 +46,6 @@
"node_modules/@taiga-ui/core/styles/taiga-ui-theme.less", "node_modules/@taiga-ui/core/styles/taiga-ui-theme.less",
"node_modules/@taiga-ui/core/styles/taiga-ui-fonts.less", "node_modules/@taiga-ui/core/styles/taiga-ui-fonts.less",
"node_modules/@taiga-ui/styles/taiga-ui-global.less", "node_modules/@taiga-ui/styles/taiga-ui-global.less",
"projects/shared/styles/variables.scss",
"projects/shared/styles/global.scss", "projects/shared/styles/global.scss",
"projects/shared/styles/shared.scss", "projects/shared/styles/shared.scss",
"projects/ui/src/styles.scss" "projects/ui/src/styles.scss"

View File

@@ -23,12 +23,12 @@
"@ng-web-apis/resize-observer": "^2.0.0", "@ng-web-apis/resize-observer": "^2.0.0",
"@start9labs/argon2": "^0.1.0", "@start9labs/argon2": "^0.1.0",
"@start9labs/emver": "^0.1.5", "@start9labs/emver": "^0.1.5",
"@taiga-ui/addon-charts": "^3.14.0", "@taiga-ui/addon-charts": "^3.16.0",
"@taiga-ui/cdk": "^3.14.0", "@taiga-ui/cdk": "^3.16.0",
"@taiga-ui/core": "^3.14.0", "@taiga-ui/core": "^3.16.0",
"@taiga-ui/icons": "^3.14.0", "@taiga-ui/icons": "^3.16.0",
"@taiga-ui/kit": "^3.14.0", "@taiga-ui/kit": "^3.16.0",
"@taiga-ui/styles": "^3.14.0", "@taiga-ui/styles": "^3.16.0",
"angular-svg-round-progressbar": "^9.0.0", "angular-svg-round-progressbar": "^9.0.0",
"ansi-to-html": "^0.7.2", "ansi-to-html": "^0.7.2",
"base64-js": "^1.5.1", "base64-js": "^1.5.1",
@@ -3277,9 +3277,9 @@
} }
}, },
"node_modules/@ng-web-apis/common": { "node_modules/@ng-web-apis/common": {
"version": "2.0.1", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/@ng-web-apis/common/-/common-2.0.1.tgz", "resolved": "https://registry.npmjs.org/@ng-web-apis/common/-/common-2.1.0.tgz",
"integrity": "sha512-DqnH+zZFFKeINpbFIzCrBTYksP+7FqrHxWo2+jIXfMLjSngwZ6WYz3F4N9s+tFc8mKe8I1/P7pZtxD7fqSPtlA==", "integrity": "sha512-6DLtrsk59z9YwfR8Pm1DiExXpxvMk/RVry/mfsAKkyRmgCGICgDdyQ+eWMVhrOIyUAtt9V+DRvHUeC6iYaHKNQ==",
"dependencies": { "dependencies": {
"tslib": "^2.2.0" "tslib": "^2.2.0"
}, },
@@ -3575,30 +3575,30 @@
} }
}, },
"node_modules/@taiga-ui/addon-charts": { "node_modules/@taiga-ui/addon-charts": {
"version": "3.14.0", "version": "3.16.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-charts/-/addon-charts-3.14.0.tgz", "resolved": "https://registry.npmjs.org/@taiga-ui/addon-charts/-/addon-charts-3.16.0.tgz",
"integrity": "sha512-KnQ4ioYWjV+xyEj7eTnGScAWLBMFtkEzdnSZdg47HFFKaqg7gW08A0Wx8qNTKeHJK7wwjeVSbcPjZBNfKv4zKQ==", "integrity": "sha512-b4jvFRKle+jBHCq71BH09ut9cs0mztR+ht99c6OZf6IeyHZuNUsVjOqONxpL3FMKbvIWvW+YXQdkcHx3+vDcFA==",
"dependencies": { "dependencies": {
"tslib": "^2.0.0" "tslib": ">=2.0.0"
}, },
"peerDependencies": { "peerDependencies": {
"@angular/common": ">=12.0.0", "@angular/common": ">=12.0.0",
"@angular/core": ">=12.0.0", "@angular/core": ">=12.0.0",
"@ng-web-apis/common": ">=2.0.0", "@ng-web-apis/common": ">=2.0.0",
"@taiga-ui/cdk": ">=3.14.0", "@taiga-ui/cdk": ">=3.16.0",
"@taiga-ui/core": ">=3.14.0", "@taiga-ui/core": ">=3.16.0",
"@tinkoff/ng-polymorpheus": ">=4.0.0" "@tinkoff/ng-polymorpheus": ">=4.0.0"
} }
}, },
"node_modules/@taiga-ui/cdk": { "node_modules/@taiga-ui/cdk": {
"version": "3.14.0", "version": "3.16.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/cdk/-/cdk-3.14.0.tgz", "resolved": "https://registry.npmjs.org/@taiga-ui/cdk/-/cdk-3.16.0.tgz",
"integrity": "sha512-Yhk0+IQOWfXmx8UWz61dBexA2hxDOEC0YHmpoQyIVQ6K5GXXpSywkf24WbxWGqQaoKYhECRmL8UxruAYZ8sI9A==", "integrity": "sha512-r6buMYMYsQfokYLwUvwZibR0JJODf/jrWDYTITxuHb4ZTVLz0YYNbERiD6Y2cgtBRUhj9W3lr712eWdYJxZpGA==",
"dependencies": { "dependencies": {
"@ng-web-apis/common": "2.0.1", "@ng-web-apis/common": "2.1.0",
"@ng-web-apis/mutation-observer": "2.0.0", "@ng-web-apis/mutation-observer": "2.0.0",
"@ng-web-apis/resize-observer": "2.0.0", "@ng-web-apis/resize-observer": "2.0.0",
"@tinkoff/ng-event-plugins": "3.0.0", "@tinkoff/ng-event-plugins": "3.1.0",
"@tinkoff/ng-polymorpheus": "4.0.10", "@tinkoff/ng-polymorpheus": "4.0.10",
"tslib": "^2.0.0" "tslib": "^2.0.0"
}, },
@@ -3615,12 +3615,12 @@
} }
}, },
"node_modules/@taiga-ui/core": { "node_modules/@taiga-ui/core": {
"version": "3.14.0", "version": "3.16.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/core/-/core-3.14.0.tgz", "resolved": "https://registry.npmjs.org/@taiga-ui/core/-/core-3.16.0.tgz",
"integrity": "sha512-qiURNOAPUmsFPkDm1v8a+4SX+aGJVry9f3+XpCgfNM6boPnZ+ggdFG2KyVow6sCAr+kDuw8aUACYtO/ngxdeuw==", "integrity": "sha512-HCJhDOIE1hO4JZxL2ZK6+bFkZB/dREIbqBr684GhcBo4s6VA9mW1tqqr4gqXXXu0FvM9FGFzZpF9Gm+teeprXw==",
"dependencies": { "dependencies": {
"@taiga-ui/i18n": "^3.14.0", "@taiga-ui/i18n": "^3.16.0",
"tslib": "^2.0.0" "tslib": ">=2.0.0"
}, },
"peerDependencies": { "peerDependencies": {
"@angular/animations": ">=12.0.0", "@angular/animations": ">=12.0.0",
@@ -3631,19 +3631,19 @@
"@angular/router": ">=12.0.0", "@angular/router": ">=12.0.0",
"@ng-web-apis/common": ">=2.0.0", "@ng-web-apis/common": ">=2.0.0",
"@ng-web-apis/mutation-observer": ">=2.0.0", "@ng-web-apis/mutation-observer": ">=2.0.0",
"@taiga-ui/cdk": ">=3.14.0", "@taiga-ui/cdk": ">=3.16.0",
"@taiga-ui/i18n": ">=3.14.0", "@taiga-ui/i18n": ">=3.16.0",
"@tinkoff/ng-event-plugins": ">=3.0.0", "@tinkoff/ng-event-plugins": ">=3.1.0",
"@tinkoff/ng-polymorpheus": ">=4.0.0", "@tinkoff/ng-polymorpheus": ">=4.0.0",
"rxjs": ">=6.0.0" "rxjs": ">=6.0.0"
} }
}, },
"node_modules/@taiga-ui/i18n": { "node_modules/@taiga-ui/i18n": {
"version": "3.14.0", "version": "3.16.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/i18n/-/i18n-3.14.0.tgz", "resolved": "https://registry.npmjs.org/@taiga-ui/i18n/-/i18n-3.16.0.tgz",
"integrity": "sha512-GK4DP+UfBy4AGNsTebfN8TvUKi3QAi2I8+5nMULAjE1qzQd5WEkeXhV+dzLcSkxPbbzPGzSB+23Nnfn/A5IDlQ==", "integrity": "sha512-ieFXTSUY4phXL4YuW5+26Ceqxx4JVMj1En3rXUr3mysLDPRYzyKfeW+gqql4n3hmkqGDsdnmtHvkqcEVePzWAw==",
"dependencies": { "dependencies": {
"tslib": "^2.0.0" "tslib": ">=2.0.0"
}, },
"peerDependencies": { "peerDependencies": {
"@angular/core": ">=12.0.0", "@angular/core": ">=12.0.0",
@@ -3651,21 +3651,21 @@
} }
}, },
"node_modules/@taiga-ui/icons": { "node_modules/@taiga-ui/icons": {
"version": "3.14.0", "version": "3.16.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/icons/-/icons-3.14.0.tgz", "resolved": "https://registry.npmjs.org/@taiga-ui/icons/-/icons-3.16.0.tgz",
"integrity": "sha512-+I2DYwFsRaYxbX92FCYWcRijvMKZWOZDlmzBkZc+CihF5wm46KTQichZ3iMZ3xACY9czDzcy123iUJqKT+fVRw==", "integrity": "sha512-7EeFTUIpwEJbwFmyRmHvgN2rECygVg/VzqCPgjxM4ThS9VNbtTa0rJzORuA4t4jN+vBBA80wMjcxcI5Kq+jvow==",
"dependencies": { "dependencies": {
"tslib": "^2.0.0" "tslib": "^2.2.0"
} }
}, },
"node_modules/@taiga-ui/kit": { "node_modules/@taiga-ui/kit": {
"version": "3.14.0", "version": "3.16.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/kit/-/kit-3.14.0.tgz", "resolved": "https://registry.npmjs.org/@taiga-ui/kit/-/kit-3.16.0.tgz",
"integrity": "sha512-YjSjpuqQYsMsMOLCn7edTGL+VEL3D0wpD7hY1GK4r+TPGTLQi1jby0PxsYbrjGZQ2EavwFuRI59/gqsDis6HIQ==", "integrity": "sha512-HfFGutcJmpzXv+99GxV8ciSOgTyOATls4lYvVoWOwnm+4fJAMJ2vh7OA5+yWBrn8I0XpVXtPGIM7e4H2tXk2HQ==",
"dependencies": { "dependencies": {
"@ng-web-apis/intersection-observer": "^3.0.0", "@ng-web-apis/intersection-observer": "^3.0.0",
"text-mask-core": "^5.0.0", "text-mask-core": "^5.1.2",
"tslib": "^2.0.0" "tslib": ">=2.0.0"
}, },
"peerDependencies": { "peerDependencies": {
"@angular/common": ">=12.0.0", "@angular/common": ">=12.0.0",
@@ -3674,22 +3674,22 @@
"@angular/router": ">=12.0.0", "@angular/router": ">=12.0.0",
"@ng-web-apis/common": ">=2.0.0", "@ng-web-apis/common": ">=2.0.0",
"@ng-web-apis/mutation-observer": ">=2.0.0", "@ng-web-apis/mutation-observer": ">=2.0.0",
"@taiga-ui/cdk": ">=3.14.0", "@taiga-ui/cdk": ">=3.16.0",
"@taiga-ui/core": ">=3.14.0", "@taiga-ui/core": ">=3.16.0",
"@taiga-ui/i18n": ">=3.14.0", "@taiga-ui/i18n": ">=3.16.0",
"@tinkoff/ng-polymorpheus": ">=4.0.0", "@tinkoff/ng-polymorpheus": ">=4.0.0",
"rxjs": ">=6.0.0" "rxjs": ">=6.0.0"
} }
}, },
"node_modules/@taiga-ui/styles": { "node_modules/@taiga-ui/styles": {
"version": "3.14.0", "version": "3.16.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/styles/-/styles-3.14.0.tgz", "resolved": "https://registry.npmjs.org/@taiga-ui/styles/-/styles-3.16.0.tgz",
"integrity": "sha512-xo/aPqexXPUNesmnPMNhwpz4gj5PVHp+aKDcvv4+MCSBaiPTd6nE4wTXMaOW/EdkXrPHqWnxwnTec8SUwX5D9g==" "integrity": "sha512-Wp2tq5njfdGCgr7t54+5XfI7Bj69+M0BdGYKoXEr74pTzFq2yG2Rqo3vSaujD8L8NnmRfHR7ue9v/BSNgbIcwg=="
}, },
"node_modules/@tinkoff/ng-event-plugins": { "node_modules/@tinkoff/ng-event-plugins": {
"version": "3.0.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/@tinkoff/ng-event-plugins/-/ng-event-plugins-3.0.0.tgz", "resolved": "https://registry.npmjs.org/@tinkoff/ng-event-plugins/-/ng-event-plugins-3.1.0.tgz",
"integrity": "sha512-3+5R86ozam9uevQ5N7+B/lPMWYCfQGxUOxuSp6qccpPTWqCjTBGjlhRVdt11r6S+3Gx2r9y4rgZ1Q1/P58MOFA==", "integrity": "sha512-HqLBes/3MV469L1S08uBqmPUIwihx43py+8Lee1Me9jMFM1ZMuAC3NcS/njUFI1OzXU2kIPyUDEw2jmVbg8mWQ==",
"dependencies": { "dependencies": {
"tslib": "^2.2.0" "tslib": "^2.2.0"
}, },
@@ -17458,9 +17458,9 @@
} }
}, },
"@ng-web-apis/common": { "@ng-web-apis/common": {
"version": "2.0.1", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/@ng-web-apis/common/-/common-2.0.1.tgz", "resolved": "https://registry.npmjs.org/@ng-web-apis/common/-/common-2.1.0.tgz",
"integrity": "sha512-DqnH+zZFFKeINpbFIzCrBTYksP+7FqrHxWo2+jIXfMLjSngwZ6WYz3F4N9s+tFc8mKe8I1/P7pZtxD7fqSPtlA==", "integrity": "sha512-6DLtrsk59z9YwfR8Pm1DiExXpxvMk/RVry/mfsAKkyRmgCGICgDdyQ+eWMVhrOIyUAtt9V+DRvHUeC6iYaHKNQ==",
"requires": { "requires": {
"tslib": "^2.2.0" "tslib": "^2.2.0"
} }
@@ -17672,22 +17672,22 @@
"integrity": "sha512-NLEY8Jq59smyiivBAxHKipsp9YkkW/K/Vm90zAyXQqukb12i2SFucWHJ1Ik7ropVlhmMVvigyxXgRfQ9quIqtg==" "integrity": "sha512-NLEY8Jq59smyiivBAxHKipsp9YkkW/K/Vm90zAyXQqukb12i2SFucWHJ1Ik7ropVlhmMVvigyxXgRfQ9quIqtg=="
}, },
"@taiga-ui/addon-charts": { "@taiga-ui/addon-charts": {
"version": "3.14.0", "version": "3.16.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-charts/-/addon-charts-3.14.0.tgz", "resolved": "https://registry.npmjs.org/@taiga-ui/addon-charts/-/addon-charts-3.16.0.tgz",
"integrity": "sha512-KnQ4ioYWjV+xyEj7eTnGScAWLBMFtkEzdnSZdg47HFFKaqg7gW08A0Wx8qNTKeHJK7wwjeVSbcPjZBNfKv4zKQ==", "integrity": "sha512-b4jvFRKle+jBHCq71BH09ut9cs0mztR+ht99c6OZf6IeyHZuNUsVjOqONxpL3FMKbvIWvW+YXQdkcHx3+vDcFA==",
"requires": { "requires": {
"tslib": "^2.0.0" "tslib": ">=2.0.0"
} }
}, },
"@taiga-ui/cdk": { "@taiga-ui/cdk": {
"version": "3.14.0", "version": "3.16.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/cdk/-/cdk-3.14.0.tgz", "resolved": "https://registry.npmjs.org/@taiga-ui/cdk/-/cdk-3.16.0.tgz",
"integrity": "sha512-Yhk0+IQOWfXmx8UWz61dBexA2hxDOEC0YHmpoQyIVQ6K5GXXpSywkf24WbxWGqQaoKYhECRmL8UxruAYZ8sI9A==", "integrity": "sha512-r6buMYMYsQfokYLwUvwZibR0JJODf/jrWDYTITxuHb4ZTVLz0YYNbERiD6Y2cgtBRUhj9W3lr712eWdYJxZpGA==",
"requires": { "requires": {
"@ng-web-apis/common": "2.0.1", "@ng-web-apis/common": "2.1.0",
"@ng-web-apis/mutation-observer": "2.0.0", "@ng-web-apis/mutation-observer": "2.0.0",
"@ng-web-apis/resize-observer": "2.0.0", "@ng-web-apis/resize-observer": "2.0.0",
"@tinkoff/ng-event-plugins": "3.0.0", "@tinkoff/ng-event-plugins": "3.1.0",
"@tinkoff/ng-polymorpheus": "4.0.10", "@tinkoff/ng-polymorpheus": "4.0.10",
"ng-morph": "^2.1.0", "ng-morph": "^2.1.0",
"parse5": "^6.0.1", "parse5": "^6.0.1",
@@ -17695,49 +17695,49 @@
} }
}, },
"@taiga-ui/core": { "@taiga-ui/core": {
"version": "3.14.0", "version": "3.16.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/core/-/core-3.14.0.tgz", "resolved": "https://registry.npmjs.org/@taiga-ui/core/-/core-3.16.0.tgz",
"integrity": "sha512-qiURNOAPUmsFPkDm1v8a+4SX+aGJVry9f3+XpCgfNM6boPnZ+ggdFG2KyVow6sCAr+kDuw8aUACYtO/ngxdeuw==", "integrity": "sha512-HCJhDOIE1hO4JZxL2ZK6+bFkZB/dREIbqBr684GhcBo4s6VA9mW1tqqr4gqXXXu0FvM9FGFzZpF9Gm+teeprXw==",
"requires": { "requires": {
"@taiga-ui/i18n": "^3.14.0", "@taiga-ui/i18n": "^3.16.0",
"tslib": "^2.0.0" "tslib": ">=2.0.0"
} }
}, },
"@taiga-ui/i18n": { "@taiga-ui/i18n": {
"version": "3.14.0", "version": "3.16.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/i18n/-/i18n-3.14.0.tgz", "resolved": "https://registry.npmjs.org/@taiga-ui/i18n/-/i18n-3.16.0.tgz",
"integrity": "sha512-GK4DP+UfBy4AGNsTebfN8TvUKi3QAi2I8+5nMULAjE1qzQd5WEkeXhV+dzLcSkxPbbzPGzSB+23Nnfn/A5IDlQ==", "integrity": "sha512-ieFXTSUY4phXL4YuW5+26Ceqxx4JVMj1En3rXUr3mysLDPRYzyKfeW+gqql4n3hmkqGDsdnmtHvkqcEVePzWAw==",
"requires": { "requires": {
"tslib": "^2.0.0" "tslib": ">=2.0.0"
} }
}, },
"@taiga-ui/icons": { "@taiga-ui/icons": {
"version": "3.14.0", "version": "3.16.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/icons/-/icons-3.14.0.tgz", "resolved": "https://registry.npmjs.org/@taiga-ui/icons/-/icons-3.16.0.tgz",
"integrity": "sha512-+I2DYwFsRaYxbX92FCYWcRijvMKZWOZDlmzBkZc+CihF5wm46KTQichZ3iMZ3xACY9czDzcy123iUJqKT+fVRw==", "integrity": "sha512-7EeFTUIpwEJbwFmyRmHvgN2rECygVg/VzqCPgjxM4ThS9VNbtTa0rJzORuA4t4jN+vBBA80wMjcxcI5Kq+jvow==",
"requires": { "requires": {
"tslib": "^2.0.0" "tslib": "^2.2.0"
} }
}, },
"@taiga-ui/kit": { "@taiga-ui/kit": {
"version": "3.14.0", "version": "3.16.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/kit/-/kit-3.14.0.tgz", "resolved": "https://registry.npmjs.org/@taiga-ui/kit/-/kit-3.16.0.tgz",
"integrity": "sha512-YjSjpuqQYsMsMOLCn7edTGL+VEL3D0wpD7hY1GK4r+TPGTLQi1jby0PxsYbrjGZQ2EavwFuRI59/gqsDis6HIQ==", "integrity": "sha512-HfFGutcJmpzXv+99GxV8ciSOgTyOATls4lYvVoWOwnm+4fJAMJ2vh7OA5+yWBrn8I0XpVXtPGIM7e4H2tXk2HQ==",
"requires": { "requires": {
"@ng-web-apis/intersection-observer": "^3.0.0", "@ng-web-apis/intersection-observer": "^3.0.0",
"text-mask-core": "^5.0.0", "text-mask-core": "^5.1.2",
"tslib": "^2.0.0" "tslib": ">=2.0.0"
} }
}, },
"@taiga-ui/styles": { "@taiga-ui/styles": {
"version": "3.14.0", "version": "3.16.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/styles/-/styles-3.14.0.tgz", "resolved": "https://registry.npmjs.org/@taiga-ui/styles/-/styles-3.16.0.tgz",
"integrity": "sha512-xo/aPqexXPUNesmnPMNhwpz4gj5PVHp+aKDcvv4+MCSBaiPTd6nE4wTXMaOW/EdkXrPHqWnxwnTec8SUwX5D9g==" "integrity": "sha512-Wp2tq5njfdGCgr7t54+5XfI7Bj69+M0BdGYKoXEr74pTzFq2yG2Rqo3vSaujD8L8NnmRfHR7ue9v/BSNgbIcwg=="
}, },
"@tinkoff/ng-event-plugins": { "@tinkoff/ng-event-plugins": {
"version": "3.0.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/@tinkoff/ng-event-plugins/-/ng-event-plugins-3.0.0.tgz", "resolved": "https://registry.npmjs.org/@tinkoff/ng-event-plugins/-/ng-event-plugins-3.1.0.tgz",
"integrity": "sha512-3+5R86ozam9uevQ5N7+B/lPMWYCfQGxUOxuSp6qccpPTWqCjTBGjlhRVdt11r6S+3Gx2r9y4rgZ1Q1/P58MOFA==", "integrity": "sha512-HqLBes/3MV469L1S08uBqmPUIwihx43py+8Lee1Me9jMFM1ZMuAC3NcS/njUFI1OzXU2kIPyUDEw2jmVbg8mWQ==",
"requires": { "requires": {
"tslib": "^2.2.0" "tslib": "^2.2.0"
} }

View File

@@ -47,12 +47,12 @@
"@ng-web-apis/resize-observer": "^2.0.0", "@ng-web-apis/resize-observer": "^2.0.0",
"@start9labs/argon2": "^0.1.0", "@start9labs/argon2": "^0.1.0",
"@start9labs/emver": "^0.1.5", "@start9labs/emver": "^0.1.5",
"@taiga-ui/addon-charts": "^3.14.0", "@taiga-ui/addon-charts": "^3.16.0",
"@taiga-ui/cdk": "^3.14.0", "@taiga-ui/cdk": "^3.16.0",
"@taiga-ui/core": "^3.14.0", "@taiga-ui/core": "^3.16.0",
"@taiga-ui/icons": "^3.14.0", "@taiga-ui/icons": "^3.16.0",
"@taiga-ui/kit": "^3.14.0", "@taiga-ui/kit": "^3.16.0",
"@taiga-ui/styles": "^3.14.0", "@taiga-ui/styles": "^3.16.0",
"angular-svg-round-progressbar": "^9.0.0", "angular-svg-round-progressbar": "^9.0.0",
"ansi-to-html": "^0.7.2", "ansi-to-html": "^0.7.2",
"base64-js": "^1.5.1", "base64-js": "^1.5.1",

View File

@@ -14,5 +14,7 @@
"high-score": 0 "high-score": 0
} }
}, },
"ack-instructions": {} "ack-instructions": {},
"theme": "Dark",
"widgets": []
} }

View File

@@ -1,4 +1,4 @@
<ion-item [routerLink]="['/marketplace', pkg.manifest.id]"> <ion-item class="service-card" [routerLink]="['/marketplace', pkg.manifest.id]">
<ion-thumbnail slot="start"> <ion-thumbnail slot="start">
<img alt="" [src]="'data:image/png;base64,' + pkg.icon | trustUrl" /> <img alt="" [src]="'data:image/png;base64,' + pkg.icon | trustUrl" />
</ion-thumbnail> </ion-thumbnail>

View File

@@ -3,7 +3,7 @@
<ion-col responsiveCol class="column" sizeSm="8" sizeLg="6"> <ion-col responsiveCol class="column" sizeSm="8" sizeLg="6">
<ion-toolbar color="transparent" class="ion-text-left"> <ion-toolbar color="transparent" class="ion-text-left">
<ion-searchbar <ion-searchbar
color="dark" [color]="(theme$ | async) === 'Light' ? 'light' : 'dark'"
debounce="250" debounce="250"
[ngModel]="query" [ngModel]="query"
(ngModelChange)="onModelChange($event)" (ngModelChange)="onModelChange($event)"

View File

@@ -2,9 +2,11 @@ import {
ChangeDetectionStrategy, ChangeDetectionStrategy,
Component, Component,
EventEmitter, EventEmitter,
inject,
Input, Input,
Output, Output,
} from '@angular/core' } from '@angular/core'
import { THEME } from '@start9labs/shared'
@Component({ @Component({
selector: 'marketplace-search', selector: 'marketplace-search',
@@ -19,6 +21,8 @@ export class SearchComponent {
@Output() @Output()
readonly queryChange = new EventEmitter<string>() readonly queryChange = new EventEmitter<string>()
readonly theme$ = inject(THEME)
onModelChange(query: string) { onModelChange(query: string) {
this.query = query this.query = query
this.queryChange.emit(query) this.queryChange.emit(query)

View File

@@ -1,3 +1,4 @@
import { CommonModule } from '@angular/common'
import { NgModule } from '@angular/core' import { NgModule } from '@angular/core'
import { FormsModule } from '@angular/forms' import { FormsModule } from '@angular/forms'
import { IonicModule } from '@ionic/angular' import { IonicModule } from '@ionic/angular'
@@ -6,7 +7,7 @@ import { ResponsiveColModule } from '@start9labs/shared'
import { SearchComponent } from './search.component' import { SearchComponent } from './search.component'
@NgModule({ @NgModule({
imports: [IonicModule, FormsModule, ResponsiveColModule], imports: [IonicModule, FormsModule, CommonModule, ResponsiveColModule],
declarations: [SearchComponent], declarations: [SearchComponent],
exports: [SearchComponent], exports: [SearchComponent],
}) })

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View File

@@ -46,6 +46,11 @@ export * from './services/emver.service'
export * from './services/error-toast.service' export * from './services/error-toast.service'
export * from './services/http.service' export * from './services/http.service'
export * from './themes/dark-theme/dark-theme.component'
export * from './themes/dark-theme/dark-theme.module'
export * from './themes/light-theme/light-theme.component'
export * from './themes/light-theme/light-theme.module'
export * from './types/api' export * from './types/api'
export * from './types/http.types' export * from './types/http.types'
export * from './types/rpc.types' export * from './types/rpc.types'
@@ -53,6 +58,7 @@ export * from './types/url'
export * from './types/workspace-config' export * from './types/workspace-config'
export * from './tokens/relative-url' export * from './tokens/relative-url'
export * from './tokens/theme'
export * from './util/base-64' export * from './util/base-64'
export * from './util/copy-to-clipboard' export * from './util/copy-to-clipboard'

View File

@@ -0,0 +1 @@
@import '../../../styles/variables';

View File

@@ -0,0 +1,15 @@
import {
ChangeDetectionStrategy,
Component,
ViewEncapsulation,
} from '@angular/core'
import { AbstractTuiThemeSwitcher } from '@taiga-ui/cdk'
@Component({
selector: 'dark-theme',
template: '',
styleUrls: ['./dark-theme.component.scss'],
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class DarkThemeComponent extends AbstractTuiThemeSwitcher {}

View File

@@ -0,0 +1,9 @@
import { NgModule } from '@angular/core'
import { DarkThemeComponent } from './dark-theme.component'
@NgModule({
declarations: [DarkThemeComponent],
exports: [DarkThemeComponent],
})
export class DarkThemeModule {}

View File

@@ -0,0 +1,95 @@
// Ionic Variables and Theming. For more info, please see:
// http://ionicframework.com/docs/theming/
/** Ionic CSS Variables **/
:root {
--ion-color-primary: #0075e1;
--ion-color-primary-rgb: 66, 140, 255;
--ion-color-primary-contrast: #ffffff;
--ion-color-primary-contrast-rgb: 255, 255, 255;
--ion-color-primary-shade: #3a7be0;
--ion-color-primary-tint: #5598ff;
--ion-color-secondary: #50c8ff;
--ion-color-secondary-rgb: 80, 200, 255;
--ion-color-secondary-contrast: #ffffff;
--ion-color-secondary-contrast-rgb: 255, 255, 255;
--ion-color-secondary-shade: #46b0e0;
--ion-color-secondary-tint: #62ceff;
--ion-color-tertiary: #6a64ff;
--ion-color-tertiary-rgb: 106, 100, 255;
--ion-color-tertiary-contrast: #ffffff;
--ion-color-tertiary-contrast-rgb: 255, 255, 255;
--ion-color-tertiary-shade: #5d58e0;
--ion-color-tertiary-tint: #7974ff;
--ion-color-success: #2fdf75;
--ion-color-success-rgb: 47, 223, 117;
--ion-color-success-contrast: #000000;
--ion-color-success-contrast-rgb: 0, 0, 0;
--ion-color-success-shade: #29c467;
--ion-color-success-tint: #44e283;
--ion-color-warning: #ffb506;
--ion-color-warning-rgb: 255, 213, 52;
--ion-color-warning-contrast: #000000;
--ion-color-warning-contrast-rgb: 0, 0, 0;
--ion-color-warning-shade: #e0bb2e;
--ion-color-warning-tint: #ffd534;
--ion-color-danger: #ff4961;
--ion-color-danger-rgb: 255, 73, 97;
--ion-color-danger-contrast: #ffffff;
--ion-color-danger-contrast-rgb: 255, 255, 255;
--ion-color-danger-shade: #e04055;
--ion-color-danger-tint: #ff5b71;
//--ion-color-light: #f4f5f8;
//--ion-color-light-rgb: 244, 245, 248;
//--ion-color-light-contrast: #000000;
//--ion-color-light-contrast-rgb: 0, 0, 0;
//--ion-color-light-shade: #d7d8da;
//--ion-color-light-tint: #f5f6f9;
//
//--ion-color-medium: #f4f5f8;
//--ion-color-medium-rgb: 244, 245, 248;
//--ion-color-medium-contrast: #000000;
//--ion-color-medium-contrast-rgb: 0, 0, 0;
//--ion-color-medium-shade: #d7d8da;
//--ion-color-medium-tint: #f5f6f9;
//
//--ion-color-dark: #92949c;
//--ion-color-dark-rgb: 146, 148, 156;
//--ion-color-dark-contrast: #ffffff;
//--ion-color-dark-contrast-rgb: 255, 255, 255;
//--ion-color-dark-shade: #808289;
//--ion-color-dark-tint: #9d9fa6;
--ion-color-step-50: #f2f2f2;
--ion-color-step-100: #e6e6e6;
--ion-color-step-150: #d9d9d9;
--ion-color-step-200: #cccccc;
--ion-color-step-250: #bfbfbf;
--ion-color-step-300: #b3b3b3;
--ion-color-step-350: #a6a6a6;
--ion-color-step-400: #999999;
--ion-color-step-450: #8c8c8c;
--ion-color-step-500: #808080;
--ion-color-step-550: #737373;
--ion-color-step-600: #666666;
--ion-color-step-650: #595959;
--ion-color-step-700: #4d4d4d;
--ion-color-step-750: #404040;
--ion-color-step-800: #333333;
--ion-color-step-850: #262626;
--ion-color-step-900: #191919;
--ion-color-step-950: #0d0d0d;
--alt-red: #ff4961;
--alt-orange: #f89248;
--alt-yellow: #e5d53e;
--alt-green: #3dcf6f;
--alt-blue: #00a8a8;
--alt-purple: #9747ff;
}

View File

@@ -0,0 +1,15 @@
import {
ChangeDetectionStrategy,
Component,
ViewEncapsulation,
} from '@angular/core'
import { AbstractTuiThemeSwitcher } from '@taiga-ui/cdk'
@Component({
selector: 'light-theme',
template: '',
styleUrls: ['./light-theme.component.scss'],
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class LightThemeComponent extends AbstractTuiThemeSwitcher {}

View File

@@ -0,0 +1,9 @@
import { NgModule } from '@angular/core'
import { LightThemeComponent } from './light-theme.component'
@NgModule({
declarations: [LightThemeComponent],
exports: [LightThemeComponent],
})
export class LightThemeModule {}

View File

@@ -0,0 +1,6 @@
import { InjectionToken } from '@angular/core'
import { EMPTY, Observable } from 'rxjs'
export const THEME = new InjectionToken<Observable<string>>('App theme', {
factory: () => EMPTY,
})

View File

@@ -48,7 +48,7 @@
--ion-color-light: #181818; --ion-color-light: #181818;
--ion-color-light-rgb: 24, 24, 24; --ion-color-light-rgb: 24, 24, 24;
--ion-color-light-contrast: #ffffff; --ion-color-light-contrast: #ffffff;
--ion-color-light-contrast-rgb: 0, 0, 0; --ion-color-light-contrast-rgb: 255, 255, 255;
--ion-color-light-shade: #000000; --ion-color-light-shade: #000000;
--ion-color-light-tint: #000000; --ion-color-light-tint: #000000;
@@ -92,4 +92,4 @@
--alt-green: #3DCF6F; --alt-green: #3DCF6F;
--alt-blue: #00A8A8; --alt-blue: #00A8A8;
--alt-purple: #9747FF; --alt-purple: #9747FF;
} }

View File

@@ -1,6 +1,6 @@
<tui-root <tui-root
*ngIf="widgetDrawer$ | async as drawer" *ngIf="widgetDrawer$ | async as drawer"
tuiMode="onDark" [tuiMode]="(theme$ | async) === 'Dark' ? 'onDark' : null"
[style.--widgets-width.px]="drawer.open ? drawer.width : 0" [style.--widgets-width.px]="drawer.open ? drawer.width : 0"
> >
<ion-app appEnter> <ion-app appEnter>
@@ -16,7 +16,7 @@
side="start" side="start"
class="left-menu" class="left-menu"
> >
<ion-content color="light" scrollY="false"> <ion-content color="light" scrollY="false" class="menu">
<app-menu *ngIf="authService.isVerified$ | async"></app-menu> <app-menu *ngIf="authService.isVerified$ | async"></app-menu>
</ion-content> </ion-content>
</ion-menu> </ion-menu>
@@ -74,4 +74,13 @@
<toast-container></toast-container> <toast-container></toast-container>
</ion-app> </ion-app>
</tui-root> </tui-root>
<tui-theme-night></tui-theme-night> <ng-container
*ngIf="authService.isVerified$ | async"
[ngSwitch]="theme$ | async"
>
<ng-container *ngSwitchCase="'Dark'">
<tui-theme-night></tui-theme-night>
<dark-theme></dark-theme>
</ng-container>
<light-theme *ngSwitchCase="'Light'"></light-theme>
</ng-container>

View File

@@ -11,6 +11,12 @@ tui-root {
--side-max-width: 280px; --side-max-width: 280px;
} }
.menu {
:host-context(body[data-theme='Light']) & {
--ion-color-base: #F4F4F5 !important;
}
}
.container { .container {
transition: filter 0.3s; transition: filter 0.3s;

View File

@@ -1,4 +1,4 @@
import { Component, OnDestroy } from '@angular/core' import { Component, inject, OnDestroy } from '@angular/core'
import { merge } from 'rxjs' import { merge } from 'rxjs'
import { AuthService } from './services/auth.service' import { AuthService } from './services/auth.service'
import { SplitPaneTracker } from './services/split-pane.service' import { SplitPaneTracker } from './services/split-pane.service'
@@ -11,6 +11,8 @@ import {
ClientStorageService, ClientStorageService,
WidgetDrawer, WidgetDrawer,
} from './services/client-storage.service' } from './services/client-storage.service'
import { ThemeSwitcherService } from './services/theme-switcher.service'
import { THEME } from '@start9labs/shared'
@Component({ @Component({
selector: 'app-root', selector: 'app-root',
@@ -21,6 +23,7 @@ export class AppComponent implements OnDestroy {
readonly subscription = merge(this.patchData, this.patchMonitor).subscribe() readonly subscription = merge(this.patchData, this.patchMonitor).subscribe()
readonly sidebarOpen$ = this.splitPane.sidebarOpen$ readonly sidebarOpen$ = this.splitPane.sidebarOpen$
readonly widgetDrawer$ = this.clientStorageService.widgetDrawer$ readonly widgetDrawer$ = this.clientStorageService.widgetDrawer$
readonly theme$ = inject(THEME)
constructor( constructor(
private readonly titleService: Title, private readonly titleService: Title,
@@ -31,6 +34,7 @@ export class AppComponent implements OnDestroy {
readonly authService: AuthService, readonly authService: AuthService,
readonly connection: ConnectionService, readonly connection: ConnectionService,
readonly clientStorageService: ClientStorageService, readonly clientStorageService: ClientStorageService,
readonly themeSwitcher: ThemeSwitcherService,
) {} ) {}
ngOnInit() { ngOnInit() {

View File

@@ -11,8 +11,10 @@ import { IonicModule } from '@ionic/angular'
import { MonacoEditorModule } from '@materia-ui/ngx-monaco-editor' import { MonacoEditorModule } from '@materia-ui/ngx-monaco-editor'
import { import {
MarkdownModule, MarkdownModule,
DarkThemeModule,
ResponsiveColModule, ResponsiveColModule,
SharedPipesModule, SharedPipesModule,
LightThemeModule,
} from '@start9labs/shared' } from '@start9labs/shared'
import { AppComponent } from './app.component' import { AppComponent } from './app.component'
@@ -58,6 +60,8 @@ import { WidgetsPageModule } from './pages/widgets/widgets.module'
TuiThemeNightModule, TuiThemeNightModule,
WidgetsPageModule, WidgetsPageModule,
ResponsiveColModule, ResponsiveColModule,
DarkThemeModule,
LightThemeModule,
], ],
providers: APP_PROVIDERS, providers: APP_PROVIDERS,
bootstrap: [AppComponent], bootstrap: [AppComponent],

View File

@@ -2,13 +2,14 @@ import { APP_INITIALIZER, Provider } from '@angular/core'
import { UntypedFormBuilder } from '@angular/forms' import { UntypedFormBuilder } from '@angular/forms'
import { Router, RouteReuseStrategy } from '@angular/router' import { Router, RouteReuseStrategy } from '@angular/router'
import { IonicRouteStrategy, IonNav } from '@ionic/angular' import { IonicRouteStrategy, IonNav } from '@ionic/angular'
import { RELATIVE_URL, WorkspaceConfig } from '@start9labs/shared' import { RELATIVE_URL, THEME, WorkspaceConfig } from '@start9labs/shared'
import { ApiService } from './services/api/embassy-api.service' import { ApiService } from './services/api/embassy-api.service'
import { MockApiService } from './services/api/embassy-mock-api.service' import { MockApiService } from './services/api/embassy-mock-api.service'
import { LiveApiService } from './services/api/embassy-live-api.service' import { LiveApiService } from './services/api/embassy-live-api.service'
import { AuthService } from './services/auth.service' import { AuthService } from './services/auth.service'
import { ClientStorageService } from './services/client-storage.service' import { ClientStorageService } from './services/client-storage.service'
import { FilterPackagesPipe } from '../../../marketplace/src/pipes/filter-packages.pipe' import { FilterPackagesPipe } from '../../../marketplace/src/pipes/filter-packages.pipe'
import { ThemeSwitcherService } from './services/theme-switcher.service'
const { const {
useMocks, useMocks,
@@ -37,6 +38,10 @@ export const APP_PROVIDERS: Provider[] = [
provide: RELATIVE_URL, provide: RELATIVE_URL,
useValue: `/${api.url}/${api.version}`, useValue: `/${api.url}/${api.version}`,
}, },
{
provide: THEME,
useExisting: ThemeSwitcherService,
},
] ]
export function appInitializer( export function appInitializer(

View File

@@ -1,11 +1,17 @@
<a class="logo ion-padding" routerLink="/home"> <a class="logo ion-padding" routerLink="/home">
<img alt="Start9" src="assets/img/logo.png" /> <img
alt="Start9"
src="assets/img/{{
(theme$ | async) === 'Dark' ? 'logo' : 'logo_dark'
}}.png"
/>
</a> </a>
<ion-item-group class="menu"> <ion-item-group class="menu">
<ion-menu-toggle *ngFor="let page of pages" auto-hide="false"> <ion-menu-toggle *ngFor="let page of pages" auto-hide="false">
<ion-item <ion-item
button button
class="link" class="link"
routerLinkActive="link_selected"
color="transparent" color="transparent"
routerDirection="root" routerDirection="root"
lines="none" lines="none"
@@ -45,7 +51,6 @@
</ion-item> </ion-item>
</ion-menu-toggle> </ion-menu-toggle>
</ion-item-group> </ion-item-group>
<img <img
appSnek appSnek
class="snek" class="snek"

View File

@@ -12,6 +12,15 @@
padding: 30px 0; padding: 30px 0;
} }
.link {
--border-radius: 0;
:host-context(body[data-theme='Light']) &_selected {
--ion-color-base: #333;
--ion-color-contrast: #fff;
}
}
.icon { .icon {
margin-left: 10px; margin-left: 10px;
} }
@@ -20,7 +29,7 @@
color: var(--ion-color-dark-shade); color: var(--ion-color-dark-shade);
&_selected { &_selected {
color: #fff; color: var(--ion-color-dark);
font-weight: bold; font-weight: bold;
} }
} }

View File

@@ -1,4 +1,9 @@
import { ChangeDetectionStrategy, Component, Inject } from '@angular/core' import {
ChangeDetectionStrategy,
Component,
inject,
Inject,
} from '@angular/core'
import { EOSService } from '../../services/eos.service' import { EOSService } from '../../services/eos.service'
import { PatchDB } from 'patch-db-client' import { PatchDB } from 'patch-db-client'
import { combineLatest, filter, first, map, Observable, switchMap } from 'rxjs' import { combineLatest, filter, first, map, Observable, switchMap } from 'rxjs'
@@ -6,7 +11,7 @@ import { AbstractMarketplaceService } from '@start9labs/marketplace'
import { MarketplaceService } from 'src/app/services/marketplace.service' import { MarketplaceService } from 'src/app/services/marketplace.service'
import { DataModel } from 'src/app/services/patch-db/data-model' import { DataModel } from 'src/app/services/patch-db/data-model'
import { SplitPaneTracker } from 'src/app/services/split-pane.service' import { SplitPaneTracker } from 'src/app/services/split-pane.service'
import { Emver } from '@start9labs/shared' import { Emver, THEME } from '@start9labs/shared'
import { ConnectionService } from 'src/app/services/connection.service' import { ConnectionService } from 'src/app/services/connection.service'
@Component({ @Component({
@@ -82,6 +87,8 @@ export class MenuComponent {
readonly sidebarOpen$ = this.splitPane.sidebarOpen$ readonly sidebarOpen$ = this.splitPane.sidebarOpen$
readonly theme$ = inject(THEME)
constructor( constructor(
private readonly patch: PatchDB<DataModel>, private readonly patch: PatchDB<DataModel>,
private readonly eosService: EOSService, private readonly eosService: EOSService,

View File

@@ -10,6 +10,7 @@ const ICONS = [
'arrow-forward', 'arrow-forward',
'arrow-up', 'arrow-up',
'briefcase-outline', 'briefcase-outline',
'brush-outline',
'bookmark-outline', 'bookmark-outline',
'cellular-outline', 'cellular-outline',
'chatbubbles-outline', 'chatbubbles-outline',

View File

@@ -4,11 +4,7 @@
[color]="connection.color" [color]="connection.color"
> >
<div class="inline" slot="start"> <div class="inline" slot="start">
<ion-icon <ion-icon [name]="connection.icon" class="icon"></ion-icon>
[name]="connection.icon"
class="icon"
[color]="connection.iconColor"
></ion-icon>
<p style="margin: 8px 0; font-weight: 600">{{ connection.message }}</p> <p style="margin: 8px 0; font-weight: 600">{{ connection.message }}</p>
<ion-spinner <ion-spinner
*ngIf="connection.dots" *ngIf="connection.dots"

View File

@@ -15,7 +15,6 @@ export class ConnectionBarComponent {
message: string message: string
color: string color: string
icon: string icon: string
iconColor: string
dots: boolean dots: boolean
}> = combineLatest([ }> = combineLatest([
this.connectionService.networkConnected$, this.connectionService.networkConnected$,
@@ -27,7 +26,6 @@ export class ConnectionBarComponent {
message: 'No Internet', message: 'No Internet',
color: 'danger', color: 'danger',
icon: 'cloud-offline-outline', icon: 'cloud-offline-outline',
iconColor: 'dark',
dots: false, dots: false,
} }
if (!websocket) if (!websocket)
@@ -35,7 +33,6 @@ export class ConnectionBarComponent {
message: 'Connecting', message: 'Connecting',
color: 'warning', color: 'warning',
icon: 'cloud-offline-outline', icon: 'cloud-offline-outline',
iconColor: 'light',
dots: true, dots: true,
} }
@@ -43,7 +40,6 @@ export class ConnectionBarComponent {
message: 'Connected', message: 'Connected',
color: 'success', color: 'success',
icon: 'cloud-done', icon: 'cloud-done',
iconColor: 'light',
dots: false, dots: false,
} }
}), }),

View File

@@ -15,7 +15,7 @@
}" }"
></form-label> ></form-label>
</h4> </h4>
<ion-item color="dark"> <ion-item [color]="(theme$ | async) === 'Light' ? 'light' : 'dark'">
<ion-textarea <ion-textarea
*ngIf="spec.type === 'string' && spec.textarea; else notTextArea" *ngIf="spec.type === 'string' && spec.textarea; else notTextArea"
[placeholder]="spec.placeholder || 'Enter ' + spec.name" [placeholder]="spec.placeholder || 'Enter ' + spec.name"
@@ -308,7 +308,9 @@
*ngIf="spec.subtype === 'string' || spec.subtype === 'number'" *ngIf="spec.subtype === 'string' || spec.subtype === 'number'"
[id]="objectId | toElementId: entry.key:i" [id]="objectId | toElementId: entry.key:i"
> >
<ion-item color="dark"> <ion-item
[color]="(theme$ | async) === 'Light' ? 'light' : 'dark'"
>
<ion-input <ion-input
type="text" type="text"
[inputmode]="spec.subtype === 'number' ? 'tel' : 'text'" [inputmode]="spec.subtype === 'number' ? 'tel' : 'text'"

View File

@@ -5,6 +5,7 @@ import {
EventEmitter, EventEmitter,
ChangeDetectionStrategy, ChangeDetectionStrategy,
Inject, Inject,
inject,
} from '@angular/core' } from '@angular/core'
import { FormArray, UntypedFormArray, UntypedFormGroup } from '@angular/forms' import { FormArray, UntypedFormArray, UntypedFormGroup } from '@angular/forms'
import { AlertButton, AlertController, ModalController } from '@ionic/angular' import { AlertButton, AlertController, ModalController } from '@ionic/angular'
@@ -20,9 +21,10 @@ import {
} from 'src/app/pkg-config/config-types' } from 'src/app/pkg-config/config-types'
import { FormService } from 'src/app/services/form.service' import { FormService } from 'src/app/services/form.service'
import { EnumListPage } from 'src/app/modals/enum-list/enum-list.page' import { EnumListPage } from 'src/app/modals/enum-list/enum-list.page'
import { pauseFor } from '@start9labs/shared' import { THEME, pauseFor } from '@start9labs/shared'
import { v4 } from 'uuid' import { v4 } from 'uuid'
import { DOCUMENT } from '@angular/common' import { DOCUMENT } from '@angular/common'
const Mustache = require('mustache') const Mustache = require('mustache')
interface Config { interface Config {
@@ -51,6 +53,8 @@ export class FormObjectComponent {
} = {} } = {}
objectId = v4() objectId = v4()
readonly theme$ = inject(THEME)
constructor( constructor(
private readonly alertCtrl: AlertController, private readonly alertCtrl: AlertController,
private readonly modalCtrl: ModalController, private readonly modalCtrl: ModalController,

View File

@@ -19,7 +19,10 @@
<form (ngSubmit)="submit()"> <form (ngSubmit)="submit()">
<div style="margin: 0 0 24px 16px"> <div style="margin: 0 0 24px 16px">
<p class="input-label">{{ options.label }}</p> <p class="input-label">{{ options.label }}</p>
<ion-item lines="none" color="dark"> <ion-item
lines="none"
[color]="(theme$ | async) === 'Light' ? 'light' : 'dark'"
>
<ion-input <ion-input
#mainInput #mainInput
type="text" type="text"

View File

@@ -1,6 +1,6 @@
import { Component, Input, ViewChild } from '@angular/core' import { Component, inject, Input, ViewChild } from '@angular/core'
import { ModalController, IonicSafeString, IonInput } from '@ionic/angular' import { ModalController, IonicSafeString, IonInput } from '@ionic/angular'
import { getErrorMessage } from '@start9labs/shared' import { getErrorMessage, THEME } from '@start9labs/shared'
import { MaskPipe } from 'src/app/pipes/mask/mask.pipe' import { MaskPipe } from 'src/app/pipes/mask/mask.pipe'
@Component({ @Component({
@@ -21,6 +21,8 @@ export class GenericInputComponent {
error: string | IonicSafeString = '' error: string | IonicSafeString = ''
readonly theme$ = inject(THEME)
constructor( constructor(
private readonly modalCtrl: ModalController, private readonly modalCtrl: ModalController,
private readonly mask: MaskPipe, private readonly mask: MaskPipe,

View File

@@ -2,6 +2,7 @@
button button
*ngIf="pkg.entry.manifest as manifest" *ngIf="pkg.entry.manifest as manifest"
detail="false" detail="false"
class="service-card"
[routerLink]="['/services', manifest.id]" [routerLink]="['/services', manifest.id]"
> >
<app-list-icon slot="start" [pkg]="pkg"></app-list-icon> <app-list-icon slot="start" [pkg]="pkg"></app-list-icon>
@@ -9,7 +10,7 @@
<img alt="" [src]="pkg.entry['static-files'].icon" /> <img alt="" [src]="pkg.entry['static-files'].icon" />
</ion-thumbnail> </ion-thumbnail>
<ion-label> <ion-label>
<h2>{{ manifest.title }}</h2> <h2 ticker>{{ manifest.title }}</h2>
<p>{{ manifest.version | displayEmver }}</p> <p>{{ manifest.version | displayEmver }}</p>
<status <status
[rendering]="pkg.primaryRendering" [rendering]="pkg.primaryRendering"

View File

@@ -7,6 +7,7 @@ import {
EmverPipesModule, EmverPipesModule,
ResponsiveColModule, ResponsiveColModule,
TextSpinnerComponentModule, TextSpinnerComponentModule,
TickerModule,
} from '@start9labs/shared' } from '@start9labs/shared'
import { BadgeMenuComponentModule } from 'src/app/components/badge-menu-button/badge-menu.component.module' import { BadgeMenuComponentModule } from 'src/app/components/badge-menu-button/badge-menu.component.module'
import { StatusComponentModule } from 'src/app/components/status/status.component.module' import { StatusComponentModule } from 'src/app/components/status/status.component.module'
@@ -37,6 +38,7 @@ const routes: Routes = [
BadgeMenuComponentModule, BadgeMenuComponentModule,
WidgetListComponentModule, WidgetListComponentModule,
ResponsiveColModule, ResponsiveColModule,
TickerModule,
], ],
declarations: [ declarations: [
AppListPage, AppListPage,

View File

@@ -3,10 +3,8 @@
<ion-buttons slot="start"> <ion-buttons slot="start">
<ion-back-button defaultHref="services"></ion-back-button> <ion-back-button defaultHref="services"></ion-back-button>
</ion-buttons> </ion-buttons>
<ion-item lines="none" color="light"> <div class="header">
<ion-thumbnail slot="start"> <img class="logo" [src]="pkg['static-files'].icon" alt="" />
<img [src]="pkg['static-files'].icon" alt="" />
</ion-thumbnail>
<ion-label> <ion-label>
<h1 <h1
class="montserrat" class="montserrat"
@@ -16,6 +14,6 @@
</h1> </h1>
<h2>{{ pkg.manifest.version | displayEmver }}</h2> <h2>{{ pkg.manifest.version | displayEmver }}</h2>
</ion-label> </ion-label>
</ion-item> </div>
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>

View File

@@ -1,3 +1,13 @@
.less-large { .less-large {
font-size: 18px !important; font-size: 18px !important;
} }
.header {
display: flex;
}
.logo {
height: 54px;
width: 54px;
margin: 0 16px;
}

View File

@@ -18,8 +18,8 @@ const routes: Routes = [
CommonModule, CommonModule,
FormsModule, FormsModule,
IonicModule, IonicModule,
RouterModule.forChild(routes),
SharedPipesModule, SharedPipesModule,
RouterModule.forChild(routes),
], ],
declarations: [LoginPage], declarations: [LoginPage],
}) })

View File

@@ -1,41 +1,35 @@
<ion-content> <ion-content class="content">
<ion-grid style="height: 100%; max-width: 540px"> <ion-grid class="grid">
<ion-row class="ion-align-items-center" style="height: 90%"> <ion-row class="row">
<ion-col class="ion-text-center"> <ion-col>
<div style="padding-bottom: 16px"> <img src="assets/img/logo.png" alt="Start9" class="logo" />
<img src="assets/img/logo.png" style="max-width: 240px" />
</div>
<ion-card> <ion-card class="card">
<ion-card-header class="ion-text-center" style="padding-bottom: 8px"> <ion-card-header>
<ion-card-title>Embassy Login</ion-card-title> <ion-card-title class="title">Embassy Login</ion-card-title>
</ion-card-header> </ion-card-header>
<ion-card-content class="ion-margin ion-text-center"> <ion-card-content class="ion-margin">
<form <form class="form" (submit)="submit()">
class="inline"
(submit)="submit()"
style="margin-bottom: 12px"
>
<ion-item-group> <ion-item-group>
<ion-item color="dark"> <ion-item color="light">
<ion-icon <ion-icon
slot="start" slot="start"
name="key-outline" name="key-outline"
style="margin-right: 16px" style="margin-right: 16px"
></ion-icon> ></ion-icon>
<ion-input <ion-input
[type]="unmasked ? 'text' : 'password'"
name="password" name="password"
placeholder="Password"
[type]="unmasked ? 'text' : 'password'"
[(ngModel)]="password" [(ngModel)]="password"
(ionChange)="error = ''" (ionChange)="error = ''"
placeholder="Password"
></ion-input> ></ion-input>
<ion-button fill="clear" color="light" (click)="toggleMask()"> <ion-button fill="clear" color="dark" (click)="toggleMask()">
<ion-icon <ion-icon
slot="icon-only" slot="icon-only"
[name]="unmasked ? 'eye-off-outline' : 'eye-outline'"
size="small" size="small"
[name]="unmasked ? 'eye-off-outline' : 'eye-outline'"
></ion-icon> ></ion-icon>
</ion-button> </ion-button>
</ion-item> </ion-item>
@@ -46,12 +40,12 @@
expand="block" expand="block"
color="tertiary" color="tertiary"
> >
<span style="font-size: larger; font-weight: bold">Login</span> Login
</ion-button> </ion-button>
<p style="text-align: left; padding-top: 4px">
<ion-text color="danger">{{ error }}</ion-text>
</p>
</form> </form>
<p class="error">
<ion-text color="danger">{{ error }}</ion-text>
</p>
</ion-card-content> </ion-card-content>
</ion-card> </ion-card>
</ion-col> </ion-col>

View File

@@ -1,10 +1,37 @@
ion-card-title { .content {
margin: 24px 0; --background: #222428;
font-family: 'Montserrat'; }
font-weight: 500;
font-size: x-large; .card {
font-variant: all-small-caps; background: #414141;
--color: var(--ion-color-dark); }
.title {
margin: 24px 0 16px;
color: #e0e0e0;
text-transform: uppercase;
}
.grid {
height: 100%;
max-width: 540px;
}
.row {
height: 90%;
align-items: center;
text-align: center;
}
.logo {
max-width: 240px;
padding-bottom: 16px;
}
.error {
display: block;
text-align: left;
padding-top: 4px;
} }
ion-button { ion-button {
@@ -15,8 +42,7 @@ ion-item {
--border-style: solid; --border-style: solid;
--border-color: var(--ion-color-light); --border-color: var(--ion-color-light);
--border-radius: 4px 0 0 4px; --border-radius: 4px 0 0 4px;
box-shadow: 0 3px 1px -2px rgba(0, 0, 0, 0.2), box-shadow: 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 2px 2px 0 rgba(0, 0, 0, 0.14),
0 2px 2px 0 rgba(0, 0, 0, 0.14),
0 1px 5px 0 rgba(0, 0, 0, 0.12); 0 1px 5px 0 rgba(0, 0, 0, 0.12);
ion-button { ion-button {
@@ -26,26 +52,22 @@ ion-item {
ion-card { ion-card {
background: var(--ion-color-step-200); background: var(--ion-color-step-200);
box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25); box-shadow: 0 4px 4px rgba(0, 0, 0, 0.25);
border-radius: 44px; border-radius: 44px;
min-height: 16rem; min-height: 16rem;
} }
.input-label {
text-align: left;
padding-bottom: 2px;
font-size: small;
font-weight: bold;
color: var(--ion-color-dark);
}
.login-button { .login-button {
margin-inline-start: 0; margin-inline-start: 0;
margin-inline-end: 0; margin-inline-end: 0;
height: 49px; height: 49px;
font-size: larger;
font-weight: bold;
} }
.inline { .form {
margin-bottom: 12px;
* { * {
display: inline-block; display: inline-block;
vertical-align: middle; vertical-align: middle;
@@ -53,5 +75,5 @@ ion-card {
} }
.item-interactive { .item-interactive {
--highlight-background: var(--ion-color-tertiary) !important; --highlight-background: #5260ff !important;
} }

View File

@@ -8,6 +8,7 @@ import { TextSpinnerComponentModule } from '@start9labs/shared'
import { BadgeMenuComponentModule } from 'src/app/components/badge-menu-button/badge-menu.component.module' import { BadgeMenuComponentModule } from 'src/app/components/badge-menu-button/badge-menu.component.module'
import { OSUpdatePageModule } from 'src/app/modals/os-update/os-update.page.module' import { OSUpdatePageModule } from 'src/app/modals/os-update/os-update.page.module'
import { BackupColorPipeModule } from 'src/app/pipes/backup-color/backup-color.module' import { BackupColorPipeModule } from 'src/app/pipes/backup-color/backup-color.module'
import { ThemeSwitcherModule } from '../theme-switcher/theme-switcher.module'
const routes: Routes = [ const routes: Routes = [
{ {
@@ -26,6 +27,7 @@ const routes: Routes = [
BadgeMenuComponentModule, BadgeMenuComponentModule,
OSUpdatePageModule, OSUpdatePageModule,
BackupColorPipeModule, BackupColorPipeModule,
ThemeSwitcherModule,
], ],
declarations: [ServerShowPage], declarations: [ServerShowPage],
}) })

View File

@@ -38,65 +38,65 @@
{{ cat.key }} {{ cat.key }}
</ion-text> </ion-text>
</ion-item-divider> </ion-item-divider>
<ng-container *ngFor="let button of cat.value"> <!-- <theme-switcher *ngIf="cat.key === 'Manage'"></theme-switcher> -->
<ion-item <ion-item
button *ngFor="let button of cat.value"
[style.display]="(button.title === 'Repair Disk' && !(showDiskRepair$ | async)) ? 'none' : 'block'" button
[detail]="button.detail" [style.display]="(button.title === 'Repair Disk' && !(showDiskRepair$ | async)) ? 'none' : 'block'"
[disabled]="button.disabled$ | async" [detail]="button.detail"
(click)="button.action()" [disabled]="button.disabled$ | async"
> (click)="button.action()"
<ion-icon slot="start" [name]="button.icon"></ion-icon> >
<ion-label> <ion-icon slot="start" [name]="button.icon"></ion-icon>
<h2>{{ button.title }}</h2> <ion-label>
<p *ngIf="button.description">{{ button.description }}</p> <h2>{{ button.title }}</h2>
<p *ngIf="button.description">{{ button.description }}</p>
<!-- "Create Backup" button only --> <!-- "Create Backup" button only -->
<p *ngIf="button.title === 'Create Backup'"> <p *ngIf="button.title === 'Create Backup'">
<ng-container *ngIf="server['status-info'] as statusInfo"> <ng-container *ngIf="server['status-info'] as statusInfo">
<ion-text
[color]="server['last-backup'] | backupColor"
*ngIf="!statusInfo['backup-progress'] && !statusInfo['update-progress']"
>
Last Backup: {{ server['last-backup'] ? (server['last-backup']
| date: 'medium') : 'never' }}
</ion-text>
<span *ngIf="!!statusInfo['backup-progress']" class="inline">
<ion-spinner
color="success"
style="height: 12px; width: 12px; margin-right: 6px"
></ion-spinner>
<ion-text color="success">Backing up</ion-text>
</span>
</ng-container>
</p>
<!-- "Software Update" button only -->
<p *ngIf="button.title === 'Software Update'">
<ion-text <ion-text
*ngIf="server['status-info'].updated; else notUpdated" [color]="server['last-backup'] | backupColor"
class="inline" *ngIf="!statusInfo['backup-progress'] && !statusInfo['update-progress']"
color="warning"
> >
Update Complete. Restart to apply changes Last Backup: {{ server['last-backup'] ? (server['last-backup'] |
date: 'medium') : 'never' }}
</ion-text> </ion-text>
<ng-template #notUpdated> <span *ngIf="!!statusInfo['backup-progress']" class="inline">
<ng-container *ngIf="showUpdate$ | async; else check"> <ion-spinner
<ion-text class="inline" color="success"> color="success"
<ion-icon name="rocket-outline"></ion-icon> style="height: 12px; width: 12px; margin-right: 6px"
Update Available ></ion-spinner>
</ion-text> <ion-text color="success">Backing up</ion-text>
</ng-container> </span>
<ng-template #check> </ng-container>
<ion-text class="inline" color="dark"> </p>
<ion-icon name="refresh"></ion-icon> <!-- "Software Update" button only -->
Check for updates <p *ngIf="button.title === 'Software Update'">
</ion-text> <ion-text
</ng-template> *ngIf="server['status-info'].updated; else notUpdated"
class="inline"
color="warning"
>
Update Complete. Restart to apply changes
</ion-text>
<ng-template #notUpdated>
<ng-container *ngIf="showUpdate$ | async; else check">
<ion-text class="inline" color="success">
<ion-icon name="rocket-outline"></ion-icon>
Update Available
</ion-text>
</ng-container>
<ng-template #check>
<ion-text class="inline" color="dark">
<ion-icon name="refresh"></ion-icon>
Check for updates
</ion-text>
</ng-template> </ng-template>
</p> </ng-template>
</ion-label> </p>
</ion-item> </ion-label>
</ng-container> </ion-item>
</div> </div>
</ion-item-group> </ion-item-group>
</ion-content> </ion-content>

View File

@@ -56,7 +56,7 @@
{{ uploadState?.message }} {{ uploadState?.message }}
</h4> </h4>
<div class="box" *ngIf="toUpload.icon && toUpload.manifest"> <div class="box" *ngIf="toUpload.icon && toUpload.manifest">
<div class="service-card"> <div class="card">
<div class="row row_end"> <div class="row row_end">
<ion-button <ion-button
style=" style="

View File

@@ -53,7 +53,7 @@
justify-content: space-evenly justify-content: space-evenly
} }
.service-card { .card {
background: radial-gradient(var(--ion-color-step-100), transparent); background: radial-gradient(var(--ion-color-step-100), transparent);
min-width: 200px; min-width: 200px;
max-width: 200px; max-width: 200px;

View File

@@ -0,0 +1,23 @@
<ion-item button [detail]="true" (click)="open = true">
<ion-icon slot="start" name="brush-outline"></ion-icon>
<ion-label>
<h2>Theme</h2>
<p>{{ value }}</p>
</ion-label>
</ion-item>
<ng-template
[(tuiDialog)]="open"
[tuiDialogOptions]="{
label: 'Select theme',
size: 's'
}"
>
<tui-radio-list
style="margin-top: 16px"
size="l"
[ngModel]="value"
[items]="themes"
(ngModelChange)="onChange($event)"
></tui-radio-list>
</ng-template>

View File

@@ -0,0 +1,23 @@
import { ChangeDetectionStrategy, Component } from '@angular/core'
import { ThemeSwitcherService } from '../../../services/theme-switcher.service'
@Component({
selector: 'theme-switcher',
templateUrl: './theme-switcher.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ThemeSwitcherComponent {
value = this.switcher.value
open = false
readonly themes = ['Dark', 'Light']
constructor(private readonly switcher: ThemeSwitcherService) {}
onChange(value: string): void {
this.value = value
this.switcher.next(value)
}
}

View File

@@ -0,0 +1,14 @@
import { NgModule } from '@angular/core'
import { FormsModule } from '@angular/forms'
import { IonicModule } from '@ionic/angular'
import { TuiDialogModule } from '@taiga-ui/core'
import { TuiRadioListModule } from '@taiga-ui/kit'
import { ThemeSwitcherComponent } from './theme-switcher.component'
@NgModule({
imports: [IonicModule, FormsModule, TuiDialogModule, TuiRadioListModule],
declarations: [ThemeSwitcherComponent],
exports: [ThemeSwitcherComponent],
})
export class ThemeSwitcherModule {}

View File

@@ -57,7 +57,7 @@
.content { .content {
height: 100%; height: 100%;
text-align: center; text-align: center;
background: #333; background: var(--tui-base-02);
border-radius: 24px; border-radius: 24px;
padding: 24px; padding: 24px;
box-sizing: border-box; box-sizing: border-box;

View File

@@ -14,6 +14,7 @@ export const mockPatchData: DataModel = {
ui: { ui: {
name: `Matt's Embassy`, name: `Matt's Embassy`,
'ack-welcome': '1.0.0', 'ack-welcome': '1.0.0',
theme: 'Dark',
widgets: BUILT_IN_WIDGETS.filter( widgets: BUILT_IN_WIDGETS.filter(
({ id }) => ({ id }) =>
id === 'favorites' || id === 'favorites' ||

View File

@@ -20,6 +20,7 @@ export interface UIData {
} }
} }
'ack-instructions': Record<string, boolean> 'ack-instructions': Record<string, boolean>
theme: string
widgets: readonly Widget[] widgets: readonly Widget[]
} }

View File

@@ -0,0 +1,33 @@
import { Inject, Injectable } from '@angular/core'
import { WINDOW } from '@ng-web-apis/common'
import { PatchDB } from 'patch-db-client'
import { BehaviorSubject } from 'rxjs'
import { ApiService } from './api/embassy-api.service'
import { DataModel } from './patch-db/data-model'
import { filter, take } from 'rxjs/operators'
@Injectable({
providedIn: 'root',
})
export class ThemeSwitcherService extends BehaviorSubject<string> {
constructor(
private readonly patch: PatchDB<DataModel>,
private readonly embassyApi: ApiService,
@Inject(WINDOW) private readonly windowRef: Window,
) {
super('Dark')
this.patch
.watch$('ui', 'theme')
.pipe(take(1), filter(Boolean))
.subscribe(theme => {
this.next(theme)
})
}
override next(currentTheme: string): void {
this.embassyApi.setDbValue(['theme'], currentTheme)
this.windowRef.document.body.setAttribute('data-theme', currentTheme)
super.next(currentTheme)
}
}

View File

@@ -306,7 +306,22 @@ ion-footer.with-widgets {
width: calc(100% - var(--widgets-width)); width: calc(100% - var(--widgets-width));
} }
body[data-theme='Light'] {
.service-card {
--background: #f4f4f5;
--inner-border-width: 0;
}
ion-header {
box-shadow: 0 1px #e0e0e0;
&::after {
display: none;
}
}
}
ul { ul {
padding-left: 40px; padding-left: 40px;
list-style-type: disc; list-style-type: disc;
} }