feat: add unsplash images source

This commit is contained in:
2023-01-05 21:00:40 +03:00
parent 3379eed4a9
commit ac1b9b63a2
15 changed files with 212 additions and 18 deletions

12
.vscode/launch.json vendored
View File

@@ -15,6 +15,18 @@
"<node_internals>/**" "<node_internals>/**"
], ],
"type": "node" "type": "node"
},
{
"args": [
"server:cpp:test"
],
"name": "server:cpp:test",
"program": "${workspaceFolder}/node_modules/gulp/bin/gulp.js",
"request": "launch",
"skipFiles": [
"<node_internals>/**"
],
"type": "node"
} }
] ]
} }

View File

@@ -1,5 +1,10 @@
{ {
"haxe.enableDiagnostics": true, "haxe.executable": {
"path": "/home/shmyga/sdk/haxe/4.2.5/haxe",
"env": {
"HAXE_STD_PATH": "/home/shmyga/sdk/haxe/4.2.5/std",
},
},
"haxe.configurations": [ "haxe.configurations": [
["build/app/flash/haxe/debug.hxml"], ["build/app/flash/haxe/debug.hxml"],
], ],

View File

@@ -45,6 +45,8 @@ const config = new Project.Config({
libs: packageInfo.haxeDependencies, libs: packageInfo.haxeDependencies,
macros: [ macros: [
`CompilationOption.set('build','${dateformat(new Date(), 'yyyy-mm-dd HH:MM:ss')}')`, `CompilationOption.set('build','${dateformat(new Date(), 'yyyy-mm-dd HH:MM:ss')}')`,
`CompilationOption.set('UNSPLASH_KEY','${Config.UnsplashKey}')`,
`CompilationOption.set('PIXABAY_KEY','${Config.PixabayKey}')`,
], ],
flags: [ flags: [
'proto_debug', 'proto_debug',

View File

@@ -0,0 +1,62 @@
package ru.m.api;
import hw.net.JsonLoader;
import promhx.Promise;
typedef UnsplashImage = {
var id:String;
var width:Int;
var height:Int;
var urls:{
raw:String,
full:String,
regular:String,
small:String,
thumb:String,
};
}
typedef UnsplashResponse = {
var total: Int;
var total_pages: Int;
var results: Array<UnsplashImage>;
}
class UnsplashApi {
private var baseUrl:String = "https://api.unsplash.com";
private var key:String;
public function new(key:String) {
this.key = key;
}
private function buildQuery(queryMap:Map<String, Dynamic>):String {
return [for (k in queryMap.keys()) '${k}=${queryMap.get(k)}'].join("&");
}
private function buildRequest(queryMap:Map<String, Dynamic>):String {
queryMap.set("client_id", key);
var query = buildQuery(queryMap);
return '${baseUrl}/search/photos?${query}';
}
public function getPage(page:Int, perPage:Int, query:String):Promise<UnsplashResponse> {
return new JsonLoader<UnsplashResponse>()
.GET(buildRequest([
"per_page" => perPage,
"page" => page,
"order_by" => "relevant",
"orientation" => "landscape",
"query" => query,
]));
}
public function get(id:String):Promise<UnsplashImage> {
var queryMap = [
"client_id" => key,
];
var query = buildQuery(queryMap);
return new JsonLoader<UnsplashImage>()
.GET('${baseUrl}/photos/${id}?${query}');
}
}

View File

@@ -9,6 +9,7 @@ import ru.m.puzzlez.settings.Settings;
import ru.m.puzzlez.source.AssetImageSource; import ru.m.puzzlez.source.AssetImageSource;
import ru.m.puzzlez.source.FileImageSource; import ru.m.puzzlez.source.FileImageSource;
import ru.m.puzzlez.source.PixabayImageSource; import ru.m.puzzlez.source.PixabayImageSource;
import ru.m.puzzlez.source.UnsplashImageSource;
import ru.m.puzzlez.storage.GameStorage; import ru.m.puzzlez.storage.GameStorage;
import ru.m.puzzlez.view.PuzzlezAppView; import ru.m.puzzlez.view.PuzzlezAppView;
import ru.m.update.Updater; import ru.m.update.Updater;
@@ -26,6 +27,7 @@ class PuzzlezApp {
sourceBundle.register(new AssetImageSource()); sourceBundle.register(new AssetImageSource());
sourceBundle.register(new FileImageSource()); sourceBundle.register(new FileImageSource());
sourceBundle.register(new PixabayImageSource()); sourceBundle.register(new PixabayImageSource());
sourceBundle.register(new UnsplashImageSource());
L.push(new TraceLogger()); L.push(new TraceLogger());
updater = new Updater(Const.instance.VERSION, "https://shmyga.ru/repo/puzzlez/packages.json"); updater = new Updater(Const.instance.VERSION, "https://shmyga.ru/repo/puzzlez/packages.json");
var app = new App(); var app = new App();

View File

@@ -6,5 +6,5 @@ import ru.m.puzzlez.proto.game.ImageId;
interface ImageSource extends DataSource<ImageId> { interface ImageSource extends DataSource<ImageId> {
public var id(default, never):String; public var id(default, never):String;
public function load(id:String):Promise<ImageValue>; public function load(id:String, thumb:Bool = false):Promise<ImageValue>;
} }

View File

@@ -23,6 +23,10 @@ abstract ImageData(ImageId) from ImageId {
return new ImageData(value); return new ImageData(value);
} }
public function withThumb():ImageData {
return new ImageData(new ImageId().setSource(this.source).setId(this.id).setThumb(true));
}
private function extractImage(key:String, value:ImageValue):Promise<BitmapData> { private function extractImage(key:String, value:ImageValue):Promise<BitmapData> {
return switch value { return switch value {
case ImageValue.BITMAP(value): case ImageValue.BITMAP(value):
@@ -39,11 +43,14 @@ abstract ImageData(ImageId) from ImageId {
public function resolve():Promise<BitmapData> { public function resolve():Promise<BitmapData> {
var key = '${this.source}:${this.id}'; var key = '${this.source}:${this.id}';
if (this.thumb) {
key = '${key}:thumb';
}
if (!cache.exists(key)) { if (!cache.exists(key)) {
if (storageCache.exists(key)) { if (storageCache.exists(key)) {
cache.set(key, extractImage(key, ImageValue.BYTES(storageCache.get(key)))); cache.set(key, extractImage(key, ImageValue.BYTES(storageCache.get(key))));
} else { } else {
cache.set(key, sourceBundle.get(this.source).load(this.id).pipe(value -> extractImage(key, value))); cache.set(key, sourceBundle.get(this.source).load(this.id, this.thumb).pipe(value -> extractImage(key, value)));
} }
} }
return cache.get(key); return cache.get(key);

View File

@@ -36,7 +36,7 @@ class AssetImageSource implements ImageSource {
}); });
} }
public function load(id:String):Promise<ImageValue> { public function load(id:String, thumb:Bool = false):Promise<ImageValue> {
return Promise.promise(ImageValue.BITMAP(Assets.getBitmapData(id))); return Promise.promise(ImageValue.BITMAP(Assets.getBitmapData(id)));
} }
} }

View File

@@ -25,7 +25,7 @@ class FileImageSource implements ImageSource {
}); });
} }
public function load(id:String):Promise<ImageValue> { public function load(id:String, thumb:Bool = false):Promise<ImageValue> {
return storage.get(id).then(bytes -> ImageValue.BYTES(bytes)); return storage.get(id).then(bytes -> ImageValue.BYTES(bytes));
} }
} }

