diff --git a/ui/entrypoints/main/index.html b/ui/entrypoints/main/index.html new file mode 100644 index 0000000..84d50fe --- /dev/null +++ b/ui/entrypoints/main/index.html @@ -0,0 +1,124 @@ + + + + + BeamNG Main UI + + + + + + + + + + + + + + +
+ UI Debug Show/Hide +
+
+
+

Current UI state:

+ + {{state.name}} + +
+
+ < previous + next > +
+
+ Show apps +
Gamestate: {{app.gameState}}
+ reload +
+
+ UI sound: + + test +
+ Gamepad Emulation: use the numpad keys 4862 +
+
+
+
+
+
... reconnecting ...
+
+
+
+ + +
+

+ Loading UI... +

+
+ Mods can slow down this process or may have broken the game (click here for help) +
+   +
+
+ + +
+ + access_time + +
+ + +
+
+ + {{:: 'ui.inputActions.general.pause.title' | translate}} +
+
+ + + + + + + + + + +
+ +
+ + Dash menu goes here + +
+ + + + + + + + + + +
+ + +
+ + + + + +
+
+ + + diff --git a/ui/entrypoints/main/main.css b/ui/entrypoints/main/main.css new file mode 100644 index 0000000..561ba86 --- /dev/null +++ b/ui/entrypoints/main/main.css @@ -0,0 +1,3110 @@ +:root{ + --bng-orange: #ff6600; + --bng-orange-shade1: rgba(255, 102, 0, 0.7); + --bng-orange-shade2: rgba(255, 102, 0, 0.4); + --bng-orange-shade1opaque: #b34700; + --bng-orange-shade2opaque: #662900; + /* --bng-gradient-orange: linear-gradient(to right,#b34700, #ff6600); */ + /* --bng-filter-orange: invert(49%) sepia(74%) saturate(3850%) hue-rotate(0deg) brightness(101%) contrast(106%); */ + --bng-black-8: rgba(0, 0, 0, 0.8); + --bng-black-6: rgba(0, 0, 0, 0.6); + --bng-black-4: rgba(0, 0, 0, 0.4); + --bng-black-2: rgba(0, 0, 0, 0.2); + --dark-neutral-grey: #252525; + --neutral-grey: #b1b3b6; + --light-neutral-grey: #e3e5e8; + --dark-grey: #505050; + --dark-grey-alpha: rgba(80, 80, 80, 0.8); + --black-1:rgba(0, 0, 0, 0.7); + --black-2: rgba(0 ,0 ,0 ,0.4); + --white-1: rgba(255, 255, 255, 0.8); + --white-2: rgba(255, 255, 255, 0.4); + + /* Additional color swatches that were added in Vue, but appear to be missing here (commented ones above are duplicates of below) */ + + + /* bng UI color palette */ + + /* bng-orange */ + --bng-orange-50: #fcd4c3; + --bng-orange-100: #ffbca0; + --bng-orange-200: #ffa37c; + --bng-orange-300: #ff8955; + --bng-orange-b400: #ff6600; + --bng-orange-500: #da5706; + --bng-orange-600: #a54106; + --bng-orange-700: #6c2f13; + --bng-orange-800: #451f0e; + --bng-orange-900: #291006; + + /* bng-cool-gray */ + --bng-cool-gray-50: #dadee8; + --bng-cool-gray-100: #cbced5; + --bng-cool-gray-200: #b9bdc8; + --bng-cool-gray-300: #aaaeb8; + --bng-cool-gray-400: #989daa; + --bng-cool-gray-500: #818693; + --bng-cool-gray-600: #606570; + --bng-cool-gray-700: #3f434e; + --bng-cool-gray-800: #282b33; + --bng-cool-gray-900: #151820; + + /* bng-ter-blue-gray */ + --bng-ter-blue-gray-50: #d2e4f0; + --bng-ter-blue-gray-100: #b5d1e4; + --bng-ter-blue-gray-200: #a1c0d5; + --bng-ter-blue-gray-300: #91b4cb; + --bng-ter-blue-gray-400: #7d9fb5; + --bng-ter-blue-gray-500: #6a8ba1; + --bng-ter-blue-gray-600: #4d697c; + --bng-ter-blue-gray-700: #2c4759; + --bng-ter-blue-gray-800: #1c2d38; + --bng-ter-blue-gray-900: #0c1a23; + + /* bng-add-blue */ + --bng-add-blue-50: #d0dff6; + --bng-add-blue-100: #bccfec; + --bng-add-blue-200: #a4bfe8; + --bng-add-blue-300: #89b0ea; + --bng-add-blue-400: #5f9df9; + --bng-add-blue-500: #5487d0; + --bng-add-blue-600: #3965a6; + --bng-add-blue-700: #234473; + --bng-add-blue-800: #152c4b; + --bng-add-blue-900: #0a192d; + + /* bng-add-green */ + --bng-add-green-50: #a0f9bc; + --bng-add-green-100: #73eb9d; + --bng-add-green-200: #4fdc88; + --bng-add-green-300: #4fca7e; + --bng-add-green-400: #49b672; + --bng-add-green-500: #429b63; + --bng-add-green-600: #33744a; + --bng-add-green-700: #224e32; + --bng-add-green-800: #173120; + --bng-add-green-900: #0f1c13; + + /* bng-ter-yellow */ + --bng-ter-yellow-50: #f9e03e; + --bng-ter-yellow-100: #e7cf30; + --bng-ter-yellow-200: #dac434; + --bng-ter-yellow-300: #c7b22d; + --bng-ter-yellow-400: #b19e22; + --bng-ter-yellow-500: #97871c; + --bng-ter-yellow-600: #726618; + --bng-ter-yellow-700: #4c4307; + --bng-ter-yellow-800: #322c05; + --bng-ter-yellow-900: #1b1804; + + /* bng-ter-peach */ + --bng-ter-peach-50: #f0dcb6; + --bng-ter-peach-100: #e6ca96; + --bng-ter-peach-200: #dbb875; + --bng-ter-peach-300: #cda961; + --bng-ter-peach-400: #b99448; + --bng-ter-peach-500: #a38038; + --bng-ter-peach-600: #7c6023; + --bng-ter-peach-700: #55400f; + --bng-ter-peach-800: #38290a; + --bng-ter-peach-900: #211602; + + /* bng-add-red */ + --bng-add-red-50: #fad4cc; + --bng-add-red-100: #f5bfb5; + --bng-add-red-200: #f5a799; + --bng-add-red-300: #f88b79; + --bng-add-red-400: #fb6752; + --bng-add-red-500: #ed3823; + --bng-add-red-600: #b12e1f; + --bng-add-red-700: #781f14; + --bng-add-red-800: #4d160f; + --bng-add-red-900: #24130f; + + /* Handy additional color presets */ + --bng-gradient-orange: linear-gradient(to right,#b34700, #ff6600); + --bng-filter-orange: invert(49%) sepia(74%) saturate(3850%) hue-rotate(0deg) brightness(101%) contrast(106%); + --bng-black-o8: rgba(0, 0, 0, 0.8); + --bng-black-o6: rgba(0, 0, 0, 0.6); + --bng-black-o4: rgba(0, 0, 0, 0.4); + --bng-black-o2: rgba(0, 0, 0, 0.2); + + --bng-corners-1: 0.25rem; + --bng-corners-2: 0.5rem; + --bng-corners-3: 1rem; + + + + /* z-index values: main stuff */ + --zorder_index_fullscreen_default: 0; + --zorder_index_broken_ui_loading: 1; + --zorder_index_waiting_screen_icon: 10; + --zorder_index_fullscreen_loading: 50; + --zorder_index_waiting_screen: 97; + --zorder_index_mdcontent: 98; + --zorder-index_popup: 9876; + --zorder_index_prevnext: 999999; + + --zorder_main_fancyBackground: -1; + --zorder_main_missionpopups: 5; + --zorder_main_contentNav: 1; + --zorder_main_reconnectScreen: 1; + --zorder_main_topbar_button_end: 5; + --zorder_main_mission_info_button: 10; + --zorder_main_mission_info_alt_button: 10; + --zorder_main_app_alignment_dot_corner: 23; + --zorder_main_menu_navigation_focus: 97; + --zorder_main_gfxSettingsLoading: 97; + + --zorder_mainmenu_blendbox: 2; + --zorder_mainmenu_shippinginfo: 97; + --zorder_mainmenu_infobox: 97; + --zorder_mainmenu_accountinfo: 97; + --zorder_mainmenu_onlineinfo: 97; + --zorder_mainmenu_versioninfo: 97; + + --zorder_index_menucontent: 2; + + --zorder_menu_dashmenu: 3; + --zorder_play_crosshair: 20; + + /* z-index values: app editing */ + --zorder_bngapp_delete: calc(var(--zorder_menu_dashmenu) + 1); + --zorder_bngapp_move: calc(var(--zorder_menu_dashmenu) + 1); + --zorder_bngapp_resize: calc(var(--zorder_menu_dashmenu) + 1); + --zorder_bngapp_cockpit:calc(var(--zorder_menu_dashmenu) + 1); + --zorder_appedit_bar: 50; + + /* z-index values: apps */ + --zorder_apps_default: 1; + --zorder_apps_dev_langMenu: 1; + --zorder_apps_dev_reftip: 1; + --zorder_apps_dev_sapop: 1; + --zorder_apps_simpleBrakeThermals_svg: 999; + + /* z-index values: modules */ + --zorder_campaignselect_search: 1; + --zorder_levelselect_search: 1; + --zorder_scenarioselect_search: 1; + --zorder_vehicleselect_search: 1; + --zorder_vehicleselect_showconfigs: 1; + + --zorder_dragrace_popover: 97; + --zorder_gamecontext_popover: 97; + --zorder_lightrunner_popover: 97; + --zorder_quickrace_popover: 97; + --zorder_scenariocontrol_popover: 97; + + --zorder_comic_canvas: 97; + --zorder_fadescreen: 97; + --zorder_iconview_iconsdemo: 97; + --zorder_loading_background: 97; + + --zorder_mapview_target_cross: 9; + --zorder_mapview_poi: 10; + --zorder_mapview_poi_focus: 12; + --zorder_mapview_arrow: 13; + --zorder_mapview_base: 15; + /* --zorder_mapview_left_pane: 20; */ + + --fnt-defs: 'Noto Sans', 'Noto Sans JP', 'Noto Sans KR', 'Noto Sans SC', sans-serif; + +} + +@font-face { + font-family: Open Sans Bold; + src: url(/ui/common/OpenSans-Bold.ttf); +} +@font-face { + font-family: Open Sans; + src: url(/ui/common/OpenSans-Regular.ttf); +} +@font-face { + font-family: Open Sans Extrabold; + src: url(/ui/common/OpenSans-ExtraBold.ttf); +} +@font-face { + font-family: Squada One; + src: url(/ui/common/SquadaOne-Regular.ttf); +} + +@font-face { + font-family: Digital; + src: url(/ui/common/Segment7Standard.otf); +} + +@font-face { + font-family: 'Roboto'; + src: url(/ui/common/Roboto-Regular.ttf); +} + +@font-face { + font-family: 'Roboto Bold'; + src: url(/ui/common/Roboto-Bold.ttf); +} + +@font-face { + font-family: 'Roboto Condensed'; + font-style: normal; + font-weight: 300; + src: url(/ui/common/RobotoCondensed-Regular.ttf); +} + +@font-face { + font-family: 'Play'; + src: url(/ui/common/Play-Regular.ttf); +} + +@font-face { + font-family: 'Play Bold'; + src: url(/ui/common/Play-Bold.ttf); +} + +@font-face { + font-family: 'News Cycle'; + src: url(/ui/common/NewsCycle-Regular.ttf); +} + +@font-face { + font-family: 'News Cycle Bold'; + src: url(/ui/common/NewsCycle-Bold.ttf); +} + +/* Main font */ + +@font-face { + font-family: 'Overpass'; + src: url('/ui/common/Overpass/Overpass-VariableFont_wght.ttf'); + font-weight: 125 950; + font-stretch: 75% 125%; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: 'Overpass'; + src: url('/ui/common/Overpass/Overpass-Italic-VariableFont_wght.ttf'); + font-weight: 125 950; + font-stretch: 75% 125%; + font-style: italic; + font-display: swap; +} + +/* Secondary font (to be used in UI) */ + +@font-face { + font-family: 'Noto Sans'; + src: url('/ui/common/NotoSans/NotoSans-SemiBoldItalic.ttf'); + font-weight: 600; + font-style: italic; + font-display: swap; +} + +@font-face { + font-family: 'Noto Sans'; + src: url('/ui/common/NotoSans/NotoSans-ExtraLight.ttf'); + font-weight: 200; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: 'Noto Sans'; + src: url('/ui/common/NotoSans/NotoSans-Bold.ttf'); + font-weight: bold; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: 'Noto Sans'; + src: url('/ui/common/NotoSans/NotoSans-SemiBold.ttf'); + font-weight: 600; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: 'Noto Sans'; + src: url('/ui/common/NotoSans/NotoSans-BoldItalic.ttf'); + font-weight: bold; + font-style: italic; + font-display: swap; +} + +@font-face { + font-family: 'Noto Sans'; + src: url('/ui/common/NotoSans/NotoSans-ExtraBoldItalic.ttf'); + font-weight: bold; + font-style: italic; + font-display: swap; +} + +@font-face { + font-family: 'Noto Sans'; + src: url('/ui/common/NotoSans/NotoSans-ExtraBold.ttf'); + font-weight: bold; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: 'Noto Sans'; + src: url('/ui/common/NotoSans/NotoSans-Regular.ttf'); + font-weight: normal; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: 'Noto Sans'; + src: url('/ui/common/NotoSans/NotoSans-LightItalic.ttf'); + font-weight: 300; + font-style: italic; + font-display: swap; +} + +@font-face { + font-family: 'Noto Sans'; + src: url('/ui/common/NotoSans/NotoSans-Medium.ttf'); + font-weight: 500; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: 'Noto Sans'; + src: url('/ui/common/NotoSans/NotoSans-Light.ttf'); + font-weight: 300; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: 'Noto Sans'; + src: url('/ui/common/NotoSans/NotoSans-MediumItalic.ttf'); + font-weight: 500; + font-style: italic; + font-display: swap; +} + +@font-face { + font-family: 'Noto Sans'; + src: url('/ui/common/NotoSans/NotoSans-Italic.ttf'); + font-weight: normal; + font-style: italic; + font-display: swap; +} + +@font-face { + font-family: 'Noto Sans'; + src: url('/ui/common/NotoSans/NotoSans-ExtraLightItalic.ttf'); + font-weight: 200; + font-style: italic; + font-display: swap; +} + +/* Set of fonts to fill the gaps for the hieroglyphic languages */ + +@font-face { + font-family: 'Noto Sans JP'; + src: url('/ui/common/NotoSans/NotoSansJP-Light.otf'); + font-weight: 300; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: 'Noto Sans JP'; + src: url('/ui/common/NotoSans/NotoSansJP-Black.otf'); + font-weight: 900; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: 'Noto Sans JP'; + src: url('/ui/common/NotoSans/NotoSansJP-Bold.otf'); + font-weight: bold; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: 'Noto Sans JP'; + src: url('/ui/common/NotoSans/NotoSansJP-Regular.otf'); + font-weight: normal; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: 'Noto Sans JP'; + src: url('/ui/common/NotoSans/NotoSansJP-Medium.otf'); + font-weight: 500; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: 'Noto Sans KR'; + src: url('/ui/common/NotoSans/NotoSansKR-Light.otf'); + font-weight: 300; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: 'Noto Sans KR'; + src: url('/ui/common/NotoSans/NotoSansKR-Black.otf'); + font-weight: 900; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: 'Noto Sans KR'; + src: url('/ui/common/NotoSans/NotoSansKR-Bold.otf'); + font-weight: bold; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: 'Noto Sans KR'; + src: url('/ui/common/NotoSans/NotoSansKR-Regular.otf'); + font-weight: normal; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: 'Noto Sans KR'; + src: url('/ui/common/NotoSans/NotoSansKR-Medium.otf'); + font-weight: 500; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: 'Noto Sans SC'; + src: url('/ui/common/NotoSans/NotoSansSC-Light.otf'); + font-weight: 300; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: 'Noto Sans SC'; + src: url('/ui/common/NotoSans/NotoSansSC-Black.otf'); + font-weight: 900; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: 'Noto Sans SC'; + src: url('/ui/common/NotoSans/NotoSansSC-Bold.otf'); + font-weight: bold; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: 'Noto Sans SC'; + src: url('/ui/common/NotoSans/NotoSansSC-Regular.otf'); + font-weight: normal; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: 'Noto Sans SC'; + src: url('/ui/common/NotoSans/NotoSansSC-Medium.otf'); + font-weight: 500; + font-style: normal; + font-display: swap; +} + +/* Monospace font just in case */ + +@font-face { + font-family: 'Noto Sans Mono'; + src: url('/ui/common/NotoSans/NotoSansMono-VariableFont_wdth,wght.ttf'); + font-weight: 100 950; + font-stretch: 75% 125%; + font-style: normal; +} + +html, body { + background: none; + cursor: default; + font-family: var(--fnt-defs); + height: 100%; + overflow: hidden !important; + + -webkit-transition: none!important; transition: none!important; + -webkit-font-feature-settings: "kern" 1; +} + +kbd, .key { + vertical-align: middle; + font-family: 'Noto Sans Mono', monospace; + font-size: 1em; +} + +kbd>span, .key>span { + display: inline-flex; +} + +kbd>span>span, .key>span>span { + font-size:0.8em; + line-height:1.25em; + padding-bottom:0.1em; +} + +a svg { + pointer-events: none; +} + +.bngApp { + color: white; + background: rgb(0, 0, 0); + background: rgba(0, 0, 0, 0.43); + box-sizing: border-box; + padding:2px; +} + +.bngAppFont { + font-family: 'Overpass', var(--fnt-defs); + font-weight: 900; + font-size: 1.2em; +} + +.active md-icon { + color: orange!important; +} + +.md-virtual-repeat-offsetter { + right: 0 !important; +} + +.stripedBackground { + background-image: url(/ui/images/stripedbackground.png); +} + +.markEveryOddChild:nth-child(2n + 1) { + background-color: rgba(0, 0, 0, 0.3); +} + +.markEveryEvenRow tr:nth-child(2n) { + background-color: rgba(221, 221, 221, 0.5); +} + +* { + -webkit-tap-highlight-color:rgba(0,0,0,0); + -webkit-user-select: none; +} + + +/* Focus adjustments */ + +/* .menu-navigation:focus::before { + outline: 6px solid rgb(252, 107, 3) !important; + outline-offset: -3px; + z-index: var(--zorder_main_menu_navigation_focus); +} */ + +.controls-edit .menu-navigation, .controls-ffb .menu-navigation, .controls-filters .menu-navigation, .controls-hardware .menu-navigation { + position: relative; +} + +.controls-edit { + min-width: 500px; +} + +button:focus { + overflow: visible; +} + +.md-button:focus { + outline: none; + overflow: visible; +} + +.md-content { + overflow-x: hidden; +} + +*:focus { /* should be global */ + outline: none; +} +/* TODO: following should be limited to .menu-navigation */ +*:focus { + box-shadow: inset 0 0 0 25em hsla(0, 0%, 0%, 0.2); + /* box-shadow: 0 0 0 2px hsla(0, 0%, 0%, 1), 0 0 0 4px hsla(24, 100%, 50%, 1), inset 0 0 0 25em hsla(0, 0%, 0%, 0.2); */ +} +*:focus::before { + content: ""; + display: block; + position: absolute; + top: -5px; + bottom: -5px; + left: -5px; + right: -5px; + border-radius: 6px; + border: 3px solid #f60; + pointer-events: none; + z-index: var(--zorder_main_menu_navigation_focus); +} +.bng-app *:focus::before { /* disable for ui apps */ + content: none; +} + +.ratio16x9 { + position: relative; + width: 100%; + flex: 0 0 auto; +} + +.ratio16x9:after { + content: ""; + display: block; + padding-top: 56.25%; +} + +.ratio2x1 { + position: relative; + width: 100%; + flex: 0 0 auto; +} + +.ratio2x1:after { + content: ""; + display: block; + padding-top: 50%; +} + +.ratio21x9 { + position: relative; + width: 100%; + flex: 0 0 auto; +} + +.ratio21x9:after { + content: ""; + display: block; + padding-top: 42.8%; +} + +.ratio1x1 { + position: relative; + width: 100%; + flex: 0 0 auto; +} + +.ratio1x1:after { + content: ""; + display: block; + padding-top: 100%; +} + +.ratio3x4 { + position: relative; + width: 100%; + flex: 0 0 auto; +} + +.ratio3x4:after { + content: ""; + display: block; + padding-top: 133%; +} + +.ratio-content { + position: absolute !important; + top: 0; + bottom: 0; + left: 0; + right: 0; + background: center / cover no-repeat; +} + +.bng-button { + position: relative; +} + +.bng-button:not(:last-child) { + margin-right: 0.5em; +} + +.bng-button-main:focus::before, +.bng-button-secondary:focus::before, +.bng-button-white:focus::before, +.bng-button-outline:focus::before, +.bng-button-link:focus::before, +.bng-button-attention:focus::before { + border: 2px solid #f60; +} + +.bng-button-main, +.bng-button-secondary, +.bng-button-white, +.bng-button-outline, +.bng-button-link, +.bng-button-attention { + font-family: var(--fnt-defs); + font-size: 1em; + line-height: 1.25em; + min-width: 3em; + min-height: 2.25em; + padding: 0.5em 1em; + border: 0; + border-radius: 0.25em; + transition: all 0.1s cubic-bezier(0.25, 0.1, 0.25, 1.0); + overflow: visible; + border: 2px solid transparent; +} + +.bng-button-main:disabled, +.bng-button-secondary:disabled, +.bng-button-white:disabled, +.bng-button-link:disabled, +.bng-button-attention:disabled, +button[disabled] { + color: hsla(0, 0%, 60%, 1); + background-color: hsla(0, 0%, 25%, 1); + border-color: transparent; + pointer-events: none; +} + +.bng-button-outline:disabled { + border-color: hsla(0, 0%, 60%, 1); + color: hsla(0, 0%, 60%, 1); +} + +.bng-button-main { + background-color: hsla(24, 100%, 45%, 1); /* change to var() */ + border-color: hsla(24, 100%, 45%, 1); + color: white; +} + +.bng-button-main:hover { + background-color: hsla(24, 100%, 56%, 1); /* change to var() */ + border-color: hsla(24, 100%, 56%, 1); +} + +.bng-button-main:active { + background-color: hsla(24, 100%, 31%, 1); /* change to var() */ + border-color: hsla(24, 100%, 31%, 1); +} + +.bng-button-main:disabled { + color: hsla(24, 50%, 56%, 1); +} + +.bng-button-secondary { + background-color: hsla(203, 30%, 26%, 1); /* change to var() */ + border-color: hsla(203, 30%, 26%, 1); + color: white; +} + +.bng-button-secondary:hover { + background-color: hsla(203, 20%, 35%, 1); /* change to var() */ + border-color: hsla(203, 20%, 35%, 1); +} + +.bng-button-secondary:active { + background-color: hsla(203, 30%, 22%, 1); /* change to var() */ + border-color: hsla(203, 30%, 22%, 1); +} + +.bng-button-outline { + background-color: var(--bng-black-4); /* change to var() */ + color: white; + border-color: hsla(24, 100%, 50%, 1) +} + +.bng-button-outline:hover { + background-color: hsla(24, 100%, 35%, 1); /* change to var() */ +} + +.bng-button-outline:focus { + background-color: hsla(24, 100%, 20%, 1); /* change to var() */ +} + +.bng-button-outline:active { + background-color: hsla(24, 100%, 10%, 1); /* change to var() */ +} + +.bng-button-link { + background-color: var(--bng-black-o2); /* change to var() */ + color: white; +} + +.bng-button-link:hover { + background-color: hsla(24, 100%, 35%, 0.25); /* change to var() */ +} + +.bng-button-link:active { + background-color: hsla(24, 0%, 0%, 1); /* change to var() */ +} + +.bng-button-link:focus { + background-color: hsla(24, 100%, 35%, 0.4); /* change to var() */ +} + +.bng-button-link:disabled { + background-color: hsla(0, 0%, 0%, 0); /* change to var() */ +} + +.bng-button-attention { + background-color: hsla(360, 75%, 40%, 1); /* change to var() */ + color: white; +} + +.bng-button-attention:hover { + background-color: hsla(360, 75%, 50%, 1); /* change to var() */ +} + +.bng-button-attention:active { + background-color: hsla(360, 75%, 30%, 1); /* change to var() */ +} + +.bng-button-attention:focus { + background-color: hsla(360, 85%, 45%, 1); /* change to var() */ +} + +.dashBrdButton:focus::before { + top: 2px; + bottom: 2px; + left: 2px; + right: 2px; + border-radius: 2px; + border: 2px solid #f60; +} + +.dashBrdButton:focus { + box-shadow: none; +} + +.orangeHover:hover { + color: var(--bng-orange); +} + +a:focus { + position: relative; +} + +a { + color: #f60; +} + +md-radio-group.onlineDialogue { + display: flex; + flex-direction: row; +} + +.onlineDialogue .md-button { + overflow: visible !important; +} + +bng-accordion bng-pane-header ._root { + position: relative; +} + +bng-accordion bng-accordion-pane { + padding: 0.25rem; +} + +bng-pane-header.bottomBorder { + aborder-bottom: 1px solid var(--bng-orange-700); + border-bottom: 1px solid var(--bng-cool-gray-600); +} + +bng-accordion { + padding: 0.25rem; +} + +md-list-item { + margin: 0 0.5rem; +} + +/* Checkbox focus fix unless options layouts is fixed properly */ +.optionsSideBarMinimal .md-proxy-focus md-checkbox > .md-label, +.optionsSideBarFullscreen .md-proxy-focus md-checkbox > .md-label { + display: none; +} + +.filler { + width: 100%; + height: 100%; +} + +.comicCanvas { + width: 100vw; +} + +@media (min-device-aspect-ratio: 16/9) { + .comicCanvas { + width: calc(100vh * 16/9); + } +} + +.container { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; +} + + +.photomode-container .tl { + position: absolute; min-width: 100px; min-height: 100px; + top: 30px; left: 30px; + border-top: 5px solid; border-left: 5px solid; +} + +.photomode-container .tr { + position: absolute; min-width: 100px; min-height: 100px; + top: 30px; right: 30px; + border-top: 5px solid; border-right: 5px solid; +} + +.photomode-container .bl { + position: absolute; min-width: 100px; min-height: 100px; + bottom: 30px; left: 30px; + border-bottom: 5px solid; border-left: 5px solid; +} + + +.photomode-container .br { + position: absolute; min-width: 100px; min-height: 100px; + bottom: 30px; right: 30px; + border-bottom: 5px solid; border-right: 5px solid; +} + + +/* ------ material's overrides ------*/ +md-slider .md-focus-ring { + display: none ; +} + +md-tooltip > .md-content{ + color: black; + background-color: white; + font-size: 14px; +} +/* --------------------------------- */ + +/* The buttons' background on focus, gets really annoying */ +.bng-no-focus:focus { + background: inherit!important; +} + + +.bng-hidden-input { + border:none; +} + +.bng-hidden-input[disabled] { + background: inherit; +} + +/* smaller sidebar */ +.smallsidenav { + fill: #737373; + /* was 48 */ + /* + min-width: 140px !important; + width: 140px !important; + max-width: 140px !important; + */ + overflow:hidden; + +} + +.contentNav { + overflow-y:auto; + z-index: var(--zorder_main_contentNav); +} + +/* some heighfixes */ +md-switch.height32{ + margin: 4px; +} + + +md-slider.height32{ + height: 32px; +} + +md-slider.height32 .md-track-container{ + top: 15px; +} + +md-slider.height32 .md-thumb{ + top: -3px; +} + +md-slider.height32 .md-focus-ring{ + top: -8px; +} + +md-slider.height32 .md-focus-thumb{ + top: -8px; +} + +md-select.height32{ + margin: 1px; +} + +md-select-label { + float: right; +} + +md-select { + margin:0em; + color:white; + background:rgba(35, 35, 35, 0); + opacity: 0.99; +} + +md-slider { + margin-right: 0.8em; + margin-left: 0.8em; +} + +.truncate { + display: inline-block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + position: relative; +} + +.transparency { + opacity: 0.9; +} +.transparencyMid { + opacity: 0.75; +} +.transparencyHigh { + opacity: 0.5; +} + +.gfxSettingsLoading { + z-index: var(--zorder_main_gfxSettingsLoading); + position: absolute; + padding:50px; + top: 0; + bottom: 0; + left: 0; + right: 0; + background-color:rgba(0,0,0,0.4); +} + +.gfxSettingsLoading .icon { + -webkit-filter: drop-shadow( 0px 0px 15px white ); + fill:white; +} + +[ng\:cloak], [ng-cloak], .ng-cloak { + display: none !important; +} + +::-webkit-scrollbar { + width: 7px; + height: 7px; +} +::-webkit-scrollbar-thumb { + background: #f60; +} +::-webkit-scrollbar-thumb:hover { + background: #f72; +} +::-webkit-scrollbar-thumb:active { + background: #d50; +} +::-webkit-scrollbar-track { + background: #222; +} +::-webkit-scrollbar-track:hover { + background: #333; +} +::-webkit-scrollbar-track:active { + background: #000; +} +::-webkit-scrollbar-corner { + background: transparent; +} + +.loadingHint { + color: white; + background: rgba(0, 0, 0, 0.5); +} + +.dark { + color: white; +} + + + +/* Material Icons Webfont */ +@font-face { + font-family: 'MIs'; + font-style: normal; + font-weight: 400; + src: local('Material Icons'), + local('MaterialIcons-Regular'), + url(/ui/common/MaterialIcons-Regular.ttf) format('truetype'); +} + +.fancy { + font-family: 'Overpass', var(--fnt-defs); + font-style: italic; + font-weight: 800; +} + +@font-face { + font-family: 'Material Icons'; + font-style: normal; + font-weight: 400; + src: url(/ui/common/MaterialIcons-Regular.ttf) format('truetype'); +} + +.material-icons { + font-family: 'Material Icons'; + font-weight: normal; + font-style: normal; + font-size: 1.5em; /* Preferred icon size */ + display: inline-block; + width: 1em; + height: 1em; + line-height: 1; + text-transform: none; + letter-spacing: normal; + word-wrap: normal; + + /* Support for all WebKit browsers. */ + -webkit-font-smoothing: antialiased; + /* Support for Safari and Chrome. */ + text-rendering: optimizeLegibility; + + /* Support for Firefox. */ + -moz-osx-font-smoothing: grayscale; + + /* Support for IE. */ + /*font-feature-settings: 'liga';*/ +} + +.md-sidenav-backdrop { + display: none !important; +} + +.my-animation.ng-enter { + /* standard transition code */ + transition: 2s linear all; + opacity:0; +} +.my-animation.ng-enter-stagger { + /* this will have a 100ms delay between each successive leave animation */ + transition-delay: 3.1s; + + /* in case the stagger doesn't work then the duration value + must be set to 0 to avoid an accidental CSS inheritance */ + transition-duration: 1s; +} +.my-animation.ng-enter.ng-enter-active { + /* standard transition styles */ + opacity:1; +} + +v-pane-content > div { -webkit-transition: none!important; transition: none!important; } + + +#binding_list .bng-binding { + margin: 0.25em 0; + padding-left:0.125em; +} + + + + +/** { animation-name: none!important; transition: none!important;}*/ + + + +/* -------------------------------------------------------------------- + -------------------------- [ ScenarioControl ] --------------------- + -------------------------------------------------------------------- */ +.contentNavScenarioControl { + width:100%; + max-width:none; + min-width:none; + display:block; + background-color: rgba(0,0,0,0.2); + color:white; +} + + +.contentNavScenarioControl .startScreen { + position:fixed; + top:10px; + left:200px; + right:100px; + bottom:10px; + max-width:800px; + background-color: rgba(0,0,0,0.4); + color:white; + /* border: 2px solid rgba(0,0,0,0.4); */ + /* -webkit-border-radius: 15px; */ +} + +.contentNavScenarioControl .endScreen { + position:fixed; + top:10px; + left:200px; + right:100px; + bottom:10px; + max-width:800px; + background-color: rgba(0,0,0,0.4); + color:white; + /* border: 2px solid rgba(0,0,0,0.4); */ + /* -webkit-border-radius: 15px; */ +} + + +/* -=-=-=--=-=-=-=-=-=-=-=-=-=-=--=-=-=--=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + ------------------------ [ Credits ] ------------------------------- + -=-=-=--=-=-=-=-=-=-=-=-=-=-=--=-=-=--=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- */ + +/* moved to Vue - uncomment the below if issues occur elsewhere */ + +/*#bng-credits-wrapper { + width:100%; + height: 100%; + background: radial-gradient(ellipse at top left, #334455 0%, #000 100%); + text-align: center; + overflow: hidden; +} + +.bng-credits-content { + position: absolute; + top: 100%; + left: 0; + right: 0; + font-family: 'Overpass', var(--fnt-defs); + /--* font-family: 'Roboto Condensed'; - if something breaks there *--/ + text-transform: uppercase; + color: #fff; +} + +.bng-credits-content creditsCategory { + display:block; + margin-top: 120px; + margin-bottom: 25px; + font-size: 32px; + font-weight: bold; +} + +.bng-credits-content creditsMember { + display:block; + margin-bottom: 8px; + font-size: 24px; + color: white; + font-style: normal; +} + +.bng-credits-content .title { + margin-bottom: 8px; + font-size: 24px; + color: var(--bng-orange); + font-style: italic; +} + + +#bng-credits-wrapper:focus { + outline: none !important; + box-shadow: none !important; +} +*/ + +/* md-select item */ + .md-select-menu-container.md-active md-select-menu { transition: none!important; } + .md-select-menu-container.md-active md-select-menu > * { opacity: 1; transition: none!important; } + .md-select-menu-container.md-leave { opacity: 0; transition: none!important; } + + md-select.bng-select-fullwidth .md-select-label { width: 100%; } + .bng-short-select-item { height: 32px; } + + + +/* alignment corners in app-editing */ +.app-alignment-dot { + width: 150px; + height: 150px; + pointer-events: none; + background-image: radial-gradient(circle, rgba(255,255,255,1) 25%, rgba(255,255,200,0.3) 45%, rgba(255,255,155,0) 65%); + z-index: var(--zorder_main_app_alignment_dot_corner); +} + +.app-alignment-dot.tl { + margin-left: -100px; + margin-top: -100px; +} + +.app-alignment-dot.tr { + margin-right: -100px; + margin-top: -100px; +} + +.app-alignment-dot.bl { + margin-left: -100px; + margin-bottom: -100px; +} + +.app-alignment-dot.br { + margin-right: -100px; + margin-bottom: -100px; +} + +.app-alignment-dot.c { + margin-left: -100px; + margin-top: -100px; +} + +.bordered { + border: solid grey 1px; + box-sizing: border-box; + background-color: rgba(255, 0, 0, 0.01); +} + + + +kbd > span { + text-transform: uppercase; +} + +.uppercase { + text-transform: uppercase; +} + +/* override for less padding in 2-line list items */ +md-list-item.md-2-line.squeezed .md-list-item-text { + padding: 6px; + padding-top: 10px 0; +} + + +.menuNavbar { + background-color:rgba(0, 0, 0, 0.6); + padding: 2px 8px; + flex: none; +} + +@media (max-width: 1200px){ + .menuNavbar { + font-size: 0.75em; + } +} + + +.gridMarkSelected md-grid-tile:hover md-grid-tile-footer { + background: rgba(252, 107, 3, 0.60); +} +.gridMarkSelected md-grid-tile:focus md-grid-tile-footer, +.gridMarkSelected md-grid-tile.gridMarkSelected md-grid-tile-footer { + background: rgba(252, 107, 3, 0.80); +} +/* .gridMarkSelected md-grid-tile:focus { + border: 2px solid rgba(252, 107, 3, 0.60); +} */ +.gridMarkSelected md-grid-tile { + outline: none; + /* border: 2px solid transparent; */ +} + +/*no f*cking clue why this is need seperatly to the above +(only for scenarios and vehicle details, rest works perfectly fine)*/ +.gridMarkSelected .selectedGridItem md-grid-tile-footer { + background: rgba(252, 107, 3, 0.80); +} +.gridMarkSelected .selectedGridItem:hover md-grid-tile-footer { + background: rgba(252, 107, 3, 0.80); +} + +.gridMarkSelected .selectedGridItem:focus md-grid-tile-footer { + background: rgba(252, 107, 3, 0.80); +} +/* .gridMarkSelected .selectedGridItem { + border: 2px solid rgba(252, 107, 3, 0.60); +} */ + +.bng-controls-aux-input { + width: 50px; + text-align: right; + margin-left: 15px; + padding-bottom: 0; +} + +/* Simple table CSS (just to avoid having a completely ugly table) */ +table.simple-table { + border-width: 1px; + border-color: #464646; + border-collapse: collapse; +} + +table.simple-table th { + text-align: left; + padding: 5px; + background-color: #f1f1f1; + font-weight:none; +} + +table.simple-table td { + background-color: white; + padding-left:5px; +} + +table.simple-table tbody tr { + border-bottom: 1px solid lightgrey; + background-color: blue; +} + +table.simple-table td.number { + font-family: 'Noto Sans Mono', monospace; +} + +.bng-app { + user-select: none; +} + +.bng-app.editable { + cursor: move; +} + +.bng-app.editable .resizeHandle { + cursor: nw-resize; +} + +.bng-app.editable:active { + box-shadow: 0 0 0px 2px rgba(252, 107, 3, 0.9); +} + +table.bng-list-table { + border-collapse: collapse; +} + +table.bng-list-table td { + padding: 0.5em; + vertical-align: top; +} + + +/* ----------------------- TEST ONLY ------------------------- */ + @font-face { + font-family: 'beamng-icons'; + src: url(/ui/common/beamng-icons.ttf) format('truetype'); + font-weight: normal; + font-style: normal; +} + +.bng-icon { + /* use !important to prevent issues with browser extensions that change fonts */ + font-family: 'beamng-icons' !important; + speak: none; + font-style: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + line-height: 1; + + /* Enable Ligatures ================ */ + letter-spacing: 0; + -webkit-font-feature-settings: "liga"; + -moz-font-feature-settings: "liga=1"; + -moz-font-feature-settings: "liga"; + -ms-font-feature-settings: "liga" 1; + font-feature-settings: "liga"; + -webkit-font-variant-ligatures: discretionary-ligatures; + font-variant-ligatures: discretionary-ligatures; + + /* Better Font Rendering =========== */ + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.bng-logo:before { + content: "\e900"; +} + + +/* -- MISSION POPUP START -- */ + + +.mission-popups { + display: block; + position: absolute; + top: 10px; + bottom: 10px; + left: 10px; + right: 10px; +} + +.mission-info { + position: absolute; + top: 25px; + left: 0; + right: 0; + margin: auto; + width: 600px; + max-width: 100%; + color: white; + font-family: 'Overpass', var(--fnt-defs); + font-weight: 800; + user-select: none; + z-index: var(--zorder_main_mission_info_button); +} +.mission-info .header { + display: flex; + align-items: center; + justify-content: center; + padding: 16px; + border-top: solid 4px #ff6600; + background-color: rgba(0, 0, 0, 0.8); +} +.mission-info .header-title { + font-size: 1.5em; +} +.mission-info .header-subtitle { + font-size: 1.2em; +} +.mission-info .header-icon { + width: 50px; + height: 50px; + fill: white; + stroke: none; + position: absolute; + left: -70px +} +.mission-info .body { + padding: 10px 0; + background-color: rgba(0, 0, 0, 0.7); +} +.mission-info .row { + display: flex; + justify-content: center; + padding: 5px 0; + line-height: 1.5em; +} +.mission-info .cell { + display: flex; + align-items: center; + padding: 0 10px; +} +.mission-info .cell.entry-label { + flex: 1; + /*font-size: 1.2em;*/ + text-transform: uppercase; + justify-content: flex-end; + align-items: flex-start; +} +.mission-info .cell.entry-val { + flex: 1; +} +.mission-info .buttons { + display: flex; + justify-content: center; + flex-wrap: wrap; + margin-top: 5px; + text-transform: uppercase; +} + +.mission-info .buttons .button { + background: rgba(0, 0, 0, 0.7); + border-top: solid 4px transparent; + border-bottom: solid 4px transparent; + padding: 5px 20px; + margin: 5px; + cursor: pointer; + font-size: 1.2em; + display: flex; + align-items: center; + z-index: var(--zorder_main_mission_info_button); +} + +.mission-info .buttons .button:focus, +.mission-info .buttons .button:hover { + border-top: solid 4px #ff7700; +} +.mission-info .buttons .button:active { + border-color: #ff7700; + color: #ff7700; + background-color: rgba(0, 0, 0, 0.8); + outline: none; +} + +/* Alternate Mode */ +.mission-info-alt { + position: absolute; + bottom: 30px; + left: 0; + right: 0; + margin: auto; + width: 600px; + max-width: 100%; + color: white; + font-family: 'Overpass', var(--fnt-defs); + font-weight: 800; + user-select: none; +} + +.mission-info-alt .header { + display: flex; + align-items: center; + justify-content: center; + padding: 16px; + border-top: solid 4px #ff7700; + background-color: rgba(0, 0, 0, 0.8); +} +.mission-info-alt .header-title { + font-size: 1.5em; +} +.mission-info-alt .header-subtitle { + font-size: 1.2em; +} +.mission-info-alt .header-icon { + width: 50px; + height: 50px; + fill: white; + stroke: none; + position: absolute; + left: -70px +} +.mission-info-alt .body { + padding: 10px 0; + background-color: rgba(0, 0, 0, 0.7); +} +.mission-info-alt .row { + display: flex; + justify-content: center; + padding: 5px 0; + line-height: 1.5em; +} +.mission-info-alt .cell { + display: flex; + align-items: center; + padding: 0 10px; +} +.mission-info-alt .cell.entry-label { + flex: 1; + /*font-size: 1.2em;*/ + text-transform: uppercase; + justify-content: flex-end; + align-items: flex-start; +} +.mission-info-alt .cell.entry-val { + flex: 1; +} +.mission-info-alt .buttons { + display: flex; + justify-content: center; + flex-wrap: wrap; + margin-top: 5px; + text-transform: uppercase; +} + +.mission-info-alt .buttons .button { + background: rgba(0, 0, 0, 0.7); + border-top: solid 4px transparent; + border-bottom: solid 4px transparent; + padding: 5px 20px; + margin: 5px; + cursor: pointer; + font-size: 1.2em; + display: flex; + align-items: center; + z-index:var(--zorder_main_mission_info_alt_button); +} + +.mission-info-alt .buttons .button:focus, +.mission-info-alt .buttons .button:hover { + border-top: solid 4px #ff7700; +} +.mission-info-alt .buttons .button:active { + border-color: #ff7700; + color: #ff7700; + background-color: rgba(0, 0, 0, 0.8); + outline: none; +} + + + + + + +/* MISSION POPUPS END */ + + + + + + + + +/* very short fade-in */ +.moduleStartScreenFade.ng-enter { + transition:0.1s linear all; + opacity: 0; +} +.moduleStartScreenFade.ng-enter-active { + opacity: 1; +} + +/* very short fade-out */ +.moduleStartScreenFade.ng-leave { + transition:0.3s linear all; + opacity: 1; +} +.moduleStartScreenFade.ng-leave-active { + opacity: 0; +} + +.moduleBlendOnLeave.ng-leave { + transition: 1s; + opacity: 1; +} +.moduleBlendOnLeave.ng-leave-active { + opacity: 0; +} + + +.font1 { + font-family: 'Overpass', var(--fnt-defs); + font-weight: 800; +} + +.font2 { + font-family: var(--fnt-defs); +} + +.color1 { + color: white; +} + +.color2 { + color: #b9b9ba; +} + +.color3 { + color: #FF6700; +} + +.color4 { + color: #C80000; +} + +.bg-color1 { + background-color: white; +} + +.bg-color2 { + background-color: #b9b9ba; +} + +.bg-color3 { + background-color: #FF6700; +} + +.bg-color4 { + background-color: #C80000; +} + +.fill-color1 { + fill: white; +} + +.fill-color2 { + fill: #b9b9ba; +} + +.fill-color3 { + fill: #FF6700; +} + +.fill-color4 { + fill: #C80000; +} + +.hg-color3 { + background-color: rgba(255, 103, 0, 0.3); +} + + +.possibleActionKey { + padding: 10px; + margin-right: 20px; + cursor: pointer; + border: 3px solid transparent; + background: rgba(0, 0, 0, 0.01); /* mouse focus hack */ +} + +.possibleActionKey:hover { + /*background-color: #FF6700;*/ + -webkit-border-image: -webkit-linear-gradient(70deg, #FF6700 0%, transparent 35%, transparent 65%, #FF6700); + border-image-slice: 1; +} + + + + + + +.box { + position: relative; + width: inherit; +} + +.box1_1:before{ + content: ""; + display: block; + padding-top: 100%; +} + +.box16_19:before { + content: ""; + display: block; + padding-top: 56.25%; +} + +.display { + width: 100vw; +} + +@media (min-device-aspect-ratio: 16/9) { + .display { + width: calc(100vh * 16/9); + } +} + +/* image sliders start */ +.imageslider { + position: relative; + width: 100%; + width: 100%; + margin: auto; +} +.imageslideItem { + position: absolute; + top: 0; + left: 0; + opacity: 1; + transition: 0.5s linear opacity; +} + +.imageslideItem.ng-hide-add, .imageslideItem.ng-hide-remove { + transition: 0.5s linear opacity; +} + +/* starting animations for remove */ +.imageslideItem.ng-hide-remove { opacity: 0; } +/* terminal animations for remove */ +.imageslideItem.ng-hide-remove.ng-hide-remove-active { opacity: 1; } + +/* starting animations for add */ +.imageslideItem.ng-hide-add { opacity: 1; } +/* terminal animations for add */ +.imageslideItem.ng-hide-add.ng-hide-add-active { opacity: 0; } + +/* image sliders end */ + + +/* image carousel start */ + +.img-carousel { + position: relative; + display: inline-block; + width: 100%; + height: 100%; +} +.img-carousel, +.img-carousel-prev { + background-size: cover; + background-position: 50% 50%; +} + +.img-carousel-prev { + position: absolute !important; + display: inline-block; + top: 0; + bottom: 0; + left: 0; + right: 0; +} + +.img-carousel-nav { + position: absolute !important; + bottom: 0; + left: 0; + right: 0; + display: inline-block; + font-size: 0.8em; + height: 1.4em; + text-align: center; + background: linear-gradient(rgba(0,0,0, 0.0), rgba(0,0,0, 0.65)); +} +.img-carousel-nav > * { + box-sizing: border-box; + display: inline-block; + width: 0.5em; + height: 0.5em; + margin: 0.3em; + background-color: #fff; + border-radius: 100%; + cursor: pointer; +} +.img-carousel-nav > *.selected { + top: 0; + width: 0.6em; + height: 0.6em; + margin: 0.2em; +} + +/* image carousel end */ + + +.no-mouse { + pointer-events: none; +} + +/* bng virtual repeat */ +.bng-v-list__item { + display: inline-block; +} +.bng-v-list { + overflow-y: auto; + display: block; + box-sizing: border-box; + overflow-x: hidden; + padding: 1em 1em 2em 0.5em; +} + +/* bng 16:9 box */ +.box { + margin: 0 auto; + width: inherit; +} + +.box:after { + content: ""; + display: block; + padding-top: 48% +} + +.box1_1 { + position: relative; + width: inherit; +} +.box1_1:after { + content: ""; + display: block; + padding-top: 100%; +} + + +/* ----------------------------- for news link */ + +.tile a { + background-color: rgba(0,0,0,0); + text-decoration: none; + color: white; +} + +.tile a:visited { + background-color: rgba(0,0,0,0); + color: white; +} + +/*------------------------------- md-tab test for translation issues */ + +/* +.md-tab { + padding: 0px; + align-items: center; + justify-content: center; + display: flex; +} +*/ + + +#reconnectScreen { + position: fixed; /* or absolute */ + top:0; + left:0; + right:0; + bottom:0; + z-index: var(--zorder_main_reconnectScreen) !important; + background-color:rgba(0, 0, 0, 0.815); +} +.reconnectScreenText { + position: fixed; /* or absolute */ + top: 50%; + left: 50%; + /* bring your own prefixes */ + transform: translate(-50%, -50%); + color:white; + font-family: 'Noto Sans Mono', monospace; +} +.md-subheader{ + background-color: transparent; +} +input{ + color: white; + background-color: #373737; + border-width: 0px; + border-radius: 5px; + padding: 1%; +} + +input { + background-color: var(--bng-black-o2); + box-sizing: border-box; + border-bottom: 0.125em solid rgba(255, 255, 255, 0.6); + border-radius: var(--bng-corners-1) var(--bng-corners-1) 0 0; + padding: 0.5em 0.25em; + overflow: visible; + position: relative; + transition: all 0.2s ease-in-out; + font-family: var(--fnt-defs); +} + +input:focus { + border-bottom: 0.125em solid var(--bng-orange-b400); +} + +input:disabled { + background-color: rgba(var(--bng-cool-gray-700), 0.5); + border-bottom: 0.125em solid var(--bng-cool-gray-600); +} + +/* Suffix and prefix styling */ + +.input-wrapper { + display: flex; + flex-direction: row; + flex-wrap: nowrap; + padding: 0; + position: relative; + font-size: 1em; + line-height: 1.25em; + color: white; +} + +.input-wrapper > span { + background-color: var(--bng-black-o8); + border-bottom: 0.125em solid var(--bng-cool-gray-700); + padding: 0.5em 0.25em; +} + +.input-wrapper.suffix > input { + border-radius: var(--bng-corners-1) 0 0 0; +} + +.input-wrapper.prefix > input { + border-radius: 0 var(--bng-corners-1) 0 0; +} + +.input-wrapper.suffix > span { + border-radius: 0 var(--bng-corners-1) 0 0; +} + +.input-wrapper.prefix > span { + border-radius: var(--bng-corners-1) 0 0 0; +} + + +/* AngularJS form validation specific style */ + +input.ng-invalid { + background-color: rgba(var(--bng-add-red-700), 0.5); + border-bottom: 0.125em solid var(--bng-add-red-500); +} + +.input-light{ + color: black; + background-color: white; + border-width: 0px; + border-radius: 5px; + padding: 1%; +} + +md-input-container label { + color:white; +} + +.bng-list-item { + display: flex; + flex-direction: row; + align-items: center; + justify-content: flex-start; + flex: 0 0 auto; + box-sizing: border-box; + min-height: 3em; +} + +.bng-list-item .settings-label { + display: flex; + flex-direction: row; + align-items: center; + justify-content: flex-start; + flex: 0 0 35%; + padding-right: 0.5em; +} + + +.bng-list-item .settings-input { + display: flex; + flex-direction: row; + align-items: center; + justify-content: flex-start; + flex: 1 0 auto; +} + +.bngPauseContent { + color: white; + text-shadow: -3px 3px 15px #DB7B00, 3px -3px 15px #DB7B00; + cursor: pointer; +} + +/* the button bar */ +.dashBrdButton { + margin:0 !important; + padding: 0.375em 0.75em 0.5em 0.75em !important; + border-radius:0 !important; + line-height: inherit; + height: auto; + text-align:center; + /* min-width: 80px; disabled for now */ + white-space: nowrap; + border-top: 0.125em solid transparent; +} + +.dashBrdButton:not(.dashBrdButtonActive):hover, .dashBrdButton:not(.dashBrdButtonActive):focus { + background-color:rgba(0, 0, 0, 0.6); + border-top-color: var(--bng-orange); +} + +/* the active button */ +.dashBrdButtonActive { + background-color:white; + border-top-color: var(--bng-orange); +} + +/* prevent user events */ +.dashBrdButtonInactive { + pointer-events: none; +} + +/* position */ +.dashBrdButtonStart { + /* margin-right: auto; */ + position: absolute; + top: 0; + left: 0; + bottom: 0; +} +.dashBrdButtonEnd { + /* margin-left: auto; */ + position: absolute; + top: 0; + right: 0; + z-index: var(--zorder_main_topbar_button_end); +} + +/* pause button */ +.dashBrdButtonPause { + background-color: var(--bng-orange-shade2); +} +.dashBrdButtonPaused { + animation: 2s infinite cubic-bezier(.17,.67,.83,.67) pauseButtonBreathing; +} + +@keyframes pauseButtonBreathing { + 0% + { + background-color: var(--bng-orange); + } + 50% + { + background-color: var(--bng-black-6); + } + 100% + { + background-color: var(--bng-orange); + } +} + +.dashBrdButtonPaused:hover { + background-color: var(--bng-orange-shade1opaque) !important; +} +.dashBrdButtonPause .dashBrdSvgIcon { + filter: unset !important; +} +.dashBrdButtonPause .dashBrdText { + color: white !important; +} + +/* topleft button */ +.dashBrdButtonTopLeft { + background-color: var(--bng-orange-shade2); +} + +@media (max-width: 1400px){ + .dashBrdButtonPause .dashBrdText, + .dashBrdButtonExit .dashBrdText { + display: none; + } +} + + +/* the icon inside the button */ +.dashBrdSvgIcon { + margin-top: -0.1em; + height: 100%; + color:white; + vertical-align:middle; + display: inline-block; +} + +.dashBrdSvgIcon > span{ + margin: 0 0.125em 0 0 !important; + font-size: 1em !important; +} + +.dashBrdButtonActive .dashBrdSvgIcon { + filter: var(--bng-filter-orange); +} +.dashBrdText { + font-family: 'Overpass', var(--fnt-defs); + color:white; + vertical-align: middle; + font-weight: 600; + /* line-height: 150%; */ + letter-spacing: 0.01em; +} +.dashBrdButtonActive .dashBrdText { + color:black; +} +.dashMenuHeight { + height: 100%; + max-height: 2.5em; + overflow: hidden; +} +@media (max-width: 1500px){ + .dashBrdText { + font-size: 0.75em; + } +} +@media (min-width: 1280px){ + .dashBrdSvgIcon { + margin-right: 0.25em; /* that's for text, but there's a case when icon won't be shown */ + } +} +@media (max-width: 1279px){ + #dashmenu > *:not(.dashBrdButtonActive) > .dashBrdText { + display: none; + } +} + +.fancyBackgroundFill { + z-index:var(--zorder_main_fancyBackground); + position: absolute; + top: 0px; + bottom: 0px; + left: 0px; + right: 0px; +} + +.contentNavMainmenu > * { + position: relative; + width: 100%; + height: 100%; + pointer-events: auto; +} + +menu-content > .filler > * { + pointer-events: auto; +} + +.menubar { + width: 100%; + height: 40px; + background: rgba(0, 0, 0, 0.6); +} + + +.headingContainer { + flex: 0 0 auto; + margin: 0em 0em 1em 4.5em; + font-family: 'Overpass', var(--fnt-defs) !important; + font-style: italic; + font-size: 1rem !important; + font-weight: 800; + color: white; + position: relative; + display: flex; + flex-direction: column; + align-items: flex-start; + line-height: 1.25em; + /* white-space: nowrap; */ +} + +.headingContainer::before { + content: ""; + position: absolute; + top: 0; + background-color: var(--bng-orange); + transform-origin: bottom left; + transform: skewX(-20deg); + left: -4.5em; + width: 2em; + height: 4.75em; + z-index: 1; +} + +.headingContainer > div { + background:rgba(0, 0, 0, 0.6); + display: inline-flex; + padding: 0 0.5em 0 0.5rem; + border-radius: 0.25em 0.25em 0.25em 0; +} + +.headingContainer h3, +.headingContainer h4, +.headingContainer h5, +.headingContainer h6 { + margin: 0.25em 0; + display: inline-block; + line-height: 1.25em; +} + +.headingContainer h1, +.headingContainer h2 { + margin: 0; + display: inline-block; + background:rgba(0, 0, 0, 0.6); + padding: 0.25em 0.75em 0.25em 0.5rem; + border-radius: 0 0.25em 0.25em 0.25em; + font-weight: 800; + letter-spacing: 0.01em; + line-height: 1.25em; +} + +.headingContainer h2 { + font-size: 1.75em; + line-height: 1.25em; +} + +.fixd-ratio { + display: grid; +} +.fixd-ratio > * { + grid-area: 1/1; +} + +/* override hard-coded colours and paddings in md stylesheets */ + +md-select-menu md-option { + padding: 0 0.625em; +} + +md-select-menu md-option:focus:not([selected]) { + background: transparent !important; +} +md-select-menu md-option[selected] { + color: var(--bng-orange) !important; + font-weight: 600; +} + +md-select-menu > md-content { + padding: 0.5em; + background-color: var(--bng-add-blue-900); +} + +.uiapps-hidden { + /* + when canvas or apps are hidden with display:none - apps are glitching + when apps or their controls have no pointer-events (none) - app's md-tooltips are glitching + therefore we have to just move it offscreen + */ + transform: translateY(500%); + overflow: hidden; +} + +md-list-item { + position: relative; +} + + + +.dialog:not(.experimental) { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + height: auto; + max-width: 25%; + padding: 15px; + border: 5px; + border-color: var(--bng-orange); + border-radius: 20px; + color: white; + background-color: var(--dark-neutral-grey); + text-align: center; +} +.dialog.dialog:not(.experimental) .broken_mods { + list-style-type: none; + text-align: left; + max-height:15em; + overflow-y: auto; +} + +.dialog.brokenModPrompt { + max-width: 80%; +} + +.dialog .options { + margin-top: 1rem; +} + +.dialog.experimental { + border: 3px solid rgba(255,0,0, 0.4); + display: flex; + box-sizing: border-box; + color: #FFF; + background: var(--dark-neutral-grey); + background-clip: padding-box; + border: solid 6px transparent; + border-radius: 1em; + padding: 0; + position: relative; + left: unset; + margin: auto; + width: 80ch; + max-width: 80%; + height: auto; + max-height: 80%; + top:40%; +} + +.dialog.experimental:after { + content: ""; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + margin: -6px; + border-radius: inherit; + z-index: -1; + background: repeating-linear-gradient(45deg, rgba(255, 0, 0, 0.3), rgba(255, 0, 0, 0.3), 10px, red 10px, red 20px); +} + +.dialog.experimental .bng-button:not(:last-child) { + margin-right: 0.8em; +} + +.dialog.experimental .header { + background: rgba(255,0,0, 0.4); + border-radius: 0.6em 0.6em 0 0; + padding: 0.3em; + font-size: 1.2em; +} + +.dialog.experimental .content { + border-radius: 0 0 0.6em 0.6em; + padding: 1em; +} + +.dialog.experimental .text { + margin-bottom: 1em; + padding: 0 2.5vw; + font-weight: 300; +} + +.vertical-divider { + margin: 0.4em 0.4em 0.4em 0.5em; + padding-left: 2px; + background: #fff; + transform: skewX(-20deg); + align-self: stretch; +} + +/* Career-styles */ + +.career-content, .career-card-buttons, .career-manage { + background: var(--bng-black-6); +} + +.career-content { + flex: 0.0001 1 auto; + display: flex; + flex-flow: column; + padding: 0 1em 1em; + overflow: hidden; +} + +.career-status-progress { + padding: 0.25em 0; +} + +.career-status-value { + flex-direction: row; + display: flex; + justify-content: center; + padding: 0; + align-items: flex-start; + font-style: italic; + font-weight: 700; +} + +.career-status-progress .career-status-value { + font-size: 2.25em; + line-height: 1.25em; + font-weight: 900; + padding-right: 0.05em; +} + +.career-status-progress .career-status-value > :first-child { + margin-right: 0.25em; +} + +.career-status-progress, .career-status-stars { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; +} + +.career-status-stars { + flex: 1 0 auto; + font-size: 1.5em; + line-height: 1em; + font-style: italic; + font-weight: 700; + display: none; /* Because we need some data first =( */ +} + +.career-status-stars .vertical-divider { + margin-top: 0.25em; + margin-bottom: 0.25em; +} + +.career-main-star, .career-bonus-star { + width: 1em; + height: 1em; + -webkit-mask-repeat: no-repeat; + -webkit-mask-size: contain; + -webkit-mask-position: 50% 50%; + margin-left: 0.25em; + margin-bottom:0.1em; +} + +.career-main-star { + -webkit-mask-image: url(/ui/assets/SVG/24/star.svg); + background: #ffffff; +} + +.career-bonus-star { + -webkit-mask-image: url(/ui/assets/SVG/24/star-secondary.svg); + background: hsl(240, 100%, 85%) +} + +.career-status-stars .label { + flex: 1 0 auto; +} + +.levels-progress { + padding: 0.5em 0 0; + display: flex; + flex-direction: row; + justify-content: flex-start; + align-items: center; +} + +.levels-progressbar-wrapper { + flex: 1 0 auto; +} + +.branch-icon-assembly { + width: 2.25em; + height: 2.25em; + flex: 0 0 auto; + position: relative; + margin-right: 0.5em; +} + +.branch-icon, .branch-bgcolor { + position: absolute !important; + top: 0; + bottom: 0; + left: 0; + right: 0; + -webkit-mask-repeat: no-repeat; + -webkit-mask-size: contain; + -webkit-mask-position: 50% 50%; +} + +.branch-icon { + background-color: #fff; + -webkit-mask-image: url(/ui/assets/SVG/24/branchXP-generic.svg); + filter: drop-shadow(0 0 0.5em #000); +} + +.branch-bgcolor { + background-color: #505050; + -webkit-mask-image: url(/ui/assets/SVG/24/branchXP-bg.svg); +} + +.levels-progressbar-wrapper .progressbar-labels { + display: flex; + flex-direction: row; + justify-content: flex-start; + align-items: flex-start; + font-style: italic; + font-weight: 700; + font-size: 1em; + line-height: 1.25em; +} + +.levels-progressbar-wrapper .progressbar-labels :first-child { + flex:1 0 auto; +} + +.progressbar-background { + /* height: 0.5em; */ + background: var(--bng-black-6); + overflow: hidden; + padding: 0.125em; + box-sizing: content-box; +} + +.progressbar-background .progressbar-fill { + padding:0.25em 0; + background-color: var(--bng-orange-b400); + box-shadow: #000 1em; + overflow: visible; +} + +.progressbar-value { + color:#fff; + font-size: 0.75em; + text-shadow: rgba(0,0,0, 0.2) 0 2px 8px; + /* position: static; */ + font-weight: 700; + padding-right: 0.5em; +} + +.levels-progress.inactive { + opacity: 0.5; +} + +.beamxp-label :first-child { + font-size: 0.75em; + line-height: 1em; + padding-left: 0.25em; +} + +.beamxp-label :nth-child(2) { + font-weight: 800; + padding-bottom: 0.1em; + font-size: 1.5em; + line-height: 1em; +} + +.money-label { + padding-bottom: 0.1em; + font-weight: 800; + font-size: 1.5em; + line-height: 1em; + order: -1; +} + +.career-modded-badge { + position: absolute !important; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + top: 0.5em; + right: -7.7em; + width: 20em; + padding: 0.2em 0 0.1em 0; + text-align: center; + font-family: "Overpass", var(--fnt-defs); + font-size: 0.75em; + font-weight: 600; + background: #2F4858; + transform: rotate(45deg); + transform-origin: center; +} + +/* star icon defaults */ + +.mission-star-wrapper { + display: flex; + flex-direction: row; + justify-content: flex-start; + align-items: center; + flex-wrap: nowrap; + padding-left: 0.6em; +} +.mission-star-container { + flex: 0 1 auto; +} +.mission-star-container > * { + display: block; +} + +.mission-star { + flex: 0 0 auto; + width: 1.5em; + height: 1.5em; + background-color: #666; + -webkit-mask-image: url(/ui/assets/SVG/24/star.svg); + -webkit-mask-repeat: no-repeat; + -webkit-mask-size: contain; + -webkit-mask-position: 50% 50%; + align-self: flex-start; + +} + +.mission-star-with-content .mission-star { + width: 3em; + height: 3em; +} +.mission-star.tiny { + width: 0.5em; + height: 0.5em; +} +.mission-star-with-content .mission-star.tiny { + width: 1em; + height: 1em; +} + +.mission-star.unlocked { + background-color: #fff; + +} + + + +.mission-star.bonus-star { + -webkit-mask-image: url(/ui/assets/SVG/24/star-secondary.svg); +} +.mission-star.multi-stars-1 { + -webkit-mask-image: url(/ui/assets/SVG/24/1-star.svg); +} +.mission-star.multi-stars-2 { + -webkit-mask-image: url(/ui/assets/SVG/24/2-star.svg); +} +.mission-star.multi-stars-3 { + -webkit-mask-image: url(/ui/assets/SVG/24/3-star.svg); +} + + + +.mission-reward-list { + display: flex; + flex-direction: row; + justify-content: flex-start; + align-items: center; + flex-wrap: nowrap; +} + +.mission-reward-list .unlocked { + text-decoration: line-through; +} + +.mission-reward-tuple { + display: flex; + flex-direction: row; + justify-content: flex-start; + align-items: center; + flex-wrap: nowrap; +} +.mission-amount-container { + flex: 0 1 auto; + font-size: 0.75em; + margin-right: 1em; +} +.mission-star-container > * { + display: block; +} + +.mission-tiny-attribute-icon { + flex: 0 0 auto; + width: 0.75em; + height: 0.75em; + background-color: #fff; + -webkit-mask-image: url(/ui/assets/SVG/24/star.svg); + -webkit-mask-repeat: no-repeat; + -webkit-mask-size: contain; + -webkit-mask-position: 50% 50%; + align-self: flex-start; +} + +.icon-mask { + -webkit-mask-repeat: no-repeat; + -webkit-mask-size: contain; + -webkit-mask-position: 50% 50%; +} + +.icon-beamxp-large { + width: 1em; + height: 1em; + -webkit-mask-image: url(/ui/assets/SVG/24/beamXP-full.svg); + background: #fff; +} + +.icon-beamxp-small { + width: 1em; + height: 1em; + -webkit-mask-image: url(/ui/assets/SVG/24/beamXP-small.svg); + background: #fff; +} + +.icon-beambucks { + width: 1em; + height: 1em; + -webkit-mask-image: url(/ui/assets/SVG/24/beambucks.svg); + background: #fff; +} + +/* Card style defaults */ + + +.card { + display: flex; + flex-flow: column; + flex-wrap: nowrap; + justify-content: flex-start; + align-items: flex-start; + /* padding: 0.25em; */ + color: white; + background-color: var(--bng-black-6); + /* background: center / cover no-repeat url(/ui/images/noimage.png); */ + border-radius: 0.25em; + text-align: left; + text-decoration: none; + cursor: default; + font-family: "Overpass", var(--fnt-defs) !important; + width: 100%; +} + +.card-title { + font-family: "Overpass", var(--fnt-defs); + font-weight: 800; + font-style: italic; + font-size: 1.5em; + letter-spacing: 0.02em; + line-height: 1.2em; + padding: 0.5em 2em 0.5em 1em; + align-self: stretch; + overflow-wrap: break-word; + overflow: hidden; + flex: 0 0 auto; +} + +.card-title::before { + content: ""; + position: absolute; + background-color: var(--bng-orange); + transform: skewX(-20deg); + left: -1.25em; + width: 2em; + height: 1.2em; + z-index: 1; +} + +.card-content-slot:first-child { + border-radius: 0.25em 0.25em 0 0; + overflow: hidden; +} + +.card-content-slot { + align-self: stretch; +} + +.mission-gallery:not(last-child) { + padding-bottom: 0.5em; +} + +.card-actions-wrapper { + padding: 1em 0.75em; + align-self: flex-end; +} + +/* define card styles as card-style-NAME, where NAME is the one you specify on a card */ +.card-style-default { /* default style if required */ +} +.card-style-hero .card-actions-wrapper { /* default style if required */ + align-self: stretch; + /* align-self: center; */ + margin-bottom: 0.25em; + text-align: center; + font-weight: 800; + font-style: italic; + font-size: 1.25em; + background-color: var(--bng-orange); +} + +/* Reward meter (3 stars with linear gradient fill) If you want to show more stars — override the width. +If you want to change the size of the whole block — use font-size */ +.reward-stars-meter { + background: no-repeat linear-gradient(90deg, rgb(200, 200, 0) 50%, rgba(255, 255, 255, 0.2) 50%) 0 0 / 200% 200%; + -webkit-mask-image: url(/ui/assets/SVG/24/star-nopaddings.svg); + -webkit-mask-repeat: space; + width:3.2em; + height:1em; + flex: 0 0 auto; +} + +.flex-row { + display: flex; + flex-flow: row wrap; +} + +.flex-column { + display: flex; + flex-flow: column wrap; +} + +.flex-nowrap { + flex-wrap: nowrap; +} + +.align-center { + align-items: center; +} + +.align-stretch { + align-items: stretch; +} + +.mission-rewards-label { + margin-right: 2em; +} + +.beamng-message-toast { + background-color: var(--bng-orange) !important; + opacity: 1 !important; +} + +@keyframes flashing-animation { + 50% { + fill: #000; + } +} +.flashing { + animation: flashing-animation 1.4s linear infinite; +} + +.ui-sheet { + height: 100vh; + position: absolute !important; + width: 100vw; + z-index: -100; + left: 0; + opacity: 0.1; + background-color: black; +} + +.binding-icon-mask { + height: 1.5em; + width: 1.5em; + background-color: rgba(255, 255, 255, 1); + mask-size: contain; + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + -webkit-mask-size: contain; +} + +.icon-light { + background-color: rgba(255, 255, 255, 1); +} + +.icon-dark { + background-color: rgba(0, 0, 0, 1); +} + +.bng-horizontal-divider { + margin: 0.5em; + border-top: 1px solid var(--bng-cool-gray-600); +} + +bng-accordion-pane.pane-vertical { + display: flex; + flex-direction: column; + align-items: stretch; + justify-content: flex-start; + padding-left: 1.5em; + min-height: 3em; +} + +.warning * { + flex: auto; + flex-grow: 1; + flex-shrink: 1; + flex-basis: auto; +} +.warning { + background-color: var(--bng-add-red-600); + padding: 0.75em 1em; + border-radius: var(--bng-corners-2); + display: inline-flex; + justify-content: center; + align-items: flex-start; + margin: 0.5em 0; +} + +/* md-switch tweaks */ + +md-switch { + margin: 0; + padding: 0.5em; + border-radius: var(--bng-corners-1); + position: relative; +} + +md-switch.md-checked .md-bar { + background-color: var(--bng-orange-600); +} + +md-switch.md-checked .md-thumb { + background-color: var(--bng-orange-b400); +} + +md-switch .md-bar { + background-color: var(--bng-cool-gray-800); +} + +md-switch .md-thumb { + background-color: var(--bng-cool-gray-500); +} + +md-switch:focus .md-thumb { + background-color: var(--bng-cool-gray-300); +} + +md-switch.md-checked:focus .md-thumb { + background-color: var(--bng-orange-300); +} + +.lottie-animation { + width: 3em; + height: 3em; + margin: -0.5em; +} \ No newline at end of file diff --git a/ui/entrypoints/main/main.js b/ui/entrypoints/main/main.js new file mode 100644 index 0000000..f17b61a --- /dev/null +++ b/ui/entrypoints/main/main.js @@ -0,0 +1,2335 @@ +angular.module('beamng.stuff', ['ngAnimate', 'toastr']) +angular.module('beamng.gameUI', ['ngAnimate', 'toastr']) +angular.module('beamng.color', []) +angular.module('beamng.gamepadNav', []) +angular.module('beamng.controls', []) + +angular.module('BeamNG.ui', ['beamng.core', 'beamng.components', 'beamng.data', 'ngMaterial', 'ngAnimate', 'ui.router', 'beamng.stuff', 'beamng.gameUI', 'beamng.apps', 'beamng.color', 'pascalprecht.translate', 'beamng.gamepadNav', 'beamng.controls', 'fc.paging','ngSanitize','jkAngularRatingStars','ngFitText']) + +.config(['$compileProvider', '$logProvider', '$stateProvider', '$urlRouterProvider', '$mdThemingProvider', '$translateProvider', 'toastrConfig', '$provide', + function($compileProvider, $logProvider, $stateProvider, $urlRouterProvider, $mdThemingProvider, $translateProvider, toastrConfig, $provide) { + + $translateProvider.useStaticFilesLoader({ + prefix: '/locales/', + suffix: '.json' + }) + $translateProvider.useSanitizeValueStrategy('escaped') + $translateProvider.preferredLanguage('en-US') // this is the default language to load + // this is the fallback in case individual translations are missing: + if (beamng.shipping) { + $translateProvider.fallbackLanguage('en-US') + } else { + $translateProvider.fallbackLanguage(['en-US', 'not-shipping.internal']) + } + //$translateProvider.use('de-DE') + + //DISABLE_TRANSLATIONS = true + + $logProvider.debugEnabled(false) + + + // ..... User Interface states + $stateProvider + + .state('play', { + url: '/play', + templateUrl: '/ui/modules/play/play.html', + controller: 'PlayController as playCtrl', + menuActionMapEnabled: false, // defaults to true + uiAppsShown: true, // defaults to false + }) + + .state('menu', { + url: '/menu', + templateUrl: '/ui/modules/menu/menu.html', + controller: 'MenuController as menuCtrl', + uiLayout: 'menu', + uiAppsShown: true, // defaults to false + }) + + // so the trick is: we load the real menu in the background in another view while the startscreen is covering it up + // This ensures that everything is properly loaded before we switch to it. + // Ideally, no dom changes are required then :) + // more docs to read: https://github.com/angular-ui/ui-router/wiki/Multiple-Named-Views#view-names---relative-vs-absolute-names + // this is a three stage process right now: + // 1) load menu.start which just loads the startscreen + // 2) the controller will navigate after some tiny bit to 'menu.start_loadmainmenu' + // 3) the controller will navigate after 3 seconds to 'menu.mainmenu' + // this process ensures that: + // a) BeamNG Logo is not perceived as 'lagging' as the mainmenu is loading at the same time. + // b) Mainmenu can load safely with the start screen fully done with everything. + + .state('menu.start', { + loaderVisible: true, + views: { + 'loader@': { // target the loader view in parent menu state + templateUrl: '/ui/modules/startScreen/startScreen.html', + controller: 'startScreenController as startScreen', + }, + '@menu': { } + }, + }) + + .state('menu.start_loadmainmenu', { + loaderVisible: true, + views: { + 'loader@': { // target the loader view in parent menu state + templateUrl: '/ui/modules/startScreen/startScreen.html', + controller: 'startScreenController as startScreen', + }, + '@menu': { // target the unnamed view in parent menu state + templateUrl: '/ui/modules/menu/menu.html', + controller: 'MenuController as menuCtrl', + } + }, + transitionAnimation: 'moduleBlendOnLeave', + }) + + .state('menu.mainmenu', { + views: { + 'loader':{}, // empty the loader view + '@menu': { // targe the unnamed default view in the menu parent state + templateUrl: `/ui/modules/mainmenu/drive/mainmenu.html`, + controller: 'MainMenuController as mmCtrl', + } + } + }) + + .state('menu.onlineFeatures', { + url: '/onlineFeatures', + templateUrl: `/ui/modules/onlineFeatures/online.html`, + controller: 'OnlineFeaturesController', + backState: 'menu.mainmenu', + }) + + .state('menu.bigmap', { + url: '/bigmap', + templateUrl: '/ui/modules/bigmap/bigmap.html', + controller: 'BigMapController', + backState: 'BACK_TO_MENU', + params: { + missionId: null + }, + careerUiLayout: 'careerBigMap', + uiLayout: 'blank', + uiAppsShown: true, // defaults to false + }) + + .state('menu.levels', { + url: '/levels', + templateUrl: '/ui/modules/levelselect/levelselect.html', + controller: 'LevelSelectController as lsCtrl', + backState: 'menu.mainmenu', + }) + + .state('menu.levelDetails', { + url: '/levels-details/:levelName', + templateUrl: '/ui/modules/levelselect/levelselect-details.html', + controller: 'LevelSelectDetailsController as levelsDetails', + backState: 'menu.levels', + }) + + .state('menu.busRoutes', { + url: '/bus', + templateUrl: '/ui/modules/busRoute/busRoute.html', + controller: 'BusRoutesController as busCtrl', + backState: 'menu.mainmenu', + }) + + .state('menu.busRoutesLevelSelect', { + url: '/bus/level', + templateUrl: '/ui/modules/busRoute/levelSelect.html', + controller: 'BusRoutesLevelController', + backState: 'menu.busRoutes', + }) + + // .state('menu.busRoutesVehicleSelect', { + // url: '/bus/vehicle/:garage/:mode/:event', + // templateUrl: '/ui/modules/vehicleselect/vehicleselect.html', + // controller: 'VehicleSelectController as vehicles', + // backState: 'menu.busRoutes', + // }) + + .state('menu.busRoutesRouteSelect', { + url: '/bus/route', + templateUrl: '/ui/modules/busRoute/routeSelect.html', + controller: 'BusRoutesRouteController', + backState: 'menu.busRoutes', + }) + + .state('menu.environment', { + url: '/environment', + templateUrl: '/ui/modules/environment/environment.html', + controller: 'EnvironmentController as environment', + backState: 'BACK_TO_MENU', + }) + + // Track Builder + // .state('menu.trackBuilder', { + // url: '/trackBuilder', + // templateUrl: '/ui/modules/trackBuilder/trackBuilder.html', + // controller: 'TrackBuilderController as trackBuilder' + // }) + + .state('menu.scenarios', { + url: '/scenarios', + templateUrl: '/ui/modules/scenarioselect/scenarioselect.html', + controller: 'ScenarioSelectController', + backState: 'menu.mainmenu', + }) + + .state('menu.campaigns', { + url: '/campaigns', + templateUrl: '/ui/modules/campaignselect/campaignselect.html', + controller: 'CampaignSelectController as campaignSelect', + backState: 'menu.mainmenu', + }) + + + + .state('menu.appedit', { + url: '/appedit/:mode', + templateUrl: '/ui/modules/appedit/appedit.html', + controller: 'AppEditController as ctrl', + backState: 'BACK_TO_MENU', + uiAppsShown: true, // defaults to false + }) + + .state('menu.appselect', { + url: '/appselect', + templateUrl: '/ui/modules/appselect/appselect.html', + controller: 'AppSelectController as apps', + backState: 'menu.appedit', + }) + + .state('menu.vehicles', { + url: '/vehicleselect/:garage/:mode/:event', + templateUrl: '/ui/modules/vehicleselect/vehicleselect.html', + controller: 'VehicleSelectController as vehicles', + backState($scope, $state, $stateParams) { + if ($scope.gameState === 'garage') + return 'garage'; + if ($stateParams && $stateParams.hasOwnProperty('mode')) { + switch ($stateParams.mode) { + case 'busRoutes': return 'menu.busRoutes'; + case 'lightRunner': return 'menu.lightrunnerOverview'; + } + } + return 'BACK_TO_MENU'; + }, + }) + + .state('menu.vehiclesdetails', { + url: '/vehicle-details/:model/:config/:mode/:event/{showAuxiliary:bool}', + templateUrl: '/ui/modules/vehicleselect/vehicleselect-details.html', + controller: 'VehicleDetailsController as vehicle', + backState: 'menu.vehicles', + }) + + .state('menu.options', { + url: '/options', + templateUrl: '/ui/modules/options/options.html', + controller: 'OptionsController', + controllerAs: 'options', + backState: 'BACK_TO_MENU', + abstract: true + }) + .state('menu.options.help', { + url: '/help', + templateUrl: '/ui/modules/options/help.partial.html', + controller: 'SettingsHelpCtrl as opt', + backState: 'BACK_TO_MENU', + }) + .state('menu.options.performance', { + url: '/performance', + templateUrl: '/ui/modules/options/performance.partial.html', + controller: 'SettingsPerformanceCtrl as opt', + backState: 'BACK_TO_MENU', + }) + .state('menu.options.display', { + url: '/display', + templateUrl: '/ui/modules/options/display.partial.html', + controller: 'SettingsGraphicsCtrl as opt', + backState: 'BACK_TO_MENU', + }) + .state('menu.options.graphics', { + url: '/graphics', + templateUrl: '/ui/modules/options/graphics.partial.html', + controller: 'SettingsGraphicsCtrl as opt', + backState: 'BACK_TO_MENU', + }) + + .state('menu.options.audio', { + url: '/audio', + templateUrl: '/ui/modules/options/audio.partial.html', + controller: 'SettingsAudioCtrl as opt', + backState: 'BACK_TO_MENU', + }) + + .state('menu.options.gameplay', { + url: '/gameplay', + templateUrl: '/ui/modules/options/gameplay.partial.html', + controller: 'SettingsGameplayCtrl as opt', + backState: 'BACK_TO_MENU', + }) + + .state('menu.options.camera', { + url: '/camera', + templateUrl: '/ui/modules/options/camera.partial.html', + controller: 'SettingsCameraCtrl as opt', + backState: 'BACK_TO_MENU', + }) + + .state('menu.options.userInterface', { + url: '/userInterface', + templateUrl: '/ui/modules/options/userinterface.partial.html', + controller: 'SettingsUserInterfaceCtrl as opt', + backState: 'BACK_TO_MENU', + }) + + .state('menu.options.language', { + url: '/language', + templateUrl: '/ui/modules/options/language.partial.html', + controller: 'SettingsLanguageCtrl as opt', + backState: 'BACK_TO_MENU', + }) + + .state('menu.options.stats', { + url: '/stat:category', + templateUrl: '/ui/modules/stat/stats.html', + controller: 'StatsController as statCtrl', + backState: 'menu.mainmenu', + }) + + .state('menu.options.other', { + url: '/other', + templateUrl: '/ui/modules/options/other.partial.html', + controller: 'SettingsOtherCtrl as opt', + backState: 'BACK_TO_MENU', + }) + + .state('menu.options.licenses', { + url: '/licenses', + templateUrl: '/ui/modules/options/licenses.partial.html', + controller: 'SettingsLicensesCtrl as opt', + backState: 'BACK_TO_MENU', + }) + + .state('menu.options.controls', { + url: '/controls', + templateUrl: '/ui/modules/options/controls.html', + controller: 'ControlsController as controls', + backState: 'BACK_TO_MENU', + }) + .state('menu.options.controls.bindings', { + views: { + '': { + url: '/bindings', + templateUrl: '/ui/modules/options/controls-bindings.html', + controller: 'ControlsBindingsCtrl as controlsBindings' + } + }, + backState: 'BACK_TO_MENU', + }) + .state('menu.options.controls.bindings.edit', { + views: { + 'edit@menu.options': { + url: '/edit', + templateUrl: '/ui/modules/options/controls-edit.html', + controller: 'ControlsEditCtrl as controlsEdit' + } + }, + params: {action: '', oldBinding: {}}, + backState: 'BACK_TO_MENU', + }) + + .state('menu.options.controls.filters', { + views: { + '': { + url: '/filters', + templateUrl: '/ui/modules/options/controls-filters.html', + controller: 'ControlsFiltersCtrl as controlsFilters' + }, + }, + backState: 'BACK_TO_MENU', + }) + + .state('menu.options.controls.ffb', { + views: { + '': { + url: '/ffb', + templateUrl: '/ui/modules/options/controls-ffb.html', + controller: 'ControlsFfbCtrl as controlsFfb' + } + }, + backState: 'BACK_TO_MENU', + }) + + .state('menu.options.controls.ffb.edit', { + views: { + 'edit@menu.options': { + url: '/edit', + templateUrl: '/ui/modules/options/controls-edit.html', + controller: 'ControlsEditCtrl as controlsEdit' + } + }, + params: {action: '', oldBinding: {}}, + backState: 'BACK_TO_MENU', + }) + + .state('menu.options.controls.hardware', { + views: { + '': { + url: '/hardware', + templateUrl: '/ui/modules/options/controls-hardware.html', + controller: 'ControlsHardwareCtrl as controlsHw' + } + }, + backState: 'BACK_TO_MENU', + }) + + .state('menu.vehicleconfig', { + url: '/vehicle-config', + templateUrl: '/ui/modules/vehicleconfig/vehicleconfig.html', + controller: 'VehicleconfigCtrl', + redirectTo: 'menu.vehicleconfig.parts', + backState: 'BACK_TO_MENU', + }) + .state('menu.vehicleconfig.parts', { + url: '/vehicle-config/parts', + templateUrl: '/ui/modules/vehicleconfig/partial.parts.html', + controller: 'Vehicleconfig_parts as vehConf_parts', + backState: 'BACK_TO_MENU', + uiAppsShown: true, // defaults to false + }) + .state('menu.vehicleconfig.tuning', { + url: '/vehicle-config/tuning', + templateUrl: '/ui/modules/vehicleconfig/partial.tuning.html', + controller: 'Vehicleconfig_tuning as vehConf_tuning', + backState: 'BACK_TO_MENU', + uiAppsShown: true, // defaults to false + }) + .state('menu.vehicleconfig.color', { + url: '/vehicle-config/color', + templateUrl: '/ui/modules/vehicleconfig/partial.color.html', + controller: 'Vehicleconfig_color as vehConf_color', + backState: 'BACK_TO_MENU', + }) + .state('menu.vehicleconfig.save', { + url: '/vehicle-config/save', + templateUrl: '/ui/modules/vehicleconfig/partial.save.html', + controller: 'Vehicleconfig_save as vehConf_save', + backState: 'BACK_TO_MENU', + }) + .state('menu.vehicleconfig.debug', { + url: '/vehicle-config/debug', + templateUrl: '/ui/modules/vehicleconfig/debug.partial.html', + controller: 'Vehicleconfig_debug as vehConf_debug', + backState: 'BACK_TO_MENU', + uiAppsShown: true, // defaults to false + }) + + + + .state('menu.mods', { + url: '/mods', + template: '', + abstract: true, + controller: 'ModManagerController as modCtrl', + backState: 'BACK_TO_MENU', + }) + + .state('menu.mods.local', { + url: '/local', + views: { + '': { + controller: 'LocalModController as modLoclCtrl', + templateUrl: '/ui/modules/modmanager/mods.html' + }, + 'content@menu.mods.local': { + templateUrl: '/ui/modules/modmanager/local.html' + }, + 'filter@menu.mods.local': { + templateUrl: '/ui/modules/modmanager/filter.html' + } + }, + backState: 'BACK_TO_MENU', + }) + + .state('menu.mods.downloaded', { + url: '/downloaded', + views: { + '': { + controller: 'DownloadModController as modDwlCtrl', + templateUrl: '/ui/modules/modmanager/mods.html' + }, + 'content@menu.mods.downloaded': { + templateUrl: '/ui/modules/modmanager/downloaded.html', + } + }, + backState: 'BACK_TO_MENU', + }) + + .state('menu.mods.scheduled', { + url: '/scheduled', + views: { + '': { + controller: 'ScheduledModController as modSchCtrl', + templateUrl: '/ui/modules/modmanager/mods.html' + }, + 'content@menu.mods.scheduled': { + templateUrl: '/ui/modules/modmanager/scheduled.html', + }, + 'filter@menu.mods.scheduled': { + templateUrl: '/ui/modules/modmanager/scheduled_conflict.html' + } + }, + backState: 'BACK_TO_MENU', + }) + + .state('menu.mods.repository', { + url: '/repository?query', + views: { + '': { + controller: 'RepositoryController as repo', + templateUrl: '/ui/modules/modmanager/mods.html' + }, + 'content@menu.mods.repository': { + templateUrl: '/ui/modules/repository/repository.html' + }, + 'filter@menu.mods.repository': { + templateUrl: '/ui/modules/repository/filter.html' + } + }, + backState: 'BACK_TO_MENU', + }) + + .state('menu.mods.automation', { + url: '/automation?query', + views: { + '': { + controller: 'AutomationController as automation', + templateUrl: '/ui/modules/modmanager/mods.html' + }, + 'content@menu.mods.automation': { + templateUrl: '/ui/modules/automation/automation.html' + }, + 'filter@menu.mods.automation': { + templateUrl: '/ui/modules/automation/filter.html' + } + }, + backState: 'BACK_TO_MENU', + }) + + .state('menu.mods.automationDetails', { + url: '/automation/detail/{modId:[0-9A-Z]+}?page¶m', + views: { + '': { + controller: 'AutomationDetailsController as automationDetailCtrl', + templateUrl: '/ui/modules/modmanager/mods.html' + }, + 'content@menu.mods.automationDetails': { + templateUrl: '/ui/modules/automation/automation-details.html' + }, + 'filter@menu.mods.automationDetails': { + templateUrl: '/ui/modules/automation/info.html' + } + }, + backState: 'BACK_TO_MENU', + }) + + .state('menu.mods.details', { + url: '/detail/{modId:[0-9A-Z]+}?page¶m', + views: { + '': { + controller: 'RepositoryDetailsController as repoDetailCtrl', + templateUrl: '/ui/modules/modmanager/mods.html' + }, + 'content@menu.mods.details': { + templateUrl: '/ui/modules/repository/repository-details.html' + }, + 'filter@menu.mods.details': { + templateUrl: '/ui/modules/repository/info.html' + } + }, + backState: 'menu.mods.repository', + }) + + .state('menu.modsDetails', { + url: '/modmanager/details:modFilePath', + templateUrl: '/ui/modules/modmanager/info.html', + controller: 'ModManagerControllerDetails as managerDetailCtrl', + backState: 'menu.mods.repository', + }) + + .state('menu.gameContext', { + url: '/gameContext', + templateUrl: '/ui/modules/gameContext/gameContext.html', + controller: 'GameContextController', + backState: 'BACK_TO_MENU', + }) + + .state('scenario-start', { + url: '/scenariocontrol/start', + params: { + data: {} + }, + templateUrl: '/ui/modules/scenariocontrol/start.html', + controller: 'ScenarioStartController as scenarioStart', + backState: 'BACK_TO_MENU', + uiLayout: 'blank', + uiAppsShown: true + }) + + .state('scenario-end', { + url: '/scenariocontrol/end', + params: { + missionData: {}, + stats: {}, + rewards: {}, + portrait: {} + }, + templateUrl: '/ui/modules/scenariocontrol/end.html', + controller: 'ScenarioEndController', + backState: 'BACK_TO_MENU', + careerUiLayout: 'careerMissionEnd', + uiAppsShown: true + }) + + .state('quickrace-end', { + url: '/quickraceEnd', + params: { + stats: {}, + mockScenario: {} + }, + templateUrl: '/ui/modules/scenariocontrol/quickraceEnd.html', + controller: 'ScenarioEndController', + backState: 'BACK_TO_MENU', + }) + + .state('chapter-end', { + url: '/cchapterEnd', + params: { + stats: {} + }, + templateUrl: '/ui/modules/scenariocontrol/end.html', + controller: 'ScenarioEndController', + backState: 'BACK_TO_MENU', + }) + + // Transition to this state is handled by some unknown dark force (Torque?). + // Until this chanages, keep the url hash to "loading". + .state('loading', { + url: '/loading', + templateUrl: '/ui/modules/loading/loading.html', + controller: 'LoadingController as loading', + transitionAnimation: 'moduleBlendOnLeave', + backState: 'BLOCK', + }) + + .state('comic', { + url: '/comic', + params: { + comiclist: {} + }, + templateUrl: '/ui/modules/comic/comic.html', + controller: 'ComicController', + backState: null, + }) + + .state('menu.photomode', { + url: '/photo-mode', + templateUrl: '/ui/modules/photomode/photomode.html', + controller: 'PhotoModeController as photo', + backState: 'BACK_TO_MENU', + }) + + .state('menu.replay', { + url: '/replay', + templateUrl: '/ui/modules/replay/replay.html', + controller: 'ReplayController as replay', + backState: 'BACK_TO_MENU', + }) + + .state('blank', { + uiAppsShown: true + }) + + .state('iconViewer', { + url: '/iconViewer', + templateUrl: '/ui/modules/iconView/icons.html', + controller: 'iconViewerCtrl as iconCtrl', + backState: 'BACK_TO_MENU', + }) + + .state('fadeScreen', { + url: '/fadeScreen', + templateUrl: '/ui/modules/fadeScreen/fadeScreen.html', + params: { + fadeIn: 1, + pause: 0, + fadeOut: 1, + data: {} + }, + controller: 'fadeScreen', + backState: 'BLOCK', + }) + + .state('mapview', { + url: '/mapview', + templateUrl: '/ui/modules/mapview/mapview.html', + controller: 'MapViewCtrl as mapview', + params: { + data: {} + }, + backState: 'BACK_TO_MENU', + // params: { + // baseImg: '', + // points: [], + // onClick: '' + // } + }) + + //Dragrace states WIP + .state('menu.dragRaceOverview', { + url: '/dragrace/overview', + templateUrl: '/ui/modules/dragrace/overview.html', + controller: 'DragRaceController', + params: { + results: {}, + cinematicEnabled: true + }, + backState: 'BACK_TO_MENU', + }) + + // LightRunner States + .state('menu.lightrunnerOverview', { + url: '/lightrunner/overview', + templateUrl: '/ui/modules/lightrunner/overview.html', + controller: 'LightRunnerController', + backState: 'BACK_TO_MENU', + }) + + .state('menu.lightrunnerTrackSelect', { + url: '/lightrunner/track', + templateUrl: '/ui/modules/lightrunner/trackSelect.html', + controller: 'LightRunnerTrackController', + backState: 'BACK_TO_MENU', + }) + + //Quickrace states WIP + .state('menu.quickraceOverview', { + url: '/quickrace/overview', + templateUrl: '/ui/modules/quickrace/overview.html', + controller: 'QuickraceController', + backState: 'BACK_TO_MENU', + }) + + .state('menu.quickraceLevelselect', { + url: '/quickrace/level', + templateUrl: '/ui/modules/quickrace/levelSelect.html', + controller: 'QuickraceLevelController', + backState: 'menu.quickraceOverview', + }) + + .state('menu.quickraceTrackselect', { + url: '/quickrace/track', + templateUrl: '/ui/modules/quickrace/trackSelect.html', + controller: 'QuickraceTrackController', + backState: 'menu.quickraceLevelselect', + }) + + .state('campaign', { + url: '/campaign', + template: '', + backState: 'BACK_TO_MENU', + }) + + .state('campaign.quickraceOverview', { + url: '/quickrace/overview', + params: { + level: {}, + track: {}, + vehicles: {}, + }, + templateUrl: '/ui/modules/quickrace/overview.html', + controller: 'QuickraceController', + backState: 'BACK_TO_MENU', + }) + + .state('campaign.quickraceLevelselect', { + url: '/quickrace/level', + templateUrl: '/ui/modules/quickrace/levelSelect.html', + controller: 'QuickraceLevelController', + backState: 'BACK_TO_MENU', + }) + + + .state('campaign.quickraceTrackselect', { + url: '/quickrace/track', + templateUrl: '/ui/modules/quickrace/trackSelect.html', + controller: 'QuickraceTrackController', + backState: 'BACK_TO_MENU', + }) + + .state('campaign.vehicles', { + url: '/vehicleselect/:garage/:mode', + templateUrl: '/ui/modules/vehicleselect/vehicleselect.html', + controller: 'VehicleSelectController as vehicles', + backState: 'BACK_TO_MENU', + }) + + .state('campaign.vehicleDetails', { + url: '/vehicle-details/:model/:config/:mode', + templateUrl: '/ui/modules/vehicleselect/vehicleselect-details.html', + controller: 'VehicleDetailsController as vehicle', + backState: 'BACK_TO_MENU', + }) + + .state('garage', { + url: '/garage', + templateUrl: '/ui/modules/garage/garage.html', + controller: 'GarageController as garageCtrl', + // menuActionMapEnabled: false, + uiAppsShown: true, + // uiLayout: 'garage', + backState: "menu.mainmenu", + }) + + .state('menu.partInventory', { + url: '/partInventory', + params: { + }, + templateUrl: '/ui/modules/partInventory/partInventory.html', + controller: 'PartInventoryController', + uiAppsShown: true, + backState: "menu.mainmenu", + }) + + + .state('menu.career', { + url: '/career', + templateUrl: '/ui/modules/career/career.html', + controller: 'CareerController', + backState: 'BACK_TO_MENU', + }) + + .state('menu.careermission', { + url: '/career-mission', + templateUrl: '/ui/modules/careermission/mission.html', + controller: 'GameContextController', + params: { + isCareer: true + }, + backState: 'BACK_TO_MENU', + uiLayout: 'blank', + uiAppsShown: true, // defaults to false, + }) + + .state('menu.careerPause', { + url: '/careerPause', + templateUrl: '/ui/modules/careerPause/careerPause.html', + controller: 'CareerPauseController', + backState: 'BACK_TO_MENU', + careerUiLayout: 'careerPause', + uiAppsShown: true, + }) + + .state('menu.careerQuests', { + url: '/career-quest', + templateUrl: '/ui/modules/careerQuests/questsOverview.html', + controller: 'QuestOverviewController', + params: { + questId: undefined + }, + backState: 'BACK_TO_MENU', + }) + + .state('menu.careerVehicleSelect', { + url: '/careerVehicleSelect', + templateUrl: '/ui/modules/careerVehicleSelect/careerVehicleSelect.html', + params: { + data: {} + }, + //uiAppsShown: false, + controller: 'CareerVehicleSelectController', + backState: 'BACK_TO_MENU', + }) + + .state('menu.threeElementSelect', { + url: '/threeElementSelect', + templateUrl: '/ui/modules/threeElementSelect/threeElementSelect.html', + params: { + data: {} + }, + //uiAppsShown: false, + controller: 'ThreeElementSelectController', + backState: 'BACK_TO_MENU', + }) + + .state('menu.careerLogBook', { + url: '/careerLogBook', + templateUrl: '/ui/modules/careerLogBook/logBook.html', + params: { + entryId: undefined + }, + //uiAppsShown: false, + controller: 'CareerLogBookController', + backState: 'BACK_TO_MENU', + }) + + // default entry that is loaded on startup: + $urlRouterProvider.otherwise('menu.start') + + $compileProvider.debugInfoEnabled(false) + + // whitelist for local:// prefix + $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|ftp|mailto|local):/) + $compileProvider.imgSrcSanitizationWhitelist(/^\s*(https?|ftp|mailto|local):/) + + let theme = $mdThemingProvider.theme('default') + theme.dark() + + $mdThemingProvider.definePalette('customPrimary', { + '50': '#ffffff', + '100': '#e9e9e9', + '200': '#d3d3d3', + '300': '#bebebe', + '400': '#a9a9a9', + '500': '#959595', + '600': '#818181', + '700': '#6d6d6d', + '800': '#5b5b5b', + '900': '#484848', + 'A100': '#373737', + 'A200': '#262626', // ### hue-3 + 'A400': '#171717', // ### hue-2 + 'A700': '#000000', + 'contrastDefaultColor':'dark', + 'contrastLightColors': ['600', '700', '800', '900', 'A100', 'A200', 'A400', 'A700'] + }) + theme.primaryPalette('customPrimary', { + 'default': '800', // by default use shade A800 from the custom palette for primary intentions + 'hue-1': 'A100', // use shade A900 for the md-hue-1 class + 'hue-2': 'A400', // use shade A400 for the md-hue-2 class + 'hue-3': '50', // use shade A100 for the md-hue-3 class + }) + + $mdThemingProvider.definePalette('customAccent', { + '50': '#662800', + '100': '#803200', + '200': '#993c00', + '300': '#b34600', + '400': '#cc5000', + '500': '#e65a00', + '600': '#ff741a', + '700': '#ff8333', + '800': '#ff934d', + '900': '#ffa266', + 'A100': '#ff741a', + 'A200': '#ff6400', + 'A400': '#e65a00', + 'A700': '#ffb280', + 'contrastDefaultColor':'dark', + 'contrastLightColors': ['500', '600', '700', '800', '900', 'A100', 'A200', 'A400', 'A700'] + }) + theme.accentPalette('customAccent', { + 'default': '500', + 'hue-1': '300', + 'hue-2': '800', + 'hue-3': 'A100', + }) + + $mdThemingProvider.definePalette('customWarn', { + '50': '#ff7b82', + '100': '#ff626a', + '200': '#ff4852', + '300': '#ff2f3a', + '400': '#ff1522', + '500': '#fb000d', + '600': '#e1000c', + '700': '#c8000a', + '800': '#ae0009', + '900': '#950008', + 'A100': '#ff959a', + 'A200': '#ffaeb3', + 'A400': '#ffc8cb', + 'A700': '#7b0006', + 'contrastDefaultColor':'dark', + 'contrastLightColors': ['300','400','500', '600', '700', '800', '900', 'A200', 'A400', 'A700'] + }) + theme.warnPalette('customWarn', { + 'default': '400', + 'hue-1': '500', + 'hue-2': '100', + 'hue-3': 'A700', + }) + + $mdThemingProvider.definePalette('customBackground', { + '50': 'rgba(35,35,35,0.35)', + '100': 'rgba(35,35,35,0.4)', + //workaround for now, no clue why checkboxes are using '200' + //200': 'rgba(35,35,35,0.45)', + '200': '#FFF', + '300': 'rgba(35,35,35,0.5)', + '400': 'rgba(35,35,35,0.55)', + '500': 'rgba(35,35,35,0.6)', + '600': 'rgba(35,35,35,0.65)', + '700': 'rgba(35,35,35,0.7)', + '800': 'rgba(35,35,35,0.75)', + '900': 'rgba(35,35,35,0.8)', + 'A100':'rgba(35,35,35,0.85)', + 'A200':'rgba(35,35,35,0.9)', + 'A400':'rgba(35,35,35,0.95)', + 'A700':'rgba(35,35,35,1)', + 'contrastDefaultColor':'light', + }) + theme.backgroundPalette('customBackground', { + 'default': 'A400', + 'hue-1': '300', + 'hue-2': '600', + 'hue-3': '900', + }) + + + // debug with this: + //console.log("UI theme setup: ", $mdThemingProvider) + + angular.extend(toastrConfig, { + autoDismiss: false, + containerId: 'toast-container', + maxOpened: 10, + newestOnTop: true, + positionClass: 'toast-top-right', + preventOpenDuplicates: true, + allowHtml: true, + }) + + +}]) + +.run(['$animate', '$http', '$rootScope', '$templateCache', '$window', '$translate', 'UIAppStorage', 'Settings', 'SettingsAuxData', 'bngWSApi', '$state', +function ($animate, $http, $rootScope, $templateCache, $window, $translate, UIAppStorage, Settings, SettingsAuxData, bngWSApi, $state) { + + // apply language settings + $rootScope.$on('SettingsChanged', function(evt, data) { + if(data.values.uiLanguage) { + let lang = data.values.uiLanguage + if(lang == '') lang = 'en-US' + $http.get(`/locales/${lang}.json`,).then(function(res) { + vueI18n.global.locale = lang + vueI18n.global.setLocaleMessage(lang, res.data) + }) + } + }) + + + $http.get('/ui/modules/vehicleconfig/vehicle-config-tree.html').then(function (tmpl) { + $templateCache.put('vehicle-config-tree', tmpl.data) + }) + + $http.get('/ui/assets/sprites/svg-symbols.svg') + .success(svgSprite => { + var iconsSprite = angular.element(svgSprite) + angular.element(document.head).append(iconsSprite) + }) + + window.globalAngularRootScope = $rootScope + + /* --- VUE3 START --- */ + // i18n vue3 basics + window.bngVue && window.bngVue.start({ + i18n: vueI18n + }) + i18NLanguageFinished = true + + + /* --- VUE3 END --- */ + $rootScope.$on('$translateChangeSuccess', (event, data) => { + i18nLanguageUsed = data.language + }) + + $rootScope.$on('$translateChangeStart', () => { + i18NLanguageFinished = false + }) + + $rootScope.$on('$translateChangeEnd', () => { + i18NLanguageFinished = true + }) + + /* + $rootScope.$on('$translateChangeError', () => { console.log('translateChangeError')}) + $rootScope.$on('$translateLoadingStart', () => { console.log('translateLoadingStart')}) + $rootScope.$on('$translateLoadingSuccess', () => { console.log('translateLoadingSuccess')}) + $rootScope.$on('$translateLoadingError', () => { console.log('translateLoadingError')}) + $rootScope.$on('$translateLoadingEnd', () => { console.log('translateLoadingEnd')}) + $rootScope.$on('$translatePartialLoaderStructureChanged', () => { console.log('translatePartialLoaderStructureChanged')}) + */ + + //$animate.enabled(false) + + bngApi.engineLua('ui_apps.requestUIAppsData()') + + // ..... Define all objects attached directly to the window object here + + /** + * HookManager is maybe the most important object to share status between + * the game and the user interface. Common usage from the game modules looks + * like HookManager.trigger(, ). In order to catch these events + * inside a controller, one has to set up a listener like + * + * @example + * $scope.$on('EventName', function (event, data) { + * // do all kinds of stuff with data... + * }) + * + * Angular's event system manages the listener's removal on the $destroy event of + * the current scope. + **/ + + // listen to the window resize event. Maybe this can also be handled from the CEF side. + angular.element($window).bind('resize', function () { + var size = {width: window.innerWidth, height: window.innerHeight} + $rootScope.$broadcast('windowResize', size) + }) + + // This should not be a function attached to the window object, but rather a HookManager event. + // Until this is done, we just mock up the process. + // $window.updateProgress = function(val, txt) { + // $rootScope.$broadcast('UpdateProgress', {value: Math.floor(100 * val), text: txt }) + // } + + // Update game state each time a route change is triggered. + // Maybe an overkill, but why not be sure? + //$rootScope.$on('$stateChangeSuccess', function (event, toState, toStateParams) { + //}) + + // // settings storage for simple consumers in JS + // $rootScope.Settings = null + // $rootScope.$on('SettingsChanged', function (event, data) { + // $rootScope.Settings = data + // }) + + // use this to imitate settings lag + // let lag = false; + $rootScope.$on('SettingsChanged', function (event, data) { + // if (lag) + // Settings.loaded = true; + // lag = true; + Settings.loaded = true; // flag Settings as loaded + Settings.options = data.options; + Settings.values = data.values; + }) + + bngApi.engineLua('settings.notifyUI()') + bngApi.engineLua('core_gamestate.requestMainMenuState()') + bngApi.engineLua('core_gamestate.requestGameState()') + // bngApi.engineLua('print("requesting gamestate here and now")') + + // settings storage end + + // navigate to start pages + if (beamng.shipping && beamng.ingame && beamng.buildtype === 'RELEASE') { + $state.go('menu.start') + } else { + $state.go('menu.mainmenu') + } + +}]) + +//------------Trying filter for date translation --------- put on separate file!!! + +.filter('formattedDate', function(dateFilter, $translate) { + + var format = null, translated = false + + function returnFilter(inputDate) { + if(format){ + return dateFilter(inputDate, format) + }else{ + return '-' + } + } + + function formattedDateFilter(inputDate){ + if( format === null ) { + if( !translated ){ + translated = true + $translate('general.time_format').then(function (result) { + format = result + },function (translationId) { + format = translationId + }) + } + + } + else return returnFilter(inputDate) + } + + formattedDateFilter.$stateful = true + return formattedDateFilter +}) + + +angular.module('beamng.stuff') + +.service('translateService', ['$translate', function($translate){ + + contextTranslate = function(val, translateContext) { + if(typeof val == "string") { + return $translate.instant(val) + } else { + if (val && val.txt && val.context) { + let context = val.context + if(translateContext) { + let newContext = {} + for (let key in context) { + if (context.hasOwnProperty(key)) { + newContext[key] = contextTranslate(context[key], true); + } + } + context = newContext + } + return $translate.instant(val.txt, context) + } + } + return val + } + multiContextTranslate = function(val) { + if(val.txt) { + return contextTranslate(val) + } + let description = "" + for (var i = 0; i < val.length; i++) { + description = description + contextTranslate(val[i]) + } + return description + } + return { + contextTranslate: contextTranslate, + multiContextTranslate: multiContextTranslate + } +}]) + +.filter('contextTranslate', ['translateService', function($translateService) { + function contextTranslateFilter(input){ + return $translateService.contextTranslate(input, true) + } + + contextTranslateFilter.$stateful = true + return contextTranslateFilter +}]) + +.service('gamepadNav', ['$rootScope', function ($rootScope) { + 'use strict' + + // TODO: hook this up to lua settings + // TODO: think about using a list of actions, so when one module unregisters it's action the old action gets used. + // this would have the benefit for example of dropdowns beeing opened, and while open their actions would be used + // todo: actually test the list approach + let useCrossfire = true + let useGamepadNavigation = false + let noop = () => {} + let actions = { + up: [{module: 'root', func: noop}], + down: [{module: 'root', func: noop}], + right: [{module: 'root', func: noop}], + left: [{module: 'root', func: noop}], + confirm: [{module: 'root', func: noop}], + back: [{module: 'root', func: noop}], + } + let prefix = { + up: 'menu_item_up', + down: 'menu_item_down', + right: 'menu_item_right', + left: 'menu_item_left', + confirm: 'menu_item_select', + back: 'menu_item_back', + 'radial-x': 'menu_item_radial_x', + 'radial-y': 'menu_item_radial_y', + 'tab-right': 'menu_tab_right', + 'tab-left': 'menu_tab_left', + } + + + function assignNavFunc (module, data) { + for (var name in data) { + if (actions[name] !== undefined) { + if (nonAssignable.indexOf(name) === -1) { + actions[name].push({module: module, func: data[name]}) + // console.debug('Registered new function to "' + name+ '"') + } else { + // console.error('"' + name + '" is an unchangable action') + } + } else { + // console.error('"' + name + '" is not a valid action') + } + } + } + + function unregisterActions (module, data) { + for (var name in data) { + if (actions[name] !== undefined) { + var helper = actions[name].map((elem) => elem.module) + if (helper.indexOf(module) !== -1) { + if (nonAssignable.indexOf(name) === -1) { + actions[name].splice(helper.indexOf(module), 1) + // console.debug('Succesfully unregistered "' + name+ '"') + } else { + // console.error('"' + name + '" is an unchangable action') + } + } else { + // console.error('Could not unregister "' + name + '" because there was no registered action from this modul') + } + } else { + // console.warn('Could not unregister "' + name + '" because it is not a valid action') + } + } + } + + $rootScope.$on('MenuItemNavigation', function (event, action, val) { + //console.log('MenuItemNavigation - Got action: ' + action) + //console.log('Enabled Librarys', useCrossfire, useGamepadNavigation) + if(!beamng.ingame) return + + if (action == 'toggleMenues') { + $rootScope.$broadcast('MenuToggle', val) + return + } + if(action == 'back') { + $rootScope.$broadcast('MenuToggle') + return + } + if (["left", "right", "up", "down"].indexOf(action) != -1) { + bngApi.engineLua('extensions.hook("onMenuItemNavigation")') + } + + if (useCrossfire) { + if (action == 'confirm') { + const active = document.activeElement; + if (isNavigatable(active)) { + if (typeof active.click === "function") { + active.click() + } else { + let click = new CustomEvent("click") + active.dispatchEvent(click) + } + } + } else if(action == 'back') { + $rootScope.$broadcast('MenuToggle') + } else if (["left", "right", "up", "down"].indexOf(action) != -1) { + bngApi.engineLua('extensions.hook("onMenuItemNavigation")') + const targets = collectRects(action); + navigate(targets, action); + //console.log(`navigation ${action} handled by Crossfire`) + } else if (action == 'tab-left') { + $rootScope.$broadcast('$tabLeft') + } else if (action == 'tab-right') { + $rootScope.$broadcast('$tabRight') + } + } + + if (useGamepadNavigation && actions[action]) { + //console.log(actions[action]) + // console.log(actions[action][0]) + $rootScope.$evalAsync(actions[action][0].func) + } + }) + + return { + crossfireEnabled: () => useCrossfire, + gamepadNavEnabled: () => useGamepadNavigation, + spatialNavEnabled: () => useCrossfire, + // TODO: make this intuitive (omiting the value shouldn't do something unexpected) + enableCrossfire: (val) => useCrossfire = val, + enableGamepadNav: (val) => useGamepadNavigation = val, + enableSpatialNav: (val) => { log.error("SpatialNavigation is deprecated. Please use Crossfire."); useCrossfire = val }, + registerActions: assignNavFunc, + unregisterActions: unregisterActions, + provideScope: (scope) => scope = scope, + prefix: (val) => prefix[val] || val, + } +}]) + +/** + * @ngdoc controller + * @name beamng.stuff.controller:AppCtrl + * @description This is the top-level controller used throughout the game +**/ +.controller('AppCtrl', ['$document', '$log', '$rootScope', '$scope', '$sce', '$compile', '$state', '$stateParams', '$translate', '$window', 'ControlsUtils', 'Utils', 'Settings', 'toastr', '$timeout', 'gamepadNav', '$injector', '$location', 'translateService', 'UiAppsService', 'MessageToasterService', 'InputCapturer', + function($document, $log, $rootScope, $scope, $sce, $compile, $state, $stateParams, $translate, $window, ControlsUtils, Utils, Settings, toastr, $timeout, gamepadNav, $injector, $location, translateService, UiAppsService,messageToasterService, InputCapturer) { + var vm = this + vm.uiSheetActive = false + + // hack to fix backspace navigating between different menus. + // https://stackoverflow.com/questions/29006000/prevent-backspace-from-navigating-back-in-angularjs + $document.on('keydown', function(e){ + if(e.which === 8 && ( e.target.nodeName !== "INPUT" && e.target.nodeName !== "TEXTAREA" && e.target.nodeName !== "SELECT" ) ){ // you can add others here inside brackets. + e.preventDefault() + } + }) + + // // Attempted fix to prevent keyboard events getting through to game when a textbox is being edited + // // Unfortunately causes other issues, but this may be on the right lines. Commented for now as it causes + // // more issues, and probably doesn't address some instances where the problem occurs. Related ticket is GE-4138 + + // $document.on('mouseup', function(e){ + // $timeout(() => { + // vm.uiSheetActive = ["INPUT", "TEXTAREA"].includes(document.activeElement.tagName) ? true : false; + // }, 10); + // }) + + // Handle "Messages" of the category 'career' with MessageToasterService + messageToasterService.handledCategories = ['career'] + messageToasterService.active = true + + + setTimeout(() => { + gamepadNav.provideScope($scope) + }) + + bngApi.engineLua('extensions.hook("onUIInitialised")') + + $scope.$on('requestUIInitialised', () => { + bngApi.engineLua('core_gamestate.onUIInitialised()') + }) + + vm.shipping = beamng.shipping + vm.uitest = false + vm.uitestshow = false + + // on CEF devtools toggle + $scope.$on('onCEFDevToolsVisibility', (event, enabled) => { + $scope.$applyAsync(function () { + vm.uitest = enabled + bngVue.debug(enabled) + }) + }) + + // figure out if CEF devtools are already open + bngApi.engineLua("getCefDevConsoleOpen()", (enabled)=> { + $scope.$applyAsync(function () { + vm.uitest = enabled + bngVue.debug(enabled) + }) + }) + + // *** DEBUG START + vm.currentStateName = ''; + vm.stickyPlayState = null; + + $scope.$state = $state + vm.states = $state.get().filter(state => !state.abstract) // filter abstract states + //console.log("vm.states = ", vm.states) + + vm.emitMenuNav = function(action, val) { + $rootScope.$broadcast('MenuItemNavigation', action, val) + } + vm.switchState = function(stateName) { + if(stateName !== undefined) { + $state.go(stateName) + } + } + vm.prevState = function() { + if(vm.currentStateName === '') vm.currentStateName = vm.states[0].name + let nextStateIdx = -1 + for(let i in vm.states) { + if(vm.states[i].name == vm.currentStateName) { + nextStateIdx = parseInt(i) - 1 + if(nextStateIdx < 0) nextStateIdx = vm.states.length - 1 + break + } + } + if(nextStateIdx != -1) { + console.log("Switching to new state: " + vm.states[nextStateIdx].name) + $state.go(vm.states[nextStateIdx].name) + } + } + vm.nextState = function() { + if(vm.currentStateName === '') vm.currentStateName = vm.states[0].name + let nextStateIdx = -1 + for(let i in vm.states) { + if(vm.states[i].name == vm.currentStateName) { + nextStateIdx = parseInt(i) + 1 + if(nextStateIdx >= vm.states.length) nextStateIdx = 0 + break + } + } + if(nextStateIdx != -1) { + console.log("Switching to new state: " + vm.states[nextStateIdx].name) + $state.go(vm.states[nextStateIdx].name) + } + } + vm.reloadUI = function() { + window.location.reload() + } + + // shortcut for debugging + window.openState = function(name) { + console.log("Switching to new state: " + name) + //bngApi.engineLua("ActionMap.enableInputCommands(false)") + $state.go(name) + } + // *** DEBUG END + + + vm.replayPaused = false + vm.replayActive = false + vm.physicsPaused = false + vm.physicsMaybePaused = false + vm.showPauseIcon = false + vm.showCrosshair = false + // vm.uiLayoutPrevious = false + vm.playmodeState = null + + function updatePauseState() { + vm.physicsPaused= !vm.replayActive && vm.physicsMaybePaused + vm.showPauseIcon = vm.physicsPaused || vm.replayPaused + //console.log("updatePauseState", $state.current.name, vm.showPauseIcon) + } + + // quite a hack, but the alternative would have been to manage a list and wait for each state to be actiavated + // the problem only occured because changeState was called almost simultaniously and before on state could be transitioned to the other ocnditinal was already executed next. + // TODO change this to use the $state.transition promise + var transitioningTo + + // Screens that tasklist UI app will be visible + const captureInput = InputCapturer(); + $scope.$on('$stateChangeSuccess', function (event, toState, toParams, fromState, fromParams) { + //console.log(`switching stage from ${fromState.name} to ${toState.name}`) + //console.trace() + vm.currentStateName = toState.name + + // make sure Vue sees all state changes that affect location hash + if (toState.url && location.hash!=toState.url) location.hash = toState.url + + + // update activated action maps for UI bindings + let menuActionMapEnabled = typeof $state.current.menuActionMapEnabled === "boolean" ? $state.current.menuActionMapEnabled : true; // true by default + // bngApi.engineLua(`extensions.core_input_bindings.setMenuActionMapEnabled(${menuActionMapEnabled})`) + captureInput(menuActionMapEnabled); + bngApi.engineLua(`extensions.hook("onUiChangedState", "${toState.name}", "${fromState.name}")`) + + // bngApi.engineLua("career_career.isCareerActive()", data => { + // if (data) { + // console.log(toState) + // if (toState.name == 'play') { + // console.log(menuActionMapEnabled) + // bngApi.engineLua(`bullettime.pause(${menuActionMapEnabled})`); + // } + // if (fromState.name == 'play') { + // console.log(menuActionMapEnabled) + // bngApi.engineLua(`bullettime.pause(${menuActionMapEnabled})`); + // } + // } + // }); + + // update ui apps layout + bngApi.engineLua("career_career.isCareerActive()", isCareerActive => { + $scope.$evalAsync(() => { + if (isCareerActive && $state.current.careerUiLayout) { + $scope.$emit('appContainer:loadLayoutByType', $state.current.careerUiLayout) + isUILayoutOverriden = true + } else { + if (vm.currentStateName === 'blank') { + console.log('Current state name is blank. Ignoring layout update because this may be already handled ' + + 'ChangeState for Vue screens.') + } else if ($state.current.uiLayout === 'blank') { + console.log('Current layout name is blank. Clearing layout.') + $scope.$emit('appContainer:clear') + // $scope.$emit('appContainer:clear') + } else if ($state.current.uiLayout === undefined) { + // no particular ui layout defined, ensure we are in the default/previous one (whichever that may have been) + // console.log(`No layout defined - using previous (${vm.uiLayoutPrevious})`) + + if (!(vm.playmodeState && (vm.playmodeState.state || vm.playmodeState.appLayout)) || + // ignore menu.appselect from emitting layout to prevent issue menu.appedit + // selected layout will always be current game layout + vm.currentStateName === "menu.appselect") + return + + if (typeof vm.playmodeState.appLayout == "string") { + $scope.$emit('appContainer:loadLayoutByType', vm.playmodeState.appLayout) + } else if (typeof vm.playmodeState.appLayout == "object") { + $scope.$emit('appContainer:loadLayoutByObject', vm.playmodeState.appLayout) + } + else { + $scope.$emit('appContainer:loadLayoutByReqData', {type: vm.playmodeState.state}); + } + // if (vm.uiLayoutPrevious) { + // $scope.$emit('appContainer:loadLayoutByReqData', vm.uiLayoutPrevious) + // vm.uiLayoutPrevious = null + // } + } else { + // console.log(`Layout defined (${$state.current.uiLayout})`) + + // this state requires a particular ui layout, set + // vm.uiLayoutPrevious = UiAppsService.getLayout() + $scope.$emit('appContainer:loadLayoutByType', $state.current.uiLayout) + } + } + + // update ui apps visibility + $scope.$emit("ShowApps", !!$state.current.uiAppsShown); + + $state.previous = fromState; + $state.previousArgs = fromParams; + + if ( + fromState.name !== "menu" && fromState.name.indexOf("menu.") !== 0 && + (toState.name === "menu" || toState.name.indexOf("menu.") === 0) + ) { + $state.gamestate = fromState; + $state.gamestateArgs = fromParams; + } + + transitioningTo = undefined + updatePauseState() + }) + }) + }) + + $scope.$on('GameStateUpdate', function (event, data) { + vm.playmodeState = data; + }) + + $scope.$on('setNavigationStickyPlayState', function(event, stateName) { + vm.stickyPlayState = stateName + }) + + $scope.$on('$stateChangeCancel', function ( event, toState, toParams, fromState, fromParams) { + //console.warn('$stateChangeCancel', JSON.stringify({toState: toState, toParams: toParams, fromState: fromState, fromParams: fromParams}, null, ' ')) + }) + + $scope.$on('$stateChangeError', function ( event, toState, toParams, fromState, fromParams, error) { + console.error('$stateChangeError', toState, toParams, fromState, fromParams, error) + }) + + + vm.changeAngularStateFromVue = function(state) { + vm.switchState(state) + } + + const vueTasklistScreens = ['menu.refueling'] + $scope.$on('$stateNotFound', function (event, unfoundState, fromState, fromParams) { + if (vueTasklistScreens.includes(unfoundState.to)) { + bngApi.engineLua("career_career.isCareerActive()", isCareerActive => { + if (isCareerActive) { + $scope.$emit('appContainer:loadLayoutByType', 'tasklist') + } + }) + } + // angular doesn't recognise the state, so try Vue (making sure it doesn't pingpong back to here) + bngVue.gotoGameState(unfoundState.to, {tryAngularJS: false}) + unfoundState.to = 'blank' + }) + + $scope.$on('$stateChangeStart', function (event, toState, toParams, fromState, fromParams) { + if(toState.name == 'loading' && !beamng.ingame) { + // in external UI: do not ever allow transitioning into the loading state + event.preventDefault() + return + } + //hack to prevent undesired sound effect stacking + if(toState.name == 'menu.bigmap') { + dontPlayPauseSound = true + } + newPageSilenceEventCounter = 2 + newPageTimestamp = Date.now() + //console.log('stateChangeStart', toState, toParams, fromState, fromParams) + //console.trace() + transitioningTo = toState.name + vm.transitionAnimation = toState.transitionAnimation || fromState.transitionAnimation // prefer the animation of the target state, otherwise use the state we came from + }) + + + $scope.$on('ChangeState', function (event, target, ifCurrent) { + let targetName = typeof target === "string" ? target : target.state; + let current = $state.current.name; + // console.log('received ForceStateChange w/', { targetName, ifCurrent, current, transitioningTo}); + // set current name if we're during a transition + if (typeof transitioningTo !== "undefined" && transitioningTo !== current) { + current = transitioningTo; + } + + // fix ifCurrent + if (typeof ifCurrent !== "undefined" && !Array.isArray(ifCurrent)) { + ifCurrent = [ifCurrent]; + } + // console.log(current, ifCurrent, ifCurrent.includes(current)); + // decide if we're going to change state + if (!ifCurrent || ifCurrent.includes(current)) { + // console.log(`switching to state: ${targetName}`); + let params = target.params || {}; + let stateTransitioning = $state.go(targetName, params, { reload: true }); + // console.log("switched:", stateTransitioning); + } + }) + + $scope.$on('onCrosshairVisibilityChanged', function (event, visible) { + $scope.$applyAsync(function () { + vm.showCrosshair = visible + }) + }) + + vm.showApps = true + vm.uiVisible = true + vm.mainmenu = true + vm.gameState = null + vm.ingame = beamng.ingame + vm.settings = Settings + vm.uiReady = false + + // downloader start + var dlinfo = {} + function cancelHelper (id, ctr) { + if (dlinfo[id] && dlinfo[id][0] == 1) { + // hacky fix to remove downloading toastr when it gets stuck. + // This seems to be an issue with ngAnimate, more info here: + // https://github.com/Foxandxss/angular-toastr/issues/136 + dlinfo[id][1].el[0].style.display = "none" + toastr.clear(dlinfo[id][1]) + delete dlinfo[id] + } else { + if (ctr < 4) { + // setTimeout(cancelHelper.bind(undefined, id, ctr + 1),200) + } + } + } + $window.downloadStateChanged = function(data) { + if(data.filename == '') return + + //console.log('downloadStateChanged', data) + if(data.state == 'working' && !dlinfo[data.id]) { + // the 0% is imporatne here, so the toaster library doesn't think the success msg later one to be a duplicate, so please leave it there + var t = toastr.info(data.filename + ': 0%', 'Downloading mod', { + positionClass: 'toast-top-right', + timeOut: 0, + extendedTimeOut: 0, + // progressBar: true, + closeButton: true, + onTap: function () { + $state.go('menu.mods.downloaded') + } + }) + dlinfo[data.id] = [1, t] + console.warn(dlinfo[data.id]) + } else if(data.state == 'working' && dlinfo[data.id][0] == 1) { + $scope.$evalAsync(function () { + if(dlinfo[data.id]) { + dlinfo[data.id][1].scope.message = $sce.trustAsHtml(data.filename + ": " + Math.floor(data.dlnow / data.dltotal * 100) + "%") + } + }) + + } else if(data.state == 'finished') { + cancelHelper(data.id) + + var t = toastr.success(data.filename, 'Downloaded mod', { + positionClass: 'toast-top-right', + timeOut: 20000, + closeButton: true, + onTap: function () { + var help = data.filename + if (help.slice(-4) === '.zip') { + help = help.slice(0, -4) + } + $state.go('menu.mods.downloaded', { + // modFilePath: encodeURIComponent(help) + }) + } + }) + } + } + // downloader end + + // let Lua know the UI is up and running + let ngLoaded = false, made2ndSettingsRequest = false; + function checkReadiness(canForce=false) { + if (vm.uiReady) // may happen when SettingsChanged event fired twice at once and this function not yet evaluated + return; + if (Settings.loaded) { + lsnSettingsChange(); + if (ngLoaded) { // if this is false - either UI is still loading or broken due to mods + bngApi.engineLua("uiReady()"); + vm.uiReady = true; + } + } else if (!made2ndSettingsRequest) { + // just to make sure, we'll send another request for settings but only once - we have a listener here anyway + // to imitate lag, find line above with $rootScope.$on('SettingsChanged' + // console.log("Settings are lagging behind..."); + bngApi.engineLua("settings.notifyUI()"); + made2ndSettingsRequest = true; + } else if (ngLoaded && canForce) { + console.warn("Settings not being loaded after subsequent requests. This error should never happen. Forcing UI to show."); + bngApi.engineLua("uiReady()"); + vm.uiReady = true; + } + } + let lsnSettingsChange = $scope.$on("SettingsChanged", () => { + setTimeout(() => checkReadiness(true), 50); + }); + angular.element(document).ready(() => { + ngLoaded = true; + checkReadiness(true); + }); + $scope.$on("isUIReady", function (event) { // is this ever firing? + if (vm.uiReady) { + bngApi.engineLua("uiReady()"); + } else { // not necessary, just for additional safety + checkReadiness(); + } + }) + + $scope.$on('modmanagerError', function (event, data) { + $scope.$emit('app:waiting', false) + toastr.error(data, 'Error in Modmanager', { + positionClass: 'toast-top-right', + preventDuplicates: true, + progressBar: true, + timeOut: 10000, + extendedTimeOut: 1000, + closeButton: true + }) + }) + + var toasts = {} + + $scope.$on('toastrMsg', function (ev, data) { + toasts[data.title] = toastr[data.type]($translate.instant(data.msg, data.context), $translate.instant(data.title, data.context), data.config) + }) + + $scope.$on('toastrClose', function (ev, name) { + toastr.clear(toasts[name]) + // delete toasts[name] + }) + + vm.sections = { // defines in which state after which icons should be a divider + freeroam: ['menu.vehicleconfig.parts', 'menu.environment', 'menu.photomode', 'menu.options.graphics'], + scenario: ['menu.photomode'], + } + + $scope.$on('ShowEntertainingBackground', (ev, mainmenu) => { + //console.log("ShowEntertainingBackground") + $scope.$evalAsync(() => { + vm.mainmenu = mainmenu + }) + }) + + vm.openRepo = function() { + var onlineState = Settings.values.onlineFeatures + if (onlineState === 'enable') { + $state.go('menu.mods.repository') + } + else { + window.location.href = 'http-external://www.beamng.com/resources/' + } + } + + // The "GameStateUpdate" is triggered by calling "getGameState()" in the Lua engine. + // $rootScope takes care of this each time there is a state change. + $scope.$on('GameStateUpdate', function (event, data) { + //console.log(`got game state: ${data.state}`, data) + + $scope.$evalAsync(() => { + vm.gameState = data.menuItems + }) + }) + + $scope.$on('ShowApps', function (event, data) { + // console.log('got', (data ? 'show' : 'hide'), 'apps') + vm.showApps = data + }) + + $scope.$on("MenuFocusShow", function (event, enabled) { + //if (!enabled) uncollectRects($scope) + }) + + // Method used to show mods on repository when 'view ingame' on https://www.beamng.com/resources/ is clicked. + $scope.$on('ShowMod', function (event, data) { + var startTimeout + + if (data) { + (function checkStart() { + // check if startScreen is still active every 100ms + if($state.current.name === 'startScreen') { + startTimeout = setTimeout(checkStart, 100) + } else { + // if no startScreen then we can continue to show mod + window.location.href = `local://local/ui/entrypoints/main/index.html#/menu/mods/detail/${data}` + clearTimeout(startTimeout) + } + })() + } + }) + + $scope.$on('MenuToggle', (event, data) => { + //console.log('toggleMenu', data, $state.current) + //console.trace() + if(!beamng.ingame) return + + // *** navigation back logic here + let backState = $state.current.backState; + // this hack allows to catch Esc or (B) gamepad button + // currently used in garage mode and bigmap + if (typeof $state.preventStateChange === "function" && $state.preventStateChange()) { + backState = "BLOCK"; + } else if (typeof backState === "function") + backState = backState(vm, $state, $stateParams); + if(backState) { + let targetState = backState + + if(targetState === 'BLOCK') { + if(!$state.current.tryCounter) { + $state.current.tryCounter = 0 + } + $state.current.tryCounter++ + if($state.current.tryCounter < 6) { + return + } + $state.current.tryCounter = null + targetState = 'BACK_TO_MENU' + } + + if (targetState === 'BACK_TO_MENU') { + targetState = selectTopMenu() + } else if (targetState == 'play' && vm.stickyPlayState) { + targetState = vm.stickyPlayState + } + + $state.go(targetState, getPrevArgs($state, targetState)) + return + } + + //console.log(`received MenuToggle in gamestate: ${vm.gameState}. currently in state: ${$state.current.name}`) + + let showMenu = false + $scope.$evalAsync(function () { + if (typeof(data) == 'boolean') { + showMenu = data + } else { + showMenu = $state.current.name !== (vm.gameState === 'garage' ? 'menu.mainmenu' : 'menu') + } + let targetState + if (showMenu) { + targetState = selectTopMenu() + } else { + // figure out where to go 'back' to. Normally the play state, but in scenarios it might be different + targetState = vm.stickyPlayState || 'play' + } + $state.go(targetState, getPrevArgs($state, targetState)) + }) + bngApi.engineLua(`extensions.hook("onMenuToggled", ${showMenu})`) + }) + function getPrevArgs($state, targetState) { + // exiting menus + if ($state.gamestate && $state.gamestateArgs && + $state.gamestate.name === targetState) { + return $state.gamestateArgs; + } + // generic going back + if ($state.previous && $state.previousArgs && + $state.previous.name === targetState) { + return $state.previousArgs; + } + return null; + } + function selectTopMenu() { + return vm.mainmenu || vm.gameState === 'garage' ? 'menu.mainmenu' : 'menu' + } + + + $scope.$on('MenuHide', function (event, data) { + if(!beamng.ingame) return + // TODO FIXME + //console.log(">>>> MENUHIDE", data) + //console.trace() + // TODO: FIXME + let showMenu = false + if (typeof(data) == 'boolean') { + showMenu = data + } + if (showMenu) { + $state.go(vm.mainmenu ? 'menu.mainmenu' : 'menu') + } else { + $state.go('play') + } + }) + + $scope.$on('onCefVisibilityChanged', function (event, cefVisible) { + $scope.$evalAsync(function () { + vm.uiVisible = cefVisible + }) + }) + + $scope.$on('hide_ui', function (event, visible) { + let cmd = (visible === undefined) ? `extensions.ui_visibility.toggleCef()` : `extensions.ui_visibility.set(${visible})` + console.error('The hide_ui function is deprecated and will stop working in the future. Please use ' + cmd) + bngApi.engineLua(cmd) + }) + + vm.quit = function () { + if (vm.mainmenu) { + bngApi.engineScript('quit();') //It should work but doesn't, `Platform::postQuitMessage` is executed but nothing happens, maybe CEF catch that message + bngApi.engineLua("TorqueScript.eval('quit();')") + } else { + bngApi.engineLua("returnToMainMenu()") + } + } + + $scope.$on('CloseMenu', () => { + var newTarget = vm.mainmenu ? 'menu.mainmenu' : 'menu' + console.log("target", newTarget) + $state.go(newTarget) + }) + + $scope.$on('quit', vm.quit) + + $scope.$on('SettingsChanged', (ev, data) => { + //console.log('SettingsChanged, updating languages... ', data.values.uiLanguage) + if(data.values.uiLanguage && data.values.uiLanguage !== '' && i18NLanguageFinished && i18nLanguageUsed !== data.values.uiLanguage) { + $rootScope.$eval(function() { + $translate.use(data.values.uiLanguage) + }) + } + }) + // ************************************************************************** + + + // The "MenuOpenModule" event is used to quickly open a state from keyboard. + // The various arguments are defined in the lua/t3d/input_actions.json file + // and map actions to HookManager calls (and stuff for other modules). + // + // NOTE: Remember that transitioning to a state is not enough - menu must be open too!! + $scope.$on('MenuOpenModule', function (event, data) { + //console.log('received MenuOpenModule w/', data) + switch (data) { + case 'help': + $state.go('menu.options.help') + break + case 'vehicleselect': + $state.go('menu.vehicles') + break + case 'vehicleconfig': + $state.go('menu.vehicleconfig.parts') + break + case 'vehicledebug': + $state.go('menu.vehicleconfig.debug') + break + case 'options': + $state.go('menu.options.display') + break + case 'appedit': + $state.go('menu.appedit') + break + default: + $state.go(data) + break + } + }) + + $scope.$on('InputBindingsChanged', function (event, data) { + $scope.pauseControlText = "" + $scope.pauseControlIcon = "" + for (var i = 0; i < data.bindings.length; i++) { + for (var j = 0; j < data.bindings[i].contents.bindings.length; j++) { + var binding = data.bindings[i].contents.bindings[j] + if (binding.action != "pause") continue + $scope.pauseControlText = binding.control + $scope.pauseControlIcon = ControlsUtils.deviceIcon(data.bindings[i].contents.devicetype) + break + } + } + }) + + $scope.$on('physicsStateChanged', function (event, state) { + $scope.$evalAsync(function () { + // Clicking Options in menu will trigger pause even if game is already paused + // Need to check if previous state is already paused + if (vm.physicsMaybePaused === false && state === false && typeof dontPlayPauseSound === "undefined") { + bngApi.engineLua(`Engine.Audio.playOnce('AudioGui', 'event:>UI>Generic>Pause')`) + } + if(typeof dontPlayPauseSound !== "undefined") { + delete dontPlayPauseSound + } + vm.physicsMaybePaused = !state + updatePauseState() + }) + }) + + $scope.$on('replayStateChanged', function (event, core_replay) { + $scope.$evalAsync(function () { + vm.replayActive = core_replay.state === 'playing' + vm.replayPaused = vm.replayActive && core_replay.paused + updatePauseState() + }) + }) + + + vm.unpause = function () { + bngApi.engineLua('bullettime.pause(false)') + } + + $scope.$on('requestPhysicsState', function (event) { + $scope.$broadcast('physicsStateChanged', !vm.physicsPaused) + }) + + + + vm.isWaiting = false + + $scope.$on('app:waiting', function (event, value, callback) { + vm.isWaiting = value + Utils.waitForCefAndAngular(() => { + if (callback !== undefined && typeof callback === 'function') { + callback(vm.isWaiting) + } + bngApi.engineLua('extensions.hook("onUiWaitingState", ' + String(value) + ')') + }) + }) + + $scope.$on('onLevelsChanged', function(event, data){ + levelsData = data + }) + bngApi.engineLua('extensions.core_levels.requestData()') + + /* + $rootScope.$watch(function() { + console.log("### DIGEST ###") + /// if you want to find out where the digest is triggered: + //console.trace() + }) + */ + +}]) + +.service('BlurGame', [function () { + // todo: find a solution if i should actually overflow at some point + let highestId = 0 + let list = {} + + + function updateLua () { + if(!beamng.ingame) return + //console.log('---------update blur to lua' + JSON.stringify(list)) + bngApi.engineLua(`extensions.ui_gameBlur.replaceGroup("uiBlur", ${bngApi.serializeToLua(list)})`) + } + + return { + register: function (coord) { + if (coord === undefined) { + throw new Error('Cannot register bng-blur with coordinates: ' + coord) + } + + if (list.isEmpty()) { + bngApi.engineLua('extensions.load("ui_gameBlur")') + } + + highestId += 1 + list[highestId] = coord + updateLua() + return highestId + }, + unregister: function (i) { + if (i in list) { + delete list[i] + updateLua() + } + + if (list.isEmpty()) { + highestId = 0 + bngApi.engineLua('extensions.unload("ui_gameBlur")') + } + }, + update: function (i, coord) { + if (!(i in list)) { + console.error("Trying to update bng-blur with an ID that is not registered: " + i + " (of " + Object.keys(list) + ")") + throw new Error('Trying to update bng-blur with an ID that is not registered: ' + i + " (of " + Object.keys(list) + ")") + } + list[i] = coord + updateLua() + }, + } +}]) + +.directive('bngBlur', ['BlurGame', 'RateLimiter', function (BlurGame, RateLimiter) { + return { + restrict: 'A', + link: function (scope, elem, attrs) { + let id + let blurAmount = 1 + let blurUpdateWrapper = RateLimiter.debounce(updateBlur, 50) + + const resizeObserver = new ResizeObserver(blurUpdateWrapper); + resizeObserver.observe(elem[0]); + + scope.$watch(attrs.bngBlur, val => { + switch (typeof val) { + case "undefined": + val = 1; + break; + case "boolean": + val = val ? 1 : 0; + break; + case "number": + if (val < 0 || val > 1) { + console.error(`Attempted to use bng-blur with a number out of range 0..1: ${val}\nSee stack:\n${new Error().stack}`); + val = 1; + } + // all fine + break; + default: + console.error(`Attempted to use bng-blur with a non-number, non-boolean value: ${val}\nSee stack:\n${new Error().stack}`) + val = 0; + break; + } + blurAmount = val; + blurUpdateWrapper(); + }); + + function calcBlur () { + let rect = elem[0].getBoundingClientRect(); + if ( + // valid size (at least 1px) + rect.width > 0 && rect.height > 0 && + // on screen (for at least 1px) + rect.bottom > 0 && rect.top < screen.height && + rect.right > 0 && rect.left < screen.width + ) { + return [ + rect.left / screen.width, // x + rect.top / screen.height, // y + rect.width / screen.width, // width + rect.height / screen.height, // height + blurAmount + ]; + } + return null; + } + + function updateBlur () { + if (blurAmount > 0 && isVisibleFast(elem[0])) { + const blur = calcBlur(); + if (!id && blur) + id = BlurGame.register(blur); + else if (!blur) + return BlurGame.unregister(id); + else + BlurGame.update(id, blur); + } else { + if (id) { + BlurGame.unregister(id); + id = null; + } + } + } + + scope.$on('$destroy', () => { + resizeObserver.disconnect(); + blurAmount = 0 + blurUpdateWrapper() + }) + + scope.$on('windowResize', () => { + blurUpdateWrapper() + }) + } + } +}]) + +// this directive translates the view frustum a bit to make up for lost space behind side menu and alike +// use it like this: +.directive('bngFrustumMover', ['RateLimiter', function (RateLimiter) { + return { + restrict: 'A', + link: function (scope, elem, attrs) { + function updateGE() { + let screenWidth = window.screen.width + let sideBarWidth = elem[0].getBoundingClientRect().width + let percentHidden = (sideBarWidth / screenWidth) + if(attrs.bngFrustumMover === 'left') { + percentHidden *= -1 + } else if(attrs.bngFrustumMover === 'right') { + //all good + } else { + console.error("only left/rigth supported right now") + } + if(Math.abs(percentHidden) < 0.0001) {`` + //console.log("complete overlap") + percentHidden = 0 + } + //console.log("adjusting frustom side offset:", screenWidth, sideBarWidth, percentHidden) + bngApi.engineLua(`scenetree.OnlyGui:setFrustumCameraCenterOffset(Point2F(${percentHidden}, 0))`) + } + + angular.element(elem).ready(function () { + updateGE() + }) + + // update on resize + const resizeObserver = new ResizeObserver(entries => { + updateGE() + }) + resizeObserver.observe(elem[0]) + + // rest on destroy + scope.$on('$destroy', () => { + //console.log("resetting frustom side offset") + bngApi.engineLua(`scenetree.OnlyGui:setFrustumCameraCenterOffset(Point2F(0, 0))`) + }) + + scope.$on('windowResize', () => { + updateGE() + }) + } + } +}]) + +.service("InputCapturer", ["$state", function ($state) { + // captures input on UI + // usage: + // const captureInput = InputCapturer(); // create an instance + // captureInput(true|false); // change the value for that instance + // options can be specified: + // const captureInput = InputCapturer({ + // // do something on Esc/B press + // backAction() { + // if (something) return true; // return true to prevent default action (e.g. BACK_TO_MENU) + // else return false; // if returned false or nothing - default action is allowed + // } + // }); + let isEnabled = false, counter = 0; + function enabler(enable) { + counter += enable ? 1 : -1; + if (counter < 0) + counter = 0; + // console.log(counter); + if ((isEnabled && enable) || (!isEnabled && !enable) || (isEnabled && !enable && counter > 0)) + return; + isEnabled = enable; + bngApi.engineLua(`extensions.core_input_bindings.setMenuActionMapEnabled(${enable})`); + } + const backActions = []; + $state.preventStateChange = () => { + if (backActions.length > 0) + return backActions[0](); + return false; + }; + return opts => { + if (typeof opts !== "object") + opts = {}; + // return a function that tracks previous state and passes it further only when change happened + let current; + return enable => { + if (typeof current !== "undefined" && current === enable) + return; + current = enable; + enabler(enable); + if (typeof opts.backAction === "function") { + if (enable) + backActions.unshift(opts.backAction); + else + backActions.shift(); + } + }; + }; +}]) diff --git a/ui/entrypoints/main/resources.js b/ui/entrypoints/main/resources.js new file mode 100644 index 0000000..7d60bf8 --- /dev/null +++ b/ui/entrypoints/main/resources.js @@ -0,0 +1,170 @@ +import { onCondition, inPath } from "./resourceLoader.js" + +const paths = { + MAIN_ENTRYPOINT: '/ui/entrypoints/main', + EXTERNAL_LIBS: '/ui/lib/ext', + INTERNAL_LIBS: '/ui/lib/int', + VUE_DIST: '/ui/ui-vue/dist', + ANGULAR_MODULES: '/ui/modules', +} + +const conditions = { + VUE_DEV: env => env.isVueDev, + NOT_VUE_DEV : env => !env.isVueDev +} + + +export default [ + + // for external clients: emulation over websocket + `${paths.MAIN_ENTRYPOINT}/comms.js`, + + + // Vue START + ...onCondition(conditions.NOT_VUE_DEV, + inPath(paths.VUE_DIST, [ + { type: "module", src: '/index.js' }, + '/index.css' + ]) + ), + ...onCondition(conditions.VUE_DEV, + {src:'http://localhost:9000/src/main.js', type: 'module'} + ), + {src: `${paths.EXTERNAL_LIBS}/vue-i18n-next/vue-i18n.global.prod.js`, defer: true}, + {src: `${paths.EXTERNAL_LIBS}/tiny-emitter/tinyemitter.js`, defer: true}, + {src: `${paths.INTERNAL_LIBS}/vueService.js`}, //, defer: true}, + + + {src: `${paths.MAIN_ENTRYPOINT}/angularModules.js`, defer: true}, + + + // 3rd party includes + ...inPath(paths.EXTERNAL_LIBS, [ + '/angular/angular.js', + '/angular/angular-animate.js', + '/angular/angular-aria.js', + '/angular/angular-sanitize.js', + + '/angular-ui-router.min.js', + '/angular-material/angular-material.js', + '/smoothie.min.js', + '/angular-translate/angular-translate-beamng.js', + '/angular-translate/angular-translate-loader-static-files/angular-translate-loader-static-files.js', + '/angular-translate/angular-translate-handler-log/angular-translate-handler-log.js', + + '/ocLazyLoad.min.js', + '/angular-toastr.tpls.min.js', + '/spine-canvas.js', + '/angular-material-paging.js', + '/jk-rating-stars.min.js', + '/chartist.min.js', + '/hu.js', + + '/qrcode.min.js', + + // LottieJS + '/lottie.min.js', + + // 3rd party test + '/ng-FitText.js', + + '/angular-toastr.min.css', + '/angular-material-paging.css', + '/jk-rating-stars.min.css', + '/chartist.min.css', + + ]), + + + // BeamNG UI + `${paths.MAIN_ENTRYPOINT}/main.js`, + + ...inPath(paths.INTERNAL_LIBS, [ + '/beamng-core.js', + '/utilities.js', + '/beamng-data.js' + ]), + ...inPath(paths.EXTERNAL_LIBS, [ + '/mdx.js', + '/crossfire-chrome.js', + ]), + + // Controls + ...inPath(paths.INTERNAL_LIBS, [ + '/beamng.controls.js', + '/beamng.controls.css', + ]), + + // Angular Modules + ...inPath(paths.ANGULAR_MODULES, [ + '/levelselect/levelselect.js', + '/photomode/photomode.js', + '/replay/replay.js', + '/environment/environment.js', + '/scenarioselect/scenarioselect.js', + '/scenariocontrol/scenariocontrol.js', + '/comic/comic.js', + '/vehicleselect/vehicleselect.js', + '/options/options.js', + '/startScreen/startScreen.js', + '/vehicleconfig/vehicleconfig.js', + '/loading/loading.js', + '/repository/repository.js', + '/automation/automation.js', + '/quickrace/quickrace.js', + '/lightrunner/lightrunner.js', + '/dragrace/dragrace.js', + '/mapview/mapview.js', + '/gameContext/gameContext.js', + '/bigmap/bigmap.js', + + '/mainmenu/mainmenu.js', + '/menu/menu.js', + '/onlineFeatures/online.js', + '/career/career.js', + '/careerPause/careerPause.js', + '/introPopup/intro.js', + '/careerQuests/quests.js', + '/careerTasklist/tasklist.js', + '/careerVehicleSelect/careerVehicleSelect.js', + '/threeElementSelect/threeElementSelect.js', + '/careerLogBook/logBook.js', + + '/appselect/appselect.js', + '/appedit/appedit.js', + '/modmanager/modmanager.js', + '/play/play.js', + '/garage/garage.js', + '/refueling/refueling.js', + '/partInventory/partInventory.js', + '/apps/app-service.js', + '/apps/app-container.js', + '/apps/app.js', + + '/campaignselect/campaignselect.js', + + '/busRoute/busRoute.js', + '/iconView/icons.js', + + '/fadeScreen/fadeScreen.js', + '/atom/title/title.js', + '/atom/card/card.js', + '/atom/tabs/tabs.js', + + '/mainmenu/mainmenu.css', + '/menu/menu.css', + '/introPopup/intro.css', + ]), + + ...inPath(paths.INTERNAL_LIBS, [ + '/mission-popups/mission-popups.js', + '/colorpicker/color.js', + '/ui-components/bng-components.js', + '/ui-components/bng-components.css' + ]), + + `${paths.EXTERNAL_LIBS}/keys.css` + + +] + diff --git a/ui/modules/mainmenu/drive/icons/autobahn.svg b/ui/modules/mainmenu/drive/icons/autobahn.svg new file mode 100644 index 0000000..bf36ed7 --- /dev/null +++ b/ui/modules/mainmenu/drive/icons/autobahn.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/ui/modules/mainmenu/drive/icons/busroutes.svg b/ui/modules/mainmenu/drive/icons/busroutes.svg new file mode 100644 index 0000000..0b60ba9 --- /dev/null +++ b/ui/modules/mainmenu/drive/icons/busroutes.svg @@ -0,0 +1,3 @@ + + + diff --git a/ui/modules/mainmenu/drive/icons/campaigns.svg b/ui/modules/mainmenu/drive/icons/campaigns.svg new file mode 100644 index 0000000..194789f --- /dev/null +++ b/ui/modules/mainmenu/drive/icons/campaigns.svg @@ -0,0 +1,3 @@ + + + diff --git a/ui/modules/mainmenu/drive/icons/career.svg b/ui/modules/mainmenu/drive/icons/career.svg new file mode 100644 index 0000000..8123f50 --- /dev/null +++ b/ui/modules/mainmenu/drive/icons/career.svg @@ -0,0 +1,3 @@ + + + diff --git a/ui/modules/mainmenu/drive/icons/freeroam.svg b/ui/modules/mainmenu/drive/icons/freeroam.svg new file mode 100644 index 0000000..f7231f0 --- /dev/null +++ b/ui/modules/mainmenu/drive/icons/freeroam.svg @@ -0,0 +1,3 @@ + + + diff --git a/ui/modules/mainmenu/drive/icons/garage.svg b/ui/modules/mainmenu/drive/icons/garage.svg new file mode 100644 index 0000000..2c3b670 --- /dev/null +++ b/ui/modules/mainmenu/drive/icons/garage.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/ui/modules/mainmenu/drive/icons/infinity.svg b/ui/modules/mainmenu/drive/icons/infinity.svg new file mode 100644 index 0000000..a1206bf --- /dev/null +++ b/ui/modules/mainmenu/drive/icons/infinity.svg @@ -0,0 +1,3 @@ + + + diff --git a/ui/modules/mainmenu/drive/icons/lightrunner.svg b/ui/modules/mainmenu/drive/icons/lightrunner.svg new file mode 100644 index 0000000..7946c84 --- /dev/null +++ b/ui/modules/mainmenu/drive/icons/lightrunner.svg @@ -0,0 +1,3 @@ + + + diff --git a/ui/modules/mainmenu/drive/icons/play.svg b/ui/modules/mainmenu/drive/icons/play.svg new file mode 100644 index 0000000..578c025 --- /dev/null +++ b/ui/modules/mainmenu/drive/icons/play.svg @@ -0,0 +1,3 @@ + + + diff --git a/ui/modules/mainmenu/drive/icons/quit.svg b/ui/modules/mainmenu/drive/icons/quit.svg new file mode 100644 index 0000000..d1af5c4 --- /dev/null +++ b/ui/modules/mainmenu/drive/icons/quit.svg @@ -0,0 +1,4 @@ + + + + diff --git a/ui/modules/mainmenu/drive/icons/scenarios.svg b/ui/modules/mainmenu/drive/icons/scenarios.svg new file mode 100644 index 0000000..b70f8c0 --- /dev/null +++ b/ui/modules/mainmenu/drive/icons/scenarios.svg @@ -0,0 +1,3 @@ + + + diff --git a/ui/modules/mainmenu/drive/icons/stat.svg b/ui/modules/mainmenu/drive/icons/stat.svg new file mode 100644 index 0000000..e424ee1 --- /dev/null +++ b/ui/modules/mainmenu/drive/icons/stat.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ui/modules/mainmenu/drive/icons/timetrials.svg b/ui/modules/mainmenu/drive/icons/timetrials.svg new file mode 100644 index 0000000..8717ad0 --- /dev/null +++ b/ui/modules/mainmenu/drive/icons/timetrials.svg @@ -0,0 +1,3 @@ + + + diff --git a/ui/modules/mainmenu/drive/images/1.jpg b/ui/modules/mainmenu/drive/images/1.jpg new file mode 100644 index 0000000..01005ae Binary files /dev/null and b/ui/modules/mainmenu/drive/images/1.jpg differ diff --git a/ui/modules/mainmenu/drive/images/1_blur.jpg b/ui/modules/mainmenu/drive/images/1_blur.jpg new file mode 100644 index 0000000..acd6f35 Binary files /dev/null and b/ui/modules/mainmenu/drive/images/1_blur.jpg differ diff --git a/ui/modules/mainmenu/drive/images/2.jpg b/ui/modules/mainmenu/drive/images/2.jpg new file mode 100644 index 0000000..6d918a5 Binary files /dev/null and b/ui/modules/mainmenu/drive/images/2.jpg differ diff --git a/ui/modules/mainmenu/drive/images/2_blur.jpg b/ui/modules/mainmenu/drive/images/2_blur.jpg new file mode 100644 index 0000000..a545246 Binary files /dev/null and b/ui/modules/mainmenu/drive/images/2_blur.jpg differ diff --git a/ui/modules/mainmenu/drive/images/3.jpg b/ui/modules/mainmenu/drive/images/3.jpg new file mode 100644 index 0000000..4b2e7d1 Binary files /dev/null and b/ui/modules/mainmenu/drive/images/3.jpg differ diff --git a/ui/modules/mainmenu/drive/images/3_blur.jpg b/ui/modules/mainmenu/drive/images/3_blur.jpg new file mode 100644 index 0000000..550569b Binary files /dev/null and b/ui/modules/mainmenu/drive/images/3_blur.jpg differ diff --git a/ui/modules/mainmenu/drive/images/4.jpg b/ui/modules/mainmenu/drive/images/4.jpg new file mode 100644 index 0000000..4592505 Binary files /dev/null and b/ui/modules/mainmenu/drive/images/4.jpg differ diff --git a/ui/modules/mainmenu/drive/images/4_blur.jpg b/ui/modules/mainmenu/drive/images/4_blur.jpg new file mode 100644 index 0000000..ec0cf43 Binary files /dev/null and b/ui/modules/mainmenu/drive/images/4_blur.jpg differ diff --git a/ui/modules/mainmenu/drive/images/5.jpg b/ui/modules/mainmenu/drive/images/5.jpg new file mode 100644 index 0000000..131498d Binary files /dev/null and b/ui/modules/mainmenu/drive/images/5.jpg differ diff --git a/ui/modules/mainmenu/drive/images/5_blur.jpg b/ui/modules/mainmenu/drive/images/5_blur.jpg new file mode 100644 index 0000000..cd0dd17 Binary files /dev/null and b/ui/modules/mainmenu/drive/images/5_blur.jpg differ diff --git a/ui/modules/mainmenu/drive/images/6.jpg b/ui/modules/mainmenu/drive/images/6.jpg new file mode 100644 index 0000000..26b951a Binary files /dev/null and b/ui/modules/mainmenu/drive/images/6.jpg differ diff --git a/ui/modules/mainmenu/drive/images/6_blur.jpg b/ui/modules/mainmenu/drive/images/6_blur.jpg new file mode 100644 index 0000000..9f39658 Binary files /dev/null and b/ui/modules/mainmenu/drive/images/6_blur.jpg differ diff --git a/ui/modules/mainmenu/drive/images/7.jpg b/ui/modules/mainmenu/drive/images/7.jpg new file mode 100644 index 0000000..27d3e7e Binary files /dev/null and b/ui/modules/mainmenu/drive/images/7.jpg differ diff --git a/ui/modules/mainmenu/drive/images/7_blur.jpg b/ui/modules/mainmenu/drive/images/7_blur.jpg new file mode 100644 index 0000000..f3cd86a Binary files /dev/null and b/ui/modules/mainmenu/drive/images/7_blur.jpg differ diff --git a/ui/modules/mainmenu/drive/images/8.jpg b/ui/modules/mainmenu/drive/images/8.jpg new file mode 100644 index 0000000..d5c5335 Binary files /dev/null and b/ui/modules/mainmenu/drive/images/8.jpg differ diff --git a/ui/modules/mainmenu/drive/images/8_blur.jpg b/ui/modules/mainmenu/drive/images/8_blur.jpg new file mode 100644 index 0000000..a9752fa Binary files /dev/null and b/ui/modules/mainmenu/drive/images/8_blur.jpg differ diff --git a/ui/modules/mainmenu/drive/mainmenu.html b/ui/modules/mainmenu/drive/mainmenu.html new file mode 100644 index 0000000..183938d --- /dev/null +++ b/ui/modules/mainmenu/drive/mainmenu.html @@ -0,0 +1,269 @@ +
bug_reportDEV RELEASE
+ +
+ + +
diff --git a/ui/modules/mainmenu/drive/tech_images/1.jpg b/ui/modules/mainmenu/drive/tech_images/1.jpg new file mode 100644 index 0000000..d7d2d77 Binary files /dev/null and b/ui/modules/mainmenu/drive/tech_images/1.jpg differ diff --git a/ui/modules/mainmenu/drive/tech_images/1_blur.jpg b/ui/modules/mainmenu/drive/tech_images/1_blur.jpg new file mode 100644 index 0000000..2acb0ef Binary files /dev/null and b/ui/modules/mainmenu/drive/tech_images/1_blur.jpg differ diff --git a/ui/modules/mainmenu/mainmenu.css b/ui/modules/mainmenu/mainmenu.css new file mode 100644 index 0000000..278d4bc --- /dev/null +++ b/ui/modules/mainmenu/mainmenu.css @@ -0,0 +1,651 @@ +/*thanks to https://codepen.io/shshaw/pen/OVGWLG*/ +.contentNavMainmenu .video { + min-width: 100%; + min-height: 100%; + width: auto; + height: auto; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%,-50%); +} + +.contentNavMainmenu md-icon { + color: inherit; +} + + +.mainmenu { + display: flex; + flex-flow: column; +} + +.menu-size { + position: relative; + margin: 0 auto; /* horizontal centering */ + width: 90vw; /* we want margins here */ + height: 100%; /* for vertical centering */ + font-size: 0.6vw; /* this controls the scale of all elements inside */ +} +.menu-size::before, .menu-size::after { /* vertical centering */ + flex: 0.2 0.2 auto; + content: ""; +} +@media (min-width: 1320px) { /* big screens */ + .menu-size { + max-width: 90vw; + font-size: 8px; + } +} +/* medium screens are responsive */ +@media (max-width: 800px) { /* small screens */ + .menu-size { + max-width: 90vw; + font-size: 4.5px; + } + .mainmenu-buttons-smwrap { + flex-wrap: wrap !important; + } + .mainmenu-buttons-smwrap > .menu-button { + margin-bottom: 0.5rem !important; + } + .mainmenu-buttongroups .mainmenu-buttons-smwrap { + margin-bottom: -0.5rem; + } + .mainmenu-buttongroups .mainmenu-buttons-smwrap > .menu-button { + flex-basis: 40%; + } + .mainmenu-buttongroups .mainmenu-buttons-smwrap > .menu-button:nth-child(2n) { + margin-right: 0 !important; + } + .mainmenu-buttongroups .mainmenu-buttons-smwrap > .menu-button:nth-child(2n+1) { + margin-right: 0.5rem !important; + } + .menu-button-big { + width: 3em !important; + height: 8.5em !important; + } + .menu-button-small { + height: 1.2em !important; + } + .menu-button-quit { + flex: 0 1 9em !important; + max-width: unset !important; + } +} + +.mainmenu * { + position: relative; +} +.mainmenu > *:not(:last-child) { + margin-bottom: 2em; +} + +.mainmenu-title { + margin-bottom: 5em !important; +} + +.logo-container { + width: 50em; + padding-top: 9em; + float: left; +} +.logo-container > * { + position: absolute; + top: 17%; + bottom: 17%; + left: 0; + right: 0; + height: unset !important; + width: unset !important; +} + +.user-container { + /* width: 50em; */ + /* padding-top: 9em; */ + height: 100%; + float: right; + min-width: 250px; + background: var(--bng-black-6); + border-radius: 50px; + overflow: hidden; + white-space: nowrap; + display: flex; +} +/*.user-container > * { + position: absolute; + top: 17%; + bottom: 17%; + left: 0; + right: 0; + height: unset !important; + width: unset !important; +}*/ +.user-container-icon { + height: 66px !important; + width: 66px; + border: solid white; + border-radius: 50px; +} +.user-container-divider { + width: 2px !important; + height: 52px !important; + background: #848484; + display: block; + position: relative; + margin: 10px 20px; +} +.user-container-details { + margin: 10px 25px 10px 0px; + color: white; +} +.user-container-details-name { + font-size: large; + font-weight: 700; +} +.user-container-details-show-profile { + font-size: large; + font-weight: 300; +} + +.nav-container { + overflow: hidden; + white-space: nowrap; + font-size: x-large; + font-weight: 700; + color: white; + background: var(--bng-black-6); + border-radius: 10px; + display: flex; +} +.nav-container-menu { + display: flex; + width: 100%; +} +.nav-container-divider { + margin: 10px 15px; + background: #848484; + width: 2px !important; +} +.nav-container-menu-item { + padding: 15px 10px; + width: calc(100% / 5); + text-align: center; +} +.nav-container-menu-item[disabled] { + padding: 15px 10px; + width: calc(100% / 5); + text-align: center; + border-radius: 10px; + box-shadow: 0 0 20px rgba(0,0,0, 0.4); + background-color: rgba(0,0,0, 0.4); +} +.nav-container-menu-item:active { + padding: 15px 10px; + width: calc(100% / 5); + background: var(--bng-orange); + border-radius: 10px; + box-shadow: 0 0 20px var(--bng-orange); +} +.nav-container-menu-item:hover { + padding: 15px 10px; + width: calc(100% / 5); + background: var(--bng-orange-shade1); + border-radius: 10px; + box-shadow: 0 0 20px var(--bng-orange); +} + +.controller-button-large { + border: solid 3px var(--bng-cool-gray-300); + border-radius: 10px; + margin: 7px; + padding: 5px 10px; + background: var(--bng-cool-gray-800); +} + +.col-6 { + width: calc(50%); + float: initial; +} + +.row { + display: flex; +} + +.col-3 { + width: calc(33.3%); + float: initial; +} + +.menu-page-header-item { + border: solid black 2px; + margin: 5px; + border-radius: 10px; + min-height: 300px; +} + +.menu-page-header-item-title { + height: 50px; + background-image: linear-gradient(to right, var(--bng-black-8), rgba(0,0,0,0)); + color: white; + font-size: x-large; + font-weight: 700; + display: flex; +} + +.menu-page-header-item-title-icon { + background: var(--bng-orange); + width: 50px; + height: 100%; + border-radius: 10px 0px 0px; + margin-right: 15px; +} + +.menu-page-header-item-title-text { + padding-top: 6px; +} + +.logo-tech, +.logo-drive, +.logo-research { + display: inline-block; + width: 100%; + height: 100%; + background-repeat: no-repeat; + background-position: 50% 50%; + background-size: contain; +} +.logo-tech { + background-image: url("/ui/images/tech.png"); +} +.logo-drive { + background-image: url("/ui/images/logos.svg#bng-drive-white"); +} +.logo-research { + background-image: url("/ui/images/research.svg"); +} + +.mainmenu-buttons { + margin: 0.5rem 0; +} +.mainmenu-buttons .menu-button { + margin: 0; +} +.mainmenu-buttons .menu-button:not(.menu-button-small):not(:last-child) { + margin-right: 1em; +} +.mainmenu-buttons .menu-button.menu-button-small { + margin-left: 0.5em; + margin-right: 0.5em; +} + +.menu-column { + display: flex; + flex-flow: column; + justify-content: stretch; + align-items: stretch; +} +.menu-row { + display: flex; + flex-flow: row; + flex-wrap: wrap; + justify-content: center; + align-items: flex-start; +} +.menu-row-nowrap { + flex-wrap: nowrap; +} +.menu-row-stretch { + justify-content: stretch; + align-items: stretch; +} +.menu-row > * { + flex: 1 1 auto; +} + +.menu-button { + flex: 1 1 auto; + display: flex; + flex-flow: column; + justify-content: center; + align-items: center; + margin: 0.5rem; + padding: 1rem; + line-height: 1.1em; + color: white; + background-color: rgba(0,0,0, 0.6); + /* backdrop-filter: blur(10px); -- doesn't work on old cef */ + /* border: 4px solid transparent; */ + border-radius: var(--bng-corners-1); + text-align: center; + text-decoration: none; + cursor: default; +} + +.menu-button-big { + flex: 0.25 1 auto; + font-size: 4em; + width: 4em; + height: 4em; + margin-left: 0; + margin-top: 0.5rem; + margin-bottom: 0.5rem; + margin-right: 0.8em; + padding: 1.75em 3em; +} +.menu-button-big .menu-button-icon { + min-height: 3em; +} +.menu-button-med { + flex: 1 0 20%; + font-size: 2.5em; + height: 5em; + padding: 0.25em; +} +.menu-button-small { + flex: 0 1 9em; + font-size: 2.5em; + height: 1.5em; +} + +.menu-button-big, +.menu-button-med, +.menu-button-small { + display: flex; + justify-content: center; + align-items: center; +} +.menu-button-big, +.menu-button-med { + flex-flow: column; +} +.menu-button-small { + flex-flow: row; + flex-wrap: nowrap; +} +.menu-button small { + font-size: 0.6em; + line-height: 1em; + opacity: 0.8; +} +.menu-button-icon { + min-width: 2em; + min-height: 2em; + background-repeat: no-repeat; + background-size: contain; + background-position: 50% 50%; + margin-bottom: 0.25em; +} +.menu-button-text { + font-family: "Overpass", var(--fnt-defs); + font-style: italic; + font-weight: 900; + letter-spacing: 0.02em; + line-height: 1.2em; +} +.menu-button-subtext { + display: block; +} +.menu-button-small.menu-button-withicon .menu-button-text { + text-align: left; +} +.menu-button[disabled] { + background-color: rgba(0,0,0, 0.4); + pointer-events: none; +} +.menu-button[disabled] > *:not(.fancy-blur) { + opacity: 0.65; +} +.menu-button[disabled].semi-disabled { + pointer-events: initial; +} +.menu-button[disabled].semi-disabled:focus { + box-shadow: initial; +} +.menu-button[disabled].semi-disabled:focus::before { + content: none; +} + +.menu-button-bad { + /* border-color: rgba(255,0,0, 0.6) !important; */ + border: 3px solid rgba(255,0,0, 0.6); + text-shadow: 0 0 1em #000; +} +.menu-button-bad > * { + z-index: 2; +} +.menu-button-bad::after { + content: ""; + position: absolute; + top: 0; + left: 45%; + width: 10%; + height: 100%; + transform: skewX(-23deg); + background-color: rgba(255,0,0, 0.6); + z-index: 1; +} +.menu-button-bad:focus { + border-color: rgba(255,0,0, 0.8) !important; +} +.menu-button-bad:focus::after { + background-color: rgba(255,0,0, 0.8) !important; +} + +.menu-button-quit { + background-color: rgba(255,0,0, 0.6); + flex: 0 1 auto; + max-width: 7em; +} +.menu-button-quit .menu-button-icon { + min-height: 1.5em; + margin: 0; +} + + +.menuNavbar .navBtn { + cursor: pointer; + color: white; + padding: 0.5em 0.75em 0.5em 0.5em; + border-radius: var(--bng-corners-1); +} + +.menuNavbar .navBtn:hover { + background-color: rgba(255,102,0, 1); +} + +.menuNavbar .navBtn .bng-binding { + margin-left: 0.25em !important; + margin-right: 0.25em !important; +} + + +.contentNavMainmenu { + width:100%; + height: 100%; + overflow-y:hidden !important; + overflow-x:hidden !important; + position: relative; +} + +.contentNavMainmenu #video-viewport { + position:absolute; + left:0; + right:0; + bottom:0; + top:0; +} + +.contentNavMainmenu #shippinginfo { + position: absolute; + bottom: 0; + right: left; + color: black; + border-radius: 0 var(--bng-corners-3) 0 0; + z-index: var(--zorder_mainmenu_shippinginfo); + background-color:white; + padding:3px; + font-weight:bold; + font-family:var(--fnt-defs); + border-top: 2px solid #333; + border-right: 2px solid #333; +} + +.contentNavMainmenu #infobox { + position: absolute; + bottom: 0px; + z-index: var(--zorder_mainmenu_infobox); + max-width: 700px; +} + +.contentNavMainmenu #infobox p { + margin: -3px; +} + +.contentNavMainmenu #account-info { + position: absolute; + top: 0px; + right: 0px; + z-index: var(--zorder_mainmenu_accountinfo); + padding: 5px; +} + +#onlinestate { + /* border-bottom:2px solid #56D70B; */ + color: #56D70B; + text-align:center; + padding:5px; + font-weight: 700; +} + +#versioninfo a { + color: rgba(255, 103, 0, 1);; +} + +.contentNavMainmenu #onlineInfo { + position: absolute; + top: 120px; + right: 0px; + z-index: var(--zorder_mainmenu_onlineinfo); + padding: 5px; +} + +.contentNavMainmenu #versioninfo { + position: absolute; + bottom: 0px; + right: 0px; + z-index: var(--zorder_mainmenu_versioninfo); +} + +.contentNavMainmenu .blendbox { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + background-color: rgba(0,0,0,1); + z-index: var(--zorder_mainmenu_blendbox); + + opacity:0; + -webkit-transition: 3s linear all; + transition: 3s linear all; +} + +.new-tile { + font-size: 3vh; + font-family: 'Overpass', var(--fnt-defs); + cursor: pointer; + color: rgb(255, 255, 255); + text-shadow: -3px 3px 6px #00000050; + background-color: rgba(0, 0, 0, 0.5); + filter: brightness(0.8); + transition: filter .3s; +} + +.new-tile:hover { + filter: brightness(1.1); +} + + + +.new-tile figure { + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-align-items: center; + -ms-flex-align: center; + align-items: flex-start; + -webkit-justify-content: center; + -ms-flex-pack: center; + justify-content: flex-start; + height: 100%; + width: 100%; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + padding: 18px; + margin: 0; +} + +.new-tile figure md-grid-tile-header { + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: row; + -ms-flex-direction: row; + flex-direction: row; + -webkit-align-items: center; + -ms-flex-align: center; + align-items: flex-start; + height: 100%; + width: 100%; + color: #fff; + background: rgba(0,0,0,.18); + overflow: hidden; + position: absolute; + left: 0; + right: 0; + padding: 18px; +} + +.new-tile figure md-grid-tile-header figcaption h2 { + margin: 0 0 0 0; +} + +.new-tile figure md-grid-tile-header figcaption h3 { + margin: 0 0 0 0; + opacity: 0; + transition: opacity 0.2s; +} + +.new-tile:hover figure md-grid-tile-header figcaption h3 { + opacity: 1; +} + +.main-menu-image { + position: absolute; + width: 40%; + height: 20%; + top: 0%; + left: 10%; +} + +.fancy-blur { + position: absolute; + overflow: hidden; + clip: rect(0,auto,auto,0); + z-index: -1; + pointer-events: none; +} +.fancy-blur > * { + position: fixed; +} +.fancy-blur, +.fancy-blur > * { + top: 0; + left: 0; + right: 0; + bottom: 0; +} diff --git a/ui/modules/mainmenu/mainmenu.js b/ui/modules/mainmenu/mainmenu.js new file mode 100644 index 0000000..f38eb42 --- /dev/null +++ b/ui/modules/mainmenu/mainmenu.js @@ -0,0 +1,536 @@ +'use strict' + +angular.module('beamng.stuff') + + +// no fucking clue, why this doesn't just work in the videoBackgroundDirective itself, but well you know +// idea from: http://stackoverflow.com/questions/28016476/angular-on-video-load-event +.directive('onVideoStart', function () { + return { + restrict: 'A', + scope: { + onVideoStart: '&' + }, + link: function (scope, $element) { + $element[0].addEventListener('play', function () { + scope.onVideoStart() + }) + } + } +}) + +.directive("fancyBackground", ["TechLicenseState", function (techLicenseState) { + return { + template: ` + + `, + scope: { + videoLoaded: "&", + }, + link: function (scope, elem, attr) { + scope.files = [ + "/ui/modules/mainmenu/drive/images/1.jpg", + "/ui/modules/mainmenu/drive/images/2.jpg", + "/ui/modules/mainmenu/drive/images/3.jpg", + "/ui/modules/mainmenu/drive/images/4.jpg", + "/ui/modules/mainmenu/drive/images/5.jpg", + "/ui/modules/mainmenu/drive/images/6.jpg", + "/ui/modules/mainmenu/drive/images/7.jpg", + "/ui/modules/mainmenu/drive/images/8.jpg", + ]; + + bngApi.engineLua("sailingTheHighSeas", ahoy => { + if (ahoy) + scope.files = ["/ui/modules/mainmenu/unofficial_version.jpg"]; + }); + techLicenseState.state.then(licenseVerified => { + if (licenseVerified) + scope.files = ["/ui/modules/mainmenu/drive/tech_images/1.jpg"]; + }); + } + } +}]) + +.directive('menuNavbar', ['Utils', '$rootScope', function (Utils, $rootScope, ) { + return { + template: ` + + `, + scope: { + }, + link: function (scope, $element, attr) { + // do quick check right in main menu, to ensure *some* of our UI/LUA code can work in languages with certain non-english characters + bngApi.serializeToLuaCheck("English: " + "hello") + bngApi.serializeToLuaCheck("Spanish: " + "güeñes") + bngApi.serializeToLuaCheck("French: " + "bâguéttè, garçon") + bngApi.serializeToLuaCheck("Czech: " + "Kl�vesnice") + bngApi.serializeToLuaCheck("Korean: " + decodeURIComponent("%0D%EF%BF%BD%EF%BF%BD%0B%EF%BF%BD%EF%BF%BD%0B%EF%BF%BD%EF%BF%BD")) + bngApi.serializeToLuaCheck("Chinese Sim: " + "欢迎来到简体中文版的") + bngApi.serializeToLuaCheck("Chinese Tr: " + "歡迎來到") + bngApi.serializeToLuaCheck("Japanese: " + "』日本語版にようこそ") + bngApi.serializeToLuaCheck("Polish: " + "źćłąóę") + bngApi.serializeToLuaCheck("Russian: " + "абвгеёьъ") + + scope.nav = function(action, val) { + $rootScope.$broadcast('MenuItemNavigation', action, val) + } + scope.radialMenu = function(action, val) { + bngApi.engineLua("extensions.core_quickAccess.setEnabled(true)") + } + // VERSION INFO + scope.showBuildInfo = false + scope.versionStr = beamng.version + + // convert from 1.2.3.4 to 1.2.3 as we do not want to attach the build number in the simple display + var versionSplit = scope.versionStr.split('.') + if(versionSplit.length == 5) versionSplit.pop() // remove build number (5th) + for(var i = 0; i < 3; i++) { + if(versionSplit[versionSplit.length - 1] == '0') versionSplit.pop(); // remove any '0' for simplicity reasons + } + scope.versionSimpleStr = versionSplit.join('.') + scope.buildInfoStr = beamng.buildinfo + + // account info + scope.$on('SteamInfo', function (event, data) { + scope.$apply(function () { + scope.steamData = data + }) + }) + scope.onlineState = false + scope.$on('OnlineStateChanged', function (event, data) { + scope.$applyAsync(function () { + scope.onlineState = data + }) + }) + scope.$on('ShowEntertainingBackground', (ev, mainmenu) => { + scope.$evalAsync(() => { + scope.mainmenu = mainmenu + }) + }) + bngApi.engineLua('core_online.requestState()') + } + } +}]) + + +.directive('onlineMessage', ['$sce', 'Utils', '$state', function ($sce, Utils, $state) { + return { + restrict: 'E', + replace: true, + template: ` +
+
+
+
News
+
+ close +
+
+ + +
+
+
+ {{btn.label | translate}} +
+
+
+
+
+ `, + link: function (scope) { + var uids = {} + + scope.$on('OnlineMessage', function (ev, data) { + scope.messageData = data + scope.$evalAsync(() => { + // uid = data.uid + for (var key in scope.messageData) { + switch (scope.messageData[key].contenttype) { + case 'html': + scope.messageData[key].msg = $sce.trustAsHtml(scope.messageData[key].msg) + break + case 'bbcode': + scope.messageData[key].msg = $sce.trustAsHtml(Utils.parseBBCode(scope.messageData[key].msg)) + break + default: + scope.messageData[key].msg = scope.messageData[key].msg + } + uids[key] = scope.messageData[key].uid + scope.style = scope.messageData[key].css //{position: 'fixed', top: 0, left: 0, right: 0, bottom: 0, 'background': ' repeating-linear-gradient(45deg, rgba(0, 0, 0, 0.75), rgba(0, 0, 0, 0.75) 35px, rgba(0, 0, 0, 0.8) 35px, rgba(0, 0, 0, 0.8) 70px)'} + } + }) + }) + + scope.close = function () { + for (var key in uids) { + bngApi.engineLua(`extensions.hook("onUIOnlineMessageHide", "${uids[key]}")`) + } + scope.messageData = undefined + } + + scope.clicked = function (btn) { + switch (btn.type) { + case 'hide-temp': + scope.messageData = undefined + break + case 'hide-perm': + scope.messageData = undefined + bngApi.engineLua(`extensions.hook("onUIOnlineMessageHide", "${uid}")`) + break + case 'luacmd': + bngApi.engineLua(btn.cmd) + break + case 'url': + document.location.href = btn.url + break + case 'state': + $state.go(btn.state, btn.params || {}) + break + default: + console.warn('Unknown action type in online message button') + } + } + + bngApi.engineLua('extensions.hook("onUIOnlineMessageReady")') + } + } +}]) + +.controller('MainMenuController', ['$rootScope', '$scope', 'toastr', '$state', 'Settings', '$http', '$filter', 'Utils', 'gamepadNav', 'TechLicenseState', 'ConfirmationDialog', function($rootScope, $scope, toastr, $state, Settings, $http, $filter, Utils, gamepadNav, techLicenseState, ConfirmationDialog) { + let vm = this + + vm.product = beamng.product + vm.techLicense = false + techLicenseState.state.then((licenseVerified) => { + vm.techLicense = licenseVerified + }) + + // account info + $scope.$on('SteamInfo', function (event, data) { + vm.steamData = data + }) + + // always hide apps in main menu + $scope.$parent.app.showApps = false + + // settings + { + vm.settings = Settings.values + $scope.$on('SettingsChanged', (ev, data) => { + vm.settings = data.values + }) + } + + if (!vm.techLicense && (Settings.values.onlineFeatures === 'ask' || Settings.values.telemetry === 'ask')) { + $state.go('menu.onlineFeatures') + } + bngApi.engineLua('core_online.requestState()') + + // hardwareinfo warnings, etc + { + bngApi.engineLua('core_hardwareinfo.requestInfo()') + $scope.$on('HardwareInfo', function (event, data) { + if (data.globalState !== 'ok') { + for (var key in data) { + if(data[key].warnings === undefined) continue + + for (var i=0; i < data[key].warnings.length; i++) { + if(data[key].warnings[i].ack !== undefined) continue + + var txt = $filter('translate')('ui.performance.warnings.' + data[key].warnings[i].msg) + var html = Utils.parseBBCode(txt) + toastr[(data.globalState === 'warn' ? 'warning' : 'error')]( + $filter('translate')('ui.mainmenu.warningdetails'),//need a unique message + html, + { + positionClass: 'toast-top-right', + timeOut: 0, + extendedTimeOut: 0, + onTap: function () { + $state.go('menu.options.performance') + } + } + ) + } + } + } + }) + } + + // repository button: X of Y mods active + { + vm.modsTotal = 0 + vm.modsActive = 0 + bngApi.engineLua('core_modmanager.requestState()') + $scope.$on('ModManagerModsChanged', function (event, data) { + var list = data.convertToArray() + vm.modsActive = list.filter((elem) => elem.modname != 'translations' && elem.active).length + vm.modsTotal = list.filter((elem) => elem.modname != 'translations').length + }) + } + + + { + // navigation things + let prevCross = gamepadNav.crossfireEnabled() + let prevGame = gamepadNav.gamepadNavEnabled() + gamepadNav.enableCrossfire(true) + gamepadNav.enableGamepadNav(true) + $scope.$on('$destroy', () => { + gamepadNav.enableCrossfire(prevCross) + gamepadNav.enableGamepadNav(prevGame) + }) + } + + $scope.quit = function() { + // copypasted from entrypoints/main/main.js + bngApi.engineScript('quit();'); //It should work but doesn't, `Platform::postQuitMessage` is executed but nothing happens, maybe CEF catch that message + bngApi.engineLua("TorqueScript.eval('quit();')") + } + + $scope.radialmenu = function() { + } + + + let buttonsDefault = [ + // big + { + translateid: 'ui.playmodes.freeroam', + icon: '/ui/modules/mainmenu/drive/icons/play.svg', + targetState: 'menu.levels' + }, + // rows with 4 elements each + { + translateid: 'ui.playmodes.campaigns', + icon: '/ui/modules/mainmenu/drive/icons/campaigns.svg', + targetState: 'menu.campaigns' + }, + { + translateid: 'ui.playmodes.scenarios', + icon: '/ui/modules/mainmenu/drive/icons/scenarios.svg', + targetState: 'menu.scenarios' + }, + { + translateid: 'ui.playmodes.quickrace', + icon: '/ui/modules/mainmenu/drive/icons/timetrials.svg', + targetState: 'menu.quickraceOverview' + }, + { + translateid: 'ui.mainmenu.garage', + icon: '/ui/modules/mainmenu/drive/icons/garage.svg', + action: () => { + bngApi.engineLua(`gameplay_garageMode.start()`) + }, + get disabled() { return $scope.$parent.app.gameState === "garage" }, + }, + { + translateid: 'ui.playmodes.bus', + icon: '/ui/modules/mainmenu/drive/icons/busroutes.svg', + targetState: 'menu.busRoutes' + }, + { + translateid: 'ui.playmodes.lightRunner', + icon: '/ui/modules/mainmenu/drive/icons/lightrunner.svg', + targetState: 'menu.lightrunnerOverview' + }, + // { + // translateid: 'ui.dashboard.trackBuilder', + // icon: '/ui/modules/mainmenu/drive/icons/autobahn.svg', + // action: () => bngApi.engineLua("extensions.trackbuilder_trackBuilder.toggleTrackBuilder()") + // }, + { + translateid: 'ui.playmodes.trackBuilder', + icon: '/ui/modules/mainmenu/drive/icons/autobahn.svg', + action: () => { // TODO: toggle track builder in current level + // bngApi.engineLua("extensions.trackbuilder_trackBuilder.toggleTrackBuilder()") + bngApi.engineLua(`freeroam_freeroam.startTrackBuilder('glow_city')`) + }, + get disabled() { return $scope.$parent.app.gameState === "garage" }, + }, + // { + // translateid: 'ui.quickrace.tracks.procedural', + // icon: '/ui/modules/mainmenu/drive/icons/infinity.svg', + // targetState: 'quickraceTrackSelect' + // }, + { + translateid: 'ui.playmodes.career', + subtranslateid: 'ui.playmodes.comingSoon', + icon: '/ui/modules/mainmenu/drive/icons/career.svg', + disabled: true, + class: "semi-disabled", // semi-disabled will make the button clickable, not changing the style if it was disabled + // targetState: 'menu.career' + action: () => { + if (!$scope.inCareer) { + ConfirmationDialog.open( + "ui.career.experimentalTitle", "ui.career.experimentalPrompt", + [ + { label: "ui.common.no", key: false, isCancel: true }, + // { label: "Enter and don't show this again", key: true }, + { label: "ui.career.experimentalAgree", key: true, default: true }, + ], + { class: "experimental" } + ).then(res => { + if (!res) + return; + $state.go("menu.career"); + }); + } else { + $state.go("menu.career"); + } + }, + }, + ]; + + let clickytmr, clickycnt = 0; + function clicky(cnt=6, timeout=300) { + clickytmr = clearInterval(clickytmr); + clickycnt++; + if (clickycnt === cnt) { + clickycnt = 0; + return true; + } + clickytmr = setInterval(() => { + clickycnt--; + if (clickycnt <= 0) { + clickycnt = 0; + clickytmr = clearInterval(clickytmr); + } + }, timeout); + return false; + } + + $scope.inCareer = false; + bngApi.engineLua("career_career.isCareerActive()", data => { + $scope.inCareer = !!data; + }); + + vm.buttons = { + big: null, + groups: [] + }; + + { + let buttons = []; + vm.addButton = function (button, draw=true) { + if (!vm.buttons.big) { + vm.buttons.big = button; + } else { + // add new button + buttons.push(button); + // rebuild layout + if (draw) + vm.rebuildButtons(); + } + }; + + let max1 = 3, max2 = 4; + vm.rebuildButtons = function () { + let len = buttons.length; + // find out optimal count per row + let max = max1; + if ((len % max1 || max1) < (len % max2 || max2)) + max = max2; + // calculate new layout + let top = len % max || max, + groupsmax = Math.ceil(len / max); + // clear/add rows + let groupslen = vm.buttons.groups.length; + for (let i = 0; i < groupsmax; i++) { + if (i < groupslen) + vm.buttons.groups[i].list = []; + else + vm.buttons.groups.push({ list: [] }); + } + // rebuild the layout + let group = 0; + for (let button of buttons) { + if (vm.buttons.groups[group].list.length === (group === 0 ? top : max)) + group++; + vm.buttons.groups[group].list.push(button); + } + // allow/disallow wrapping the buttons on even count + for (let grp of vm.buttons.groups) { + let len = grp.list.length; + grp.class = len > 2 && len / 2 % 1 === 0 ? "mainmenu-buttons-smwrap" : null; + } + // sync with fancy-background + setTimeout(fancySync, 100); + }; + } + + // populate menu with default buttons + for (let button of buttonsDefault) + vm.addButton(button, false); + vm.rebuildButtons(); + + // broadcast event for an additional buttons + $rootScope.$broadcast('MainMenuButtons', vm.addButton); + + vm.openRepo = function () { + window.location.href = 'http-external://www.beamng.com/resources/?ingame=2' + } + + vm.handleClick = function(card) { + if(card.action) { + card.action() + return + } + if(card.targetState) { + $state.go(card.targetState) + return + } + } + + $scope.fancyblur = false; + function fancySync() { + // find fancy bg + const fancybg = document.querySelector("fancy-background > .img-carousel"); + if (!fancybg) { + $scope.fancyblur = false; + return; + } + $scope.fancyblur = true; + $scope.$evalAsync(() => { + // get all target blur elements + const blurs = Array.from(document.querySelectorAll(".fancy-blur > .img-carousel")); + // and connect them to master so they will work in sync + fancybg.__connect( + blurs, + // function to modify images list for targets - function (orig_list) { return orig_list } + // here, we change images from "image.jpg" to "image_blur.jpg" + images => images.map(img => img.replace(/\.(.{3,4})$/, "_blur.$1")) + // note: blurred images are 1280x720 with gaussian blur 6.0 (resize, then blur) + ); + }); + } + fancySync(); + +}]) diff --git a/ui/modules/mainmenu/offbtn.svg b/ui/modules/mainmenu/offbtn.svg new file mode 100644 index 0000000..0654131 --- /dev/null +++ b/ui/modules/mainmenu/offbtn.svg @@ -0,0 +1,82 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + diff --git a/ui/modules/mainmenu/offline.png b/ui/modules/mainmenu/offline.png new file mode 100644 index 0000000..8b37c86 Binary files /dev/null and b/ui/modules/mainmenu/offline.png differ diff --git a/ui/modules/mainmenu/online.png b/ui/modules/mainmenu/online.png new file mode 100644 index 0000000..2942fc3 Binary files /dev/null and b/ui/modules/mainmenu/online.png differ diff --git a/ui/modules/mainmenu/steamicon.png b/ui/modules/mainmenu/steamicon.png new file mode 100644 index 0000000..8f5957a Binary files /dev/null and b/ui/modules/mainmenu/steamicon.png differ diff --git a/ui/modules/mainmenu/unofficial_version.jpg b/ui/modules/mainmenu/unofficial_version.jpg new file mode 100644 index 0000000..3879abe Binary files /dev/null and b/ui/modules/mainmenu/unofficial_version.jpg differ diff --git a/ui/modules/mainmenu/unofficial_version_blur.jpg b/ui/modules/mainmenu/unofficial_version_blur.jpg new file mode 100644 index 0000000..e4f868f Binary files /dev/null and b/ui/modules/mainmenu/unofficial_version_blur.jpg differ diff --git a/ui/modules/menu/icon-adjust.svg b/ui/modules/menu/icon-adjust.svg new file mode 100644 index 0000000..1413a19 --- /dev/null +++ b/ui/modules/menu/icon-adjust.svg @@ -0,0 +1,3 @@ + + + diff --git a/ui/modules/menu/icon-autobahn.svg b/ui/modules/menu/icon-autobahn.svg new file mode 100644 index 0000000..057dd81 --- /dev/null +++ b/ui/modules/menu/icon-autobahn.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/ui/modules/menu/icon-bug.svg b/ui/modules/menu/icon-bug.svg new file mode 100644 index 0000000..775f761 --- /dev/null +++ b/ui/modules/menu/icon-bug.svg @@ -0,0 +1,3 @@ + + + diff --git a/ui/modules/menu/icon-car.svg b/ui/modules/menu/icon-car.svg new file mode 100644 index 0000000..429f2e6 --- /dev/null +++ b/ui/modules/menu/icon-car.svg @@ -0,0 +1,3 @@ + + + diff --git a/ui/modules/menu/icon-engine.svg b/ui/modules/menu/icon-engine.svg new file mode 100644 index 0000000..82c7bee --- /dev/null +++ b/ui/modules/menu/icon-engine.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/ui/modules/menu/icon-exit.svg b/ui/modules/menu/icon-exit.svg new file mode 100644 index 0000000..1c6feff --- /dev/null +++ b/ui/modules/menu/icon-exit.svg @@ -0,0 +1,61 @@ + + + + + + image/svg+xml + + + + + + + + + diff --git a/ui/modules/menu/icon-flag.svg b/ui/modules/menu/icon-flag.svg new file mode 100644 index 0000000..77968f8 --- /dev/null +++ b/ui/modules/menu/icon-flag.svg @@ -0,0 +1,3 @@ + + + diff --git a/ui/modules/menu/icon-fps.svg b/ui/modules/menu/icon-fps.svg new file mode 100644 index 0000000..3223c5f --- /dev/null +++ b/ui/modules/menu/icon-fps.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/ui/modules/menu/icon-gamemodes.svg b/ui/modules/menu/icon-gamemodes.svg new file mode 100644 index 0000000..b9c0be7 --- /dev/null +++ b/ui/modules/menu/icon-gamemodes.svg @@ -0,0 +1,3 @@ + + + diff --git a/ui/modules/menu/icon-help.svg b/ui/modules/menu/icon-help.svg new file mode 100644 index 0000000..1e269de --- /dev/null +++ b/ui/modules/menu/icon-help.svg @@ -0,0 +1,3 @@ + + + diff --git a/ui/modules/menu/icon-hud.svg b/ui/modules/menu/icon-hud.svg new file mode 100644 index 0000000..880d30f --- /dev/null +++ b/ui/modules/menu/icon-hud.svg @@ -0,0 +1,3 @@ + + + diff --git a/ui/modules/menu/icon-map.svg b/ui/modules/menu/icon-map.svg new file mode 100644 index 0000000..fdfa951 --- /dev/null +++ b/ui/modules/menu/icon-map.svg @@ -0,0 +1,3 @@ + + + diff --git a/ui/modules/menu/icon-pause.svg b/ui/modules/menu/icon-pause.svg new file mode 100644 index 0000000..1e01be9 --- /dev/null +++ b/ui/modules/menu/icon-pause.svg @@ -0,0 +1,3 @@ + + + diff --git a/ui/modules/menu/icon-photo.svg b/ui/modules/menu/icon-photo.svg new file mode 100644 index 0000000..5618241 --- /dev/null +++ b/ui/modules/menu/icon-photo.svg @@ -0,0 +1,3 @@ + + + diff --git a/ui/modules/menu/icon-puzzle.svg b/ui/modules/menu/icon-puzzle.svg new file mode 100644 index 0000000..67d4ede --- /dev/null +++ b/ui/modules/menu/icon-puzzle.svg @@ -0,0 +1,3 @@ + + + diff --git a/ui/modules/menu/icon-replay.svg b/ui/modules/menu/icon-replay.svg new file mode 100644 index 0000000..6c9041c --- /dev/null +++ b/ui/modules/menu/icon-replay.svg @@ -0,0 +1,3 @@ + + + diff --git a/ui/modules/menu/icon-weather.svg b/ui/modules/menu/icon-weather.svg new file mode 100644 index 0000000..6ba6be4 --- /dev/null +++ b/ui/modules/menu/icon-weather.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/ui/modules/menu/menu.css b/ui/modules/menu/menu.css new file mode 100644 index 0000000..7453a93 --- /dev/null +++ b/ui/modules/menu/menu.css @@ -0,0 +1,97 @@ + +/* .vertical-divider { + border-right: 2px solid var(--bng-orange); + margin: 8px 4px 8px 4px; +} */ + +.menuLimiterContainer { + height:100%; +} + +.menuLimiter { + height:100%; + /* TODO: find sensible limits for this */ + max-width:3440px; /* 1080p = 1920x1080 */ + margin: 0 auto; + display: flex; + flex-direction: column; +} + +/* Dashboard Menu */ +#dashmenu { + flex: none; + display: flex; + flex-flow: row; + flex-wrap: nowrap; + justify-content: stretch; + align-items: stretch; + background-color:rgba(0, 0, 0, 0.6); + position: relative; + z-index: var(--zorder_menu_dashmenu); +} + +/* limiters to fit all labels (both must be the same size to center the menu items) */ +#dashmenu::before, #dashmenu::after { + flex: 1 0 150px; + width: 150px; + content: ""; + display: inline-block; +} +.dashBrdButtonDiv::after { + content: ""; + flex: 0 0 2px; + width: 2px; + height: 100%; + margin: 8px -13px 8px 12px; + border-radius: 2px; + background-color: #f60; + z-index: 2; +} + +/* fix for long strings */ +#dashmenu * { + box-sizing: border-box; +} +#dashmenu > * { + display: flex; + flex-wrap: nowrap; +} +#dashmenu > *:not(.dashBrdButtonActive) { + overflow: hidden; +} +#dashmenu > *:not(.dashBrdButtonActive) > .dashBrdText { + flex: 0 1 auto; + overflow: hidden; + text-overflow: ellipsis; +} +/* do not change the order of next two rules */ +#dashmenu > * > *:last-child { + flex: 0 0 auto; +} +#dashmenu > * > *:first-child { + flex: 0 1 auto; +} +#dashmenu > *:not(.dashBrdButtonStart):not(.dashBrdButtonEnd) { + flex: 0 1 auto; + position: relative; + height: 100%; +} + +#dashmenu .dashBrdButton { + display: flex; + flex-wrap: nowrap; + justify-content: center; + align-items: center; + /* width: 100%; */ +} +#dashmenu .dashBrdSvgIcon { + flex: 0 0 auto; +} + +@media (min-width: 1024px) and (max-width: 1600px) { + #dashmenu > *:not(.dashBrdButtonActive):not(.dashBrdButtonStart):not(.dashBrdButtonEnd) { + min-width: 5%; + max-width: 10%; + } +} + diff --git a/ui/modules/menu/menu.html b/ui/modules/menu/menu.html new file mode 100644 index 0000000..8268954 --- /dev/null +++ b/ui/modules/menu/menu.html @@ -0,0 +1,3 @@ + + +
diff --git a/ui/modules/menu/menu.js b/ui/modules/menu/menu.js new file mode 100644 index 0000000..03b2c97 --- /dev/null +++ b/ui/modules/menu/menu.js @@ -0,0 +1,330 @@ +'use strict' + +angular.module('beamng.stuff') +.controller('MenuController', ['$rootScope', '$scope', 'Utils', '$state', '$timeout', 'mdx', function ($rootScope, $scope, Utils, $state, $timeout, mdx) { + let vm = this + + let availableMenuEntries = [ + { + translateid: 'ui.inputActions.menu.menu_item_back.title', + icon: 'arrow_back', + class: "dashBrdButtonStart dashBrdButtonTopLeft", + soundClass: "bng_back_hover_generic", + targetState: '', + action: () => { + $rootScope.$broadcast('MenuToggle') + }, + onlyIngame: false, + gamepadNavigateable: false, + navigationDisabled: true, + }, + { + translateid: 'ui.dashboard.menu', + iconSVG: '/ui/modules/menu/icon-gamemodes.svg', + targetState: 'menu.mainmenu', + onlyIngame: false, + }, + { + translateid: 'ui.playmodes.career', + iconSVG: '/ui/modules/menu/icon-gamemodes.svg', + targetState: 'menu.careerPause', + onlyIngame: true, + isVisible: () => $scope.inCareer, + }, + { + translateid: 'ui.mainmenu.garage', + iconSVG: '/ui/modules/mainmenu/drive/icons/garage.svg', + targetState: '', + action: () => { + bngApi.engineLua("career_modules_playerDriving.onResetGameplay()"); + }, + onlyIngame: true, + isVisible: () => $scope.inCareer, + blackListStates: ['scenario','mission','garage'], + }, + { + translateid: 'ui.dashboard.bigmap', + //iconSVG: '/ui/modules/menu/icon-map.svg', + icon: 'map', + targetState: 'menu.bigmap', + blackListStates: ['scenario','mission','garage'], + onlyIngame: true, + isVisible: () => !$scope.inCareer || ($scope.inCareer && $state.gamestate.name!=='garage') + }, + { + translateid: 'ui.dashboard.gameContext', + iconSVG: '/ui/modules/menu/icon-flag.svg', + targetState: 'menu.careermission', + isVisible: () => vm.missionEnabled, + blackListStates: ['scenario','garage'], + onlyIngame: true, + }, + { + translateid: 'ui.dashboard.mods', + iconSVG: '/ui/modules/menu/icon-puzzle.svg', + targetState: 'menu.mods.local', + blackListStates: ['scenario','mission','garage'], + substate: 'menu.mods', + onlyIngame: true, + }, + { + translateid: 'ui.dashboard.vehicles', + iconSVG: '/ui/modules/menu/icon-car.svg', + targetState: 'menu.vehicles', + blackListStates: ['scenario','mission','garage','career'], + substate: 'menu.vehicles', + onlyIngame: true, + }, + { + translateid: 'ui.dashboard.vehicleconfig', + iconSVG: '/ui/modules/menu/icon-engine.svg', + targetState: 'menu.vehicleconfig.parts', + blackListStates: ['scenario','mission','garage','career'], + substate: 'menu.vehicleconfig', + onlyIngame: true, + }, + { + translateid: 'ui.dashboard.environment', + iconSVG: '/ui/modules/menu/icon-weather.svg', + targetState: 'menu.environment', + blackListStates: ['scenario','mission','garage'], + onlyIngame: true, + }, + { + translateid: 'ui.dashboard.photomode', + iconSVG: '/ui/modules/menu/icon-photo.svg', + targetState: 'menu.photomode', + onlyIngame: true, + blackListStates: ['garage'], + }, + { + translateid: 'ui.dashboard.appedit', + iconSVG: '/ui/modules/menu/icon-hud.svg', + targetState: 'menu.appedit', + onlyIngame: true, + blackListStates: ['scenario'], + }, + { + translateid: 'ui.dashboard.options', + iconSVG: '/ui/modules/menu/icon-adjust.svg', + targetState: 'menu.options.graphics', + substate: 'menu.options', + onlyIngame: true, + }, + ] + + for(let m of availableMenuEntries) { + if(!m.action && m.targetState) { + // if no action is defined, toggle the states + m.action = () => { + if ($state.current.name !== m.targetState) { + $state.go(m.targetState); + } + else + $scope.$emit("MenuToggle"); + } + } + if(!m.action) { + console.error('state is missing action or targetState') + } + } + + // Mission Menu and Career Stuff + vm.missionEnabled = false + $scope.inCareer = false; + bngApi.engineLua(`{ + gameContext = core_gameContext.getGameContext(), + isCareerActive = career_career.isCareerActive() + }`, data => { + $scope.$evalAsync(() => { + vm.missionEnabled = data.gameContext && data.gameContext.context && data.gameContext.context !== "empty"; + $scope.inCareer = !!data.isCareerActive; + if ($state.current && $state.current.name === "menu") { + if (vm.missionEnabled) + $state.go("menu.careermission"); + else if ($scope.inCareer) + $state.go("menu.careerPause"); + } + updateMenu(); + }) + }); + + $scope.$on("onMissionAvailabilityChanged", (event, res) => { + if(res && res.missionCount !== undefined) { + vm.missionEnabled = res.missionCount > 0 + updateMenu() + if($state.current && $state.current.name === "menu.careermission" && !vm.missionEnabled) { + $state.go("menu"); + } + } + }) + + vm.pause = function() { + bngApi.engineLua(`bullettime.pause(true)`) + } + + $rootScope.$broadcast("TopMenuButtons", (button, index=-1) => { + if (typeof index !== "number" && index < availableMenuEntries.length) + index = -1; + if (index > 0) + availableMenuEntries.splice(index, 0, button); + else + availableMenuEntries.push(button); + updateMenu(); + }); + + function updateMenu() { + let entries = []; + const app = $scope.$parent && $scope.$parent.app ? $scope.$parent.app : {}; + if ($state.current.name !== 'menu.mainmenu' || !app.mainmenu) { + // TODO: revert to normal gamestate when it will be implemented for career (but don't forget about career's garage) + if ($scope.inCareer && ["freeroam", "garage"].includes(app.gameState)) + $scope.gameState = "career"; + else + $scope.gameState = app.gameState; + for(let entry of availableMenuEntries) { + // visibility + if(entry.blackListStates && entry.blackListStates.includes($scope.gameState)) { + continue; + } + if(entry.isVisible && !entry.isVisible()) { + continue; + } + if($scope.$parent && app.mainmenu && entry.onlyIngame) { + //console.log('ignored menu entry in main menu: ', entry) + continue + } + + if (entry.navigationDisabled === undefined) { + entry.navigationDisabled = false + } + + // determine if the thing is 'active' as in 'used / switched on' + entry.active = false + if (entry.isActive) { + entry.active = entry.isActive() + } else { + let isInSubState = false + if(entry.substate) { + isInSubState = $state.current.name.startsWith(entry.substate) + } + entry.active = ($state.includes(entry.targetState) | isInSubState) && entry.targetState != '.' && entry.targetState != '' + } + entries.push(entry) + } + //console.log('updateMenu = ', entries) + } + + if(entries.length > 0) { + entries[entries.length-1].isLastItem = true + } + + $scope.$applyAsync(() => { + for (let i = 0; i < entries.length; i++) + entries[i].id = `top-menu-${i}`; + vm.entries = entries; + }) + } + $scope.$on('$stateChangeSuccess', function (event, toState, toParams, fromState, fromParams) { + updateMenu() + }) + updateMenu() + + + // Angular material theme palette tests + let palettes = ['primary', 'accent', 'warn', 'background'] + let types = ['default', 'hue-1', 'hue-2', 'hue-3'] + let colors = {} + let theme = mdx.mdxThemeColors.theme('default') + for(let pIdx in palettes) { + let p = palettes[pIdx] + for(let tIdx in types) { + let t = types[tIdx] + let paletteName = 'custom' + p.substr(0, 1).toUpperCase() + p.substr(1) + let ref = theme.colors[p]['hues'][t] + if(paletteName in mdx.mdxThemeColors._PALETTES) { + let col = 'rgb(' + (mdx.mdxThemeColors._PALETTES[paletteName][ref].value.toString()) + ')' + let colContrast = 'rgb(' + (mdx.mdxThemeColors._PALETTES[paletteName][ref].contrast.toString()) + ')' + colors[paletteName + "/" + t] = [ref, col, colContrast] + } + } + } + //vm.colorDebug = colors + + // switches to the next or previous visible menu entry (increment +1 or -1 respectively) + vm.selectVisibleMenuEntry = function(increment) { + if ($scope.$parent && $scope.$parent.app && $scope.$parent.app.mainmenu) + return; // tab navigation shouldn't work while in the Main Menu + + // first, find the current active element + let currMenuEntryIndex = 0 + for (let i in vm.entries) { + if (!vm.entries[i].active) continue + currMenuEntryIndex = Number(i) + break + } + + // then find the next suitable one + let newIndex = currMenuEntryIndex + for(let i = 0; i < 5; i++) { + newIndex = newIndex + increment + if (newIndex >= vm.entries.length) newIndex = 0 + if (newIndex < 0) newIndex = vm.entries.length - 1 + if (vm.entries[newIndex].gamepadNavigateable === false || vm.entries[newIndex].targetState === undefined) + continue; + if (!vm.entries[newIndex].targetState && !vm.entries[newIndex].action) + continue; + // found a suitable element ... + break + } + + // navigate left (increment -1) or right (increment +1), wrapping around when reaching the menu borders + //console.log('newIndex = ', newIndex, 'vm.entries = ', vm.entries) + if (vm.entries[newIndex].targetState) { + $state.go(vm.entries[newIndex].targetState) + } else if (vm.entries[newIndex].action && vm.entries[newIndex].id) { + try { + document.getElementById(vm.entries[newIndex].id).focus(); + for (let i = 0; i < vm.entries.length; i++) + vm.entries[i].active = i === newIndex; + vm.entries[newIndex].activeSilent = true; + } catch (err) { } + } + } + + $scope.$on('$tabLeft', () => { vm.selectVisibleMenuEntry(-1) }) + $scope.$on('$tabRight', () => { vm.selectVisibleMenuEntry( 1) }) +}]) + +.directive('dashMenu', ['Utils', '$rootScope', function (Utils, $rootScope, ) { + return { + restrict: 'E', + // controller: 'MenuController', + // controllerAs: 'menuCtrl', + replace: true, + template: ` +
+
+
+ + {{entry.icon}} + + {{ entry.translateid | translate}} +
+ +
+ + {{ 'ui.inputActions.general.pause.title' | translate }} + {{ 'ui.inputActions.general.pause.title' | translate}} +
+
+ `, + } +}])