feat(app): add nginx inage source

This commit is contained in:
2026-02-24 00:47:25 +03:00
parent f974d5361e
commit 5a7d23f7e3
18 changed files with 215 additions and 68 deletions

View File

@@ -1,5 +1,8 @@
{
"indentation": {
"character": " "
},
"wrapping": {
"maxLineLength": 120
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "puzzlez",
"version": "0.5.1",
"version": "0.6.0",
"private": true,
"devDependencies": {
"dateformat": "^3.0.3",

View File

@@ -1,46 +1,53 @@
{
"folders": [
{
"path": "."
}
"path": ".",
},
],
"settings": {
"haxe.executable": {
"path": "/home/shmyga/sdk/haxe/4.2.5/haxe",
"env": {
"HAXE_STD_PATH": "/home/shmyga/sdk/haxe/4.2.5/std"
}
"HAXE_STD_PATH": "/home/shmyga/sdk/haxe/4.2.5/std",
},
},
"haxe.configurations": [["build/app/flash/haxe/debug.hxml"]],
"haxe.displayServer": {
"arguments": [
//"-v"
]
}
],
},
},
"extensions": {
"recommendations": ["nadako.vshaxe"]
"recommendations": ["nadako.vshaxe"],
},
"launch": {
"version": "0.2.0",
"configurations": [
{
"args": ["app:flash:test"],
"name": "app:flash:test",
"args": ["${input:target}"],
"name": "${input:target}",
"program": "${workspaceFolder}/node_modules/gulp/bin/gulp.js",
"request": "launch",
"skipFiles": ["<node_internals>/**"],
"type": "node"
"type": "node",
"consoleTitle": "${input:target}",
},
{
"args": ["server:cpp:test"],
"name": "server:cpp:test",
"program": "${workspaceFolder}/node_modules/gulp/bin/gulp.js",
"request": "launch",
"skipFiles": ["<node_internals>/**"],
"type": "node"
}
],
"compounds": []
}
"inputs": [
{
"id": "target",
"description": "Please enter target name",
"options": [
"app:flash:test",
"app:html5:test",
"app:linux:test",
"server:cpp:test",
],
"type": "pickString",
"default": "app:flash:test",
},
],
"compounds": [],
},
}

View File

