Working arrows
This commit is contained in:
parent
60b14fb49e
commit
1f83f6c9f7
@ -74,6 +74,13 @@ git pull
|
||||
sudo systemctl restart cyberhybridhub-api cyberhybridhub-web
|
||||
```
|
||||
|
||||
Quick redeploy (code only, no `pub get`, restarts services):
|
||||
|
||||
```bash
|
||||
git pull
|
||||
./scripts/deploy-dev.sh
|
||||
```
|
||||
|
||||
## Firebase authorized domains
|
||||
|
||||
```bash
|
||||
|
||||
@ -1,11 +1,33 @@
|
||||
/// Base URL for the Cyber Hybrid Hub API (Postgres-backed).
|
||||
///
|
||||
/// Override at build/run time:
|
||||
/// flutter run --dart-define=API_BASE_URL=http://localhost:3000
|
||||
const String apiBaseUrl = String.fromEnvironment(
|
||||
import 'package:flutter/foundation.dart' show kIsWeb;
|
||||
|
||||
const String _apiBaseUrlFromEnvironment = String.fromEnvironment(
|
||||
'API_BASE_URL',
|
||||
defaultValue: 'http://localhost:3000',
|
||||
);
|
||||
|
||||
bool _isLocalDevHost(String host) {
|
||||
final String h = host.toLowerCase();
|
||||
return h == 'localhost' || h == '127.0.0.1';
|
||||
}
|
||||
|
||||
/// Base URL for the Cyber Hybrid Hub API (Postgres-backed).
|
||||
///
|
||||
/// Local: `flutter run --dart-define=API_BASE_URL=http://localhost:3000`
|
||||
///
|
||||
/// Production web (https://cyberhybridhub.com): always [Uri.base.origin] so
|
||||
/// /v1 and /hubs hit HAProxy on :443 — never :3000 over HTTP (mixed content).
|
||||
String get apiBaseUrl {
|
||||
if (!kIsWeb) {
|
||||
return _apiBaseUrlFromEnvironment;
|
||||
}
|
||||
|
||||
final Uri page = Uri.base;
|
||||
if (_isLocalDevHost(page.host)) {
|
||||
return _apiBaseUrlFromEnvironment;
|
||||
}
|
||||
|
||||
return page.origin;
|
||||
}
|
||||
|
||||
/// SignalR hub for real-time incoming questions.
|
||||
String get questionsHubUrl => '$apiBaseUrl/hubs/questions';
|
||||
|
||||
@ -126,6 +126,8 @@ class HomeScreen extends StatelessWidget {
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(8, 4, 8, 8),
|
||||
child: FocusScope(
|
||||
autofocus: true,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: <Widget>[
|
||||
@ -154,6 +156,7 @@ class HomeScreen extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (!showQuestionPanel)
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
|
||||
@ -6,6 +6,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import '../guid/guid_glyph_shape.dart';
|
||||
import '../services/questions_hub_service.dart';
|
||||
import '../theme/app_theme.dart';
|
||||
|
||||
const double _holdEdgeButtonWidth = 58;
|
||||
@ -487,16 +488,29 @@ class _SwipeQuestionTileState extends State<SwipeQuestionTile>
|
||||
_stepSliderBy(scrollDy > 0 ? -1 : 1);
|
||||
}
|
||||
|
||||
/// Web/production fallback when [Focus.onKeyEvent] does not receive arrows.
|
||||
/// True while the question panel is open and this tile should own arrow keys.
|
||||
bool get _captureArrowKeys =>
|
||||
mounted &&
|
||||
!widget.busy &&
|
||||
!_acting &&
|
||||
QuestionsHubService.instance.questionPanelOpen.value;
|
||||
|
||||
/// Primary path for web/production: does not require widget focus.
|
||||
bool _handleHardwareKey(KeyEvent event) {
|
||||
if (!_focusNode.hasFocus) {
|
||||
if (!_captureArrowKeys || !_isArrowKey(event.logicalKey)) {
|
||||
return false;
|
||||
}
|
||||
return _onKeyEvent(_focusNode, event) == KeyEventResult.handled;
|
||||
return _processArrowKey(event) == KeyEventResult.handled;
|
||||
}
|
||||
|
||||
static bool _isArrowKey(LogicalKeyboardKey key) =>
|
||||
key == LogicalKeyboardKey.arrowUp ||
|
||||
key == LogicalKeyboardKey.arrowDown ||
|
||||
key == LogicalKeyboardKey.arrowLeft ||
|
||||
key == LogicalKeyboardKey.arrowRight;
|
||||
|
||||
/// Arrow up/down step value; hold left/right to defer / save (edge buttons).
|
||||
KeyEventResult _onKeyEvent(FocusNode node, KeyEvent event) {
|
||||
KeyEventResult _processArrowKey(KeyEvent event) {
|
||||
if (widget.busy || _acting) {
|
||||
return KeyEventResult.ignored;
|
||||
}
|
||||
@ -630,7 +644,6 @@ class _SwipeQuestionTileState extends State<SwipeQuestionTile>
|
||||
focusNode: _focusNode,
|
||||
autofocus: true,
|
||||
descendantsAreFocusable: false,
|
||||
onKeyEvent: _onKeyEvent,
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
clipBehavior: Clip.none,
|
||||
|
||||
@ -34,23 +34,36 @@ _load_env_api_url() {
|
||||
|
||||
API_BASE_URL="https://cyberhybridhub.com"
|
||||
|
||||
_server_api="$(_load_env_api_url "$ROOT/server/.env")"
|
||||
if [ -n "$_server_api" ]; then
|
||||
API_BASE_URL="$_server_api"
|
||||
fi
|
||||
|
||||
# hosting.env wins over server/.env for web builds (server may still say :3000).
|
||||
if [ -f "$ROOT/hosting/hosting.env" ]; then
|
||||
# shellcheck source=/dev/null
|
||||
source "$ROOT/hosting/hosting.env"
|
||||
API_BASE_URL="${API_PUBLIC_URL:-$API_BASE_URL}"
|
||||
fi
|
||||
|
||||
_server_api="$(_load_env_api_url "$ROOT/server/.env")"
|
||||
if [ -n "$_server_api" ] && [ ! -f "$ROOT/hosting/hosting.env" ]; then
|
||||
API_BASE_URL="$_server_api"
|
||||
fi
|
||||
|
||||
if [ -n "$_CLI_OVERRIDE" ]; then
|
||||
API_BASE_URL="$_CLI_OVERRIDE"
|
||||
fi
|
||||
|
||||
if [[ "$API_BASE_URL" == http://* ]] || [[ "$API_BASE_URL" == *:3000* ]]; then
|
||||
echo "ERROR: Production web must not use HTTP or port 3000 in API_BASE_URL." >&2
|
||||
echo " Got: $API_BASE_URL" >&2
|
||||
echo " Set API_PUBLIC_URL=https://cyberhybridhub.com in hosting/hosting.env" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Building release web with API_BASE_URL=$API_BASE_URL"
|
||||
flutter build web --release \
|
||||
_flutter_pub_flag=()
|
||||
if [ "${SKIP_PUB_GET:-0}" = "1" ]; then
|
||||
_flutter_pub_flag=(--no-pub)
|
||||
echo "SKIP_PUB_GET=1 — not running flutter pub get"
|
||||
fi
|
||||
flutter build web --release "${_flutter_pub_flag[@]}" \
|
||||
--dart-define=API_BASE_URL="$API_BASE_URL" \
|
||||
--dart-define=AUTH_VERBOSE_LOG=true
|
||||
|
||||
@ -58,4 +71,8 @@ if [ -x "$ROOT/scripts/copy_flutter_js_source_map.sh" ]; then
|
||||
"$ROOT/scripts/copy_flutter_js_source_map.sh" "$ROOT/build/web" || true
|
||||
fi
|
||||
|
||||
if [ -x "$ROOT/scripts/stamp-web-cache-bust.sh" ]; then
|
||||
"$ROOT/scripts/stamp-web-cache-bust.sh"
|
||||
fi
|
||||
|
||||
echo "Done: $ROOT/build/web"
|
||||
|
||||
44
scripts/deploy-dev.sh
Executable file
44
scripts/deploy-dev.sh
Executable file
@ -0,0 +1,44 @@
|
||||
#!/usr/bin/env bash
|
||||
# Fast deploy: build release web (no pub get) and restart production services.
|
||||
# Use after git pull when dependencies are already installed.
|
||||
#
|
||||
# ./scripts/deploy-dev.sh
|
||||
#
|
||||
# Full deploy with package refresh: ./scripts/deploy-production.sh
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
cd "$ROOT"
|
||||
|
||||
if [ -f "$ROOT/scripts/flutter-env.sh" ]; then
|
||||
# shellcheck source=/dev/null
|
||||
source "$ROOT/scripts/flutter-env.sh"
|
||||
fi
|
||||
|
||||
echo "==> Flutter web (release, no pub get)"
|
||||
SKIP_PUB_GET=1 "$ROOT/scripts/build-production-web.sh"
|
||||
|
||||
if grep -qE 'http://cyberhybridhub\.com:3000|https://cyberhybridhub\.com:3000' \
|
||||
"$ROOT/build/web/main.dart.js" 2>/dev/null; then
|
||||
echo "ERROR: build/web still bakes :3000 API URL — fix hosting/hosting.env and rebuild." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "==> Restart services"
|
||||
if ! command -v systemctl >/dev/null 2>&1; then
|
||||
echo "systemctl not found; skip service restart." >&2
|
||||
exit 0
|
||||
fi
|
||||
|
||||
for unit in cyberhybridhub-api cyberhybridhub-web; do
|
||||
if ! systemctl list-unit-files --type=service "$unit.service" >/dev/null 2>&1; then
|
||||
echo "Warning: $unit.service not installed (run ./hosting/install-systemd.sh once)" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
sudo systemctl restart cyberhybridhub-api cyberhybridhub-web
|
||||
sudo systemctl --no-pager --full status cyberhybridhub-api cyberhybridhub-web || true
|
||||
|
||||
echo ""
|
||||
echo "Dev deploy done. Hard-refresh the browser if the UI looks stale."
|
||||
29
scripts/stamp-web-cache-bust.sh
Executable file
29
scripts/stamp-web-cache-bust.sh
Executable file
@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env bash
|
||||
# After flutter build web: bust CDN/browser cache for bootstrap + main.dart.js.
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
WEB="$ROOT/build/web"
|
||||
INDEX="$WEB/index.html"
|
||||
BOOT="$WEB/flutter_bootstrap.js"
|
||||
|
||||
if [ ! -f "$INDEX" ]; then
|
||||
echo "Missing $INDEX — run flutter build web first." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
STAMP="$(git -C "$ROOT" rev-parse --short HEAD 2>/dev/null || echo nogit)-$(date +%Y%m%d%H%M%S)"
|
||||
echo "Web cache-bust stamp: $STAMP"
|
||||
|
||||
# index.html: versioned bootstrap URL (index.html is no-cache at origin).
|
||||
if grep -q 'flutter_bootstrap.js' "$INDEX"; then
|
||||
sed -i "s|flutter_bootstrap.js[^\"']*|flutter_bootstrap.js?v=${STAMP}|g" "$INDEX"
|
||||
fi
|
||||
|
||||
# flutter_bootstrap.js: versioned main.dart.js load path in buildConfig.
|
||||
if [ -f "$BOOT" ]; then
|
||||
sed -i "s|\"mainJsPath\":\"main.dart.js\"|\"mainJsPath\":\"main.dart.js?v=${STAMP}\"|g" "$BOOT"
|
||||
fi
|
||||
|
||||
printf '%s\n' "$STAMP" >"$WEB/BUILD_STAMP.txt"
|
||||
echo "Stamped: $INDEX and flutter_bootstrap.js (if present)"
|
||||
@ -138,7 +138,12 @@ void _applyCacheHeaders(HttpResponse response, String filePath) {
|
||||
}
|
||||
|
||||
final String lower = filePath.toLowerCase();
|
||||
if (lower.endsWith('.html') || lower.endsWith('.htm')) {
|
||||
final String name = lower.split('/').last;
|
||||
if (lower.endsWith('.html') ||
|
||||
lower.endsWith('.htm') ||
|
||||
name.startsWith('main.dart.js') ||
|
||||
name.startsWith('flutter_bootstrap.js') ||
|
||||
name == 'flutter.js') {
|
||||
response.headers.set(
|
||||
'Cache-Control',
|
||||
'no-cache, must-revalidate',
|
||||
|
||||
@ -36,6 +36,14 @@
|
||||
<link rel="manifest" href="manifest.json">
|
||||
</head>
|
||||
<body>
|
||||
<!-- Clear stale Flutter service workers that cache old main.dart.js after deploys. -->
|
||||
<script>
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.getRegistrations().then(function (regs) {
|
||||
for (const r of regs) { r.unregister(); }
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<!--
|
||||
You can customize the "flutter_bootstrap.js" script.
|
||||
This is useful to provide a custom configuration to the Flutter loader
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user