Compare commits
2 Commits
0df5dc03fa
...
a1701e216a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a1701e216a | ||
|
|
83eba7ff54 |
@ -41,6 +41,12 @@ class TranslationInputAndResourceContainer extends StatefulWidget {
|
|||||||
class _TranslationInputAndResourceContainerState
|
class _TranslationInputAndResourceContainerState
|
||||||
extends State<TranslationInputAndResourceContainer> {
|
extends State<TranslationInputAndResourceContainer> {
|
||||||
late Map<String, QuillController> _controllers;
|
late Map<String, QuillController> _controllers;
|
||||||
|
|
||||||
|
/// Meme piege que dans `translation_input_container` : sans `focusNode` ni
|
||||||
|
/// `scrollController`, `QuillEditor.basic` en cree de neufs a chaque
|
||||||
|
/// construction et le champ perd le curseur des qu'un rebuild survient.
|
||||||
|
late Map<String, FocusNode> _focusNodes;
|
||||||
|
late Map<String, ScrollController> _editorScrollControllers;
|
||||||
int _selected = 0;
|
int _selected = 0;
|
||||||
bool _isEnforcingLimit = false;
|
bool _isEnforcingLimit = false;
|
||||||
bool _isTranslating = false;
|
bool _isTranslating = false;
|
||||||
@ -50,6 +56,14 @@ class _TranslationInputAndResourceContainerState
|
|||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_controllers = _buildControllers();
|
_controllers = _buildControllers();
|
||||||
|
_focusNodes = {
|
||||||
|
for (final translation in widget.newValues)
|
||||||
|
translation.language!: FocusNode(),
|
||||||
|
};
|
||||||
|
_editorScrollControllers = {
|
||||||
|
for (final translation in widget.newValues)
|
||||||
|
translation.language!: ScrollController(),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static const _emptyDelta = [{'insert': '\n'}];
|
static const _emptyDelta = [{'insert': '\n'}];
|
||||||
@ -172,6 +186,12 @@ class _TranslationInputAndResourceContainerState
|
|||||||
for (final c in _controllers.values) {
|
for (final c in _controllers.values) {
|
||||||
c.dispose();
|
c.dispose();
|
||||||
}
|
}
|
||||||
|
for (final node in _focusNodes.values) {
|
||||||
|
node.dispose();
|
||||||
|
}
|
||||||
|
for (final c in _editorScrollControllers.values) {
|
||||||
|
c.dispose();
|
||||||
|
}
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -396,6 +416,8 @@ class _TranslationInputAndResourceContainerState
|
|||||||
),
|
),
|
||||||
child: QuillEditor.basic(
|
child: QuillEditor.basic(
|
||||||
controller: controller,
|
controller: controller,
|
||||||
|
focusNode: _focusNodes[lang],
|
||||||
|
scrollController: _editorScrollControllers[lang],
|
||||||
config: const QuillEditorConfig(
|
config: const QuillEditorConfig(
|
||||||
scrollable: true,
|
scrollable: true,
|
||||||
expands: false,
|
expands: false,
|
||||||
|
|||||||
@ -47,6 +47,13 @@ class TranslationInputContainer extends StatefulWidget {
|
|||||||
class _TranslationInputContainerState extends State<TranslationInputContainer> {
|
class _TranslationInputContainerState extends State<TranslationInputContainer> {
|
||||||
late Map<String, QuillController> _controllers;
|
late Map<String, QuillController> _controllers;
|
||||||
late Map<String, TextEditingController> _textControllers;
|
late Map<String, TextEditingController> _textControllers;
|
||||||
|
|
||||||
|
/// `QuillEditor.basic` fabrique un `FocusNode` et un `ScrollController` neufs
|
||||||
|
/// a chaque construction quand on ne lui en passe pas : le curseur sautait de
|
||||||
|
/// l'editeur des la premiere frappe, puisque chaque frappe reconstruit ce
|
||||||
|
/// widget (compteur de caracteres, pastilles du rail).
|
||||||
|
late Map<String, FocusNode> _focusNodes;
|
||||||
|
late Map<String, ScrollController> _editorScrollControllers;
|
||||||
int _selected = 0;
|
int _selected = 0;
|
||||||
bool _isEnforcingLimit = false;
|
bool _isEnforcingLimit = false;
|
||||||
bool _isTranslating = false;
|
bool _isTranslating = false;
|
||||||
@ -57,6 +64,14 @@ class _TranslationInputContainerState extends State<TranslationInputContainer> {
|
|||||||
super.initState();
|
super.initState();
|
||||||
_controllers = widget.isHTML ? _buildControllers() : {};
|
_controllers = widget.isHTML ? _buildControllers() : {};
|
||||||
_textControllers = widget.isHTML ? {} : _buildTextControllers();
|
_textControllers = widget.isHTML ? {} : _buildTextControllers();
|
||||||
|
_focusNodes = {
|
||||||
|
for (final translation in widget.newValues)
|
||||||
|
translation.language!: FocusNode(),
|
||||||
|
};
|
||||||
|
_editorScrollControllers = {
|
||||||
|
for (final translation in widget.newValues)
|
||||||
|
translation.language!: ScrollController(),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, TextEditingController> _buildTextControllers() {
|
Map<String, TextEditingController> _buildTextControllers() {
|
||||||
@ -207,6 +222,12 @@ class _TranslationInputContainerState extends State<TranslationInputContainer> {
|
|||||||
for (final c in _textControllers.values) {
|
for (final c in _textControllers.values) {
|
||||||
c.dispose();
|
c.dispose();
|
||||||
}
|
}
|
||||||
|
for (final node in _focusNodes.values) {
|
||||||
|
node.dispose();
|
||||||
|
}
|
||||||
|
for (final c in _editorScrollControllers.values) {
|
||||||
|
c.dispose();
|
||||||
|
}
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -467,6 +488,8 @@ class _TranslationInputContainerState extends State<TranslationInputContainer> {
|
|||||||
),
|
),
|
||||||
child: QuillEditor.basic(
|
child: QuillEditor.basic(
|
||||||
controller: controller,
|
controller: controller,
|
||||||
|
focusNode: _focusNodes[lang],
|
||||||
|
scrollController: _editorScrollControllers[lang],
|
||||||
config: const QuillEditorConfig(
|
config: const QuillEditorConfig(
|
||||||
scrollable: true,
|
scrollable: true,
|
||||||
expands: false,
|
expands: false,
|
||||||
|
|||||||
@ -492,9 +492,9 @@ class _ResourceBodyGridState extends State<ResourceBodyGrid> {
|
|||||||
TextButton.icon(
|
TextButton.icon(
|
||||||
icon: const Icon(Icons.download, size: 16),
|
icon: const Icon(Icons.download, size: 16),
|
||||||
label: Text(l.download, style: const TextStyle(fontSize: 12.5)),
|
label: Text(l.download, style: const TextStyle(fontSize: 12.5)),
|
||||||
onPressed: () {
|
onPressed: () async {
|
||||||
for (final resource in selected) {
|
for (final resource in selected) {
|
||||||
downloadResource(resource);
|
await downloadResource(resource);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
@ -8,12 +8,29 @@ import 'resource_formatting.dart';
|
|||||||
///
|
///
|
||||||
/// L'ancienne modale forçait `'${label}.json'` quel que soit le type : un PNG
|
/// L'ancienne modale forçait `'${label}.json'` quel que soit le type : un PNG
|
||||||
/// arrivait en `mon-image.json` et ne s'ouvrait nulle part sans renommage.
|
/// arrivait en `mon-image.json` et ne s'ouvrait nulle part sans renommage.
|
||||||
void downloadResource(ResourceDTO resource) {
|
///
|
||||||
|
/// Le fichier est d'abord récupéré en blob : l'attribut `download` d'un lien est
|
||||||
|
/// ignoré quand la cible est sur une autre origine (Firebase Storage ici), et le
|
||||||
|
/// navigateur se contentait d'ouvrir la ressource dans un onglet. Si la requête
|
||||||
|
/// échoue (CORS, réseau), on retombe sur l'ouverture dans un nouvel onglet.
|
||||||
|
Future<void> downloadResource(ResourceDTO resource) async {
|
||||||
final url = resource.url;
|
final url = resource.url;
|
||||||
if (url == null) return;
|
if (url == null) return;
|
||||||
|
|
||||||
final anchor = html.AnchorElement(href: url)
|
final fileName = downloadFileNameFor(resource);
|
||||||
..download = downloadFileNameFor(resource)
|
|
||||||
..target = '_blank';
|
try {
|
||||||
anchor.click();
|
final request = await html.HttpRequest.request(url, responseType: 'blob');
|
||||||
|
final blob = request.response as html.Blob;
|
||||||
|
final objectUrl = html.Url.createObjectUrlFromBlob(blob);
|
||||||
|
html.AnchorElement(href: objectUrl)
|
||||||
|
..download = fileName
|
||||||
|
..click();
|
||||||
|
html.Url.revokeObjectUrl(objectUrl);
|
||||||
|
} catch (_) {
|
||||||
|
html.AnchorElement(href: url)
|
||||||
|
..download = fileName
|
||||||
|
..target = '_blank'
|
||||||
|
..click();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user