10 Commits

Author SHA1 Message Date
347e20ab6b feat(app): add network game mode 2026-05-20 00:06:13 +03:00
9e67513897 build(npm): update gulp-haxetool 2026-05-20 00:05:30 +03:00
e41d461ba6 feat(app): update nginx image source 2026-05-20 00:03:52 +03:00
acf0706bef docs: add screenshot 2026-05-04 16:12:46 +03:00
8874f5c04f build(scripts): update build and publish scripts 2026-04-17 20:08:29 +03:00
73206d41ec 0.6.1 2026-04-17 11:51:32 +03:00
86345117f7 feat(app): update icons 2026-04-17 11:51:21 +03:00
5cae80636b build(docker): add docker web image build 2026-04-10 13:59:44 +03:00
7cadfac350 build(docker): add docker builder 2026-04-08 16:55:18 +03:00
5a7d23f7e3 feat(app): add nginx inage source 2026-03-10 23:13:58 +03:00
62 changed files with 14401 additions and 13846 deletions

20
.env.default Normal file
View File

@@ -0,0 +1,20 @@
PROJECT=puzzlez
VERSION=$(grep -m 1 'version' ./package.json | grep -oP '"version"\s*:\s*"\K\d+\.\d+.\d+')
SDK_PATH=$HOME/sdk
PUBLISH_PATH=$HOME/public/$PROJECT
BUILD_PATH=./build
TARGET_PATH=./target
KEY_STORE=<keystore.jks>
KEY_PASS=<passphrase>
# publish
REPO=https://git.shmyga.ru/api/packages/InfernalGames
PUBLISH_USER=<username>
PUBLISH_PASSWORD=<passphrase>
# docker
DOCKER_REPO=git.shmyga.ru
DOCKER_GROUP=infernalgames
DOCKER_ROOT="$DOCKER_REPO/$DOCKER_GROUP"
DOCKER_PROJECTS="$PROJECT-web:web"
DOCKER_ARGS="PROJECT_NAME=$PROJECT"

1
.gitignore vendored
View File

