[add] GameListFrame paginator
This commit is contained in:
@@ -34,37 +34,41 @@ class DefaultConverter<D> extends Converter<D, String> {
|
||||
}
|
||||
}
|
||||
|
||||
class DataFilter<D> extends DefaultConverter<Filter> {
|
||||
private var builder:D -> Filter;
|
||||
class MetaBuilder<D> extends DefaultConverter<DataMeta> {
|
||||
private var builder:D -> DataMeta;
|
||||
|
||||
public function new(builder:D -> Filter) {
|
||||
public function new(builder:D -> DataMeta) {
|
||||
super();
|
||||
this.builder = builder;
|
||||
}
|
||||
|
||||
public function build(item:D):Filter {
|
||||
public function build(item:D):DataMeta {
|
||||
return builder(item);
|
||||
}
|
||||
}
|
||||
|
||||
typedef DataMeta = Filter;
|
||||
|
||||
typedef IdValue<I> = {id:I, meta:DataMeta};
|
||||
|
||||
class DataStorage<I, D:{id:I}> implements IDataSource<I, D> {
|
||||
|
||||
inline private static var DATA_KEY = "item";
|
||||
|
||||
private var name:String;
|
||||
private var filterConverter:DataFilter<D>;
|
||||
private var metaBuilder:MetaBuilder<D>;
|
||||
private var idConverter:Converter<I, String>;
|
||||
private var dataConverter:Converter<D, Dynamic>;
|
||||
private var indexData:SharedObject;
|
||||
|
||||
public function new(
|
||||
name:String,
|
||||
filterConverter:DataFilter<D>,
|
||||
metaBuilder:MetaBuilder<D>,
|
||||
idConverter:Converter<I, String> = null,
|
||||
dataConverter:Converter<D, Dynamic> = null
|
||||
) {
|
||||
this.name = name;
|
||||
this.filterConverter = filterConverter;
|
||||
this.metaBuilder = metaBuilder;
|
||||
this.idConverter = idConverter != null ? idConverter : new DefaultConverter();
|
||||
this.dataConverter = dataConverter != null ? dataConverter : new DefaultConverter();
|
||||
this.indexData = SharedObject.getLocal('${name}/index');
|
||||
@@ -78,14 +82,14 @@ class DataStorage<I, D:{id:I}> implements IDataSource<I, D> {
|
||||
return idConverter.deserialize(data);
|
||||
}
|
||||
|
||||
private function buildFilter(item:D):Filter {
|
||||
return filterConverter.build(item);
|
||||
private function buildMeta(item:D):DataMeta {
|
||||
return metaBuilder.build(item);
|
||||
}
|
||||
|
||||
private function checkFilter(filter:Null<Filter>, data:Filter):Bool {
|
||||
private function checkFilter(filter:Null<Filter>, meta:DataMeta):Bool {
|
||||
if (filter != null) {
|
||||
for (k => v in filter) {
|
||||
if (data.get(k) != v) {
|
||||
if (meta.get(k) != v) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -93,6 +97,23 @@ class DataStorage<I, D:{id:I}> implements IDataSource<I, D> {
|
||||
return true;
|
||||
}
|
||||
|
||||
private function sort(order:Order, values:Array<IdValue<I>>):Void {
|
||||
if (order != null) {
|
||||
values.sort((a:IdValue<I>, b:IdValue<I>) -> {
|
||||
for (item in order) {
|
||||
var av = a.meta.get(item.key);
|
||||
var bv = b.meta.get(item.key);
|
||||
if (av > bv) {
|
||||
return item.reverse ? - 1 : 1;
|
||||
} else if (av < bv) {
|
||||
return item.reverse ? 1 : -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private function serialize(item:D):Dynamic {
|
||||
return dataConverter.serialize(item);
|
||||
}
|
||||
@@ -103,23 +124,25 @@ class DataStorage<I, D:{id:I}> implements IDataSource<I, D> {
|
||||
|
||||
public function getIndexPage(page:Page):Promise<DataPage<I>> {
|
||||
var data:DynamicAccess<String> = indexData.data;
|
||||
var result:Array<I> = [];
|
||||
var values:Array<IdValue<I>> = [];
|
||||
for (k => v in data) {
|
||||
var filter = this.filterConverter.deserialize(v);
|
||||
if (checkFilter(page.filter, filter)) {
|
||||
result.push(desirealizeId(k));
|
||||
var meta = metaBuilder.deserialize(v);
|
||||
if (checkFilter(page.filter, meta)) {
|
||||
values.push({id: desirealizeId(k), meta: meta});
|
||||
}
|
||||
}
|
||||
sort(page.order, values);
|
||||
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: result.length,
|
||||
total: values.length,
|
||||
data: result,
|
||||
});
|
||||
}
|
||||
|
||||
public function getPage(page:Page):Promise<DataPage<D>> {
|
||||
return getIndexPage(page).pipe((indexPage:DataPage<I>) -> {
|
||||
Promise.whenAll([for (id in indexPage.data) get(id)]).then((data:Array<D>) -> {
|
||||
return Promise.whenAll([for (id in indexPage.data) get(id)]).then((data:Array<D>) -> {
|
||||
return {
|
||||
page: indexPage.page,
|
||||
total: indexPage.total,
|
||||
@@ -144,7 +167,7 @@ class DataStorage<I, D:{id:I}> implements IDataSource<I, D> {
|
||||
var itemData = SharedObject.getLocal('${name}/${stringId}');
|
||||
itemData.setProperty(DATA_KEY, serialize(item));
|
||||
itemData.flush();
|
||||
indexData.setProperty(stringId, filterConverter.serialize(filterConverter.build(item)));
|
||||
indexData.setProperty(stringId, metaBuilder.serialize(metaBuilder.build(item)));
|
||||
indexData.flush();
|
||||
return Promise.promise(item);
|
||||
}
|
||||
|
||||
@@ -4,10 +4,12 @@ import promhx.Promise;
|
||||
|
||||
typedef Filter = Map<String, String>;
|
||||
|
||||
typedef Order = Array<{key: String, ?reverse:Bool}>;
|
||||
|
||||
typedef Page = {
|
||||
var index:Int;
|
||||
var count:Int;
|
||||
@:optional var order:Array<String>;
|
||||
@:optional var order:Order;
|
||||
@:optional var filter:Filter;
|
||||
}
|
||||
|
||||
|
||||
@@ -27,9 +27,10 @@ class Game implements IGame {
|
||||
case READY:
|
||||
shuffle();
|
||||
state.status = STARTED;
|
||||
case _:
|
||||
}
|
||||
signal.emit(START(state));
|
||||
case _:
|
||||
signal.emit(RESUME(state));
|
||||
}
|
||||
}
|
||||
|
||||
public function shuffle():Void {
|
||||
|
||||
@@ -14,6 +14,7 @@ enum GameChange {
|
||||
|
||||
enum GameEvent {
|
||||
START(state:GameState);
|
||||
RESUME(state:GameState);
|
||||
ACTION(action:GameAction);
|
||||
CHANGE(change:GameChange);
|
||||
COMPLETE;
|
||||
|
||||
@@ -71,7 +71,7 @@ class Render extends SpriteView implements IRender {
|
||||
|
||||
public function onGameEvent(event:GameEvent):Void {
|
||||
switch event {
|
||||
case START(state):
|
||||
case START(state) | RESUME(state):
|
||||
onStart(state);
|
||||
case CHANGE(PART_UPDATE(id, TABLE(point))):
|
||||
var part:PartView = parts[id];
|
||||
|
||||
@@ -6,12 +6,12 @@ import ru.m.data.DataStorage;
|
||||
|
||||
@:provide class GameStorage extends DataStorage<ImageId, GameState> {
|
||||
inline private static var NAME = "game";
|
||||
inline private static var VERSION = 3;
|
||||
inline private static var VERSION = 4;
|
||||
|
||||
public function new() {
|
||||
super(
|
||||
'${NAME}/${VERSION}',
|
||||
new DataFilter<GameState>(item -> ["status" => item.status]),
|
||||
new MetaBuilder<GameState>(item -> ["status" => item.status, "date" => Date.now().toString()]),
|
||||
new Converter<ImageId, String>(id -> id.toString(), data -> ImageId.fromString(data))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -40,7 +40,9 @@ import ru.m.puzzlez.view.popup.PreviewPopup;
|
||||
}
|
||||
|
||||
override public function onHide():Void {
|
||||
if (saveTimer != null) {
|
||||
save();
|
||||
}
|
||||
if (game != null) {
|
||||
render.signal.disconnect(game.signal.emit);
|
||||
game.stop();
|
||||
@@ -50,8 +52,8 @@ import ru.m.puzzlez.view.popup.PreviewPopup;
|
||||
}
|
||||
|
||||
private function toSave():Void {
|
||||
if (saveTimer != null) {
|
||||
saveTimer = Timer.delay(save, 500);
|
||||
if (saveTimer == null) {
|
||||
saveTimer = Timer.delay(save, 5000);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package ru.m.puzzlez.view;
|
||||
|
||||
import haxework.view.data.DataView;
|
||||
import haxework.view.form.ToggleButtonView;
|
||||
import haxework.view.frame.FrameSwitcher;
|
||||
import haxework.view.frame.FrameView;
|
||||
import haxework.view.popup.ConfirmView;
|
||||
@@ -14,7 +15,8 @@ import ru.m.puzzlez.view.PuzzleImageView;
|
||||
|
||||
public static var ID(default, never) = "game_list";
|
||||
|
||||
@:view var images:ActionDataView<ImageId, PuzzleImageView, Action>;
|
||||
@:view("images") var imagesView:ActionDataView<ImageId, PuzzleImageView, Action>;
|
||||
@:view("pages") var pagesView:DataView<Int, ToggleButtonView>;
|
||||
|
||||
@:provide var switcher:FrameSwitcher;
|
||||
@:provide var storage:GameStorage;
|
||||
@@ -22,28 +24,48 @@ import ru.m.puzzlez.view.PuzzleImageView;
|
||||
private var status:GameStatus;
|
||||
private var page:Page;
|
||||
|
||||
private var data(default, set):DataPage<ImageId>;
|
||||
|
||||
private function set_data(value) {
|
||||
data = value;
|
||||
imagesView.data = data.data;
|
||||
pagesView.data = [for (i in 0...Std.int(data.total / data.page.count) + 1) i];
|
||||
return data;
|
||||
}
|
||||
|
||||
public function new() {
|
||||
super(ID);
|
||||
page = {index: 0, count: 10};
|
||||
page = {index: 0, count: 6, order: [{key: "date", reverse: true}]};
|
||||
}
|
||||
|
||||
private function pageFactory(index:Int, value:Int):ToggleButtonView {
|
||||
var result = new ToggleButtonView();
|
||||
result.text = '${value}';
|
||||
result.on = data.page.index == value;
|
||||
return result;
|
||||
}
|
||||
|
||||
override public function onShow(data:GameStatus):Void {
|
||||
status = data;
|
||||
page.filter = ["status" => status];
|
||||
storage.getIndexPage(page).then(page -> images.data = page.data);
|
||||
refresh();
|
||||
}
|
||||
|
||||
private function start(id:ImageId):Void {
|
||||
storage.get(id).then(state -> switcher.change(GameFrame.ID, state));
|
||||
}
|
||||
|
||||
private function refresh():Void {
|
||||
storage.getIndexPage(page).then(data -> this.data = data);
|
||||
}
|
||||
|
||||
private function onAction(imageId:ImageId, action:Action):Void {
|
||||
switch action {
|
||||
case CLEAN:
|
||||
ConfirmView.confirm("Delete state?").then(result -> {
|
||||
if (result) {
|
||||
storage.delete(imageId);
|
||||
storage.getIndexPage(page).then(page -> images.data = page.data);
|
||||
refresh();
|
||||
}
|
||||
});
|
||||
case _:
|
||||
|
||||
@@ -13,6 +13,18 @@ views:
|
||||
+onDataAction: ~onAction
|
||||
geometry.margin: 5
|
||||
overflow.y: scroll
|
||||
- id: pages
|
||||
$type: haxework.view.data.DataView
|
||||
geometry.width: 100%
|
||||
layout:
|
||||
$type: haxework.view.layout.TailLayout
|
||||
margin: 5
|
||||
factory: ~pageFactory
|
||||
+onDataSelect: |
|
||||
~(index) -> {
|
||||
page.index = index;
|
||||
refresh();
|
||||
}
|
||||
- $type: haxework.view.form.ButtonView
|
||||
text: Back
|
||||
geometry.position: absolute
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
package ru.m.puzzlez.view;
|
||||
|
||||
import ru.m.update.Updater;
|
||||
import haxework.view.data.DataView;
|
||||
import haxework.view.form.ButtonView;
|
||||
import haxework.view.frame.FrameSwitcher;
|
||||
import haxework.view.frame.FrameView;
|
||||
import haxework.view.popup.ConfirmView;
|
||||
import ru.m.data.IDataSource;
|
||||
import ru.m.puzzlez.core.GameState.GameStatus;
|
||||
import ru.m.puzzlez.source.AssetSource;
|
||||
import ru.m.puzzlez.source.FileSource;
|
||||
import ru.m.puzzlez.source.PixabaySource;
|
||||
import ru.m.puzzlez.storage.GameStorage;
|
||||
import ru.m.puzzlez.storage.ImageStorage;
|
||||
import ru.m.update.Updater;
|
||||
|
||||
@:template class StartFrame extends FrameView<Dynamic> {
|
||||
public static var ID = "start";
|
||||
@@ -38,12 +39,14 @@ import ru.m.puzzlez.storage.ImageStorage;
|
||||
}
|
||||
|
||||
private function refresh():Void {
|
||||
gameStorage.getIndexPage({index: 0, count: 0, filter: ["status" => STARTED]}).then(page -> {
|
||||
var count = page.total;
|
||||
loadButton.text = 'Resume (${count})';
|
||||
loadButton.disabled = count == 0;
|
||||
var startedRequest:Page = {index: 0, count: 0, filter: ["status" => STARTED]};
|
||||
gameStorage.getIndexPage(startedRequest).then(page -> {
|
||||
var total = page.total;
|
||||
loadButton.text = 'Resume (${total})';
|
||||
loadButton.disabled = total == 0;
|
||||
});
|
||||
gameStorage.getIndexPage({index: 0, count: 0, filter: ["status" => COMPLETE]}).then(page -> {
|
||||
var completeRequest:Page = {index: 0, count: 0, filter: ["status" => COMPLETE]};
|
||||
gameStorage.getIndexPage(completeRequest).then(page -> {
|
||||
var total = page.total;
|
||||
completeButton.text = 'Complete (${total})';
|
||||
completeButton.disabled = total == 0;
|
||||
|
||||
Reference in New Issue
Block a user