diff --git a/.idea/php.xml b/.idea/php.xml index d4a408e06..e919655d7 100644 --- a/.idea/php.xml +++ b/.idea/php.xml @@ -188,6 +188,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -280,4 +316,4 @@ - + \ No newline at end of file diff --git a/.idea/trwl.iml b/.idea/trwl.iml index 5a8ee2934..02049aa55 100644 --- a/.idea/trwl.iml +++ b/.idea/trwl.iml @@ -2,11 +2,7 @@ - - - - - + @@ -180,6 +176,8 @@ + + diff --git a/app/Http/Controllers/Frontend/SettingsController.php b/app/Http/Controllers/Frontend/SettingsController.php index b0010ed19..a0e05e907 100644 --- a/app/Http/Controllers/Frontend/SettingsController.php +++ b/app/Http/Controllers/Frontend/SettingsController.php @@ -46,13 +46,14 @@ public function renderMutedUsers(): Renderable { public function updateMainSettings(Request $request): RedirectResponse { $validated = $request->validate([ - 'username' => [ + 'username' => [ 'required', 'string', 'max:25', 'regex:/^[a-zA-Z0-9_]*$/' ], - 'name' => ['required', 'string', 'max:50'], - 'email' => ['required', 'string', 'email:rfc,dns', 'max:255'], - 'mapprovider' => ['required', new Enum(MapProvider::class)], - 'timezone' => ['required', Rule::in(DateTimeZone::listIdentifiers())] + 'name' => ['required', 'string', 'max:50'], + 'email' => ['required', 'string', 'email:rfc,dns', 'max:255'], + 'mapprovider' => ['required', new Enum(MapProvider::class)], + 'timezone' => ['required', Rule::in(DateTimeZone::listIdentifiers())], + 'experimental' => ['required', 'boolean'], ]); if (auth()->user()->username !== $validated['username']) { diff --git a/app/Models/User.php b/app/Models/User.php index 635cf3df2..acde04c99 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -36,6 +36,7 @@ * @property boolean private_profile * @property boolean prevent_index * @property boolean likes_enabled + * @property boolean $experimental * @property MapProvider mapprovider * @property int privacy_hide_days * @property string language @@ -50,7 +51,7 @@ class User extends Authenticatable implements MustVerifyEmail protected $fillable = [ 'username', 'name', 'avatar', 'email', 'email_verified_at', 'password', 'home_id', 'privacy_ack_at', 'default_status_visibility', 'likes_enabled', 'private_profile', 'prevent_index', 'privacy_hide_days', - 'language', 'last_login', 'mapprovider', 'timezone', + 'language', 'last_login', 'mapprovider', 'timezone', 'experimental', ]; protected $hidden = [ 'password', 'remember_token', 'email', 'email_verified_at', 'privacy_ack_at', @@ -67,6 +68,7 @@ class User extends Authenticatable implements MustVerifyEmail 'home_id' => 'integer', 'private_profile' => 'boolean', 'likes_enabled' => 'boolean', + 'experimental' => 'boolean', 'default_status_visibility' => StatusVisibility::class, 'prevent_index' => 'boolean', 'privacy_hide_days' => 'integer', diff --git a/database/migrations/2023_09_22_194607_add_experimental_setting_to_users.php b/database/migrations/2023_09_22_194607_add_experimental_setting_to_users.php new file mode 100644 index 000000000..f4d56ae6b --- /dev/null +++ b/database/migrations/2023_09_22_194607_add_experimental_setting_to_users.php @@ -0,0 +1,22 @@ +boolean('experimental')->default(false)->after('likes_enabled'); + }); + } + + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn('experimental'); + }); + } +}; diff --git a/package-lock.json b/package-lock.json index 896a36732..df5341ad0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "croppie": "^2.6.5", "leaflet": "^1.9.4", "Leaflet-MovingMaker": "^0.0.1", + "luxon": "^3.4.3", "mdb-ui-kit": "^6.4.1", "notyf": "^3.10.0", "vue": "^3.3.4" @@ -6037,6 +6038,14 @@ "yallist": "^3.0.2" } }, + "node_modules/luxon": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.3.tgz", + "integrity": "sha512-tFWBiv3h7z+T/tDaoxA8rqTxy1CHV6gHS//QdaH4pulbq/JuBSGgQspQQqcgnwdAx6pNI7cmvz5Sv/addzHmUg==", + "engines": { + "node": ">=12" + } + }, "node_modules/magic-string": { "version": "0.30.3", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.3.tgz", diff --git a/package.json b/package.json index 7093f16be..206b99f9c 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "croppie": "^2.6.5", "leaflet": "^1.9.4", "Leaflet-MovingMaker": "^0.0.1", + "luxon": "^3.4.3", "mdb-ui-kit": "^6.4.1", "notyf": "^3.10.0", "vue": "^3.3.4" diff --git a/resources/js/app.js b/resources/js/app.js index ebf646866..1c8f5c02c 100644 --- a/resources/js/app.js +++ b/resources/js/app.js @@ -5,6 +5,8 @@ import {Notyf} from 'notyf'; import {createApp} from 'vue'; import NotificationBell from "../vue/components/NotificationBell.vue"; import ActiveJourneyMap from "../vue/components/ActiveJourneyMap.vue"; +import Stationboard from "../vue/components/Stationboard.vue"; +import StationAutocomplete from "../vue/components/StationAutocomplete.vue"; require("./bootstrap"); require("awesomplete/awesomplete"); @@ -24,6 +26,11 @@ document.addEventListener("DOMContentLoaded", function () { app2.component('ActiveJourneyMap', ActiveJourneyMap); app2.mount('#activeJourneys'); + const app3 = createApp({}); + app3.component('Stationboard', Stationboard); + app3.component('Stationautocomplete', StationAutocomplete); + app3.mount('#station-board-new'); + window.notyf = new Notyf({ duration: 5000, position: { x: "right", y: "top" }, diff --git a/resources/lang/de.json b/resources/lang/de.json index f3eba52b2..e2eef3544 100644 --- a/resources/lang/de.json +++ b/resources/lang/de.json @@ -383,6 +383,8 @@ "settings.prevent-indexing": "Suchmaschinenindexierung verhindern", "settings.allow": "Erlauben", "settings.prevent": "Verbieten", + "settings.experimental": "Experimentelle Features", + "settings.experimental.description": "Die experimentellen Features sind noch nicht fertig und können Fehler enthalten. Sie sind nicht für den täglichen Gebrauch geeignet.", "settings.privacy.update.success": "Deine Privatsphäreneinstellungen wurden aktualisiert.", "settings.search-engines.description": "Wir setzen ein noindex-Tag auf deinem Profil um den Suchmaschinen mitzuteilen, dass dein Profil nicht indexiert werden darf. Wir haben keinen Einfluss darauf, ob die Suchmaschine diesen Wunsch berücksichtigt.", "settings.no-tokens": "Es haben aktuell keine externen Anwendungen Zugriff auf deinen Account.", diff --git a/resources/lang/en.json b/resources/lang/en.json index e0256c990..045d960b2 100644 --- a/resources/lang/en.json +++ b/resources/lang/en.json @@ -362,6 +362,8 @@ "settings.prevent-indexing": "Prevent search engine indexing", "settings.allow": "Allow", "settings.prevent": "Prevent", + "settings.experimental": "Experimental features", + "settings.experimental.description": "The experimental features are not yet ready and may contain bugs. This is not recommended for daily use.", "settings.privacy.update.success": "Your privacy settings have been updated.", "settings.search-engines.description": "We set a noindex tag on your profile to tell the search engines that your profile should not be indexed. We have no influence on whether the search engine takes this wish into account.", "settings.last-accessed": "Last accessed", diff --git a/resources/views/dashboard.blade.php b/resources/views/dashboard.blade.php index a4a56a360..27ee2dbbc 100644 --- a/resources/views/dashboard.blade.php +++ b/resources/views/dashboard.blade.php @@ -64,7 +64,13 @@ @endif - @include('includes.station-autocomplete') + @if(auth()->user()->experimental ?? false) +
+ +
+ @else + @include('includes.station-autocomplete') + @endif @if($future->count() >= 1)
diff --git a/resources/views/settings/profile.blade.php b/resources/views/settings/profile.blade.php index 04fd074a5..794d2e384 100644 --- a/resources/views/settings/profile.blade.php +++ b/resources/views/settings/profile.blade.php @@ -137,6 +137,28 @@ class="form-control"
+
+ +
+ + + @error('prevent_index') + {{ $message }} + @enderror +
+
+
diff --git a/resources/views/stationboard.blade.php b/resources/views/stationboard.blade.php index b20c010f1..b83bb3040 100644 --- a/resources/views/stationboard.blade.php +++ b/resources/views/stationboard.blade.php @@ -5,181 +5,184 @@ @section('content')
-
- @include('includes.station-autocomplete') - -
-
- -
-
-
- - -
- - +
+ @if(auth()->user()->experimental ?? false) + + @else + @include('includes.station-autocomplete') +
+
+ - +
+
+
+ + +
+ + +
+
+
-
- @if ( - count($departures) > 0 && - \Carbon\Carbon::parse($departures[0]->when)->tz->toOffsetName() - !== \Carbon\CarbonTimeZone::create(auth()->user()->timezone)->toOffsetName() - ) - - @endif + @if ( + count($departures) > 0 && + \Carbon\Carbon::parse($departures[0]->when)->tz->toOffsetName() + !== \Carbon\CarbonTimeZone::create(auth()->user()->timezone)->toOffsetName() + ) + + @endif -
-
-
- - - +
+
+
+ + + +
+ {{ $station->name }} + + + {{ userTime($times['now'], __('time-format.with-day')) }} +
- {{ $station->name }} - - - {{ userTime($times['now'], __('time-format.with-day')) }} - -
-
- @if(count($departures) === 0) - - - - -
{{ __('stationboard.no-departures') }}
- @else - - +
+ @if(count($departures) === 0) +
- - - + - - - @foreach($departures as $departure) - @if(!$loop->first && !$loop->last && \Carbon\Carbon::parse($departures[$loop->index - 1]->when)->isPast() && \Carbon\Carbon::parse($departures[$loop->index]->when)->isAfter(\Carbon\Carbon::now()->setSecond(0))) - - - - @endif - - station->id !== $station->id && $departure->station->name !== $station->name) - data-searched-station="{{$station->id}}" +
- {{__('stationboard.dep-time')}} - - {{__('stationboard.line')}} - - {{__('stationboard.destination')}} - {{ __('stationboard.no-departures') }}
- {{__('request-time', ['time' => userTime(now())])}} -
+ @else + + + + + + + + + + @foreach($departures as $departure) + @if(!$loop->first && !$loop->last && \Carbon\Carbon::parse($departures[$loop->index - 1]->when)->isPast() && \Carbon\Carbon::parse($departures[$loop->index]->when)->isAfter(\Carbon\Carbon::now()->setSecond(0))) + + + @endif - @if(!isset($departure->cancelled)) class="trainrow" @endif - > - station->id !== $station->id && $departure->station->name !== $station->name) + data-searched-station="{{$station->id}}" + @endif + @if(!isset($departure->cancelled)) class="trainrow" @endif + > + - - + + - - @endforeach - -
+ {{__('stationboard.dep-time')}} + + {{__('stationboard.line')}} + + {{__('stationboard.destination')}} +
+ {{__('request-time', ['time' => userTime(now())])}} +
- @if($departure->delay === null) - + +
+ @if($departure->delay === null) + {{userTime($departure->plannedWhen)}} - @elseif($departure->delay === 0) - + @elseif($departure->delay === 0) + {{userTime($departure->plannedWhen)}} - @elseif($departure->delay < (5*60)) - + @elseif($departure->delay < (5*60)) + {{userTime($departure->when)}} - - {{userTime($departure->plannedWhen)}} - - @else - + + {{userTime($departure->plannedWhen)}} + + @else + {{userTime($departure->when)}} - - {{userTime($departure->plannedWhen)}} - - @endif - - @if (file_exists(public_path('img/'.$departure->line->product.'.svg'))) - {{$departure->line->product}} - @else - - @endif -   - @if($departure->line->name) - {!! str_replace(" ", " ", $departure->line->name) !!} - @else - {!! str_replace(" ", " ", $departure->line->fahrtNr) !!} - @endif - - @if(isset($departure->cancelled)) - + + {{userTime($departure->plannedWhen)}} + + @endif + + @if (file_exists(public_path('img/'.$departure->line->product.'.svg'))) + {{$departure->line->product}} + @else + + @endif +   + @if($departure->line->name) + {!! str_replace(" ", " ", $departure->line->name) !!} + @else + {!! str_replace(" ", " ", $departure->line->fahrtNr) !!} + @endif + + @if(isset($departure->cancelled)) + {{ __('stationboard.stop-cancelled') }} -
- - {{__('stationboard.to')}} +
+ + {{__('stationboard.to')}} + {{$departure->direction}} + + @else {{$departure->direction}} -
- @else - {{$departure->direction}} - @endif + @endif - @if($departure->station->id !== $station->id && $departure->station->name !== $station->name) -
- - {{__('stationboard.dep')}} {{$departure->station->name}} - - @endif -
- @endif + @if($departure->station->id !== $station->id && $departure->station->name !== $station->name) +
+ + {{__('stationboard.dep')}} {{$departure->station->name}} + + @endif + + + @endforeach + + + @endif +
-
+ @endif
diff --git a/resources/vue/components/CheckinInterface.vue b/resources/vue/components/CheckinInterface.vue new file mode 100644 index 000000000..f81e858f1 --- /dev/null +++ b/resources/vue/components/CheckinInterface.vue @@ -0,0 +1,180 @@ + + + + + diff --git a/resources/vue/components/CheckinLineRun.vue b/resources/vue/components/CheckinLineRun.vue new file mode 100644 index 000000000..42dbf8ca5 --- /dev/null +++ b/resources/vue/components/CheckinLineRun.vue @@ -0,0 +1,93 @@ + + + + + diff --git a/resources/vue/components/FullScreenModal.vue b/resources/vue/components/FullScreenModal.vue new file mode 100644 index 000000000..ffa10960f --- /dev/null +++ b/resources/vue/components/FullScreenModal.vue @@ -0,0 +1,53 @@ + + + + + diff --git a/resources/vue/components/LineIndicator.vue b/resources/vue/components/LineIndicator.vue new file mode 100644 index 000000000..9e00863d4 --- /dev/null +++ b/resources/vue/components/LineIndicator.vue @@ -0,0 +1,102 @@ + + + + + diff --git a/resources/vue/components/ProductIcon.vue b/resources/vue/components/ProductIcon.vue new file mode 100644 index 000000000..a37690ef3 --- /dev/null +++ b/resources/vue/components/ProductIcon.vue @@ -0,0 +1,38 @@ + + + + + diff --git a/resources/vue/components/StationAutocomplete.vue b/resources/vue/components/StationAutocomplete.vue new file mode 100644 index 000000000..81378199f --- /dev/null +++ b/resources/vue/components/StationAutocomplete.vue @@ -0,0 +1,126 @@ + + + + + diff --git a/resources/vue/components/Stationboard.vue b/resources/vue/components/Stationboard.vue new file mode 100644 index 000000000..6fad68f0f --- /dev/null +++ b/resources/vue/components/Stationboard.vue @@ -0,0 +1,170 @@ + + + + +