@@ -14,3 +14,4 @@ config.json
/log
/ansible/*.retry
.npmrc
.env

35
Dockerfile Normal file
View File

@@ -0,0 +1,35 @@
FROM ubuntu:noble AS base
RUN apt update && apt install -y \
nodejs \
npm \
openjdk-11-jre \
protobuf-compiler \
jq
RUN node -v
RUN npm -v
FROM base AS builder
ENV GRADLE_USER_HOME=/sdk/gradle
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY gulpfile.js protohx.json ./
COPY tasks ./tasks
COPY config.example.json ./config.example.json
RUN --mount=type=secret,id=KEY_STORE \
cat /run/secrets/KEY_STORE > ./key.jks
RUN --mount=type=secret,id=KEY_PASS,env=KEY_PASS \
jq --arg key_pass "$KEY_PASS" \
'.SdkDir = "/sdk" | .PublishDir = "/app/publish" | .Key.store = "key.jks" | .Key.pass = $key_pass' \
config.example.json > config.json
COPY src ./src
COPY dependencies ./dependencies
VOLUME ["/sdk", "/app/build", "/app/target", "/app/publish", "/app/src-gen"]
CMD ["npx", "gulp", "default"]
FROM hello-world AS web
ARG PROJECT_NAME
VOLUME ["/app/${PROJECT_NAME}"]
COPY ./target/app/html5 /app/${PROJECT_NAME}/html5
COPY ./target/app/flash /app/${PROJECT_NAME}/flash
CMD ["/hello"]

View File

@@ -1,10 +1,13 @@
`.bashrc`
```bash
export NEKO_VERSION="2.2.0"
export HAXE_VERSION="4.2.5"
# Puzzle'z
export NEKO_SDK="$HOME/sdk/neko/$NEKO_VERSION"
export HAXE_SDK="$HOME/sdk/haxe/$HAXE_VERSION"
Puzzle game
alias haxe-activate=". $NEKO_SDK/activate && . $HAXE_SDK/activate"
```
![Puzzle'z](docs/Screenshot_2026-05-04_15-42-42.jpg "Puzzle'z")
## Play
https://shmyga.ru/puzzlez/html5/index.html
## Releases
https://git.shmyga.ru/InfernalGames/puzzlez/releases

23
docker-compose.yaml Normal file
View File

@@ -0,0 +1,23 @@
name: $PROJECT
services:
builder:
container_name: $PROJECT-builder
image: infernal-games/$PROJECT-builder
build:
context: .
target: builder
secrets:
- KEY_STORE
- KEY_PASS
volumes:
- ${SDK_PATH}:/sdk
- ${BUILD_PATH}:/app/build
- ${TARGET_PATH}:/app/target
- ${PUBLISH_PATH}:/app/publish
- ./src-gen:/app/src-gen
secrets:
KEY_STORE:
file: $KEY_STORE
KEY_PASS:
environment: KEY_PASS

Binary file not shown.

After

Width:  |  Height:  |  Size: 357 KiB

View File

@@ -1,139 +1,149 @@
const gulp = require('gulp');
const gulpClean = require('gulp-clean');
const Config = require('./config.json');
const packageInfo = require('./package.json');
const {System, Sdk, Haxe, Project} = require('gulp-haxetool');
const dateformat = require('dateformat');
const argv = require('yargs').argv;
const publish = require('./tasks/gulp-publish');
const gulp = require("gulp");
const gulpClean = require("gulp-clean");
const Config = require("./config.json");
const packageInfo = require("./package.json");
const { System, Sdk, Haxe, Project } = require("gulp-haxetool");
const dateformat = require("dateformat");
const argv = require("yargs").argv;
const publish = require("./tasks/gulp-publish");
Project.useRuffle();
if (packageInfo.haxe) {
Haxe.VERSION = packageInfo.haxe;
Haxe.VERSION = packageInfo.haxe;
}
if (Config.SdkDir) {
Sdk.dir = Config.SdkDir;
Sdk.dir = Config.SdkDir;
}
if (Config.BuildDir) {
Haxe.buildDir = Config.BuildDir;
Haxe.buildDir = Config.BuildDir;
}
exports.clean = function clean() {
return gulp.src('target/*', {read: false}).pipe(gulpClean());
return gulp.src("target/*", { read: false }).pipe(gulpClean());
};
exports.generate = function generate() {
return new Haxe().haxelib(['run', 'protohx', 'generate', 'protohx.json']).then(({stdout}) => {
if (stdout.indexOf('FAIL') > -1) {
throw stdout;
exports.setup = function setup() {
const builder = Project.Builder.new(
app.config,
Project.Platform.ANDROID,
app.buildSystem,
);
return builder.prepare();
};
exports.generate = function generate() {
return exports.setup().then(() =>
new Haxe()
.haxelib(["run", "protohx", "generate", "protohx.json"])
.then(({ stdout }) => {
if (stdout.indexOf("FAIL") > -1) {
throw stdout;
}
});
}),
);
};
const config = new Project.Config({
meta: {
title: 'Puzzle\'z',
filename: 'puzzlez',
icon: 'src/app/resources/icon.png',
pack: 'ru.m.puzzlez',
author: 'shmyga <shmyga.z@gmail.com>',
company: 'MegaLoMania',
version: packageInfo.version + (Config.Develop ? '-SNAPSHOT' : ''),
},
key: Config.Key,
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',
]
meta: {
title: "Puzzle'z",
filename: "puzzlez",
icon: "src/app/resources/icon.png",
pack: "ru.m.puzzlez",
author: "shmyga <shmyga.z@gmail.com>",
company: "MegaLoMania",
version: packageInfo.version + (Config.Develop ? "-SNAPSHOT" : ""),
},
key: Config.Key,
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"],
});
const host = argv.host || "localhost";
const port = argv.port || 5000;
const app = new Project(
Project.BuildSystem.OPENFL,
[
Project.Platform.FLASH,
Project.Platform.HTML5,
Project.Platform.LINUX,
Project.Platform.WINDOWS,
Project.Platform.ANDROID,
Project.BuildSystem.OPENFL,
[
Project.Platform.FLASH,
Project.Platform.HTML5,
Project.Platform.LINUX,
Project.Platform.WINDOWS,
Project.Platform.ANDROID,
],
config.branch({
name: "app",
sources: ["src-gen/haxe", "src/common/haxe", "src/app/haxe"],
android: [
{
path: "dependencies/android",
extensions: ["ru.m.android.FileUtil"],
},
],
config.branch({
name: 'app',
sources: [
'src-gen/haxe',
'src/common/haxe',
'src/app/haxe',
],
android: [{
path: 'dependencies/android',
extensions: ['ru.m.android.FileUtil'],
}],
assets: [
'src/app/resources',
],
main: 'ru.m.puzzlez.PuzzlezApp',
meta: {
width: 1280,
height: 768,
},
flags: [
'app',
]
}),
assets: ["src/app/resources"],
main: "ru.m.puzzlez.PuzzlezApp",
meta: {
width: 1280,
height: 768,
},
macros: [
`CompilationOption.set('host','${host}')`,
`CompilationOption.set('port',${port})`,
],
flags: ["app"],
}),
).bind(module, gulp);
const server = new Project(
Project.BuildSystem.HAXE,
[
Project.Platform.NEKO,
Project.Platform.CPP,
],
config.branch({
name: 'server',
sources: [
'src-gen/haxe',
'src/common/haxe',
'src/server/haxe',
],
main: 'ru.m.puzzlez.PuzzlezServer',
})
Project.BuildSystem.HAXE,
[Project.Platform.NEKO, Project.Platform.CPP],
config.branch({
name: "server",
sources: ["src-gen/haxe", "src/common/haxe", "src/server/haxe"],
main: "ru.m.puzzlez.PuzzlezServer",
}),
).bind(module, gulp);
module.exports.publish = publish(packageInfo.name, packageInfo.version, Config.PublishDir, Config.PublishUrl);
module.exports.publish = publish(
packageInfo.name,
packageInfo.version,
Config.PublishDir,
Config.PublishUrl,
);
const defaultSeries = [
exports.clean,
exports.generate,
module.exports['app:flash:build'],
module.exports['app:flash:html'],
module.exports['app:html5:build'],
exports.clean,
exports.generate,
module.exports["app:flash:build"],
module.exports["app:flash:html"],
module.exports["app:html5:build"],
];
if (System.isLinux) {
defaultSeries.push(
module.exports['app:linux:build'],
module.exports['app:linux:archive'],
module.exports['app:linux:deb'],
defaultSeries.push(
module.exports["app:linux:build"],
module.exports["app:linux:archive"],
module.exports["app:linux:deb"],
module.exports['app:android:build'],
);
module.exports["app:android:build"],
);
}
if (System.isWindows) {
defaultSeries.push(
module.exports['app:windows:build'],
module.exports['app:windows:archive'],
module.exports['app:windows:installer'],
);
defaultSeries.push(
module.exports["app:windows:build"],
module.exports["app:windows:archive"],
module.exports["app:windows:installer"],
);
}
defaultSeries.push(
exports.publish,
);
defaultSeries.push(exports.publish);
module.exports.default = gulp.series(defaultSeries);

View File

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

27325
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "puzzlez",
"version": "0.5.1",
"version": "0.6.1",
"private": true,
"devDependencies": {
"dateformat": "^3.0.3",
@@ -9,7 +9,7 @@
"gulp-add": "0.0.2",
"gulp-clean": "^0.4.0",
"gulp-cli": "^2.2.0",
"gulp-haxetool": "^0.1.9",
"gulp-haxetool": "^0.2.1",
"yargs": "^13.2.4"
},
"haxeDependencies": {
@@ -20,7 +20,8 @@
"svg": "1.1.3",
"protohx": "0.4.6",
"yield": "3.2.2",
"formatter": "1.16.0"
"formatter": "1.16.0",
"hxWebSockets": "1.4.0"
},
"haxe": "4.2.5"
}

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": [],
},
}

9
scripts/build Executable file
View File

@@ -0,0 +1,9 @@
#!/usr/bin/env bash
set -e
cd "$(dirname $(dirname "$0"))" || exit
source .env
mkdir -p "$SDK_PATH" "$PUBLISH_PATH" "$BUILD_PATH" "$TARGET_PATH" "src-gen"
docker compose run --rm --user $(id -u):$(id -g) --build --remove-orphans builder
./scripts/docker-action

45
scripts/docker-action Executable file
View File

@@ -0,0 +1,45 @@
#!/usr/bin/env bash
set -e
cd "$(dirname $(dirname "$0"))" || exit
. .env
build () {
echo "build: $1"
. "$1/.env"
for PROJECT in "${DOCKER_PROJECTS[@]}"; do
IFS=: read -r PROJECT_NAME PROJECT_TARGET <<< "$PROJECT"
ARGS=("build")
for ARG in ${DOCKER_ARGS[@]}; do
ARGS+=("--build-arg" "$ARG")
done
if [ -n "$PROJECT_TARGET" ]; then
ARGS+=("--target" "$PROJECT_TARGET")
fi
ARGS+=("-t" "$DOCKER_GROUP/$PROJECT_NAME" ".")
ARGS+=("-f" "$1/Dockerfile")
echo "${ARGS[@]}"
docker "${ARGS[@]}"
done
}
publish () {
echo "publish: $1"
. "$1/.env"
for PROJECT in "${DOCKER_PROJECTS[@]}"; do
IFS=: read -r PROJECT_NAME PROJECT_TARGET <<< "$PROJECT"
docker tag $DOCKER_GROUP/$PROJECT_NAME $DOCKER_ROOT/$PROJECT_NAME:$VERSION
docker tag $DOCKER_GROUP/$PROJECT_NAME $DOCKER_ROOT/$PROJECT_NAME:latest
docker push $DOCKER_ROOT/$PROJECT_NAME:$VERSION
docker push $DOCKER_ROOT/$PROJECT_NAME:latest
done
}
DEFAULT_TARGETS="."
TARGETS="${@-$DEFAULT_TARGETS}"
DOCKER_ACTION="${DOCKER_ACTION-build}"
for TARGET in $TARGETS; do
$DOCKER_ACTION "$TARGET"
done

35
scripts/publish Executable file
View File

@@ -0,0 +1,35 @@
#!/usr/bin/env bash
set -e
cd "$(dirname $(dirname "$0"))" || exit
source .env
PACKAGES=(
"android/${PROJECT}_${VERSION}.apk"
"debian/${PROJECT}_${VERSION}_all.deb"
"archive/${PROJECT}_${VERSION}_linux.tar.gz"
"archive/${PROJECT}_${VERSION}_win.zip"
"installer/${PROJECT}_${VERSION}.exe"
)
for PACKAGE in "${PACKAGES[@]}"
do
if [ -f "${PUBLISH_PATH}/${PACKAGE}" ]; then
PACKAGE_NAME=$(basename "$PACKAGE")
echo "publish: $PACKAGE"
curl --user "${PUBLISH_USER}:${PUBLISH_PASSWORD}" -X DELETE \
"${REPO}/generic/${PROJECT}/${VERSION}/${PACKAGE_NAME}"
curl --user "${PUBLISH_USER}:${PUBLISH_PASSWORD}" \
--upload-file "${PUBLISH_PATH}/${PACKAGE}" \
"${REPO}/generic/${PROJECT}/${VERSION}/${PACKAGE_NAME}"
if [[ "$PACKAGE" == *.deb ]]; then
curl --user "${PUBLISH_USER}:${PUBLISH_PASSWORD}" -X DELETE \
"${REPO}/debian/pool/noble/main/${PROJECT}/${VERSION}/all"
curl --user "${PUBLISH_USER}:${PUBLISH_PASSWORD}" \
--upload-file "${PUBLISH_PATH}/${PACKAGE}" \
"${REPO}/debian/pool/noble/main/upload"
fi
fi
done
DOCKER_ACTION=publish ./scripts/docker-action

View File

@@ -4,10 +4,13 @@ import hw.app.App;
import hw.app.Const;
import hw.log.TraceLogger;
import ru.m.puzzlez.image.ImageSourceBundle;
import ru.m.puzzlez.net.Network;
import ru.m.puzzlez.net.NetworkSource;
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;
@@ -17,16 +20,20 @@ import ru.m.update.Updater;
class PuzzlezApp {
@:provide static var updater:Updater;
@:provide static var sourceBundle:ImageSourceBundle;
@:provide static var network:Network;
public static function main() {
// ToDo: fix @:provide macro
Settings;
IPartBuilder;
GameStorage;
Network;
NetworkSource;
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();
@@ -34,5 +41,6 @@ class PuzzlezApp {
app.icon = openfl.Assets.getBitmapData("resources/icon.png");
app.view = new PuzzlezAppView();
L.d("Puzzlez", "started");
network.auth();
}
}

View File

@@ -11,19 +11,25 @@ import ru.m.skin.ButtonSVGSkin;
class PuzzlezTheme extends Theme {
private static var ICONS:Map<String, String> = [
"close" => "times-circle-solid.svg",
"setting" => "cog-solid.svg",
"image" => "image-polaroid.svg",
"lock" => "lock-alt-solid.svg",
"restore" => "window-restore-solid.svg",
"compress" => "compress-solid.svg",
"expand" => "expand-solid.svg",
"spread" => "clone.svg",
"close" => "close-circle.svg",
"setting" => "cog.svg",
"image" => "image.svg",
"lock" => "lock.svg",
"lock-open" => "lock-open.svg",
"restore" => "window-restore.svg",
"compress" => "arrow-collapse-all.svg",
"expand" => "arrow-expand-all.svg",
"shuffle" => "shuffle.svg",
"spread" => "format-align-justify.svg",
];
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,
@@ -37,6 +43,7 @@ class PuzzlezTheme extends Theme {
"geometry.height" => SizeValue.fromString("8h"),
"skin" => function() return new ButtonSVGSkin(),
"skin.color" => colors.light,
"skin.svgScale" => 16.0,
]));
for (key in ICONS.keys()) {
register(new Style('icon.${key}', ["skin.svg" => Assets.getText('resources/icon/${ICONS.get(key)}'),]));

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

@@ -5,7 +5,6 @@ import hw.connect.IConnection;
import hw.signal.Signal;
import hw.storage.SharedObjectStorage;
import promhx.Promise;
import ru.m.data.IDataSource;
import ru.m.puzzlez.proto.core.User;
import ru.m.puzzlez.proto.event.GameAction;
import ru.m.puzzlez.proto.event.GameEvent;
@@ -13,6 +12,7 @@ import ru.m.puzzlez.proto.game.GamePreset;
import ru.m.puzzlez.proto.game.GameState;
import ru.m.puzzlez.proto.pack.AuthRequest;
import ru.m.puzzlez.proto.pack.GameActionRequest;
import ru.m.puzzlez.proto.pack.GameCreateRequest;
import ru.m.puzzlez.proto.pack.GameJoinRequest;
import ru.m.puzzlez.proto.pack.GameLeaveRequest;
import ru.m.puzzlez.proto.pack.GameListRequest;
@@ -21,21 +21,26 @@ import ru.m.puzzlez.proto.pack.NotificationResponse;
import ru.m.puzzlez.proto.pack.Request;
import ru.m.puzzlez.proto.pack.Response;
@:provide class Network implements IDataSource<String, GameState> {
using ru.m.SignalUtil;
@:provide class Network {
public var userSignal:Signal<User> = new Signal();
public var notificationSignal:Signal<NotificationResponse> = new Signal();
public var listSignal:Signal<GameListResponse> = new Signal();
public var joinSignal:Signal<GameState> = new Signal();
public var gameEventSignal:Signal<GameEvent> = new Signal();
private var connection:IConnection<Request, Response>;
public var connection:IConnection<Request, Response>;
private var storage:SharedObjectStorage;
private static var USER_KEY = "user";
public function new() {
storage = new SharedObjectStorage("network/2");
connection = ConnectionFactory.buildClientConnection("127.0.0.1", 5000, Response);
var host:String = CompilationOption.get("host");
var port:Int = cast(CompilationOption.get("port"), Int);
connection = ConnectionFactory.buildClientConnection(host, port, Response);
connection.handler.connect(onConnectionChange);
connection.receiveHandler.connect(onReceivePacket);
connection.connect().catchError(_ -> {});
@@ -45,17 +50,23 @@ import ru.m.puzzlez.proto.pack.Response;
if (storage.exists(USER_KEY)) {
return storage.read(USER_KEY);
} else {
return new User().setName('Anonimus #${IdUtil.generate()}');
var uuid = IdUtil.generate();
return new User().setUuid(uuid).setName('Anonimus #${uuid}');
}
}
public function auth():Promise<User> {
connection.send(new Request().setAuth(new AuthRequest().setUser(restoreUser())));
return userSignal.next();
try {
connection.send(new Request().setAuth(new AuthRequest().setUser(restoreUser())));
return userSignal.next();
} catch (error:Dynamic) {
L.w("network", "auth", error);
return Promise.promise(null);
}
}
public function createGame(preset:GamePreset):Promise<GameState> {
connection.send(new Request().setJoin(new GameJoinRequest().setPreset(preset)));
connection.send(new Request().setCreate(new GameCreateRequest().setPreset(preset)));
return joinSignal.next();
}
@@ -99,22 +110,4 @@ import ru.m.puzzlez.proto.pack.Response;
}
}
}
public function getPage(page:Page):Promise<DataPage<GameState>> {
connection.send(new Request().setList(new GameListRequest().setCount(page.count).setPage(page.index)));
return listSignal.next().then((list:GameListResponse) -> ({
page: {
index: list.page,
count: list.count,
filter: null,
order: null,
},
total: list.total,
data: list.games,
}));
}
public function get(id:String):GameState {
return null;
}
}

View File

@@ -2,10 +2,12 @@ package ru.m.puzzlez.net;
import ru.m.puzzlez.proto.event.GameStart;
import hw.signal.Signal;
import ru.m.puzzlez.image.IGame;
import ru.m.puzzlez.game.IGame;
import ru.m.puzzlez.proto.event.GameAction;
import ru.m.puzzlez.proto.event.GameEvent;
import ru.m.puzzlez.proto.game.GameState;
import ru.m.puzzlez.proto.event.gameaction.Action;
import ru.m.puzzlez.game.EventUtil;
class NetworkGame implements IGame {
public var state(default, null):GameState;
@@ -19,7 +21,11 @@ class NetworkGame implements IGame {
}
public function action(action:GameAction):Void {
network.sendGameAction(action);
if (action.action == Action.MOVE) {
events.emit(EventUtil.action(action));
} else {
network.sendGameAction(action);
}
}
public function start():Void {

View File

@@ -0,0 +1,28 @@
package ru.m.puzzlez.net;
import promhx.Promise;
import ru.m.data.DataSource;
import ru.m.puzzlez.proto.game.GameState;
import ru.m.puzzlez.proto.pack.GameListRequest;
import ru.m.puzzlez.proto.pack.GameListResponse;
import ru.m.puzzlez.proto.pack.Request;
using ru.m.SignalUtil;
@:provide class NetworkSource implements DataSource<GameState> {
@:provide private var network:Network;
public function getPage(page:Page):Promise<DataPage<GameState>> {
network.connection.send(new Request().setList(new GameListRequest().setCount(page.count).setPage(page.index)));
return network.listSignal.next().then((list:GameListResponse) -> ({
page: {
index: list.page,
count: list.count,
filter: null,
order: null,
},
total: list.total,
data: list.games,
}));
}
}

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

@@ -1,6 +1,7 @@
package ru.m.puzzlez.settings;
import hw.storage.SharedObjectStorage;
import ru.m.puzzlez.proto.game.ImageId;
import ru.m.puzzlez.render.Background;
@:provide class Settings extends SharedObjectStorage {
@@ -10,8 +11,12 @@ import ru.m.puzzlez.render.Background;
public var background(get, set):Background;
private function defaultBackground():Background {
return Background.IMAGE(new ImageId().setSource('asset').setId('resources/texture/pool-table.png'));
}
private inline function get_background():Background {
return exists(BACKGROUND_KEY) ? read(BACKGROUND_KEY) : NONE;
return exists(BACKGROUND_KEY) ? read(BACKGROUND_KEY) : this.defaultBackground();
}
private inline function set_background(value:Background):Background {
@@ -22,7 +27,7 @@ import ru.m.puzzlez.render.Background;
public var locked(get, set):Bool;
private inline function get_locked():Bool {
return exists(LOCKED_KEY) ? read(LOCKED_KEY) : false;
return exists(LOCKED_KEY) ? read(LOCKED_KEY) : true;
}
private inline function set_locked(value:Bool):Bool {

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,83 @@
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;
private var cache:Map<String, Promise<NginxResponse>> = new Map();
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");
var url = category != null ? this.baseUrl + category + "/" : this.baseUrl;
if (!this.cache.exists(url)) {
this.cache.set(url, new JsonLoader<NginxResponse>().GET(url));
}
return this.cache.get(url).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));
}
}
data = data.slice(page.index * page.count, (page.index + 1) * page.count);
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, "@", "/");
// TODO: default size by screen width?
var width = thumb ? 360 : 1920;
var parts = url.split(".");
var index = parts.length - 2;
parts[index] = parts[index] + '-w${width}';
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

@@ -7,6 +7,7 @@ import hw.view.popup.ConfirmView;
import promhx.Promise;
import ru.m.puzzlez.game.Game;
import ru.m.puzzlez.game.IGame;
import ru.m.puzzlez.net.NetworkGame;
import ru.m.puzzlez.proto.event.GameAction;
import ru.m.puzzlez.proto.event.GameEvent;
import ru.m.puzzlez.proto.event.gameaction.Action;
@@ -34,9 +35,10 @@ 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);
game = new NetworkGame(state);
} else {
game = new Game(state);
}

View File

@@ -22,11 +22,13 @@ views:
settings.locked = render.manager.locked;
cast(button, hw.view.form.ToggleButtonView).on = !render.manager.locked;
}
visible: false
- $type: hw.view.form.ButtonView
style: icon.restore
+onPress: ~render.manager.reset()
visible: false
- $type: hw.view.form.ButtonView
style: icon.spread
style: icon.shuffle
+onPress: ~shuffle()
- $type: hw.view.form.ButtonView
style: icon.spread

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

@@ -6,6 +6,7 @@ import hw.view.form.ToggleButtonView;
import hw.view.frame.FrameSwitcher;
import hw.view.frame.FrameView;
import ru.m.puzzlez.game.GameUtil;
import ru.m.puzzlez.net.Network;
import ru.m.puzzlez.proto.game.ImageId;
import ru.m.puzzlez.view.common.PresetView;
@@ -17,7 +18,7 @@ import ru.m.puzzlez.view.common.PresetView;
@:provide var switcher:FrameSwitcher;
// @:provide var network:Network;
@:provide var network:Network;
private var imageId:ImageId;
public function new() {
@@ -48,13 +49,14 @@ 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]);
}
private function start(online:Bool = false):Void {
if (online) {
// network.createGame(imageView.state.preset).then(state -> switcher.change(GameFrame.ID, state));
network.createGame(imageView.state.preset).then(state -> switcher.change(GameFrame.ID, state));
} else {
switcher.change(GameFrame.ID, imageView.state);
}

View File

@@ -29,7 +29,7 @@ views:
geometry.margin.left: 15
text: Network
+onPress: ~start(true)
visible: false
visible: true
- id: image
$type: ru.m.puzzlez.view.common.PresetView
geometry.stretch: true

View File

@@ -4,8 +4,9 @@ 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.net.NetworkSource;
import ru.m.puzzlez.proto.game.GameStatus;
import ru.m.puzzlez.storage.FileStorage;
import ru.m.puzzlez.storage.GameStorage;
@@ -27,24 +28,46 @@ import ru.m.update.Updater;
@:provide var fileStorage:FileStorage;
@:provide var gameStorage:GameStorage;
@:provide var sourceBundle:ImageSourceBundle;
@:provide var networkSource:NetworkSource;
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]
};
private var networkGames:GameListConfig = {
title: "Network",
source: networkSource
};
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 +85,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 +98,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

@@ -39,6 +39,10 @@ views:
$type: hw.view.form.ButtonView
text: Completed
+onPress: ~showGames(completedGames)
- id: networkButton
$type: hw.view.form.ButtonView
text: Network
+onPress: ~showGames(networkGames)
- $type: hw.view.form.ButtonView
text: Upload
geometry.margin.left: 30

View File

@@ -13,6 +13,7 @@ using hw.color.ColorUtil;
@:style(null) public var svg:String;
@:style(0) public var color:Null<Color>;
@:style(false) public var solid:Null<Bool>;
@:style(0.8) public var svgScale:Null<Float>;
private var svgs:Map<ButtonState, SVG>;
private var needUpdate:Bool;
@@ -60,7 +61,7 @@ using hw.color.ColorUtil;
graphics.lineStyle(2, color.multiply(1.5));
}
// ToDo: padding
svg.render(graphics, 0, 0, Std.int(view.width * 0.8), Std.int(view.height * 0.8));
svg.render(graphics, 0, 0, Std.int(view.width * this.svgScale), Std.int(view.height * this.svgScale));
graphics.lineStyle();
graphics.endFill();
}

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),
}
});
});

View File

@@ -1 +0,0 @@
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="arrow-alt-circle-left" class="svg-inline--fa fa-arrow-alt-circle-left fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 504C119 504 8 393 8 256S119 8 256 8s248 111 248 248-111 248-248 248zm116-292H256v-70.9c0-10.7-13-16.1-20.5-8.5L121.2 247.5c-4.7 4.7-4.7 12.2 0 16.9l114.3 114.9c7.6 7.6 20.5 2.2 20.5-8.5V300h116c6.6 0 12-5.4 12-12v-64c0-6.6-5.4-12-12-12z"></path></svg>

Before

Width:  |  Height:  |  Size: 503 B

View File

@@ -1 +0,0 @@
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="arrow-alt-circle-right" class="svg-inline--fa fa-arrow-alt-circle-right fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 8c137 0 248 111 248 248S393 504 256 504 8 393 8 256 119 8 256 8zM140 300h116v70.9c0 10.7 13 16.1 20.5 8.5l114.3-114.9c4.7-4.7 4.7-12.2 0-16.9l-114.3-115c-7.6-7.6-20.5-2.2-20.5 8.5V212H140c-6.6 0-12 5.4-12 12v64c0 6.6 5.4 12 12 12z"></path></svg>

Before

Width:  |  Height:  |  Size: 499 B

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path fill="currentColor"
d="M19.5,3.09L20.91,4.5L16.41,9H20V11H13V4H15V7.59L19.5,3.09M20.91,19.5L19.5,20.91L15,16.41V20H13V13H20V15H16.41L20.91,19.5M4.5,3.09L9,7.59V4H11V11H4V9H7.59L3.09,4.5L4.5,3.09M3.09,19.5L7.59,15H4V13H11V20H9V16.41L4.5,20.91L3.09,19.5Z" />
</svg>

After

Width:  |  Height:  |  Size: 336 B

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path fill="currentColor"
d="M9.5,13.09L10.91,14.5L6.41,19H10V21H3V14H5V17.59L9.5,13.09M10.91,9.5L9.5,10.91L5,6.41V10H3V3H10V5H6.41L10.91,9.5M14.5,13.09L19,17.59V14H21V21H14V19H17.59L13.09,14.5L14.5,13.09M13.09,9.5L17.59,5H14V3H21V10H19V6.41L14.5,10.91L13.09,9.5Z" />
</svg>

After

Width:  |  Height:  |  Size: 341 B

View File

@@ -1 +0,0 @@
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="bars" class="svg-inline--fa fa-bars fa-w-14" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z"></path></svg>

Before

Width:  |  Height:  |  Size: 569 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M64 464H288c8.8 0 16-7.2 16-16V384h48v64c0 35.3-28.7 64-64 64H64c-35.3 0-64-28.7-64-64V224c0-35.3 28.7-64 64-64h64v48H64c-8.8 0-16 7.2-16 16V448c0 8.8 7.2 16 16 16zM224 304H448c8.8 0 16-7.2 16-16V64c0-8.8-7.2-16-16-16H224c-8.8 0-16 7.2-16 16V288c0 8.8 7.2 16 16 16zm-64-16V64c0-35.3 28.7-64 64-64H448c35.3 0 64 28.7 64 64V288c0 35.3-28.7 64-64 64H224c-35.3 0-64-28.7-64-64z"/></svg>

Before

Width:  |  Height:  |  Size: 604 B

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path fill="currentColor"
d="M12,2C17.53,2 22,6.47 22,12C22,17.53 17.53,22 12,22C6.47,22 2,17.53 2,12C2,6.47 6.47,2 12,2M15.59,7L12,10.59L8.41,7L7,8.41L10.59,12L7,15.59L8.41,17L12,13.41L15.59,17L17,15.59L13.41,12L17,8.41L15.59,7Z" />
</svg>

After

Width:  |  Height:  |  Size: 307 B

View File

@@ -1 +0,0 @@
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="cog" class="svg-inline--fa fa-cog fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M487.4 315.7l-42.6-24.6c4.3-23.2 4.3-47 0-70.2l42.6-24.6c4.9-2.8 7.1-8.6 5.5-14-11.1-35.6-30-67.8-54.7-94.6-3.8-4.1-10-5.1-14.8-2.3L380.8 110c-17.9-15.4-38.5-27.3-60.8-35.1V25.8c0-5.6-3.9-10.5-9.4-11.7-36.7-8.2-74.3-7.8-109.2 0-5.5 1.2-9.4 6.1-9.4 11.7V75c-22.2 7.9-42.8 19.8-60.8 35.1L88.7 85.5c-4.9-2.8-11-1.9-14.8 2.3-24.7 26.7-43.6 58.9-54.7 94.6-1.7 5.4.6 11.2 5.5 14L67.3 221c-4.3 23.2-4.3 47 0 70.2l-42.6 24.6c-4.9 2.8-7.1 8.6-5.5 14 11.1 35.6 30 67.8 54.7 94.6 3.8 4.1 10 5.1 14.8 2.3l42.6-24.6c17.9 15.4 38.5 27.3 60.8 35.1v49.2c0 5.6 3.9 10.5 9.4 11.7 36.7 8.2 74.3 7.8 109.2 0 5.5-1.2 9.4-6.1 9.4-11.7v-49.2c22.2-7.9 42.8-19.8 60.8-35.1l42.6 24.6c4.9 2.8 11 1.9 14.8-2.3 24.7-26.7 43.6-58.9 54.7-94.6 1.5-5.5-.7-11.3-5.6-14.1zM256 336c-44.1 0-80-35.9-80-80s35.9-80 80-80 80 35.9 80 80-35.9 80-80 80z"></path></svg>

Before

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path fill="currentColor"
d="M12,15.5A3.5,3.5 0 0,1 8.5,12A3.5,3.5 0 0,1 12,8.5A3.5,3.5 0 0,1 15.5,12A3.5,3.5 0 0,1 12,15.5M19.43,12.97C19.47,12.65 19.5,12.33 19.5,12C19.5,11.67 19.47,11.34 19.43,11L21.54,9.37C21.73,9.22 21.78,8.95 21.66,8.73L19.66,5.27C19.54,5.05 19.27,4.96 19.05,5.05L16.56,6.05C16.04,5.66 15.5,5.32 14.87,5.07L14.5,2.42C14.46,2.18 14.25,2 14,2H10C9.75,2 9.54,2.18 9.5,2.42L9.13,5.07C8.5,5.32 7.96,5.66 7.44,6.05L4.95,5.05C4.73,4.96 4.46,5.05 4.34,5.27L2.34,8.73C2.21,8.95 2.27,9.22 2.46,9.37L4.57,11C4.53,11.34 4.5,11.67 4.5,12C4.5,12.33 4.53,12.65 4.57,12.97L2.46,14.63C2.27,14.78 2.21,15.05 2.34,15.27L4.34,18.73C4.46,18.95 4.73,19.03 4.95,18.95L7.44,17.94C7.96,18.34 8.5,18.68 9.13,18.93L9.5,21.58C9.54,21.82 9.75,22 10,22H14C14.25,22 14.46,21.82 14.5,21.58L14.87,18.93C15.5,18.67 16.04,18.34 16.56,17.94L19.05,18.95C19.27,19.03 19.54,18.95 19.66,18.73L21.66,15.27C21.78,15.05 21.73,14.78 21.54,14.63L19.43,12.97Z" />
</svg>

After

Width:  |  Height:  |  Size: 1014 B

View File

@@ -1 +0,0 @@
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="compress" class="svg-inline--fa fa-compress fa-w-14" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M436 192H312c-13.3 0-24-10.7-24-24V44c0-6.6 5.4-12 12-12h40c6.6 0 12 5.4 12 12v84h84c6.6 0 12 5.4 12 12v40c0 6.6-5.4 12-12 12zm-276-24V44c0-6.6-5.4-12-12-12h-40c-6.6 0-12 5.4-12 12v84H12c-6.6 0-12 5.4-12 12v40c0 6.6 5.4 12 12 12h124c13.3 0 24-10.7 24-24zm0 300V344c0-13.3-10.7-24-24-24H12c-6.6 0-12 5.4-12 12v40c0 6.6 5.4 12 12 12h84v84c0 6.6 5.4 12 12 12h40c6.6 0 12-5.4 12-12zm192 0v-84h84c6.6 0 12-5.4 12-12v-40c0-6.6-5.4-12-12-12H312c-13.3 0-24 10.7-24 24v124c0 6.6 5.4 12 12 12h40c6.6 0 12-5.4 12-12z"></path></svg>

Before

Width:  |  Height:  |  Size: 741 B

View File

@@ -1 +0,0 @@
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="expand" class="svg-inline--fa fa-expand fa-w-14" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M0 180V56c0-13.3 10.7-24 24-24h124c6.6 0 12 5.4 12 12v40c0 6.6-5.4 12-12 12H64v84c0 6.6-5.4 12-12 12H12c-6.6 0-12-5.4-12-12zM288 44v40c0 6.6 5.4 12 12 12h84v84c0 6.6 5.4 12 12 12h40c6.6 0 12-5.4 12-12V56c0-13.3-10.7-24-24-24H300c-6.6 0-12 5.4-12 12zm148 276h-40c-6.6 0-12 5.4-12 12v84h-84c-6.6 0-12 5.4-12 12v40c0 6.6 5.4 12 12 12h124c13.3 0 24-10.7 24-24V332c0-6.6-5.4-12-12-12zM160 468v-40c0-6.6-5.4-12-12-12H64v-84c0-6.6-5.4-12-12-12H12c-6.6 0-12 5.4-12 12v124c0 13.3 10.7 24 24 24h124c6.6 0 12-5.4 12-12z"></path></svg>

Before

Width:  |  Height:  |  Size: 740 B

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path fill="currentColor" d="M3,3H21V5H3V3M3,7H21V9H3V7M3,11H21V13H3V11M3,15H21V17H3V15M3,19H21V21H3V19Z" />
</svg>

After

Width:  |  Height:  |  Size: 178 B

View File

@@ -1,6 +0,0 @@
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="image-polaroid" role="img"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" class="svg-inline--fa fa-image-polaroid fa-w-14 fa-2x">
<path fill="currentColor"
d="M128 192a32 32 0 1 0-32-32 32 32 0 0 0 32 32zM416 32H32A32 32 0 0 0 0 64v384a32 32 0 0 0 32 32h384a32 32 0 0 0 32-32V64a32 32 0 0 0-32-32zm-32 320H64V96h320zM268.8 209.07a16 16 0 0 0-25.6 0l-49.32 65.75L173.31 244a16 16 0 0 0-26.62 0L96 320h256z"
class=""></path>
</svg>

Before

Width:  |  Height:  |  Size: 540 B

View File

@@ -1 +0,0 @@
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="image" class="svg-inline--fa fa-image fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M464 448H48c-26.51 0-48-21.49-48-48V112c0-26.51 21.49-48 48-48h416c26.51 0 48 21.49 48 48v288c0 26.51-21.49 48-48 48zM112 120c-30.928 0-56 25.072-56 56s25.072 56 56 56 56-25.072 56-56-25.072-56-56-56zM64 384h384V272l-87.515-87.515c-4.686-4.686-12.284-4.686-16.971 0L208 320l-55.515-55.515c-4.686-4.686-12.284-4.686-16.971 0L64 336v48z"></path></svg>

Before

Width:  |  Height:  |  Size: 564 B

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path fill="currentColor"
d="M8.5,13.5L11,16.5L14.5,12L19,18H5M21,19V5C21,3.89 20.1,3 19,3H5A2,2 0 0,0 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19Z" />
</svg>

After

Width:  |  Height:  |  Size: 222 B

View File

@@ -1,6 +0,0 @@
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="lock-alt" role="img"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" class="svg-inline--fa fa-lock-alt fa-w-14 fa-2x">
<path fill="currentColor"
d="M400 224h-24v-72C376 68.2 307.8 0 224 0S72 68.2 72 152v72H48c-26.5 0-48 21.5-48 48v192c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48V272c0-26.5-21.5-48-48-48zM264 392c0 22.1-17.9 40-40 40s-40-17.9-40-40v-48c0-22.1 17.9-40 40-40s40 17.9 40 40v48zm32-168H152v-72c0-39.7 32.3-72 72-72s72 32.3 72 72v72z"
class=""></path>
</svg>

Before

Width:  |  Height:  |  Size: 582 B

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path fill="currentColor"
d="M18,8A2,2 0 0,1 20,10V20A2,2 0 0,1 18,22H6C4.89,22 4,21.1 4,20V10A2,2 0 0,1 6,8H15V6A3,3 0 0,0 12,3A3,3 0 0,0 9,6H7A5,5 0 0,1 12,1A5,5 0 0,1 17,6V8H18M12,17A2,2 0 0,0 14,15A2,2 0 0,0 12,13A2,2 0 0,0 10,15A2,2 0 0,0 12,17Z" />
</svg>

After

Width:  |  Height:  |  Size: 328 B

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path fill="currentColor"
d="M12,17A2,2 0 0,0 14,15C14,13.89 13.1,13 12,13A2,2 0 0,0 10,15A2,2 0 0,0 12,17M18,8A2,2 0 0,1 20,10V20A2,2 0 0,1 18,22H6A2,2 0 0,1 4,20V10C4,8.89 4.9,8 6,8H7V6A5,5 0 0,1 12,1A5,5 0 0,1 17,6V8H18M12,3A3,3 0 0,0 9,6V8H15V6A3,3 0 0,0 12,3Z" />
</svg>

After

Width:  |  Height:  |  Size: 342 B

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path fill="currentColor"
d="M14.83,13.41L13.42,14.82L16.55,17.95L14.5,20H20V14.5L17.96,16.54L14.83,13.41M14.5,4L16.54,6.04L4,18.59L5.41,20L17.96,7.46L20,9.5V4M10.59,9.17L5.41,4L4,5.41L9.17,10.58L10.59,9.17Z" />
</svg>

After

Width:  |  Height:  |  Size: 285 B

View File

@@ -1 +0,0 @@
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="times-circle" class="svg-inline--fa fa-times-circle fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8zm121.6 313.1c4.7 4.7 4.7 12.3 0 17L338 377.6c-4.7 4.7-12.3 4.7-17 0L256 312l-65.1 65.6c-4.7 4.7-12.3 4.7-17 0L134.4 338c-4.7-4.7-4.7-12.3 0-17l65.6-65-65.6-65.1c-4.7-4.7-4.7-12.3 0-17l39.6-39.6c4.7-4.7 12.3-4.7 17 0l65 65.7 65.1-65.6c4.7-4.7 12.3-4.7 17 0l39.6 39.6c4.7 4.7 4.7 12.3 0 17L312 256l65.6 65.1z"></path></svg>

Before

Width:  |  Height:  |  Size: 619 B

View File

@@ -1 +0,0 @@
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="window-close" class="svg-inline--fa fa-window-close fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M464 32H48C21.5 32 0 53.5 0 80v352c0 26.5 21.5 48 48 48h416c26.5 0 48-21.5 48-48V80c0-26.5-21.5-48-48-48zm-83.6 290.5c4.8 4.8 4.8 12.6 0 17.4l-40.5 40.5c-4.8 4.8-12.6 4.8-17.4 0L256 313.3l-66.5 67.1c-4.8 4.8-12.6 4.8-17.4 0l-40.5-40.5c-4.8-4.8-4.8-12.6 0-17.4l67.1-66.5-67.1-66.5c-4.8-4.8-4.8-12.6 0-17.4l40.5-40.5c4.8-4.8 12.6-4.8 17.4 0l66.5 67.1 66.5-67.1c4.8-4.8 12.6-4.8 17.4 0l40.5 40.5c4.8 4.8 4.8 12.6 0 17.4L313.3 256l67.1 66.5z"></path></svg>

Before

Width:  |  Height:  |  Size: 681 B

View File

@@ -1,6 +0,0 @@
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="window-restore" role="img"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" class="svg-inline--fa fa-window-restore fa-w-16 fa-2x">
<path fill="currentColor"
d="M512 48v288c0 26.5-21.5 48-48 48h-48V176c0-44.1-35.9-80-80-80H128V48c0-26.5 21.5-48 48-48h288c26.5 0 48 21.5 48 48zM384 176v288c0 26.5-21.5 48-48 48H48c-26.5 0-48-21.5-48-48V176c0-26.5 21.5-48 48-48h288c26.5 0 48 21.5 48 48zm-68 28c0-6.6-5.4-12-12-12H76c-6.6 0-12 5.4-12 12v52h252v-52z"
class=""></path>
</svg>

Before

Width:  |  Height:  |  Size: 580 B

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path fill="currentColor" d="M4,8H8V4H20V16H16V20H4V8M16,8V14H18V6H10V8H16M6,12V18H14V12H6Z" />
</svg>

After

Width:  |  Height:  |  Size: 165 B

View File

@@ -0,0 +1,18 @@
package ru.m;
import hw.signal.Signal;
import promhx.Deferred;
import promhx.Promise;
class SignalUtil {
public static function next<T>(signal:Signal<T>):Promise<T> {
var d:Deferred<T> = new Deferred<T>();
var receiver:T->Void;
receiver = (value:T) -> {
signal.disconnect(receiver);
d.resolve(value);
};
signal.connect(receiver);
return d.promise();
}
}

View File

@@ -5,8 +5,8 @@ import ru.m.puzzlez.proto.pack.GameListResponse;
import ru.m.puzzlez.proto.pack.GameListRequest;
import hw.connect.session.ProtoSession;
import hw.log.BaseLogger.LoggerUtil;
import ru.m.puzzlez.image.Game;
import ru.m.puzzlez.image.GameUtil;
import ru.m.puzzlez.game.Game;
import ru.m.puzzlez.game.GameUtil;
import ru.m.puzzlez.proto.core.User;
import ru.m.puzzlez.proto.event.GameAction;
import ru.m.puzzlez.proto.event.GameEvent;