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