View File

@@ -11,17 +11,19 @@ class PixabayImageSource implements ImageSource {
public var id(default, never):String = "pixabay"; public var id(default, never):String = "pixabay";
private var api:PixabayApi; private var api:PixabayApi;
private static var imageUrlsCache:Map<Int, String> = new Map(); private static var imageUrlsCache:Map<String, String> = new Map();
public function new() { public function new() {
api = new PixabayApi("14915210-5eae157281211e0ad28bc8def"); var key:String = CompilationOption.get("PIXABAY_KEY");
api = new PixabayApi(key);
} }
public function getPage(page:Page):Promise<DataPage<ImageId>> { public function getPage(page:Page):Promise<DataPage<ImageId>> {
return this.api.getPage(page.index + 1, page.count, page.filter.get("category")).then((response:PixabayResponse) -> { return this.api.getPage(page.index + 1, page.count, page.filter.get("category"))
.then((response:PixabayResponse) -> {
var data:Array<ImageId> = []; var data:Array<ImageId> = [];
for (hit in response.hits) { for (hit in response.hits) {
imageUrlsCache.set(hit.id, hit.largeImageURL); imageUrlsCache.set('${hit.id}', hit.largeImageURL);
data.push(new ImageId().setSource(id).setId(Std.string(hit.id))); data.push(new ImageId().setSource(id).setId(Std.string(hit.id)));
} }
return { return {
@@ -32,14 +34,19 @@ class PixabayImageSource implements ImageSource {
}); });
} }
public function load(id:String):Promise<ImageValue> { public function load(id:String, thumb:Bool = false):Promise<ImageValue> {
var imageId = Std.parseInt(id); var imageId = Std.parseInt(id);
if (imageUrlsCache.exists(imageId)) { var key = id;
return Promise.promise(ImageValue.URL(imageUrlsCache.get(imageId))); if (thumb) {
key = '${key}:thumb';
}
if (imageUrlsCache.exists(key)) {
return Promise.promise(ImageValue.URL(imageUrlsCache.get(key)));
} else { } else {
return api.get(imageId).then((data:PixabayImage) -> { return api.get(imageId).then((data:PixabayImage) -> {
imageUrlsCache.set(imageId, data.largeImageURL); var url = thumb ? data.previewURL : data.largeImageURL;
return ImageValue.URL(data.largeImageURL); imageUrlsCache.set(key, url);
return ImageValue.URL(url);
}); });
} }
} }

View File

@@ -0,0 +1,82 @@
package ru.m.puzzlez.source;
import promhx.Promise;
import ru.m.api.UnsplashApi;
import ru.m.data.DataSource;
import ru.m.puzzlez.core.ImageSource;
import ru.m.puzzlez.core.ImageValue;
import ru.m.puzzlez.proto.game.ImageId;
typedef Resolver = (id:String, thumb: Bool) -> Promise<String>;
class ImageResolver {
private var cache: Map<String, String>;
private var resolver:Resolver;
public function new(resolver:Resolver) {
this.cache = new Map();
this.resolver = resolver;
}
private function buildKey(id:String, thumb:Bool = false):String {
var key = id;
if (thumb) {
key = '${key}:thumb';
}
return key;
}
public function setValue(id:String, url:String):Void {
cache.set(id, url);
}
public function resolve(id:String, thumb:Bool = false) {
var key = buildKey(id, thumb);
if (cache.exists(key)) {
return Promise.promise(ImageValue.URL(cache.get(key)));
} else {
return resolver(id, thumb).then(url -> {
cache.set(key, url);
return ImageValue.URL(url);
});
}
}
}
class UnsplashImageSource implements ImageSource {
public var id(default, never):String = "unsplash";
private var api:UnsplashApi;
private static var resolver:ImageResolver;
public function new() {
var key:String = CompilationOption.get("UNSPLASH_KEY");
api = new UnsplashApi(key);
resolver = new ImageResolver((imageId, thumb) -> {
return api.get(imageId).then(data -> thumb ? data.urls.thumb : data.urls.regular);
});
}
public function getPage(page:Page):Promise<DataPage<ImageId>> {
return this.api.getPage(page.index + 1, page.count, page.filter.get("category"))
.then((response:UnsplashResponse) -> {
var data:Array<ImageId> = [];
for (image in response.results) {
resolver.setValue(image.id, image.urls.regular);
data.push(new ImageId().setSource(id).setId(image.id));
}
return {
page: page,
data: data,
total: response.total,
}
});
}
public function load(id:String, thumb:Bool = false):Promise<ImageValue> {
return resolver.resolve(id, thumb);
}
}

View File

@@ -7,7 +7,7 @@ views:
- id: images - id: images
$type: ru.m.view.DataList $type: ru.m.view.DataList
geometry.stretch: true geometry.stretch: true
list.factory: ~ru.m.puzzlez.view.common.ImageIdView.factory list.factory: ~ru.m.puzzlez.view.common.ImageIdView.factoryThumb
+list.onDataSelect: ~start +list.onDataSelect: ~start
- $type: hw.view.form.ButtonView - $type: hw.view.form.ButtonView
text: Back text: Back

View File

@@ -36,7 +36,7 @@ import ru.m.update.Updater;
// data.push({title: "Assets", sourceId: "asset"}); // data.push({title: "Assets", sourceId: "asset"});
data.push(fileSource); data.push(fileSource);
for (type in AbstractEnumTools.getValues(PixabayCategory)) { for (type in AbstractEnumTools.getValues(PixabayCategory)) {
data.push({title: type, sourceId: "pixabay", filter: ["category" => type]}); data.push({title: type, sourceId: "unsplash", filter: ["category" => type]});
} }
sources.data = data; sources.data = data;
} }

View File

@@ -9,19 +9,29 @@ import ru.m.view.LoadingWrapper;
@:template class ImageIdView extends GroupView { @:template class ImageIdView extends GroupView {
public var thumb:Bool = false;
public var imageId(default, set):ImageId; public var imageId(default, set):ImageId;
private function set_imageId(value:ImageId):ImageId { private function set_imageId(value:ImageId):ImageId {
imageId = value; imageId = value;
loading.promise = ImageData.fromImageId(imageId).resolve().then(data -> this.imageView.image = data); var imageData = ImageData
.fromImageId(imageId);
if (thumb) {
imageData = imageData.withThumb();
}
loading.promise = imageData
.resolve()
.then(data -> this.imageView.image = data);
return imageId; return imageId;
} }
@:view("image") var imageView:ImageView; @:view("image") var imageView:ImageView;
private var loading:LoadingWrapper; private var loading:LoadingWrapper;
public function new(?imageId:ImageId, ?style: StyleId) { public function new(?imageId:ImageId, ?style: StyleId, ?thumb: Bool = false) {
super(); super();
this.style = style; this.style = style;
this.thumb = thumb;
loading = new LoadingWrapper(this); loading = new LoadingWrapper(this);
if (imageId != null) { if (imageId != null) {
this.imageId = imageId; this.imageId = imageId;
@@ -31,4 +41,8 @@ import ru.m.view.LoadingWrapper;
public static function factory(index:Int, value:ImageId):ImageIdView { public static function factory(index:Int, value:ImageId):ImageIdView {
return new ImageIdView(value, "view"); return new ImageIdView(value, "view");
} }
public static function factoryThumb(index:Int, value:ImageId):ImageIdView {
return new ImageIdView(value, "view", true);
}
} }

View File

@@ -7,6 +7,7 @@ package ru.m.puzzlez.proto.game;
message ImageId { message ImageId {
string source = 1; string source = 1;
string id = 2; string id = 2;
bool thumb = 3;
} }
enum BoundType { enum BoundType {