feat: add unsplash images source
This commit is contained in:
12
.vscode/launch.json
vendored
12
.vscode/launch.json
vendored
@@ -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"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
7
.vscode/settings.json
vendored
7
.vscode/settings.json
vendored
@@ -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"],
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
62
src/app/haxe/ru/m/api/UnsplashApi.hx
Normal file
62
src/app/haxe/ru/m/api/UnsplashApi.hx
Normal 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}');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
|||||||
@@ -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>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
82
src/app/haxe/ru/m/puzzlez/source/UnsplashImageSource.hx
Normal file
82
src/app/haxe/ru/m/puzzlez/source/UnsplashImageSource.hx
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user