feat(studio): rich theme picker (presets + custom colour) + editor 0.11.0
Some checks failed
Security / Security check (push) Failing after 1s
Some checks failed
Security / Security check (push) Failing after 1s
Settings dialog's Theme Plugin section is now a grid of swatched tiles: - Built-in (none) — falls back to FaiTheme.light/.dark - One tile per installed studio.theme.* plugin, each showing the plugin's primary/secondary/tertiary as live colour dots. Tile loads its preview lazily so a dozen installed themes don't block the picker. - Custom — opens a colour-picker dialog with 12 curated Material presets + a hex input + live preview. Selecting applies ColorScheme.fromSeed for both brightnesses. main.dart's _pluginThemes parses a 'custom:#RRGGBB' sigil in the same notifier slot as plugin capability ids, so the existing persistence + restoration paths cover the custom case with no new state. Bumps editor to 0.11.0 (type-checked port connections + dynamic card width fix + card-height border allowance) and Studio to 0.58.0. Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
This commit is contained in:
parent
69864da934
commit
c1c60d5434
30 changed files with 1280 additions and 846 deletions
|
|
@ -15,12 +15,10 @@ import 'package:path/path.dart' as p;
|
|||
/// based on its own `auth.tokens:` config.
|
||||
class HubAuthToken {
|
||||
static String _faiHome() {
|
||||
final home = Platform.environment['HOME'] ??
|
||||
Platform.environment['USERPROFILE'];
|
||||
final home =
|
||||
Platform.environment['HOME'] ?? Platform.environment['USERPROFILE'];
|
||||
if (home == null || home.isEmpty) {
|
||||
throw StateError(
|
||||
'Cannot resolve home directory (no HOME / USERPROFILE)',
|
||||
);
|
||||
throw StateError('Cannot resolve home directory (no HOME / USERPROFILE)');
|
||||
}
|
||||
return p.join(home, '.fai');
|
||||
}
|
||||
|
|
@ -79,7 +77,9 @@ class HubAuthToken {
|
|||
if ((Platform.isLinux || Platform.isMacOS) && !parentExisted) {
|
||||
try {
|
||||
await Process.run('chmod', ['700', parent.path]);
|
||||
} catch (_) {/* best-effort */}
|
||||
} catch (_) {
|
||||
/* best-effort */
|
||||
}
|
||||
}
|
||||
// Write to the temp path with restrictive mode set up
|
||||
// BEFORE the rename — the final file is never visible
|
||||
|
|
@ -88,7 +88,9 @@ class HubAuthToken {
|
|||
if (Platform.isLinux || Platform.isMacOS) {
|
||||
try {
|
||||
await Process.run('chmod', ['600', tmp.path]);
|
||||
} catch (_) {/* best-effort */}
|
||||
} catch (_) {
|
||||
/* best-effort */
|
||||
}
|
||||
}
|
||||
// Atomic rename: on POSIX, replaces the destination
|
||||
// in one syscall. On Windows, Dart's File.rename uses
|
||||
|
|
@ -105,7 +107,9 @@ class HubAuthToken {
|
|||
if (Platform.isLinux || Platform.isMacOS) {
|
||||
try {
|
||||
await Process.run('chmod', ['600', f.path]);
|
||||
} catch (_) {/* best-effort */}
|
||||
} catch (_) {
|
||||
/* best-effort */
|
||||
}
|
||||
}
|
||||
await tmp.delete();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,8 @@ import 'package:path/path.dart' as p;
|
|||
/// requires the operator to fiddle with shell env vars.
|
||||
class RegistryToken {
|
||||
static String _faiHome() {
|
||||
final home = Platform.environment['HOME'] ?? Platform.environment['USERPROFILE'];
|
||||
final home =
|
||||
Platform.environment['HOME'] ?? Platform.environment['USERPROFILE'];
|
||||
if (home == null || home.isEmpty) {
|
||||
throw StateError('Cannot resolve home directory (no HOME / USERPROFILE)');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -152,7 +152,8 @@ class SystemActions {
|
|||
return (
|
||||
ok: false,
|
||||
stdout: '',
|
||||
stderr: 'Could not locate the `fai` binary. Install via the '
|
||||
stderr:
|
||||
'Could not locate the `fai` binary. Install via the '
|
||||
'platform installer or set FAI_BIN to its full path.',
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,8 +39,9 @@ class ThemePluginSchemes {
|
|||
Future<List<String>> listThemePluginCapabilities() async {
|
||||
final caps = await HubService.instance.allCapabilities();
|
||||
return caps
|
||||
.where((c) =>
|
||||
c.kind == 'wasm' && c.capability.startsWith('studio.theme.'))
|
||||
.where(
|
||||
(c) => c.kind == 'wasm' && c.capability.startsWith('studio.theme.'),
|
||||
)
|
||||
.map((c) => c.capability)
|
||||
.toList()
|
||||
..sort();
|
||||
|
|
@ -121,6 +122,7 @@ ColorScheme _tokensToColorScheme(List<int> tokens, Brightness brightness) {
|
|||
? const Color(0xFF1A1A1A)
|
||||
: const Color(0xFFEEEEEE);
|
||||
}
|
||||
|
||||
return ColorScheme(
|
||||
brightness: brightness,
|
||||
primary: slot(0),
|
||||
|
|
|
|||
|
|
@ -58,7 +58,8 @@ class TodayStoryLoader {
|
|||
/// Resolves to `~/.fai/today/active.yaml` on every supported
|
||||
/// host. The pipeline writes here from [tools/today/accept.sh].
|
||||
static String activePath() {
|
||||
final home = Platform.environment['HOME'] ??
|
||||
final home =
|
||||
Platform.environment['HOME'] ??
|
||||
Platform.environment['USERPROFILE'] ??
|
||||
'';
|
||||
return p.join(home, '.fai', 'today', 'active.yaml');
|
||||
|
|
@ -89,8 +90,8 @@ class TodayStoryLoader {
|
|||
|
||||
String s(String key) => (doc[key] ?? '').toString().trim();
|
||||
if (_bannedWords.hasMatch(
|
||||
'${s('title_en')} ${s('title_de')} ${s('body_en')} ${s('body_de')}',
|
||||
)) {
|
||||
'${s('title_en')} ${s('title_de')} ${s('body_en')} ${s('body_de')}',
|
||||
)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue