mirror of
https://gitlab.com/veilid/veilidchat.git
synced 2025-02-21 07:19:55 -05:00
ellet
This commit is contained in:
parent
711f82735e
commit
9291dc2b80
@ -163,7 +163,9 @@
|
|||||||
"developer": {
|
"developer": {
|
||||||
"title": "Developer Logs",
|
"title": "Developer Logs",
|
||||||
"command": "Command",
|
"command": "Command",
|
||||||
"copied": "Selection copied"
|
"copied": "Selection copied",
|
||||||
|
"cleared": "Logs cleared",
|
||||||
|
"are_you_sure_clear": "Are you sure you want to clear the logs?"
|
||||||
},
|
},
|
||||||
"log": {
|
"log": {
|
||||||
"error": "Error",
|
"error": "Error",
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:ansicolor/ansicolor.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
@ -23,6 +24,9 @@ void main() async {
|
|||||||
debugPrint('VeilidChat PID: $pid');
|
debugPrint('VeilidChat PID: $pid');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ansi colors
|
||||||
|
ansiColorDisabled = false;
|
||||||
|
|
||||||
// Logs
|
// Logs
|
||||||
initLoggy();
|
initLoggy();
|
||||||
|
|
||||||
|
@ -1,11 +1,16 @@
|
|||||||
|
import 'package:ansicolor/ansicolor.dart';
|
||||||
import 'package:awesome_extensions/awesome_extensions.dart';
|
import 'package:awesome_extensions/awesome_extensions.dart';
|
||||||
|
import 'package:cool_dropdown/cool_dropdown.dart';
|
||||||
|
import 'package:cool_dropdown/models/cool_dropdown_item.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter_animate/flutter_animate.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:flutter_translate/flutter_translate.dart';
|
import 'package:flutter_translate/flutter_translate.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:loggy/loggy.dart';
|
import 'package:loggy/loggy.dart';
|
||||||
|
import 'package:quickalert/quickalert.dart';
|
||||||
import 'package:xterm/xterm.dart';
|
import 'package:xterm/xterm.dart';
|
||||||
|
|
||||||
import '../tools/tools.dart';
|
import '../tools/tools.dart';
|
||||||
@ -28,60 +33,63 @@ class DeveloperPage extends ConsumerStatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class DeveloperPageState extends ConsumerState<DeveloperPage> {
|
class DeveloperPageState extends ConsumerState<DeveloperPage> {
|
||||||
final terminalController = TerminalController();
|
final _terminalController = TerminalController();
|
||||||
var logLevelDropDown = log.level.logLevel;
|
final _debugCommandController = TextEditingController();
|
||||||
final TextEditingController _debugCommandController = TextEditingController();
|
final _logLevelController = DropdownController(duration: 250.ms);
|
||||||
|
final List<CoolDropdownItem<LogLevel>> _logLevelDropdownItems = [];
|
||||||
|
var _logLevelDropDown = log.level.logLevel;
|
||||||
|
var _showEllet = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
// _scrollController = ScrollController(
|
|
||||||
// onAttach: _handlePositionAttach,
|
|
||||||
// onDetach: _handlePositionDetach,
|
|
||||||
// );
|
|
||||||
super.initState();
|
super.initState();
|
||||||
terminalController.addListener(() {
|
_terminalController.addListener(() {
|
||||||
setState(() {});
|
setState(() {});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
for (var i = 0; i < logLevels.length; i++) {
|
||||||
|
_logLevelDropdownItems.add(CoolDropdownItem<LogLevel>(
|
||||||
|
label: logLevelName(logLevels[i]),
|
||||||
|
icon: Text(logLevelEmoji(logLevels[i])),
|
||||||
|
value: logLevels[i]));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// void _handleScrollChange() {
|
void _debugOut(String out) {
|
||||||
// if (_isScrolling != _scrollController.position.isScrollingNotifier.value) {
|
final pen = AnsiPen()..cyan(bold: true);
|
||||||
// _isScrolling = _scrollController.position.isScrollingNotifier.value;
|
final colorOut = pen(out);
|
||||||
// _wantsBottom = _scrollController.position.pixels ==
|
debugPrint(colorOut);
|
||||||
// _scrollController.position.maxScrollExtent;
|
globalDebugTerminal.write(colorOut.replaceAll('\n', '\r\n'));
|
||||||
// }
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
// void _handlePositionAttach(ScrollPosition position) {
|
|
||||||
// // From here, add a listener to the given ScrollPosition.
|
|
||||||
// // Here the isScrollingNotifier will be used to inform when scrolling starts
|
|
||||||
// // and stops and change the AppBar's color in response.
|
|
||||||
// position.isScrollingNotifier.addListener(_handleScrollChange);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// void _handlePositionDetach(ScrollPosition position) {
|
|
||||||
// // From here, add a listener to the given ScrollPosition.
|
|
||||||
// // Here the isScrollingNotifier will be used to inform when scrolling starts
|
|
||||||
// // and stops and change the AppBar's color in response.
|
|
||||||
// position.isScrollingNotifier.removeListener(_handleScrollChange);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// void _scrollToBottom() {
|
|
||||||
// _scrollController.jumpTo(_scrollController.position.maxScrollExtent);
|
|
||||||
// _wantsBottom = true;
|
|
||||||
// }
|
|
||||||
|
|
||||||
Future<void> _sendDebugCommand(String debugCommand) async {
|
Future<void> _sendDebugCommand(String debugCommand) async {
|
||||||
log.info('DEBUG >>>\n$debugCommand');
|
if (debugCommand == 'ellet') {
|
||||||
final out = await Veilid.instance.debug(debugCommand);
|
setState(() {
|
||||||
log.info('<<< DEBUG\n$out');
|
_showEllet = !_showEllet;
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_debugOut('DEBUG >>>\n$debugCommand\n');
|
||||||
|
try {
|
||||||
|
final out = await Veilid.instance.debug(debugCommand);
|
||||||
|
_debugOut('<<< DEBUG\n$out\n');
|
||||||
|
} on Exception catch (e, st) {
|
||||||
|
_debugOut('<<< ERROR\n$e\n<<< STACK\n$st');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> clear(BuildContext context) async {
|
||||||
|
globalDebugTerminal.buffer.clear();
|
||||||
|
if (context.mounted) {
|
||||||
|
showInfoToast(context, translate('developer.cleared'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> copySelection(BuildContext context) async {
|
Future<void> copySelection(BuildContext context) async {
|
||||||
final selection = terminalController.selection;
|
final selection = _terminalController.selection;
|
||||||
if (selection != null) {
|
if (selection != null) {
|
||||||
final text = globalDebugTerminal.buffer.getText(selection);
|
final text = globalDebugTerminal.buffer.getText(selection);
|
||||||
terminalController.clearSelection();
|
_terminalController.clearSelection();
|
||||||
await Clipboard.setData(ClipboardData(text: text));
|
await Clipboard.setData(ClipboardData(text: text));
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
showInfoToast(context, translate('developer.copied'));
|
showInfoToast(context, translate('developer.copied'));
|
||||||
@ -92,7 +100,7 @@ class DeveloperPageState extends ConsumerState<DeveloperPage> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
//final textTheme = theme.textTheme;
|
final textTheme = theme.textTheme;
|
||||||
final scale = theme.extension<ScaleScheme>()!;
|
final scale = theme.extension<ScaleScheme>()!;
|
||||||
|
|
||||||
// WidgetsBinding.instance.addPostFrameCallback((_) {
|
// WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
@ -112,49 +120,99 @@ class DeveloperPageState extends ConsumerState<DeveloperPage> {
|
|||||||
icon: const Icon(Icons.copy),
|
icon: const Icon(Icons.copy),
|
||||||
color: scale.primaryScale.text,
|
color: scale.primaryScale.text,
|
||||||
disabledColor: scale.grayScale.subtleText,
|
disabledColor: scale.grayScale.subtleText,
|
||||||
onPressed: terminalController.selection == null
|
onPressed: _terminalController.selection == null
|
||||||
? null
|
? null
|
||||||
: () async {
|
: () async {
|
||||||
await copySelection(context);
|
await copySelection(context);
|
||||||
}),
|
}),
|
||||||
DropdownMenu<LogLevel>(
|
IconButton(
|
||||||
initialSelection: logLevelDropDown,
|
icon: const Icon(Icons.clear_all),
|
||||||
onSelected: (value) {
|
color: scale.primaryScale.text,
|
||||||
if (value != null) {
|
disabledColor: scale.grayScale.subtleText,
|
||||||
setState(() {
|
onPressed: () async {
|
||||||
logLevelDropDown = value;
|
await QuickAlert.show(
|
||||||
//log. = value;
|
context: context,
|
||||||
setVeilidLogLevel(value);
|
type: QuickAlertType.confirm,
|
||||||
});
|
title: translate('developer.are_you_sure_clear'),
|
||||||
}
|
textColor: scale.primaryScale.text,
|
||||||
},
|
confirmBtnColor: scale.primaryScale.elementBackground,
|
||||||
dropdownMenuEntries: [
|
backgroundColor: scale.primaryScale.subtleBackground,
|
||||||
DropdownMenuEntry<LogLevel>(
|
headerBackgroundColor: scale.primaryScale.background,
|
||||||
value: LogLevel.error, label: translate('log.error')),
|
confirmBtnText: translate('button.ok'),
|
||||||
DropdownMenuEntry<LogLevel>(
|
cancelBtnText: translate('button.cancel'),
|
||||||
value: LogLevel.warning, label: translate('log.warning')),
|
onConfirmBtnTap: () async {
|
||||||
DropdownMenuEntry<LogLevel>(
|
Navigator.pop(context);
|
||||||
value: LogLevel.info, label: translate('log.info')),
|
if (context.mounted) {
|
||||||
DropdownMenuEntry<LogLevel>(
|
await clear(context);
|
||||||
value: LogLevel.debug, label: translate('log.debug')),
|
}
|
||||||
DropdownMenuEntry<LogLevel>(
|
});
|
||||||
value: traceLevel, label: translate('log.trace')),
|
}),
|
||||||
])
|
CoolDropdown<LogLevel>(
|
||||||
|
controller: _logLevelController,
|
||||||
|
defaultItem: _logLevelDropdownItems
|
||||||
|
.singleWhere((x) => x.value == _logLevelDropDown),
|
||||||
|
onChange: (value) {
|
||||||
|
setState(() {
|
||||||
|
_logLevelDropDown = value;
|
||||||
|
Loggy('').level = getLogOptions(value);
|
||||||
|
setVeilidLogLevel(value);
|
||||||
|
_logLevelController.close();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
resultOptions: ResultOptions(
|
||||||
|
width: 64,
|
||||||
|
height: 40,
|
||||||
|
render: ResultRender.icon,
|
||||||
|
textStyle: textTheme.labelMedium!
|
||||||
|
.copyWith(color: scale.primaryScale.text),
|
||||||
|
padding: const EdgeInsets.fromLTRB(8, 4, 8, 4),
|
||||||
|
openBoxDecoration: BoxDecoration(
|
||||||
|
color: scale.primaryScale.activeElementBackground),
|
||||||
|
boxDecoration:
|
||||||
|
BoxDecoration(color: scale.primaryScale.elementBackground),
|
||||||
|
),
|
||||||
|
dropdownOptions: DropdownOptions(
|
||||||
|
width: 160,
|
||||||
|
align: DropdownAlign.right,
|
||||||
|
duration: 150.ms,
|
||||||
|
color: scale.primaryScale.elementBackground,
|
||||||
|
borderSide: BorderSide(color: scale.primaryScale.border),
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
padding: const EdgeInsets.fromLTRB(8, 4, 8, 4),
|
||||||
|
),
|
||||||
|
dropdownTriangleOptions: const DropdownTriangleOptions(
|
||||||
|
align: DropdownTriangleAlign.right),
|
||||||
|
dropdownItemOptions: DropdownItemOptions(
|
||||||
|
selectedTextStyle: textTheme.labelMedium!
|
||||||
|
.copyWith(color: scale.primaryScale.text),
|
||||||
|
textStyle: textTheme.labelMedium!
|
||||||
|
.copyWith(color: scale.primaryScale.text),
|
||||||
|
selectedBoxDecoration: BoxDecoration(
|
||||||
|
color: scale.primaryScale.activeElementBackground),
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
padding: const EdgeInsets.fromLTRB(8, 4, 8, 4),
|
||||||
|
selectedPadding: const EdgeInsets.fromLTRB(8, 4, 8, 4)),
|
||||||
|
dropdownList: _logLevelDropdownItems,
|
||||||
|
)
|
||||||
],
|
],
|
||||||
title: Text(translate('developer.title')),
|
title: Text(translate('developer.title'),
|
||||||
|
style:
|
||||||
|
textTheme.bodyLarge!.copyWith(fontWeight: FontWeight.bold)),
|
||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
),
|
),
|
||||||
body: Column(children: [
|
body: SafeArea(
|
||||||
TerminalView(
|
child: Column(children: [
|
||||||
globalDebugTerminal,
|
Stack(alignment: AlignmentDirectional.center, children: [
|
||||||
textStyle: kDefaultTerminalStyle,
|
Image.asset('assets/images/ellet.png'),
|
||||||
controller: terminalController,
|
TerminalView(globalDebugTerminal,
|
||||||
//autofocus: true,
|
textStyle: kDefaultTerminalStyle,
|
||||||
//backgroundOpacity: 0.9,
|
controller: _terminalController,
|
||||||
onSecondaryTapDown: (details, offset) async {
|
//autofocus: true,
|
||||||
|
backgroundOpacity: _showEllet ? 0.75 : 1.0,
|
||||||
|
onSecondaryTapDown: (details, offset) async {
|
||||||
await copySelection(context);
|
await copySelection(context);
|
||||||
},
|
})
|
||||||
).expanded(),
|
]).expanded(),
|
||||||
TextField(
|
TextField(
|
||||||
controller: _debugCommandController,
|
controller: _debugCommandController,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
@ -167,24 +225,32 @@ class DeveloperPageState extends ConsumerState<DeveloperPage> {
|
|||||||
hintText: translate('developer.command'),
|
hintText: translate('developer.command'),
|
||||||
suffixIcon: IconButton(
|
suffixIcon: IconButton(
|
||||||
icon: const Icon(Icons.send),
|
icon: const Icon(Icons.send),
|
||||||
onPressed: () async {
|
onPressed: _debugCommandController.text.isEmpty
|
||||||
final debugCommand = _debugCommandController.text;
|
? null
|
||||||
_debugCommandController.clear();
|
: () async {
|
||||||
await _sendDebugCommand(debugCommand);
|
final debugCommand = _debugCommandController.text;
|
||||||
},
|
_debugCommandController.clear();
|
||||||
|
await _sendDebugCommand(debugCommand);
|
||||||
|
},
|
||||||
)),
|
)),
|
||||||
|
onChanged: (_) {
|
||||||
|
setState(() => {});
|
||||||
|
},
|
||||||
onSubmitted: (debugCommand) async {
|
onSubmitted: (debugCommand) async {
|
||||||
_debugCommandController.clear();
|
_debugCommandController.clear();
|
||||||
await _sendDebugCommand(debugCommand);
|
await _sendDebugCommand(debugCommand);
|
||||||
},
|
},
|
||||||
).paddingAll(4)
|
).paddingAll(4)
|
||||||
]));
|
])));
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||||
super.debugFillProperties(properties);
|
super.debugFillProperties(properties);
|
||||||
properties.add(DiagnosticsProperty<TerminalController>(
|
properties
|
||||||
'terminalController', terminalController));
|
..add(DiagnosticsProperty<TerminalController>(
|
||||||
|
'terminalController', _terminalController))
|
||||||
|
..add(
|
||||||
|
DiagnosticsProperty<LogLevel>('logLevelDropDown', _logLevelDropDown));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -153,7 +153,6 @@ class NewAccountPageState extends ConsumerState<NewAccountPage> {
|
|||||||
body: _newAccountForm(
|
body: _newAccountForm(
|
||||||
context,
|
context,
|
||||||
onSubmit: (formKey) async {
|
onSubmit: (formKey) async {
|
||||||
debugPrint(_formKey.currentState?.value.toString());
|
|
||||||
FocusScope.of(context).unfocus();
|
FocusScope.of(context).unfocus();
|
||||||
try {
|
try {
|
||||||
await createAccount();
|
await createAccount();
|
||||||
|
@ -92,6 +92,8 @@ class RouterNotifier extends _$RouterNotifier implements Listenable {
|
|||||||
case '/home/settings':
|
case '/home/settings':
|
||||||
case '/new_account/settings':
|
case '/new_account/settings':
|
||||||
return null;
|
return null;
|
||||||
|
case '/developer':
|
||||||
|
return null;
|
||||||
default:
|
default:
|
||||||
return hasAnyAccount ? null : '/new_account';
|
return hasAnyAccount ? null : '/new_account';
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ import 'dart:io' show Platform;
|
|||||||
|
|
||||||
import 'package:ansicolor/ansicolor.dart';
|
import 'package:ansicolor/ansicolor.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter_translate/flutter_translate.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:loggy/loggy.dart';
|
import 'package:loggy/loggy.dart';
|
||||||
|
|
||||||
@ -54,13 +55,37 @@ final DateFormat _dateFormatter = DateFormat('HH:mm:ss.SSS');
|
|||||||
extension PrettyPrintLogRecord on LogRecord {
|
extension PrettyPrintLogRecord on LogRecord {
|
||||||
String pretty() {
|
String pretty() {
|
||||||
final tm = _dateFormatter.format(time.toLocal());
|
final tm = _dateFormatter.format(time.toLocal());
|
||||||
final lev = logEmoji(level);
|
final lev = logLevelEmoji(level);
|
||||||
final lstr = wrapWithLogColor(level, tm);
|
final lstr = wrapWithLogColor(level, tm);
|
||||||
return '$lstr $lev $message';
|
return '$lstr $lev $message';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String logEmoji(LogLevel logLevel) {
|
List<LogLevel> logLevels = [
|
||||||
|
LogLevel.error,
|
||||||
|
LogLevel.warning,
|
||||||
|
LogLevel.info,
|
||||||
|
LogLevel.debug,
|
||||||
|
traceLevel,
|
||||||
|
];
|
||||||
|
|
||||||
|
String logLevelName(LogLevel logLevel) {
|
||||||
|
switch (logLevel) {
|
||||||
|
case traceLevel:
|
||||||
|
return translate('log.trace');
|
||||||
|
case LogLevel.debug:
|
||||||
|
return translate('log.debug');
|
||||||
|
case LogLevel.info:
|
||||||
|
return translate('log.info');
|
||||||
|
case LogLevel.warning:
|
||||||
|
return translate('log.warning');
|
||||||
|
case LogLevel.error:
|
||||||
|
return translate('log.error');
|
||||||
|
}
|
||||||
|
return '???';
|
||||||
|
}
|
||||||
|
|
||||||
|
String logLevelEmoji(LogLevel logLevel) {
|
||||||
switch (logLevel) {
|
switch (logLevel) {
|
||||||
case traceLevel:
|
case traceLevel:
|
||||||
return '👾';
|
return '👾';
|
||||||
@ -69,7 +94,7 @@ String logEmoji(LogLevel logLevel) {
|
|||||||
case LogLevel.info:
|
case LogLevel.info:
|
||||||
return '💡';
|
return '💡';
|
||||||
case LogLevel.warning:
|
case LogLevel.warning:
|
||||||
return '⚠️';
|
return '🍋';
|
||||||
case LogLevel.error:
|
case LogLevel.error:
|
||||||
return '🛑';
|
return '🛑';
|
||||||
}
|
}
|
||||||
|
@ -337,6 +337,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.1"
|
version: "3.1.1"
|
||||||
|
cool_dropdown:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: cool_dropdown
|
||||||
|
sha256: "24400f57740b4779407586121e014bef241699ad2a52c506a7e1e7616cb68653"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.0"
|
||||||
cross_file:
|
cross_file:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -19,6 +19,7 @@ dependencies:
|
|||||||
charcode: ^1.3.1
|
charcode: ^1.3.1
|
||||||
circular_profile_avatar: ^2.0.5
|
circular_profile_avatar: ^2.0.5
|
||||||
circular_reveal_animation: ^2.0.1
|
circular_reveal_animation: ^2.0.1
|
||||||
|
cool_dropdown: ^2.1.0
|
||||||
cupertino_icons: ^1.0.2
|
cupertino_icons: ^1.0.2
|
||||||
equatable: ^2.0.5
|
equatable: ^2.0.5
|
||||||
fast_immutable_collections: ^9.1.5
|
fast_immutable_collections: ^9.1.5
|
||||||
@ -119,6 +120,7 @@ flutter:
|
|||||||
- assets/images/icon.svg
|
- assets/images/icon.svg
|
||||||
- assets/images/title.svg
|
- assets/images/title.svg
|
||||||
- assets/images/vlogo.svg
|
- assets/images/vlogo.svg
|
||||||
|
- assets/images/ellet.png
|
||||||
# Fonts
|
# Fonts
|
||||||
fonts:
|
fonts:
|
||||||
- family: Source Code Pro
|
- family: Source Code Pro
|
||||||
|
Loading…
x
Reference in New Issue
Block a user