@@ -8,6 +8,7 @@ import ru.m.puzzlez.render.part.IPartBuilder;
import ru.m.puzzlez.settings.Settings;
import ru.m.puzzlez.source.AssetImageSource;
import ru.m.puzzlez.source.FileImageSource;
import ru.m.puzzlez.source.NginxImageSource;
import ru.m.puzzlez.source.PixabayImageSource;
import ru.m.puzzlez.source.UnsplashImageSource;
import ru.m.puzzlez.storage.GameStorage;
@@ -25,8 +26,9 @@ class PuzzlezApp {
GameStorage;
sourceBundle.register(new AssetImageSource());
sourceBundle.register(new FileImageSource());
sourceBundle.register(new PixabayImageSource());
sourceBundle.register(new UnsplashImageSource());
sourceBundle.register(new PixabayImageSource(CompilationOption.get("PIXABAY_KEY")));
sourceBundle.register(new UnsplashImageSource(CompilationOption.get("UNSPLASH_KEY")));
sourceBundle.register(new NginxImageSource("https://home.shmyga.ru/puzzlez/"));
L.push(new TraceLogger());
updater = new Updater(Const.instance.VERSION, "https://shmyga.ru/repo/puzzlez/packages.json");
var app = new App();

View File

@@ -24,6 +24,10 @@ class PuzzlezTheme extends Theme {
public function new() {
super({embed: true}, {light: "gray"}, {base: "4h"});
register(new Style("frame", ["geometry.padding" => Box.fromFloat(8),]));
register(new Style("button.source", [
"geometry.width" => SizeValue.fromString("30%"),
"geometry.height" => SizeValue.fromString("40%"),
], "button"));
register(new Style("view", [
"skin.background.color" => colors.light,
"skin.border.color" => colors.border,

View File

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

View File

@@ -101,8 +101,8 @@ class SpritePartView extends PartView {
if (playerId != null) {
var rect = cast(part.rect, RectangleExt).clone();
rect.x += (image.width - size.x) / 2;
rect.y += (image.height - size.y) / 2;
rect.x = (image.width - size.x) / 2;
rect.y = (image.height - size.y) / 2;
var path = builder.build(rect, part.bounds);
graphics.lineStyle(4, 0xffff00, 0.3);
path.draw(graphics);

View File

@@ -13,6 +13,7 @@ class AssetImageSource implements ImageSource {
private var _data:Array<ImageId>;
private var data(get, null):Array<ImageId>;
private var key:String;
private function get_data():Array<ImageId> {
if (_data == null) {
@@ -21,11 +22,14 @@ class AssetImageSource implements ImageSource {
return _data;
}
public function new() {}
public function new(key:String = "resources/image") {
this.key = key;
}
private function resolveData():Array<ImageId> {
var keyLength = this.key.length;
return [
for (name in Assets.list(AssetType.IMAGE).filter((name:String) -> name.substr(0, 15) == "resources/image"))
for (name in Assets.list(AssetType.IMAGE).filter((name:String) -> name.substr(0, keyLength) == this.key))
new ImageId().setSource(id).setId(name)
];
}
@@ -41,4 +45,8 @@ class AssetImageSource implements ImageSource {
public function load(id:String, thumb:Bool = false):Promise<ImageValue> {
return Promise.promise(ImageValue.BITMAP(Assets.getBitmapData(id)));
}
public function getCategories():Null<Promise<Array<String>>> {
return null;
}
}

View File

@@ -27,4 +27,8 @@ class FileImageSource implements ImageSource {
public function load(id:String, thumb:Bool = false):Promise<ImageValue> {
return storage.get(id).then(bytes -> ImageValue.BYTES(bytes));
}
public function getCategories():Null<Promise<Array<String>>> {
return null;
}
}

View File

@@ -0,0 +1,78 @@
package ru.m.puzzlez.source;
import hw.net.JsonLoader;
import promhx.Promise;
import ru.m.data.DataSource;
import ru.m.puzzlez.core.ImageSource;
import ru.m.puzzlez.core.ImageValue;
import ru.m.puzzlez.proto.game.ImageId;
enum abstract NginxResponseItemType(String) from String to String {
var FILE = "file";
var DIRECTORY = "directory";
}
typedef NginxResponseItem = {
var name:String;
var type:NginxResponseItemType;
var mtime:String;
var size:Int;
}
typedef NginxResponse = Array<NginxResponseItem>;
class NginxImageSource implements ImageSource {
public var id(default, never):String = "nginx";
private var baseUrl:String;
public function new(baseUrl:String) {
this.baseUrl = baseUrl;
}
private function buildUrl(name:String):String {
return baseUrl + name;
}
public function getPage(page:Page):Promise<DataPage<ImageId>> {
var category = page.filter.get("category");
return new JsonLoader<NginxResponse>().GET(category != null ? this.baseUrl + category + "/" : this.baseUrl)
.then((response:NginxResponse) -> {
var data:Array<ImageId> = [];
for (item in response) {
if (item.type == NginxResponseItemType.FILE) {
var itemId = category != null ? category + "@" + item.name : item.name;
data.push(new ImageId().setSource(id).setId(itemId));
}
}
return {
page: page,
data: data,
total: response.length,
}
});
}
public function load(id:String, thumb:Bool = false):Promise<ImageValue> {
var url = this.baseUrl + StringTools.replace(id, "@", "/");
if (thumb) {
var parts = url.split(".");
var index = parts.length - 2;
parts[index] = parts[index] + "-thumbnail";
url = parts.join(".");
}
return Promise.promise(ImageValue.URL(url));
}
public function getCategories():Null<Promise<Array<String>>> {
return new JsonLoader<NginxResponse>().GET(this.baseUrl).then((response:NginxResponse) -> {
var data:Array<String> = [];
for (item in response) {
if (item.type == NginxResponseItemType.DIRECTORY) {
data.push(item.name);
}
}
return data;
});
}
}

View File

@@ -14,24 +14,24 @@ class PixabayImageSource implements ImageSource {
private static var imageUrlsCache:Map<String, String> = new Map();
public function new() {
var key:String = CompilationOption.get("PIXABAY_KEY");
public function new(key:String) {
api = new PixabayApi(key);
}
public function getPage(page:Page):Promise<DataPage<ImageId>> {
return this.api.getPage(page.index + 1, page.count, page.filter.get("category")).then((response:PixabayResponse) -> {
var data:Array<ImageId> = [];
for (hit in response.hits) {
imageUrlsCache.set('${hit.id}', hit.largeImageURL);
data.push(new ImageId().setSource(id).setId(Std.string(hit.id)));
}
return {
page: page,
data: data,
total: response.totalHits,
}
});
return this.api.getPage(page.index + 1, page.count, page.filter.get("category"))
.then((response:PixabayResponse) -> {
var data:Array<ImageId> = [];
for (hit in response.hits) {
imageUrlsCache.set('${hit.id}', hit.largeImageURL);
data.push(new ImageId().setSource(id).setId(Std.string(hit.id)));
}
return {
page: page,
data: data,
total: response.totalHits,
}
});
}
public function load(id:String, thumb:Bool = false):Promise<ImageValue> {
@@ -50,4 +50,8 @@ class PixabayImageSource implements ImageSource {
});
}
}
public function getCategories():Null<Promise<Array<String>>> {
return Promise.promise(AbstractEnumTools.getValues(PixabayCategory));
}
}

View File

@@ -51,8 +51,7 @@ class UnsplashImageSource implements ImageSource {
private static var resolver:ImageResolver;
public function new() {
var key:String = CompilationOption.get("UNSPLASH_KEY");
public function new(key:String) {
api = new UnsplashApi(key);
resolver = new ImageResolver((imageId, thumb) -> {
return api.get(imageId).then(data -> thumb ? data.urls.small : data.urls.regular);
@@ -60,22 +59,28 @@ class UnsplashImageSource implements ImageSource {
}
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.small, true);
resolver.setValue(image.id, image.urls.regular);
data.push(new ImageId().setSource(id).setId(image.id));
}
return {
page: page,
data: data,
total: response.total,
}
});
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.small, true);
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);
}
public function getCategories():Null<Promise<Array<String>>> {
// TODO: unsplash categories list
return Promise.promise([]);
}
}

View File

@@ -34,6 +34,7 @@ import ru.m.puzzlez.view.popup.PreviewPopup;
}
override public function onShow(state:GameState):Void {
L.d("Frame", '$ID: ${state.preset.image.source}:${state.preset.image.id}');
onHide();
if (state.online) {
// game = new NetworkGame(state);

View File

@@ -27,6 +27,7 @@ typedef GameListConfig = {
}
override public function onShow(data:GameListConfig):Void {
L.d("Frame", '$ID: $data');
games.reset();
if (data != null) {
header.text = data.title;

View File

@@ -23,11 +23,18 @@ typedef ImageListConfig = {
@:provide var switcher:FrameSwitcher;
@:provide var sourceBundle:ImageSourceBundle;
private var config:ImageListConfig;
public function new() {
super(ID);
}
override public function onShow(data:ImageListConfig):Void {
if (data == null) {
data = config;
}
L.d("Frame", '$ID: $data');
config = data;
images.reset();
if (data != null) {
header.text = data.title;

View File

@@ -48,6 +48,7 @@ import ru.m.puzzlez.view.common.PresetView;
override public function onShow(data:ImageId):Void {
super.onShow(data);
L.d("Frame", '$ID: ${data.source}:${data.id}');
imageId = data;
selectSize(sizesView.data[sizesView.data.length - 1]);
}

View File

@@ -4,8 +4,8 @@ import hw.view.data.DataView;
import hw.view.form.ButtonView;
import hw.view.frame.FrameSwitcher;
import hw.view.frame.FrameView;
import ru.m.api.PixabayApi;
import ru.m.puzzlez.FileUtil;
import ru.m.puzzlez.image.ImageSourceBundle;
import ru.m.puzzlez.proto.game.GameStatus;
import ru.m.puzzlez.storage.FileStorage;
import ru.m.puzzlez.storage.GameStorage;
@@ -27,24 +27,41 @@ import ru.m.update.Updater;
@:provide var fileStorage:FileStorage;
@:provide var gameStorage:GameStorage;
@:provide var sourceBundle:ImageSourceBundle;
private var fileSource:ImageListConfig = {title: "Files", sourceId: "file"};
private var startedGames:GameListConfig = {title: "Started", source: gameStorage, filter: ["status" => GameStatus.STARTED]};
private var completedGames:GameListConfig = {title: "Completed", source: gameStorage, filter: ["status" => GameStatus.COMPLETE]};
private var startedGames:GameListConfig = {
title: "Started",
source: gameStorage,
filter: ["status" => GameStatus.STARTED]
};
private var completedGames:GameListConfig = {
title: "Completed",
source: gameStorage,
filter: ["status" => GameStatus.COMPLETE]
};
public function new() {
super(ID);
var data:Array<ImageListConfig> = [];
// data.push({title: "Assets", sourceId: "asset"});
data.push(fileSource);
for (type in AbstractEnumTools.getValues(PixabayCategory)) {
data.push({title: type, sourceId: "unsplash", filter: ["category" => type]});
}
sourceBundle.get(fileSource.sourceId).getPage({index: 0, count: 0}).then((page) -> {
if (page.total > 0) {
data.unshift(fileSource);
sources.data = data;
}
});
sources.data = data;
sourceBundle.get("nginx").getCategories().then((categories) -> {
for (category in categories) {
data.push({title: category, sourceId: "nginx", filter: ["category" => category]});
}
sources.data = data;
});
}
private function sourceViewFactory(index:Int, source:ImageListConfig):ButtonView {
var result = new ButtonView();
result.style = "button.source";
result.text = source.title;
return result;
}
@@ -62,6 +79,7 @@ import ru.m.update.Updater;
}
override public function onShow(data:Dynamic):Void {
L.d("Frame", '$ID');
appUpdater.check().then((info:Null<PackageInfo>) -> {
if (info != null) {
updateButton.visible = true;
@@ -74,9 +92,11 @@ import ru.m.update.Updater;
private function refresh():Void {
gameStorage.getIndexPage({index: 0, count: 0, filter: startedGames.filter}).then(response -> {
startedButton.text = response.total > 0 ? 'Started (${response.total})' : 'Started';
startedButton.disabled = response.total == 0;
});
gameStorage.getIndexPage({index: 0, count: 0, filter: completedGames.filter}).then(response -> {
completedButton.text = response.total > 0 ? 'Completed (${response.total})' : 'Completed';
completedButton.disabled = response.total == 0;
});
}
}

View File

@@ -1,10 +1,10 @@
package ru.m.storage;
import haxe.Unserializer;
import flash.net.SharedObject;
import haxe.Serializer;
import haxe.Unserializer;
import haxe.crypto.Md5;
import haxe.io.Bytes;
import haxe.Serializer;
import promhx.Promise;
import ru.m.data.DataSource;
@@ -124,7 +124,8 @@ class SharedObjectDataStorage<D, I> implements DataStorage<D, I> {
}
}
applyOrder(page.order, values);
var result:Array<I> = values.slice(page.index * page.count, page.index * page.count + page.count).map(value -> value.id);
var result:Array<I> = values.slice(page.index * page.count, page.index * page.count + page.count)
.map(value -> value.id);
return Promise.promise({
page: page,
total: values.length,
@@ -138,7 +139,7 @@ class SharedObjectDataStorage<D, I> implements DataStorage<D, I> {
return {
page: indexPage.page,
total: indexPage.total,
data: data,
data: data.filter((item:D) -> item != null),
}
});
});