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 emver::VersionRange;
use serde_json::json;
use serde_json::{json, Value};
use super::v0_3_0::V0_3_0_COMPAT;
use super::*;
@@ -61,6 +61,8 @@ impl VersionT for Version {
.put(db, &parsed_url)
.await?;
}
ui["theme"] = json!("Dark".to_string());
ui["widgets"] = json!([]);
Ok(())
}
async fn down<Db: DbHandle>(&self, db: &mut Db) -> Result<(), Error> {
@@ -87,6 +89,11 @@ impl VersionT for Version {
.await?;
}
if let Value::Object(ref mut obj) = *ui {
obj.remove("theme");
obj.remove("widgets");
}
ui["marketplace"]["known-hosts"][COMMUNITY_URL].take();
ui.save(db).await?;
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-fonts.less",
"node_modules/@taiga-ui/styles/taiga-ui-global.less",
"projects/shared/styles/variables.scss",
"projects/shared/styles/global.scss",
"projects/shared/styles/shared.scss",
"projects/ui/src/styles.scss"

View File

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

View File

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

View File

@@ -14,5 +14,7 @@
"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">
<img alt="" [src]="'data:image/png;base64,' + pkg.icon | trustUrl" />
</ion-thumbnail>

View File

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

View File

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

View File

@@ -1,3 +1,4 @@
import { CommonModule } from '@angular/common'
import { NgModule } from '@angular/core'
import { FormsModule } from '@angular/forms'
import { IonicModule } from '@ionic/angular'
@@ -6,7 +7,7 @@ import { ResponsiveColModule } from '@start9labs/shared'
import { SearchComponent } from './search.component'
@NgModule({
imports: [IonicModule, FormsModule, ResponsiveColModule],
imports: [IonicModule, FormsModule, CommonModule, ResponsiveColModule],
declarations: [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/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/http.types'
export * from './types/rpc.types'
@@ -53,6 +58,7 @@ export * from './types/url'
export * from './types/workspace-config'
export * from './tokens/relative-url'
export * from './tokens/theme'
export * from './util/base-64'
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-rgb: 24, 24, 24;
--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-tint: #000000;
@@ -92,4 +92,4 @@
--alt-green: #3DCF6F;
--alt-blue: #00A8A8;
--alt-purple: #9747FF;
}
}

View File

@@ -1,6 +1,6 @@
<tui-root
*ngIf="widgetDrawer$ | async as drawer"
tuiMode="onDark"
[tuiMode]="(theme$ | async) === 'Dark' ? 'onDark' : null"
[style.--widgets-width.px]="drawer.open ? drawer.width : 0"
>
<ion-app appEnter>
@@ -16,7 +16,7 @@
side="start"
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>
</ion-content>
</ion-menu>
@@ -74,4 +74,13 @@
<toast-container></toast-container>
</ion-app>
</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;
}
.menu {
:host-context(body[data-theme='Light']) & {
--ion-color-base: #F4F4F5 !important;
}
}
.container {
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 { AuthService } from './services/auth.service'
import { SplitPaneTracker } from './services/split-pane.service'
@@ -11,6 +11,8 @@ import {
ClientStorageService,
WidgetDrawer,
} from './services/client-storage.service'
import { ThemeSwitcherService } from './services/theme-switcher.service'
import { THEME } from '@start9labs/shared'
@Component({
selector: 'app-root',
@@ -21,6 +23,7 @@ export class AppComponent implements OnDestroy {
readonly subscription = merge(this.patchData, this.patchMonitor).subscribe()
readonly sidebarOpen$ = this.splitPane.sidebarOpen$
readonly widgetDrawer$ = this.clientStorageService.widgetDrawer$
readonly theme$ = inject(THEME)
constructor(
private readonly titleService: Title,
@@ -31,6 +34,7 @@ export class AppComponent implements OnDestroy {
readonly authService: AuthService,
readonly connection: ConnectionService,
readonly clientStorageService: ClientStorageService,
readonly themeSwitcher: ThemeSwitcherService,
) {}
ngOnInit() {

View File

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

View File

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

View File

@@ -1,11 +1,17 @@
<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>
<ion-item-group class="menu">
<ion-menu-toggle *ngFor="let page of pages" auto-hide="false">
<ion-item
button
class="link"
routerLinkActive="link_selected"
color="transparent"
routerDirection="root"
lines="none"
@@ -45,7 +51,6 @@
</ion-item>
</ion-menu-toggle>
</ion-item-group>
<img
appSnek
class="snek"

View File

@@ -12,6 +12,15 @@
padding: 30px 0;
}
.link {
--border-radius: 0;
:host-context(body[data-theme='Light']) &_selected {
--ion-color-base: #333;
--ion-color-contrast: #fff;
}
}
.icon {
margin-left: 10px;
}
@@ -20,7 +29,7 @@
color: var(--ion-color-dark-shade);
&_selected {
color: #fff;
color: var(--ion-color-dark);
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 { PatchDB } from 'patch-db-client'
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 { DataModel } from 'src/app/services/patch-db/data-model'
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'
@Component({
@@ -82,6 +87,8 @@ export class MenuComponent {
readonly sidebarOpen$ = this.splitPane.sidebarOpen$
readonly theme$ = inject(THEME)
constructor(
private readonly patch: PatchDB<DataModel>,
private readonly eosService: EOSService,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -19,7 +19,10 @@
<form (ngSubmit)="submit()">
<div style="margin: 0 0 24px 16px">
<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
#mainInput
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 { getErrorMessage } from '@start9labs/shared'
import { getErrorMessage, THEME } from '@start9labs/shared'
import { MaskPipe } from 'src/app/pipes/mask/mask.pipe'
@Component({
@@ -21,6 +21,8 @@ export class GenericInputComponent {
error: string | IonicSafeString = ''
readonly theme$ = inject(THEME)
constructor(
private readonly modalCtrl: ModalController,
private readonly mask: MaskPipe,

View File

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

View File

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

View File

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

View File

@@ -1,3 +1,13 @@
.less-large {
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,
FormsModule,
IonicModule,
RouterModule.forChild(routes),
SharedPipesModule,
RouterModule.forChild(routes),
],
declarations: [LoginPage],
})

View File

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

View File

@@ -1,10 +1,37 @@
ion-card-title {
margin: 24px 0;
font-family: 'Montserrat';
font-weight: 500;
font-size: x-large;
font-variant: all-small-caps;
--color: var(--ion-color-dark);
.content {
--background: #222428;
}
.card {
background: #414141;
}
.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 {
@@ -15,8 +42,7 @@ ion-item {
--border-style: solid;
--border-color: var(--ion-color-light);
--border-radius: 4px 0 0 4px;
box-shadow: 0 3px 1px -2px rgba(0, 0, 0, 0.2),
0 2px 2px 0 rgba(0, 0, 0, 0.14),
box-shadow: 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 2px 2px 0 rgba(0, 0, 0, 0.14),
0 1px 5px 0 rgba(0, 0, 0, 0.12);
ion-button {
@@ -26,26 +52,22 @@ ion-item {
ion-card {
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;
min-height: 16rem;
}
.input-label {
text-align: left;
padding-bottom: 2px;
font-size: small;
font-weight: bold;
color: var(--ion-color-dark);
}
.login-button {
margin-inline-start: 0;
margin-inline-end: 0;
height: 49px;
font-size: larger;
font-weight: bold;
}
.inline {
.form {
margin-bottom: 12px;
* {
display: inline-block;
vertical-align: middle;
@@ -53,5 +75,5 @@ ion-card {
}
.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 { OSUpdatePageModule } from 'src/app/modals/os-update/os-update.page.module'
import { BackupColorPipeModule } from 'src/app/pipes/backup-color/backup-color.module'
import { ThemeSwitcherModule } from '../theme-switcher/theme-switcher.module'
const routes: Routes = [
{
@@ -26,6 +27,7 @@ const routes: Routes = [
BadgeMenuComponentModule,
OSUpdatePageModule,
BackupColorPipeModule,
ThemeSwitcherModule,
],
declarations: [ServerShowPage],
})

View File

@@ -38,65 +38,65 @@
{{ cat.key }}
</ion-text>
</ion-item-divider>
<ng-container *ngFor="let button of cat.value">
<ion-item
button
[style.display]="(button.title === 'Repair Disk' && !(showDiskRepair$ | async)) ? 'none' : 'block'"
[detail]="button.detail"
[disabled]="button.disabled$ | async"
(click)="button.action()"
>
<ion-icon slot="start" [name]="button.icon"></ion-icon>
<ion-label>
<h2>{{ button.title }}</h2>
<p *ngIf="button.description">{{ button.description }}</p>
<!-- <theme-switcher *ngIf="cat.key === 'Manage'"></theme-switcher> -->
<ion-item
*ngFor="let button of cat.value"
button
[style.display]="(button.title === 'Repair Disk' && !(showDiskRepair$ | async)) ? 'none' : 'block'"
[detail]="button.detail"
[disabled]="button.disabled$ | async"
(click)="button.action()"
>
<ion-icon slot="start" [name]="button.icon"></ion-icon>
<ion-label>
<h2>{{ button.title }}</h2>
<p *ngIf="button.description">{{ button.description }}</p>
<!-- "Create Backup" button only -->
<p *ngIf="button.title === 'Create Backup'">
<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'">
<!-- "Create Backup" button only -->
<p *ngIf="button.title === 'Create Backup'">
<ng-container *ngIf="server['status-info'] as statusInfo">
<ion-text
*ngIf="server['status-info'].updated; else notUpdated"
class="inline"
color="warning"
[color]="server['last-backup'] | backupColor"
*ngIf="!statusInfo['backup-progress'] && !statusInfo['update-progress']"
>
Update Complete. Restart to apply changes
Last Backup: {{ server['last-backup'] ? (server['last-backup'] |
date: 'medium') : 'never' }}
</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>
<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
*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>
</p>
</ion-label>
</ion-item>
</ng-container>
</ng-template>
</p>
</ion-label>
</ion-item>
</div>
</ion-item-group>
</ion-content>

View File

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

View File

@@ -53,7 +53,7 @@
justify-content: space-evenly
}
.service-card {
.card {
background: radial-gradient(var(--ion-color-step-100), transparent);
min-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 {
height: 100%;
text-align: center;
background: #333;
background: var(--tui-base-02);
border-radius: 24px;
padding: 24px;
box-sizing: border-box;

View File

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

View File

@@ -20,6 +20,7 @@ export interface UIData {
}
}
'ack-instructions': Record<string, boolean>
theme: string
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));
}
body[data-theme='Light'] {
.service-card {
--background: #f4f4f5;
--inner-border-width: 0;
}
ion-header {
box-shadow: 0 1px #e0e0e0;
&::after {
display: none;
}
}
}
ul {
padding-left: 40px;
list-style-type: disc;
}
}