iobroker.jetframe 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +357 -0
- package/admin/SF-Pro.ttf +0 -0
- package/admin/admin.d.ts +65 -0
- package/admin/frame.html +982 -0
- package/admin/frame.html.bak-aircraft-card-real-row-20260518-1608 +1236 -0
- package/admin/frame.html.bak-aircraft-card-structure-20260518-1517 +1236 -0
- package/admin/frame.html.bak-aircraft-logo-id-fix-20260518-1639 +1239 -0
- package/admin/frame.html.bak-shortcut-test +1236 -0
- package/admin/frame.html.bak-tablet-class-20260518-1729 +1239 -0
- package/admin/heatmap.html +216 -0
- package/admin/index.html +268 -0
- package/admin/index_m.html +1749 -0
- package/admin/jetframe.css +1260 -0
- package/admin/jetframe.css.bak-airbus-landscape-fix +4630 -0
- package/admin/jetframe.css.bak-aircraft-card-clean-equal-20260518-1438 +4899 -0
- package/admin/jetframe.css.bak-aircraft-card-real-row-20260518-1608 +4814 -0
- package/admin/jetframe.css.bak-aircraft-card-row-left-20260518-1525 +4604 -0
- package/admin/jetframe.css.bak-aircraft-card-slim-equal-20260518-1446 +4647 -0
- package/admin/jetframe.css.bak-aircraft-card-structure-20260518-1517 +4646 -0
- package/admin/jetframe.css.bak-aircraft-inline-final-20260518-1527 +4654 -0
- package/admin/jetframe.css.bak-aircraft-row-compact-fix-20260518-1639 +4763 -0
- package/admin/jetframe.css.bak-before-aircrafttype-purge +4818 -0
- package/admin/jetframe.css.bak-before-cleanup +4670 -0
- package/admin/jetframe.css.bak-before-remove-tablet-only-20260518-1711 +4896 -0
- package/admin/jetframe.css.bak-before-tablet-layout-rework-20260518-1650 +4914 -0
- package/admin/jetframe.css.bak-clean-duplicate-fonts-20260518-1340 +4975 -0
- package/admin/jetframe.css.bak-clean-old-index-fix-20260518-1937 +5167 -0
- package/admin/jetframe.css.bak-hardleft-airbus +4751 -0
- package/admin/jetframe.css.bak-index-iphone-landscape-20260518-1931 +5030 -0
- package/admin/jetframe.css.bak-index-landscape-final-20260518-1941 +5167 -0
- package/admin/jetframe.css.bak-index-landscape-real-20260518-1936 +5186 -0
- package/admin/jetframe.css.bak-landscape-compact-jumbo-bold-20260518-1343 +4802 -0
- package/admin/jetframe.css.bak-logo-align-final +4551 -0
- package/admin/jetframe.css.bak-logo-final2 +4551 -0
- package/admin/jetframe.css.bak-narrowbody-font-fix +4992 -0
- package/admin/jetframe.css.bak-nuke-airbus-align +4790 -0
- package/admin/jetframe.css.bak-pill-balance-20260518-1603 +4773 -0
- package/admin/jetframe.css.bak-pill-balance-fix +4910 -0
- package/admin/jetframe.css.bak-radar-fix-fonts +4710 -0
- package/admin/jetframe.css.bak-shortcut-test +4899 -0
- package/admin/jetframe.css.bak-smaller-aircraft-card-fonts-20260518-1345 +4897 -0
- package/admin/jetframe.css.bak-tablet-fix-real-20260518-1748 +4945 -0
- package/admin/jetframe.css.bak-tablet-fullscreen-fix-20260518-1804 +4972 -0
- package/admin/jetframe.css.bak-tablet-landscape-layout-20260518-1645 +4802 -0
- package/admin/jetframe.css.bak-tablet-layout-final-20260518-1839 +4802 -0
- package/admin/jetframe.css.bak-tablet-layout-v3-20260518-1729 +4802 -0
- package/admin/jetframe.css.bak-tablet-layout-v4-20260518-1801 +4957 -0
- package/admin/jetframe.css.bak-tablet-layout-v5-20260518-1843 +4970 -0
- package/admin/jetframe.css.bak-tablet-layout-v6-20260518-1848 +4958 -0
- package/admin/jetframe.css.bak-tablet-layout-v7-20260518-1909 +4985 -0
- package/admin/jetframe.css.bak-tablet-only-landscape-v2-20260518-1707 +4802 -0
- package/admin/jetframe.css.bak-tablet-pages-final-20260519-1857 +5188 -0
- package/admin/jetframe.css.bak-tablet-pages-final-20260519-1859 +5347 -0
- package/admin/jetframe.css.bak-tablet-pages-v2-20260519-190807 +5349 -0
- package/admin/jetframe.css.bak-typography-align-final +4818 -0
- package/admin/jetframe.png +0 -0
- package/admin/manifest.webmanifest +15 -0
- package/admin/src/app.tsx +58 -0
- package/admin/src/components/settings.tsx +97 -0
- package/admin/src/i18n/de.json +11 -0
- package/admin/src/i18n/en.json +11 -0
- package/admin/src/i18n/es.json +11 -0
- package/admin/src/i18n/fr.json +11 -0
- package/admin/src/i18n/i18n.d.ts +28 -0
- package/admin/src/i18n/it.json +11 -0
- package/admin/src/i18n/nl.json +11 -0
- package/admin/src/i18n/pl.json +11 -0
- package/admin/src/i18n/pt.json +11 -0
- package/admin/src/i18n/ru.json +11 -0
- package/admin/src/i18n/uk.json +11 -0
- package/admin/src/i18n/zh-cn.json +11 -0
- package/admin/src/index.tsx +25 -0
- package/admin/stats.html +228 -0
- package/admin/style.css +32 -0
- package/admin/tsconfig.json +11 -0
- package/admin/words.js +46 -0
- package/build/lib/adsb.js +218 -0
- package/build/lib/adsb.js.map +7 -0
- package/build/lib/airportNamesDe.js +131 -0
- package/build/lib/airportNamesDe.js.map +7 -0
- package/build/lib/airports.js +281 -0
- package/build/lib/airports.js.map +7 -0
- package/build/lib/classify.js +339 -0
- package/build/lib/classify.js.map +7 -0
- package/build/lib/config.js +103 -0
- package/build/lib/config.js.map +7 -0
- package/build/lib/flightInfo.js +1409 -0
- package/build/lib/flightInfo.js.map +7 -0
- package/build/lib/geo.js +84 -0
- package/build/lib/geo.js.map +7 -0
- package/build/lib/images.js +422 -0
- package/build/lib/images.js.map +7 -0
- package/build/lib/specialLiveries.js +342 -0
- package/build/lib/specialLiveries.js.map +7 -0
- package/build/lib/states.js +971 -0
- package/build/lib/states.js.map +7 -0
- package/build/lib/staticFiles.js +73 -0
- package/build/lib/staticFiles.js.map +7 -0
- package/build/lib/types.js +17 -0
- package/build/lib/types.js.map +7 -0
- package/build/lib/visConfig.js +52 -0
- package/build/lib/visConfig.js.map +7 -0
- package/build/main.js +1454 -0
- package/build/main.js.map +7 -0
- package/io-package.json +169 -0
- package/package.json +82 -0
|
Binary file
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "JetFrame",
|
|
3
|
+
"short_name": "JetFrame",
|
|
4
|
+
"start_url": "index.html",
|
|
5
|
+
"display": "standalone",
|
|
6
|
+
"background_color": "#05060a",
|
|
7
|
+
"theme_color": "#05060a",
|
|
8
|
+
"icons": [
|
|
9
|
+
{
|
|
10
|
+
"src": "jetframe.png",
|
|
11
|
+
"sizes": "512x512",
|
|
12
|
+
"type": "image/png"
|
|
13
|
+
}
|
|
14
|
+
]
|
|
15
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-require-imports */
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { type Theme, type StyleRules, withStyles } from '@material-ui/core/styles';
|
|
4
|
+
|
|
5
|
+
import GenericApp from '@iobroker/adapter-react/GenericApp';
|
|
6
|
+
import Settings from './components/settings';
|
|
7
|
+
import type { GenericAppProps, GenericAppSettings } from '@iobroker/adapter-react/types';
|
|
8
|
+
|
|
9
|
+
const styles = (_theme: Theme): StyleRules => ({
|
|
10
|
+
root: {},
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
class App extends GenericApp {
|
|
14
|
+
constructor(props: GenericAppProps) {
|
|
15
|
+
const extendedProps: GenericAppSettings = {
|
|
16
|
+
...props,
|
|
17
|
+
encryptedFields: [],
|
|
18
|
+
translations: {
|
|
19
|
+
en: require('./i18n/en.json'),
|
|
20
|
+
de: require('./i18n/de.json'),
|
|
21
|
+
ru: require('./i18n/ru.json'),
|
|
22
|
+
pt: require('./i18n/pt.json'),
|
|
23
|
+
nl: require('./i18n/nl.json'),
|
|
24
|
+
fr: require('./i18n/fr.json'),
|
|
25
|
+
it: require('./i18n/it.json'),
|
|
26
|
+
es: require('./i18n/es.json'),
|
|
27
|
+
pl: require('./i18n/pl.json'),
|
|
28
|
+
uk: require('./i18n/uk.json'),
|
|
29
|
+
'zh-cn': require('./i18n/zh-cn.json'),
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
super(props, extendedProps);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
onConnectionReady(): void {
|
|
36
|
+
// executed when connection is ready
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
render(): React.JSX.Element {
|
|
40
|
+
if (!this.state.loaded) {
|
|
41
|
+
return super.render();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<div className="App">
|
|
46
|
+
<Settings
|
|
47
|
+
native={this.state.native}
|
|
48
|
+
onChange={(attr, value) => this.updateNativeValue(attr, value)}
|
|
49
|
+
/>
|
|
50
|
+
{this.renderError()}
|
|
51
|
+
{this.renderToast()}
|
|
52
|
+
{this.renderSaveCloseButtons()}
|
|
53
|
+
</div>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export default withStyles(styles)(App);
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { withStyles } from '@material-ui/core/styles';
|
|
3
|
+
import type { CreateCSSProperties } from '@material-ui/core/styles/withStyles';
|
|
4
|
+
import TextField from '@material-ui/core/TextField';
|
|
5
|
+
import Input from '@material-ui/core/Input';
|
|
6
|
+
import FormHelperText from '@material-ui/core/FormHelperText';
|
|
7
|
+
import FormControl from '@material-ui/core/FormControl';
|
|
8
|
+
import Select from '@material-ui/core/Select';
|
|
9
|
+
import MenuItem from '@material-ui/core/MenuItem';
|
|
10
|
+
import I18n from '@iobroker/adapter-react/i18n';
|
|
11
|
+
|
|
12
|
+
const styles = (): Record<string, CreateCSSProperties> => ({
|
|
13
|
+
input: {
|
|
14
|
+
marginTop: 0,
|
|
15
|
+
minWidth: 500,
|
|
16
|
+
},
|
|
17
|
+
controlElement: {
|
|
18
|
+
marginBottom: 10,
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
interface SettingsProps {
|
|
23
|
+
classes: Record<string, string>;
|
|
24
|
+
native: Record<string, any>;
|
|
25
|
+
onChange: (attr: string, value: any) => void;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface SettingsState {
|
|
29
|
+
dummy?: undefined;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
class Settings extends React.Component<SettingsProps, SettingsState> {
|
|
33
|
+
constructor(props: SettingsProps) {
|
|
34
|
+
super(props);
|
|
35
|
+
this.state = {};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
renderInput(title: AdminWord, attr: string, type: string): React.JSX.Element {
|
|
39
|
+
return (
|
|
40
|
+
<TextField
|
|
41
|
+
label={I18n.t(title)}
|
|
42
|
+
className={`${this.props.classes.input} ${this.props.classes.controlElement}`}
|
|
43
|
+
value={this.props.native[attr] || ''}
|
|
44
|
+
type={type || 'text'}
|
|
45
|
+
onChange={e => this.props.onChange(attr, e.target.value)}
|
|
46
|
+
margin="normal"
|
|
47
|
+
fullWidth
|
|
48
|
+
/>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
renderSelect(title: AdminWord, attr: string, options: { value: string; title: AdminWord }[]): React.JSX.Element {
|
|
53
|
+
return (
|
|
54
|
+
<FormControl className={`${this.props.classes.input} ${this.props.classes.controlElement}`}>
|
|
55
|
+
<Select
|
|
56
|
+
value={this.props.native[attr] || 'browser'}
|
|
57
|
+
onChange={e => this.props.onChange(attr, e.target.value)}
|
|
58
|
+
input={
|
|
59
|
+
<Input
|
|
60
|
+
name={attr}
|
|
61
|
+
id={`${attr}-helper`}
|
|
62
|
+
/>
|
|
63
|
+
}
|
|
64
|
+
>
|
|
65
|
+
{options.map(item => (
|
|
66
|
+
<MenuItem
|
|
67
|
+
key={item.value}
|
|
68
|
+
value={item.value}
|
|
69
|
+
>
|
|
70
|
+
{I18n.t(item.title)}
|
|
71
|
+
</MenuItem>
|
|
72
|
+
))}
|
|
73
|
+
</Select>
|
|
74
|
+
<FormHelperText>{I18n.t(title)}</FormHelperText>
|
|
75
|
+
</FormControl>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
render(): React.JSX.Element {
|
|
80
|
+
return (
|
|
81
|
+
<form>
|
|
82
|
+
{this.renderSelect('speechMode', 'speechMode', [
|
|
83
|
+
{ value: 'browser', title: 'speechModeBrowser' },
|
|
84
|
+
{ value: 'external', title: 'speechModeExternal' },
|
|
85
|
+
{ value: 'both', title: 'speechModeBoth' },
|
|
86
|
+
{ value: 'off', title: 'speechModeOff' },
|
|
87
|
+
])}
|
|
88
|
+
|
|
89
|
+
<br />
|
|
90
|
+
|
|
91
|
+
{this.renderInput('speechTemplate', 'speechTemplate', 'text')}
|
|
92
|
+
</form>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export default withStyles(styles)(Settings);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"jetframe adapter settings": "Adaptereinstellungen für jetframe",
|
|
3
|
+
"option1": "Option1",
|
|
4
|
+
"option2": "Option2",
|
|
5
|
+
"speechMode": "Sprachausgabe",
|
|
6
|
+
"speechModeBrowser": "Browser intern",
|
|
7
|
+
"speechModeExternal": "Extern per Datenpunkt",
|
|
8
|
+
"speechModeBoth": "Browser + extern",
|
|
9
|
+
"speechModeOff": "Aus",
|
|
10
|
+
"speechTemplate": "Ansage-Template"
|
|
11
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"jetframe adapter settings": "Adapter settings for jetframe",
|
|
3
|
+
"option1": "option1",
|
|
4
|
+
"option2": "option2",
|
|
5
|
+
"speechMode": "Sprachausgabe",
|
|
6
|
+
"speechModeBrowser": "Browser intern",
|
|
7
|
+
"speechModeExternal": "Extern per Datenpunkt",
|
|
8
|
+
"speechModeBoth": "Browser + extern",
|
|
9
|
+
"speechModeOff": "Aus",
|
|
10
|
+
"speechTemplate": "Ansage-Template"
|
|
11
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"jetframe adapter settings": "Ajustes del adaptador para jetframe",
|
|
3
|
+
"option1": "opción1",
|
|
4
|
+
"option2": "opción2",
|
|
5
|
+
"speechMode": "Sprachausgabe",
|
|
6
|
+
"speechModeBrowser": "Browser intern",
|
|
7
|
+
"speechModeExternal": "Extern per Datenpunkt",
|
|
8
|
+
"speechModeBoth": "Browser + extern",
|
|
9
|
+
"speechModeOff": "Aus",
|
|
10
|
+
"speechTemplate": "Ansage-Template"
|
|
11
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"jetframe adapter settings": "Paramètres d'adaptateur pour jetframe",
|
|
3
|
+
"option1": "option1",
|
|
4
|
+
"option2": "option2",
|
|
5
|
+
"speechMode": "Sprachausgabe",
|
|
6
|
+
"speechModeBrowser": "Browser intern",
|
|
7
|
+
"speechModeExternal": "Extern per Datenpunkt",
|
|
8
|
+
"speechModeBoth": "Browser + extern",
|
|
9
|
+
"speechModeOff": "Aus",
|
|
10
|
+
"speechTemplate": "Ansage-Template"
|
|
11
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file loads the translations keys from `i18n/en.json` file and overrides
|
|
3
|
+
* the declarations for the translate function `I18n.t` available in "@iobroker/adapter-react/i18n".
|
|
4
|
+
* Using these definitions it is ensured that all used translations in the React
|
|
5
|
+
* context are defined at least in the english translations file.
|
|
6
|
+
* This will add no overhead in the generated code since it just reexports the
|
|
7
|
+
* I18n class but with a more typed `t` function.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/*
|
|
11
|
+
* DO NOT add any imports or exports in this file or it will stop working!
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Available words in `i18n/en.json`.
|
|
16
|
+
*/
|
|
17
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
|
|
18
|
+
declare type AdminWord = keyof typeof import('./en.json');
|
|
19
|
+
|
|
20
|
+
declare module '@iobroker/adapter-react/i18n' {
|
|
21
|
+
/**
|
|
22
|
+
* Translate the given string to the selected language.
|
|
23
|
+
*
|
|
24
|
+
* @param word The (key) word to look up the string. Has to be defined at least in `i18n/en.json`.
|
|
25
|
+
* @param args Optional arguments which will replace the first (second, third, ...) occurence of %s
|
|
26
|
+
*/
|
|
27
|
+
function t(word: AdminWord, ...args: string[]): string;
|
|
28
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"jetframe adapter settings": "Impostazioni dell'adattatore per jetframe",
|
|
3
|
+
"option1": "opzione1",
|
|
4
|
+
"option2": "opzione2",
|
|
5
|
+
"speechMode": "Sprachausgabe",
|
|
6
|
+
"speechModeBrowser": "Browser intern",
|
|
7
|
+
"speechModeExternal": "Extern per Datenpunkt",
|
|
8
|
+
"speechModeBoth": "Browser + extern",
|
|
9
|
+
"speechModeOff": "Aus",
|
|
10
|
+
"speechTemplate": "Ansage-Template"
|
|
11
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"jetframe adapter settings": "Adapterinstellingen voor jetframe",
|
|
3
|
+
"option1": "optie1",
|
|
4
|
+
"option2": "optie2",
|
|
5
|
+
"speechMode": "Sprachausgabe",
|
|
6
|
+
"speechModeBrowser": "Browser intern",
|
|
7
|
+
"speechModeExternal": "Extern per Datenpunkt",
|
|
8
|
+
"speechModeBoth": "Browser + extern",
|
|
9
|
+
"speechModeOff": "Aus",
|
|
10
|
+
"speechTemplate": "Ansage-Template"
|
|
11
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"jetframe adapter settings": "Ustawienia adaptera dla jetframe",
|
|
3
|
+
"option1": "opcja 1",
|
|
4
|
+
"option2": "opcja 2",
|
|
5
|
+
"speechMode": "Sprachausgabe",
|
|
6
|
+
"speechModeBrowser": "Browser intern",
|
|
7
|
+
"speechModeExternal": "Extern per Datenpunkt",
|
|
8
|
+
"speechModeBoth": "Browser + extern",
|
|
9
|
+
"speechModeOff": "Aus",
|
|
10
|
+
"speechTemplate": "Ansage-Template"
|
|
11
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"jetframe adapter settings": "Configurações do adaptador para jetframe",
|
|
3
|
+
"option1": "opção1",
|
|
4
|
+
"option2": "opção2",
|
|
5
|
+
"speechMode": "Sprachausgabe",
|
|
6
|
+
"speechModeBrowser": "Browser intern",
|
|
7
|
+
"speechModeExternal": "Extern per Datenpunkt",
|
|
8
|
+
"speechModeBoth": "Browser + extern",
|
|
9
|
+
"speechModeOff": "Aus",
|
|
10
|
+
"speechTemplate": "Ansage-Template"
|
|
11
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"jetframe adapter settings": "Настройки адаптера для jetframe",
|
|
3
|
+
"option1": "вариант 1",
|
|
4
|
+
"option2": "вариант 2",
|
|
5
|
+
"speechMode": "Sprachausgabe",
|
|
6
|
+
"speechModeBrowser": "Browser intern",
|
|
7
|
+
"speechModeExternal": "Extern per Datenpunkt",
|
|
8
|
+
"speechModeBoth": "Browser + extern",
|
|
9
|
+
"speechModeOff": "Aus",
|
|
10
|
+
"speechTemplate": "Ansage-Template"
|
|
11
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"jetframe adapter settings": "Налаштування адаптера для jetframe",
|
|
3
|
+
"option1": "варіант1",
|
|
4
|
+
"option2": "варіант2",
|
|
5
|
+
"speechMode": "Sprachausgabe",
|
|
6
|
+
"speechModeBrowser": "Browser intern",
|
|
7
|
+
"speechModeExternal": "Extern per Datenpunkt",
|
|
8
|
+
"speechModeBoth": "Browser + extern",
|
|
9
|
+
"speechModeOff": "Aus",
|
|
10
|
+
"speechTemplate": "Ansage-Template"
|
|
11
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"jetframe adapter settings": "jetframe的适配器设置",
|
|
3
|
+
"option1": "选项1",
|
|
4
|
+
"option2": "选项2",
|
|
5
|
+
"speechMode": "Sprachausgabe",
|
|
6
|
+
"speechModeBrowser": "Browser intern",
|
|
7
|
+
"speechModeExternal": "Extern per Datenpunkt",
|
|
8
|
+
"speechModeBoth": "Browser + extern",
|
|
9
|
+
"speechModeOff": "Aus",
|
|
10
|
+
"speechTemplate": "Ansage-Template"
|
|
11
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import ReactDOM from 'react-dom';
|
|
3
|
+
import { MuiThemeProvider } from '@material-ui/core/styles';
|
|
4
|
+
import theme from '@iobroker/adapter-react/Theme';
|
|
5
|
+
import Utils from '@iobroker/adapter-react/Components/Utils';
|
|
6
|
+
import App from './app';
|
|
7
|
+
|
|
8
|
+
let themeName = Utils.getThemeName();
|
|
9
|
+
|
|
10
|
+
function build(): void {
|
|
11
|
+
ReactDOM.render(
|
|
12
|
+
<MuiThemeProvider theme={theme(themeName)}>
|
|
13
|
+
<App
|
|
14
|
+
adapterName="jetframe"
|
|
15
|
+
onThemeChange={_theme => {
|
|
16
|
+
themeName = _theme;
|
|
17
|
+
build();
|
|
18
|
+
}}
|
|
19
|
+
/>
|
|
20
|
+
</MuiThemeProvider>,
|
|
21
|
+
document.getElementById('root'),
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
build();
|
package/admin/stats.html
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="de">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8"/>
|
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no,viewport-fit=cover"/>
|
|
6
|
+
<title>JetFrame Statistik</title>
|
|
7
|
+
<link rel="manifest" href="manifest.webmanifest">
|
|
8
|
+
<link rel="apple-touch-icon" href="jetframe.png">
|
|
9
|
+
<link rel="icon" type="image/png" href="jetframe.png">
|
|
10
|
+
<meta name="apple-mobile-web-app-capable" content="yes">
|
|
11
|
+
<meta name="mobile-web-app-capable" content="yes">
|
|
12
|
+
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
|
13
|
+
<meta name="theme-color" content="#050709">
|
|
14
|
+
<link rel="stylesheet" href="jetframe.css?v=20260522-fix14">
|
|
15
|
+
</head>
|
|
16
|
+
<body class="jf-page-stats">
|
|
17
|
+
|
|
18
|
+
<div id="simpleApiWarning" class="simpleApiWarning">
|
|
19
|
+
⚠️ Simple-API nicht erreichbar<br>
|
|
20
|
+
<span id="simpleApiWarningText"></span>
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
<div id="app">
|
|
24
|
+
<div class="jf-shell">
|
|
25
|
+
<div class="header">
|
|
26
|
+
<a class="backBtn" href="index.html" aria-label="Zurück">‹</a>
|
|
27
|
+
<div>
|
|
28
|
+
<div class="title">✈️ JetFrame</div>
|
|
29
|
+
<div id="dateText" class="sub">Statistik</div>
|
|
30
|
+
</div>
|
|
31
|
+
<div class="livePill">
|
|
32
|
+
<span class="dot"></span><span>Live</span>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<div class="grid">
|
|
37
|
+
<div class="stat big">
|
|
38
|
+
<div class="stLabel">Heute</div>
|
|
39
|
+
<div id="todayTotal" class="stVal">–</div>
|
|
40
|
+
<div id="todaySmall" class="stSub">Flüge heute</div>
|
|
41
|
+
</div>
|
|
42
|
+
<div class="stat">
|
|
43
|
+
<div class="stLabel">Gestern</div>
|
|
44
|
+
<div id="yesterdayTotal" class="stVal">–</div>
|
|
45
|
+
<div id="yesterdaySmall" class="stSub">Rückblick</div>
|
|
46
|
+
</div>
|
|
47
|
+
<div class="stat warn">
|
|
48
|
+
<div class="stLabel">Rekordtag</div>
|
|
49
|
+
<div id="bestDay" class="stVal">–</div>
|
|
50
|
+
<div id="bestDaySmall" class="stSub">Alltime</div>
|
|
51
|
+
</div>
|
|
52
|
+
<div class="stat">
|
|
53
|
+
<div class="stLabel">Beste Zeit</div>
|
|
54
|
+
<div id="bestHour" class="stVal">–</div>
|
|
55
|
+
<div id="bestHourSmall" class="stSub">allgemein</div>
|
|
56
|
+
</div>
|
|
57
|
+
<div class="stat">
|
|
58
|
+
<div class="stLabel">Heavy heute</div>
|
|
59
|
+
<div id="heavyToday" class="stVal">–</div>
|
|
60
|
+
<div id="heavySmall" class="stSub">A380 / B747 separat</div>
|
|
61
|
+
</div>
|
|
62
|
+
<div class="stat">
|
|
63
|
+
<div class="stLabel">Specials heute</div>
|
|
64
|
+
<div id="specialToday" class="stVal">–</div>
|
|
65
|
+
<div id="specialSmall" class="stSub">Speziallackierungen</div>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
|
|
69
|
+
<div class="panels">
|
|
70
|
+
<div class="panel"><h3>Tageshistorie</h3><pre id="dailyHistory">–</pre></div>
|
|
71
|
+
<div class="panel"><h3>Gestern</h3><pre id="yesterdayDetails">–</pre></div>
|
|
72
|
+
<div class="panel"><h3>Top Airlines Alltime</h3><pre id="alltimeAirlines">–</pre></div>
|
|
73
|
+
<div class="panel"><h3>Top Routen Alltime</h3><pre id="alltimeRoutes">–</pre></div>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
|
|
78
|
+
<script>
|
|
79
|
+
'use strict';
|
|
80
|
+
let API_BASE = (() => {
|
|
81
|
+
const u = new URL(location.href);
|
|
82
|
+
return (location.protocol||'http:')+'//'+(u.searchParams.get('apiHost')||location.hostname||'127.0.0.1')+':'+(u.searchParams.get('apiPort')||'8087');
|
|
83
|
+
})();
|
|
84
|
+
const DP_ROOT = 'jetframe.'+(new URL(location.href).searchParams.get('instance')||'0').replace(/[^0-9]/g,'');
|
|
85
|
+
let VIS_CONFIG_LOADED = false;
|
|
86
|
+
|
|
87
|
+
const BASE = DP_ROOT+'.statistics';
|
|
88
|
+
const IDS = {
|
|
89
|
+
todayDate:BASE+'.today.date', todayTotal:BASE+'.today.totalFlights',
|
|
90
|
+
todayLandings:BASE+'.today.landings', todayDepartures:BASE+'.today.departures',
|
|
91
|
+
todayOverflights:BASE+'.today.overflights', todayBestHour:BASE+'.today.bestSpotterHour',
|
|
92
|
+
todayHeavy:BASE+'.today.heavyAircraftCount', todayA380:BASE+'.today.a380Count',
|
|
93
|
+
todayB747:BASE+'.today.b747Count', todaySpecial:BASE+'.today.specialLiveryCount',
|
|
94
|
+
yesterdayDate:BASE+'.yesterday.date', yesterdayTotal:BASE+'.yesterday.totalFlights',
|
|
95
|
+
yesterdayLandings:BASE+'.yesterday.landings', yesterdayDepartures:BASE+'.yesterday.departures',
|
|
96
|
+
yesterdayOverflights:BASE+'.yesterday.overflights', yesterdayBestHour:BASE+'.yesterday.bestSpotterHour',
|
|
97
|
+
yesterdayTopAirline:BASE+'.yesterday.topAirline', yesterdayTopRoute:BASE+'.yesterday.topRoute',
|
|
98
|
+
yesterdayHeavy:BASE+'.yesterday.heavyAircraftCount', yesterdayA380:BASE+'.yesterday.a380Count',
|
|
99
|
+
yesterdayB747:BASE+'.yesterday.b747Count', yesterdaySpecial:BASE+'.yesterday.specialLiveryCount',
|
|
100
|
+
bestDayDate:BASE+'.alltime.bestDayDate', bestDayFlights:BASE+'.alltime.bestDayFlights',
|
|
101
|
+
bestHour:BASE+'.alltime.bestHour', bestHourFlights:BASE+'.alltime.bestHourFlights',
|
|
102
|
+
alltimeHeavy:BASE+'.alltime.heavyAircraftCount', alltimeA380:BASE+'.alltime.a380Count',
|
|
103
|
+
alltimeB747:BASE+'.alltime.b747Count', alltimeSpecial:BASE+'.alltime.specialLiveryCount',
|
|
104
|
+
alltimeAirlines:BASE+'.alltime.airlineRankingText', alltimeRoutes:BASE+'.alltime.routeRankingText',
|
|
105
|
+
dailyHistory:BASE+'.history.dailyText',
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
function $(id){ return document.getElementById(id); }
|
|
109
|
+
function setWarn(v,t){ const b=$('simpleApiWarning'),m=$('simpleApiWarningText'); if(!b)return; if(m)m.textContent=t||API_BASE; b.classList.toggle('visible',!!v); }
|
|
110
|
+
async function checkApi(){ try{ const r=await fetch(API_BASE+'/getPlainValue/'+encodeURIComponent(DP_ROOT+'.status'),{cache:'no-store'}); if(!r.ok)throw 0; setWarn(false); return true; }catch{ setWarn(true); return false; } }
|
|
111
|
+
|
|
112
|
+
async function loadVis(){
|
|
113
|
+
const u=new URL(location.href), no=!u.searchParams.has('apiHost')&&!u.searchParams.has('apiPort');
|
|
114
|
+
const r=await fetch('/jetframe.admin/vis-config.json?v='+Date.now(),{cache:'no-store'});
|
|
115
|
+
if(!r.ok)throw new Error('vis-config.json fehlt');
|
|
116
|
+
const c=await r.json();
|
|
117
|
+
if(no){ API_BASE=(location.protocol||'http:')+'//'+String(c.simpleApiHost||location.hostname||'127.0.0.1').trim()+':'+String(c.simpleApiPort||'8087').trim(); }
|
|
118
|
+
VIS_CONFIG_LOADED=true;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function clean(v){
|
|
122
|
+
if(v==null)return''; v=String(v).trim();
|
|
123
|
+
if(v==='null'||v==='undefined')return'';
|
|
124
|
+
if((v[0]==='"'&&v.slice(-1)==='"')||(v[0]==="'"&&v.slice(-1)==="'"))v=v.slice(1,-1);
|
|
125
|
+
return v.replace(/\\"/g,'"').replace(/\\n/g,'\n').trim();
|
|
126
|
+
}
|
|
127
|
+
function num(v){ const n=Number(String(v).replace(',','.')); return Number.isFinite(n)?n:0; }
|
|
128
|
+
function fmt(v){ return num(v).toLocaleString('de-DE'); }
|
|
129
|
+
|
|
130
|
+
async function read(id){
|
|
131
|
+
if(!VIS_CONFIG_LOADED)return'';
|
|
132
|
+
try{ const r=await fetch(API_BASE+'/getPlainValue/'+encodeURIComponent(id),{cache:'no-store'}); if(!r.ok)return''; return clean(await r.text()); }
|
|
133
|
+
catch{ setWarn(true); return''; }
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async function readAll(){
|
|
137
|
+
return Object.fromEntries(await Promise.all(Object.entries(IDS).map(async([k,id])=>[k,await read(id)])));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function set(id,v){ const e=$(id); if(e)e.textContent=v; }
|
|
141
|
+
|
|
142
|
+
function render(d){
|
|
143
|
+
set('dateText', d.todayDate?'Heute · '+d.todayDate:'Statistik');
|
|
144
|
+
set('todayTotal', fmt(d.todayTotal));
|
|
145
|
+
set('todaySmall', fmt(d.todayLandings)+' Landungen · '+fmt(d.todayDepartures)+' Starts');
|
|
146
|
+
set('yesterdayTotal', fmt(d.yesterdayTotal));
|
|
147
|
+
set('yesterdaySmall', d.yesterdayDate||'noch kein Vortag');
|
|
148
|
+
set('bestDay', fmt(d.bestDayFlights));
|
|
149
|
+
set('bestDaySmall', d.bestDayDate||'noch kein Rekord');
|
|
150
|
+
set('bestHour', d.bestHour||'–');
|
|
151
|
+
set('bestHourSmall', num(d.bestHourFlights)?fmt(d.bestHourFlights)+' Flüge insgesamt':'allgemein');
|
|
152
|
+
set('heavyToday', fmt(d.todayHeavy));
|
|
153
|
+
set('heavySmall', 'A380 '+fmt(d.todayA380)+' · B747 '+fmt(d.todayB747)+' · Alltime '+fmt(d.alltimeHeavy));
|
|
154
|
+
set('specialToday', fmt(d.todaySpecial));
|
|
155
|
+
set('specialSmall', 'Alltime '+fmt(d.alltimeSpecial));
|
|
156
|
+
set('dailyHistory', d.dailyHistory||'Noch keine Tageshistorie vorhanden');
|
|
157
|
+
set('yesterdayDetails', [
|
|
158
|
+
(d.yesterdayDate||'Gestern')+' · '+fmt(d.yesterdayTotal)+' Flüge',
|
|
159
|
+
'Landungen: '+fmt(d.yesterdayLandings),
|
|
160
|
+
'Starts: '+fmt(d.yesterdayDepartures),
|
|
161
|
+
'Überflüge: '+fmt(d.yesterdayOverflights),
|
|
162
|
+
d.yesterdayBestHour?'Beste Zeit: '+d.yesterdayBestHour:'',
|
|
163
|
+
d.yesterdayTopAirline?'Top Airline: '+d.yesterdayTopAirline:'',
|
|
164
|
+
d.yesterdayTopRoute?'Top Route: '+d.yesterdayTopRoute:'',
|
|
165
|
+
'Heavy: '+fmt(d.yesterdayHeavy)+' · A380: '+fmt(d.yesterdayA380)+' · B747: '+fmt(d.yesterdayB747),
|
|
166
|
+
'Specials: '+fmt(d.yesterdaySpecial),
|
|
167
|
+
].filter(Boolean).join('\n'));
|
|
168
|
+
set('alltimeAirlines', d.alltimeAirlines||'–');
|
|
169
|
+
set('alltimeRoutes', d.alltimeRoutes||'–');
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async function poll(){ await checkApi(); render(await readAll()); }
|
|
173
|
+
|
|
174
|
+
loadVis().then(async()=>{ await checkApi(); poll(); setInterval(poll,10000); })
|
|
175
|
+
.catch(e=>{ VIS_CONFIG_LOADED=false; console.error(e); setWarn(true,'vis-config.json fehlt'); });
|
|
176
|
+
</script>
|
|
177
|
+
|
|
178
|
+
<script>
|
|
179
|
+
/* Header clock */
|
|
180
|
+
(function(){
|
|
181
|
+
let _b=false;
|
|
182
|
+
function pad(n){return String(n).padStart(2,'0');}
|
|
183
|
+
function wanted(){const d=new Date();return'Heute · '+pad(d.getHours())+':'+pad(d.getMinutes())+' · '+pad(d.getDate())+'.'+pad(d.getMonth()+1)+'.'+d.getFullYear();}
|
|
184
|
+
function apply(){if(_b)return;_b=true;const s=document.querySelector('.header .sub');if(s){const t=wanted();if(s.textContent!==t)s.textContent=t;}_b=false;}
|
|
185
|
+
apply();setInterval(apply,5000);
|
|
186
|
+
const h=document.querySelector('.header');if(h)new MutationObserver(apply).observe(h,{childList:true,subtree:true,characterData:true});
|
|
187
|
+
})();
|
|
188
|
+
</script>
|
|
189
|
+
|
|
190
|
+
<script>
|
|
191
|
+
/* Date format */
|
|
192
|
+
(function(){
|
|
193
|
+
const RE=/\b(\d{4})-(\d{2})-(\d{2})\b/g;
|
|
194
|
+
function fmt(t){return String(t||'').replace(RE,'$3.$2.$1');}
|
|
195
|
+
function apply(){const root=document.querySelector('.jf-shell');if(!root)return;const w=document.createTreeWalker(root,NodeFilter.SHOW_TEXT);const ns=[];while(w.nextNode())ns.push(w.currentNode);for(const n of ns){if(!RE.test(n.nodeValue))continue;const nx=fmt(n.nodeValue);if(nx!==n.nodeValue)n.nodeValue=nx;}}
|
|
196
|
+
apply();setInterval(apply,5000);
|
|
197
|
+
const r=document.querySelector('.jf-shell');if(r)new MutationObserver(apply).observe(r,{childList:true,subtree:true,characterData:true});
|
|
198
|
+
})();
|
|
199
|
+
</script>
|
|
200
|
+
|
|
201
|
+
<script>
|
|
202
|
+
/* Split panel 2-column layout for ranking text */
|
|
203
|
+
(function(){
|
|
204
|
+
function esc(s){return String(s).replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));}
|
|
205
|
+
function isSplit(p){const t=(p.querySelector('h3')?.textContent||'').toLowerCase();return t.includes('gestern')||t.includes('top airlines')||t.includes('top routen');}
|
|
206
|
+
function doOne(p){
|
|
207
|
+
const pre=p.querySelector('pre'); if(!pre||!isSplit(p))return;
|
|
208
|
+
// capture raw text before first split
|
|
209
|
+
if(pre.dataset.jfRaw===undefined) pre.dataset.jfRaw=pre.textContent||'';
|
|
210
|
+
// if render() updated textContent, capture it
|
|
211
|
+
else if(!pre.querySelector('.jf-line')) pre.dataset.jfRaw=pre.textContent||'';
|
|
212
|
+
const raw=(pre.dataset.jfRaw||'').trim();
|
|
213
|
+
if(!raw||raw==='–'||raw==='-'){p.classList.remove('jf-split-panel');return;}
|
|
214
|
+
const lines=raw.split(/\n+/).map(s=>s.trim()).filter(Boolean);
|
|
215
|
+
if(lines.length<2)return;
|
|
216
|
+
p.classList.add('jf-split-panel');
|
|
217
|
+
const html=lines.map(l=>'<span class="jf-line">'+esc(l)+'</span>').join('');
|
|
218
|
+
if(pre.innerHTML!==html)pre.innerHTML=html;
|
|
219
|
+
}
|
|
220
|
+
function all(){document.querySelectorAll('.panel').forEach(doOne);}
|
|
221
|
+
let t=null;
|
|
222
|
+
new MutationObserver(()=>{clearTimeout(t);t=setTimeout(all,30);}).observe(document.body,{childList:true,subtree:true,characterData:true});
|
|
223
|
+
all();
|
|
224
|
+
})();
|
|
225
|
+
</script>
|
|
226
|
+
|
|
227
|
+
</body>
|
|
228
|
+
</html>
|
package/admin/style.css
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/* You can delete those if you want. I just found them very helpful */
|
|
2
|
+
* {
|
|
3
|
+
box-sizing: border-box;
|
|
4
|
+
}
|
|
5
|
+
.m {
|
|
6
|
+
/* Don't cut off dropdowns! */
|
|
7
|
+
overflow: initial;
|
|
8
|
+
}
|
|
9
|
+
.m.adapter-container,
|
|
10
|
+
.m.adapter-container > div.App {
|
|
11
|
+
/* Fix layout/scrolling issues with tabs */
|
|
12
|
+
height: 100%;
|
|
13
|
+
width: 100%;
|
|
14
|
+
position: relative;
|
|
15
|
+
}
|
|
16
|
+
.m .select-wrapper + label {
|
|
17
|
+
/* The positioning for dropdown labels is messed up */
|
|
18
|
+
transform: none !important;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
label > i[title] {
|
|
22
|
+
/* Display the help cursor for the tooltip icons and fix their positioning */
|
|
23
|
+
cursor: help;
|
|
24
|
+
margin-left: 0.25em;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.dropdown-content {
|
|
28
|
+
/* Don't wrap text in dropdowns */
|
|
29
|
+
white-space: nowrap;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/* Add your styles here */
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../tsconfig.json",
|
|
3
|
+
"include": [
|
|
4
|
+
"./admin.d.ts",
|
|
5
|
+
"./**/*.js",
|
|
6
|
+
// Include the adapter-config definition if it exists.
|
|
7
|
+
// It should be at either on of these paths:
|
|
8
|
+
"../lib/adapter-config.d.ts", // JS
|
|
9
|
+
"../src/lib/adapter-config.d.ts" // TS
|
|
10
|
+
]
|
|
11
|
+
}
|