Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Latest Artifacts structure for Ensemble and Remote Provider #1888

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,11 @@ class AppModel {
FirebaseFirestore db = FirebaseFirestore.instanceFor(app: app);
return db.collection('apps').doc(appId).collection('artifacts');
}
CollectionReference<Map<String, dynamic>> _getInternalArtifacts() {
final app = Ensemble().ensembleFirebaseApp!;
FirebaseFirestore db = FirebaseFirestore.instanceFor(app: app);
return db.collection('apps').doc(appId).collection('internal_artifacts');
}
/// App bundle for now only expects the theme, but we'll use this
/// opportunity to also cache the home page
Future<AppBundle> getAppBundle() async {
Expand All @@ -317,34 +322,49 @@ class AppModel {
Map code = {};
Map output = {};
Map widgets = {};
QuerySnapshot<Map<String, dynamic>> snapshot = await _getInternalArtifacts()
.where('isArchived', isEqualTo: false)
.get();
for (var doc in snapshot.docs) {
var type = doc.data()['type'];
var name = doc.data()['name'];
var content = doc.data()['content'];
if (type == ArtifactType.internal_widget.name) {
YamlMap yamlContent = await loadYaml(content);
widgets[name] = yamlContent["Widget"];
}
if (type == ArtifactType.internal_script.name) {
code[name] = content;
}
}

YamlMap? resources = artifactCache[ArtifactType.resources.name];
resources?.forEach((key, value) {
if (key == ResourceArtifactEntry.Widgets.name) {
if (value is YamlMap) {
widgets.addAll(value.value);
}
} else if (key == ResourceArtifactEntry.Scripts.name) {
if (value is YamlMap) {
//code will be in the format -
// Scripts:
// #apiUtils is the name of the code artifact
// apiUtils: |-
// function callAPI(name,payload) {
// ensemble.invokeAPI(name, payload);
// }
// #common is the name of the code artifact
// common: |-
// function sayHello() {
// return 'hello';
// }
code.addAll(value.value);
}
} else {
// copy over non-Widgets
output[key] = value;
}
});
// YamlMap? resources = artifactCache[ArtifactType.resources.name];
// resources?.forEach((key, value) {
// if (key == ResourceArtifactEntry.Widgets.name) {
// if (value is YamlMap) {
// widgets.addAll(value.value);
// }
// } else if (key == ResourceArtifactEntry.Scripts.name) {
// if (value is YamlMap) {
// //code will be in the format -
// // Scripts:
// // #apiUtils is the name of the code artifact
// // apiUtils: |-
// // function callAPI(name,payload) {
// // ensemble.invokeAPI(name, payload);
// // }
// // #common is the name of the code artifact
// // common: |-
// // function sayHello() {
// // return 'hello';
// // }
// code.addAll(value.value);
// }
// } else {
// // copy over non-Widgets
// output[key] = value;
// }
// });

// go through each imported App to include their widgets with proper namespace
for (String appId in importCache.keys) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ enum ArtifactType {
i18n,
resources, // global widgets/codes/APIs/
config, // app config
secrets
secrets,
internal_script,
internal_widget
}

// the root entries of the Resource artifact
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import 'dart:async';
import 'dart:convert';
import 'dart:ui';

import 'package:ensemble/ensemble.dart';
import 'package:ensemble/framework/definition_providers/provider.dart';
import 'package:ensemble/framework/widget/screen.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_i18n/flutter_i18n.dart';
import 'package:flutter_i18n/loaders/decoders/yaml_decode_strategy.dart';
import 'package:http/http.dart' as http;
Expand Down Expand Up @@ -43,7 +46,7 @@ class RemoteDefinitionProvider extends FileDefinitionProvider {
completer.complete(res);
return completer.future;
}
http.Response response = await http.get(Uri.parse('$path$screen.yaml'));
http.Response response = await http.get(Uri.parse('${path}screens/${screen}.yaml'));
if (response.statusCode == 200) {
dynamic res = ScreenDefinition(loadYaml(response.body));
if (cacheEnabled) {
Expand All @@ -58,16 +61,30 @@ class RemoteDefinitionProvider extends FileDefinitionProvider {

@override
Future<AppBundle> getAppBundle({bool? bypassCache = false}) async {
final env = await _readYamlFile('appConfig.yaml');
dynamic env = await _readFileAsString('config/appConfig.json');
if(env == null){
env = await _readYamlFile('appConfig.yaml');
}
else{
env = json.decode(env);
}
if (env != null) {
appConfig = UserAppConfig(
baseUrl: path,
envVariables: env as Map<String, dynamic>,
);
}
YamlMap? theme = await _readYamlFile('theme.yaml');
if(theme == null) {
theme = await _readYamlFile('theme.ensemble');
}
Map<dynamic, dynamic>? resources = await getCombinedAppBundle();
if (resources == null) {
resources = await _readYamlFile('resources.ensemble');
}
return AppBundle(
theme: await _readYamlFile('theme.ensemble'),
resources: await _readYamlFile('resources.ensemble'));
theme: theme,
resources: resources);
}

Future<YamlMap?> _readYamlFile(String file) async {
Expand All @@ -82,6 +99,83 @@ class RemoteDefinitionProvider extends FileDefinitionProvider {
return null;
}

Future<String?> _readFileAsString(String file) async {
try {
http.Response response = await http.get(Uri.parse(path + file));
if (response.statusCode == 200) {
return response.body;
}
} catch (error) {
// ignore
}
return null;
}

Future<Map?> getCombinedAppBundle() async {
Map code = {};
Map output = {};
Map widgets = {};

try {
// Get the manifest content
final manifestContent =
await http.get(Uri.parse(path + '.manifest.json'));
final Map<String, dynamic> manifestMap = json.decode(manifestContent.body);

// Process App Widgets
try {
if (manifestMap['widgets'] != null) {
final List<Map<String, dynamic>> widgetsList =
List<Map<String, dynamic>>.from(manifestMap['widgets']);

for (var widgetItem in widgetsList) {
try {
// Load the widget content in YamlMap
final widgetContent =
await _readYamlFile("widgets/${widgetItem["name"]}.yaml");
if (widgetContent is YamlMap) {
widgets[widgetItem["name"]] = widgetContent["Widget"];
} else {
debugPrint('Content in ${widgetItem["name"]} is not a YamlMap');
}
} catch (e) {
// ignore error
}
}
}
} catch (e) {
debugPrint('Error processing widgets: $e');
}

// Process App Scripts
try {
if (manifestMap['scripts'] != null) {
final List<Map<String, dynamic>> scriptsList =
List<Map<String, dynamic>>.from(manifestMap['scripts']);

for (var script in scriptsList) {
try {
// Load the script content in string
final scriptContent = await http.get(Uri.parse("${path}scripts/${script["name"]}.js"));
code[script["name"]] = scriptContent.body;
} catch (e) {
// ignore error
}
}
}
} catch (e) {
debugPrint('Error processing scripts: $e');
}

output[ResourceArtifactEntry.Widgets.name] = widgets;
output[ResourceArtifactEntry.Scripts.name] = code;

return output;
} catch (e) {
return null;
}
}

@override
UserAppConfig? getAppConfig() {
return appConfig;
Expand Down
18 changes: 18 additions & 0 deletions modules/ensemble/lib/framework/model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,13 @@ class BackgroundImage {
if (Utils.isUrl(_source)) {
imageProvider = NetworkImage(_source);
} else {
final localSource = Utils.getLocalAssetFullPath(_source);
if(Utils.isUrl(localSource)){
imageProvider = NetworkImage(localSource);
}
else{
imageProvider = AssetImage(Utils.getLocalAssetFullPath(_source));
}
}
return DecorationImage(
image: imageProvider,
Expand All @@ -56,13 +62,25 @@ class BackgroundImage {
fallbackWidget != null ? (_, __, ___) => fallbackWidget : null,
);
} else {
final localSource = Utils.getLocalAssetFullPath(_source);
if(Utils.isUrl(localSource)){
return CachedNetworkImage(
imageUrl: localSource,
fit: _fit,
alignment: _alignment,
errorWidget:
fallbackWidget != null ? (_, __, ___) => fallbackWidget : null,
);
}
else{
return Image.asset(
Utils.getLocalAssetFullPath(_source),
fit: _fit,
alignment: _alignment,
errorBuilder:
fallbackWidget != null ? (_, __, ___) => fallbackWidget : null,
);
}
}
}
}
Expand Down
11 changes: 11 additions & 0 deletions modules/ensemble/lib/framework/secrets.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import 'package:ensemble/framework/storage_manager.dart';
import 'package:ensemble_ts_interpreter/invokables/invokable.dart';
import 'package:flutter/services.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:http/http.dart' as http;

import '../ensemble.dart';

Expand Down Expand Up @@ -39,6 +40,16 @@ class SecretsStore with Invokable {
secrets![key] = value;
});
}
else if(provider == 'remote') {
String path =
EnsembleConfigService.config["definitions"]?['remote']?["path"];
final secretsString =
await http.get(Uri.parse('${path}/config/secrets.json'));
final Map<String, dynamic> appSecretsMap = json.decode(secretsString.body);
appSecretsMap["secrets"].forEach((key, value) {
secrets![key] = value;
});
}
} catch (_) {}
// add secrets from env
try {
Expand Down
26 changes: 22 additions & 4 deletions modules/ensemble/lib/framework/widget/image.dart
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ class Image extends StatelessWidget {

@override
Widget build(BuildContext context) {
return source.startsWith('https://') || source.startsWith('http://')
? CachedNetworkImage(
if (Utils.isUrl(source)){
return CachedNetworkImage(
imageUrl: source,
width: width,
height: height,
Expand All @@ -46,8 +46,24 @@ class Image extends StatelessWidget {
errorWidget: errorBuilder != null
? (context, url, error) => errorBuilder!(error.toString())
: null,
cacheManager: networkCacheManager)
: flutter.Image.asset(
cacheManager: networkCacheManager);
}
else{
final localSource = Utils.getLocalAssetFullPath(source);
if(Utils.isUrl(localSource)){
return CachedNetworkImage(
imageUrl: localSource,
width: width,
height: height,
fit: fit,
placeholder: placeholderBuilder,
errorWidget: errorBuilder != null
? (context, url, error) => errorBuilder!(error.toString())
: null,
cacheManager: networkCacheManager);
}
else{
return flutter.Image.asset(
Utils.getLocalAssetFullPath(source),
width: width,
height: height,
Expand All @@ -57,5 +73,7 @@ class Image extends StatelessWidget {
errorBuilder!(error.toString())
: null,
);
}
}
}
}
22 changes: 16 additions & 6 deletions modules/ensemble/lib/util/utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1003,12 +1003,22 @@ class Utils {
/// prefix the asset with the app root directory (i.e. ensemble/apps/<app-name>/assets/), plus
/// stripping any unnecessary query params (e.g. anything after the first ?)
static String getLocalAssetFullPath(String asset) {
String provider = EnsembleConfigService.config["definitions"]?['from'];
if(provider == 'local') {
String path = EnsembleConfigService.config["definitions"]?['local']?["path"];
return '${path}/assets/${stripQueryParamsFromAsset(asset)}';
}
else{
try {
String provider = EnsembleConfigService.config["definitions"]?['from'];
if (provider == 'local') {
String path =
EnsembleConfigService.config["definitions"]?['local']?["path"];
return '${path}/assets/${stripQueryParamsFromAsset(asset)}';
}
else if (provider == 'remote'){
String path =
EnsembleConfigService.config["definitions"]?['remote']?["path"];
return '${path}/assets/${stripQueryParamsFromAsset(asset)}';
}
else {
return 'ensemble/assets/${stripQueryParamsFromAsset(asset)}';
}
} catch (e) {
return 'ensemble/assets/${stripQueryParamsFromAsset(asset)}';
}
}
Expand Down
Loading