diff --git a/.vscode/launch.json b/.vscode/launch.json index 5444386..581850f 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -15,6 +15,18 @@ "/**" ], "type": "node" + }, + { + "args": [ + "server:cpp:test" + ], + "name": "server:cpp:test", + "program": "${workspaceFolder}/node_modules/gulp/bin/gulp.js", + "request": "launch", + "skipFiles": [ + "/**" + ], + "type": "node" } ] } \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index ed54986..e0d34e9 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -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": [ ["build/app/flash/haxe/debug.hxml"], ], diff --git a/gulpfile.js b/gulpfile.js index ff03917..c365301 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -45,6 +45,8 @@ const config = new Project.Config({ libs: packageInfo.haxeDependencies, macros: [ `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: [ 'proto_debug', diff --git a/src/app/haxe/ru/m/api/UnsplashApi.hx b/src/app/haxe/ru/m/api/UnsplashApi.hx new file mode 100644 index 0000000..58ae4bb --- /dev/null +++ b/src/app/haxe/ru/m/api/UnsplashApi.hx @@ -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; +} + +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 { + return [for (k in queryMap.keys()) '${k}=${queryMap.get(k)}'].join("&"); + } + + private function buildRequest(queryMap:Map):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 { + return new JsonLoader() + .GET(buildRequest([ + "per_page" => perPage, + "page" => page, + "order_by" => "relevant", + "orientation" => "landscape", + "query" => query, + ])); + } + + public function get(id:String):Promise { + var queryMap = [ + "client_id" => key, + ]; + var query = buildQuery(queryMap); + return new JsonLoader() + .GET('${baseUrl}/photos/${id}?${query}'); + } +} diff --git a/src/app/haxe/ru/m/puzzlez/PuzzlezApp.hx b/src/app/haxe/ru/m/puzzlez/PuzzlezApp.hx index ceaf41f..77e8189 100644 --- a/src/app/haxe/ru/m/puzzlez/PuzzlezApp.hx +++ b/src/app/haxe/ru/m/puzzlez/PuzzlezApp.hx @@ -9,6 +9,7 @@ import ru.m.puzzlez.settings.Settings; import ru.m.puzzlez.source.AssetImageSource; import ru.m.puzzlez.source.FileImageSource; import ru.m.puzzlez.source.PixabayImageSource; +import ru.m.puzzlez.source.UnsplashImageSource; import ru.m.puzzlez.storage.GameStorage; import ru.m.puzzlez.view.PuzzlezAppView; import ru.m.update.Updater; @@ -26,6 +27,7 @@ class PuzzlezApp { sourceBundle.register(new AssetImageSource()); sourceBundle.register(new FileImageSource()); sourceBundle.register(new PixabayImageSource()); + sourceBundle.register(new UnsplashImageSource()); L.push(new TraceLogger()); updater = new Updater(Const.instance.VERSION, "https://shmyga.ru/repo/puzzlez/packages.json"); var app = new App(); diff --git a/src/app/haxe/ru/m/puzzlez/core/ImageSource.hx b/src/app/haxe/ru/m/puzzlez/core/ImageSource.hx index 814c7fb..1b82921 100644 --- a/src/app/haxe/ru/m/puzzlez/core/ImageSource.hx +++ b/src/app/haxe/ru/m/puzzlez/core/ImageSource.hx @@ -6,5 +6,5 @@ import ru.m.puzzlez.proto.game.ImageId; interface ImageSource extends DataSource { public var id(default, never):String; - public function load(id:String):Promise; + public function load(id:String, thumb:Bool = false):Promise; } diff --git a/src/app/haxe/ru/m/puzzlez/image/ImageData.hx b/src/app/haxe/ru/m/puzzlez/image/ImageData.hx index 957255a..1a6756a 100644 --- a/src/app/haxe/ru/m/puzzlez/image/ImageData.hx +++ b/src/app/haxe/ru/m/puzzlez/image/ImageData.hx @@ -23,6 +23,10 @@ abstract ImageData(ImageId) from ImageId { 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 { return switch value { case ImageValue.BITMAP(value): @@ -39,11 +43,14 @@ abstract ImageData(ImageId) from ImageId { public function resolve():Promise { var key = '${this.source}:${this.id}'; + if (this.thumb) { + key = '${key}:thumb'; + } if (!cache.exists(key)) { if (storageCache.exists(key)) { cache.set(key, extractImage(key, ImageValue.BYTES(storageCache.get(key)))); } 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); diff --git a/src/app/haxe/ru/m/puzzlez/source/AssetImageSource.hx b/src/app/haxe/ru/m/puzzlez/source/AssetImageSource.hx index 2df0aaf..691d340 100644 --- a/src/app/haxe/ru/m/puzzlez/source/AssetImageSource.hx +++ b/src/app/haxe/ru/m/puzzlez/source/AssetImageSource.hx @@ -36,7 +36,7 @@ class AssetImageSource implements ImageSource { }); } - public function load(id:String):Promise { + public function load(id:String, thumb:Bool = false):Promise { return Promise.promise(ImageValue.BITMAP(Assets.getBitmapData(id))); } } diff --git a/src/app/haxe/ru/m/puzzlez/source/FileImageSource.hx b/src/app/haxe/ru/m/puzzlez/source/FileImageSource.hx index 9d3c0f5..c6a1698 100644 --- a/src/app/haxe/ru/m/puzzlez/source/FileImageSource.hx +++ b/src/app/haxe/ru/m/puzzlez/source/FileImageSource.hx @@ -25,7 +25,7 @@ class FileImageSource implements ImageSource { }); } - public function load(id:String):Promise { + public function load(id:String, thumb:Bool = false):Promise { return storage.get(id).then(bytes -> ImageValue.BYTES(bytes)); } } diff --git a/src/app/haxe/ru/m/puzzlez/source/PixabayImageSource.hx b/src/app/haxe/ru/m/puzzlez/source/PixabayImageSource.hx index c5da94f..b77fbd1 100644 --- a/src/app/haxe/ru/m/puzzlez/source/PixabayImageSource.hx +++ b/src/app/haxe/ru/m/puzzlez/source/PixabayImageSource.hx @@ -11,17 +11,19 @@ class PixabayImageSource implements ImageSource { public var id(default, never):String = "pixabay"; private var api:PixabayApi; - private static var imageUrlsCache:Map = new Map(); + private static var imageUrlsCache:Map = new Map(); 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> { - 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 = []; 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))); } return { @@ -32,14 +34,19 @@ class PixabayImageSource implements ImageSource { }); } - public function load(id:String):Promise { + public function load(id:String, thumb:Bool = false):Promise { var imageId = Std.parseInt(id); - if (imageUrlsCache.exists(imageId)) { - return Promise.promise(ImageValue.URL(imageUrlsCache.get(imageId))); + var key = id; + if (thumb) { + key = '${key}:thumb'; + } + if (imageUrlsCache.exists(key)) { + return Promise.promise(ImageValue.URL(imageUrlsCache.get(key))); } else { return api.get(imageId).then((data:PixabayImage) -> { - imageUrlsCache.set(imageId, data.largeImageURL); - return ImageValue.URL(data.largeImageURL); + var url = thumb ? data.previewURL : data.largeImageURL; + imageUrlsCache.set(key, url); + return ImageValue.URL(url); }); } } diff --git a/src/app/haxe/ru/m/puzzlez/source/UnsplashImageSource.hx b/src/app/haxe/ru/m/puzzlez/source/UnsplashImageSource.hx new file mode 100644 index 0000000..9ee0710 --- /dev/null +++ b/src/app/haxe/ru/m/puzzlez/source/UnsplashImageSource.hx @@ -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; + + +class ImageResolver { + + private var cache: Map; + 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> { + return this.api.getPage(page.index + 1, page.count, page.filter.get("category")) + .then((response:UnsplashResponse) -> { + var data:Array = []; + 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 { + return resolver.resolve(id, thumb); + } +} diff --git a/src/app/haxe/ru/m/puzzlez/view/ImageListFrame.yaml b/src/app/haxe/ru/m/puzzlez/view/ImageListFrame.yaml index ee809e2..ad48bfd 100644 --- a/src/app/haxe/ru/m/puzzlez/view/ImageListFrame.yaml +++ b/src/app/haxe/ru/m/puzzlez/view/ImageListFrame.yaml @@ -7,7 +7,7 @@ views: - id: images $type: ru.m.view.DataList geometry.stretch: true - list.factory: ~ru.m.puzzlez.view.common.ImageIdView.factory + list.factory: ~ru.m.puzzlez.view.common.ImageIdView.factoryThumb +list.onDataSelect: ~start - $type: hw.view.form.ButtonView text: Back diff --git a/src/app/haxe/ru/m/puzzlez/view/StartFrame.hx b/src/app/haxe/ru/m/puzzlez/view/StartFrame.hx index 6dc4901..4285186 100644 --- a/src/app/haxe/ru/m/puzzlez/view/StartFrame.hx +++ b/src/app/haxe/ru/m/puzzlez/view/StartFrame.hx @@ -36,7 +36,7 @@ import ru.m.update.Updater; // data.push({title: "Assets", sourceId: "asset"}); data.push(fileSource); 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; } diff --git a/src/app/haxe/ru/m/puzzlez/view/common/ImageIdView.hx b/src/app/haxe/ru/m/puzzlez/view/common/ImageIdView.hx index eea7fb4..44424e6 100644 --- a/src/app/haxe/ru/m/puzzlez/view/common/ImageIdView.hx +++ b/src/app/haxe/ru/m/puzzlez/view/common/ImageIdView.hx @@ -9,19 +9,29 @@ import ru.m.view.LoadingWrapper; @:template class ImageIdView extends GroupView { + public var thumb:Bool = false; + public var imageId(default, set):ImageId; private function set_imageId(value:ImageId):ImageId { 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; } @:view("image") var imageView:ImageView; private var loading:LoadingWrapper; - public function new(?imageId:ImageId, ?style: StyleId) { + public function new(?imageId:ImageId, ?style: StyleId, ?thumb: Bool = false) { super(); this.style = style; + this.thumb = thumb; loading = new LoadingWrapper(this); if (imageId != null) { this.imageId = imageId; @@ -31,4 +41,8 @@ import ru.m.view.LoadingWrapper; public static function factory(index:Int, value:ImageId):ImageIdView { return new ImageIdView(value, "view"); } + + public static function factoryThumb(index:Int, value:ImageId):ImageIdView { + return new ImageIdView(value, "view", true); + } } diff --git a/src/common/proto/game.proto b/src/common/proto/game.proto index b75ac53..c7d6228 100644 --- a/src/common/proto/game.proto +++ b/src/common/proto/game.proto @@ -7,6 +7,7 @@ package ru.m.puzzlez.proto.game; message ImageId { string source = 1; string id = 2; + bool thumb = 3; } enum BoundType {