[common] add DesktopConnection
This commit is contained in:
@@ -79,7 +79,7 @@ class Init {
|
||||
#elseif html5
|
||||
connection = new ru.m.connect.js.JsConnection<Request, Response>(host, 5000, Response);
|
||||
#else
|
||||
connection = new ru.m.connect.fake.FakeConnection<Request, Response>(Response);
|
||||
connection = new ru.m.connect.desktop.DesktopConnection<Request, Response>(host, 5000, Response);
|
||||
#end
|
||||
networkManager = new NetworkManager();
|
||||
}
|
||||
|
||||
@@ -146,5 +146,7 @@ class Style {
|
||||
registerButton("close", "times-circle-solid.svg");
|
||||
registerButton("next", "arrow-alt-circle-right-solid.svg");
|
||||
registerButton("start", "play-circle-solid.svg");
|
||||
registerButton("login", "sign-in-solid.svg");
|
||||
registerButton("logout", "sign-out-solid.svg");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,70 +1,80 @@
|
||||
package ru.m.tankz.network;
|
||||
|
||||
import ru.m.tankz.storage.MultiplayerStorage;
|
||||
import haxework.storage.IStorage;
|
||||
import haxework.signal.Signal;
|
||||
import ru.m.tankz.proto.pack.GameRequest;
|
||||
import ru.m.tankz.proto.core.GameProto;
|
||||
import ru.m.tankz.proto.pack.StartGameRequest;
|
||||
import ru.m.tankz.proto.game.GameChangeProto;
|
||||
import ru.m.tankz.proto.game.GameActionTypeProto;
|
||||
import ru.m.tankz.proto.pack.GameUpdateRequest;
|
||||
import ru.m.connect.IConnection;
|
||||
import ru.m.tankz.control.Control;
|
||||
import ru.m.tankz.proto.core.GameInfoProto;
|
||||
import ru.m.tankz.proto.game.GameActionTypeProto;
|
||||
import ru.m.tankz.proto.game.GameChangeProto;
|
||||
import ru.m.tankz.proto.pack.CreateGameRequest;
|
||||
import ru.m.tankz.proto.pack.GameUpdateRequest;
|
||||
import ru.m.tankz.proto.pack.JoinGameRequest;
|
||||
import ru.m.tankz.proto.pack.LeaveGameRequest;
|
||||
import ru.m.tankz.proto.pack.CreateGameRequest;
|
||||
import ru.m.connect.IConnection;
|
||||
import ru.m.tankz.proto.core.GameInfoProto;
|
||||
import ru.m.tankz.proto.pack.ListGameRequest;
|
||||
import ru.m.tankz.proto.pack.LoginRequest;
|
||||
import ru.m.tankz.proto.pack.LogoutRequest;
|
||||
import ru.m.tankz.proto.pack.Request;
|
||||
import ru.m.tankz.proto.pack.Response;
|
||||
import ru.m.tankz.proto.pack.StartGameRequest;
|
||||
import ru.m.tankz.storage.MultiplayerStorage;
|
||||
|
||||
typedef ClientConnection = IConnection<Request, Response>;
|
||||
|
||||
enum ConnectionState {
|
||||
OFFLINE;
|
||||
CONNECT;
|
||||
CONNECTED;
|
||||
LOGIN;
|
||||
ONLINE(user:User);
|
||||
ERROR(error:Dynamic);
|
||||
}
|
||||
|
||||
class NetworkManager {
|
||||
|
||||
public var state(default, null):String;
|
||||
public var stateSignal:Signal<String>;
|
||||
public var state(default, null):ConnectionState;
|
||||
public var stateSignal:Signal<ConnectionState>;
|
||||
public var listGameSignal:Signal<Array<GameInfoProto>>;
|
||||
public var gameSignal:Signal<GameInfoProto>;
|
||||
public var gameUpdateSignal:Signal<Array<GameChangeProto>>;
|
||||
public var user(default, null):User;
|
||||
public var game(default, set):NetworkGame;
|
||||
|
||||
@:provide private var connection:ClientConnection;
|
||||
@:provide private var storage:MultiplayerStorage;
|
||||
|
||||
public function new() {
|
||||
stateSignal = new Signal<String>();
|
||||
listGameSignal = new Signal<Array<GameInfoProto>>();
|
||||
gameSignal = new Signal<GameInfoProto>();
|
||||
gameUpdateSignal = new Signal<Array<GameChangeProto>>();
|
||||
updateState('offline');
|
||||
stateSignal = new Signal();
|
||||
listGameSignal = new Signal();
|
||||
gameSignal = new Signal();
|
||||
gameUpdateSignal = new Signal();
|
||||
updateState(OFFLINE);
|
||||
connection.handler.connect(onConnectionEvent);
|
||||
connection.receiveHandler.connect(onResponse);
|
||||
user = storage.user;
|
||||
if (user == null) {
|
||||
user = {name: 'User', uuid: null};
|
||||
var user = storage.user;
|
||||
if (user != null) {
|
||||
login(user.name, user.uuid);
|
||||
}
|
||||
}
|
||||
|
||||
private function updateState(value:String):Void {
|
||||
private function updateState(value:ConnectionState):Void {
|
||||
state = value;
|
||||
stateSignal.emit(value);
|
||||
}
|
||||
|
||||
public function login(name:String):Void {
|
||||
user.name = name;
|
||||
updateState('connect...');
|
||||
public function login(name:String, uuid:String = null):Void {
|
||||
updateState(CONNECT);
|
||||
connection.connect().then(function(c:ClientConnection) {
|
||||
updateState('login...');
|
||||
updateState(LOGIN);
|
||||
c.send(new Request().setLogin(
|
||||
new LoginRequest()
|
||||
.setUuid(user.uuid)
|
||||
.setName(user.name)
|
||||
.setUuid(uuid)
|
||||
.setName(name)
|
||||
));
|
||||
}).catchError(function(_) {});
|
||||
}).catchError(function(error) {
|
||||
updateState(ERROR(error));
|
||||
});
|
||||
}
|
||||
|
||||
public function logout():Void {
|
||||
connection.send(new Request().setLogout(new LogoutRequest()));
|
||||
}
|
||||
|
||||
public function listGame():Void {
|
||||
@@ -108,30 +118,30 @@ class NetworkManager {
|
||||
}
|
||||
|
||||
private function onConnectionEvent(event:ConnectionEvent):Void {
|
||||
updateState(switch (event) {
|
||||
case ConnectionEvent.CONNECTED:
|
||||
updateState(switch event {
|
||||
case CONNECTED:
|
||||
L.d('Network', '$event');
|
||||
'connected';
|
||||
case ConnectionEvent.DISCONNECTED:
|
||||
CONNECTED;
|
||||
case DISCONNECTED:
|
||||
L.d('Network', '$event');
|
||||
'offline';
|
||||
case ConnectionEvent.ERROR(error):
|
||||
OFFLINE;
|
||||
case ERROR(error):
|
||||
L.e('Network', '$error', error);
|
||||
'error';
|
||||
ERROR(error);
|
||||
});
|
||||
}
|
||||
|
||||
private function onResponse(packet:Response):Void {
|
||||
if (packet.hasLogin()) {
|
||||
user = {
|
||||
var user = {
|
||||
uuid: packet.login.user.uuid,
|
||||
name: packet.login.user.name,
|
||||
};
|
||||
storage.user = user;
|
||||
updateState('online');
|
||||
updateState(ONLINE(user));
|
||||
} else if (packet.hasLogout()) {
|
||||
user = null;
|
||||
updateState('connected');
|
||||
storage.user = null;
|
||||
updateState(CONNECTED);
|
||||
} else if (packet.hasListGame()) {
|
||||
listGameSignal.emit(packet.listGame.games);
|
||||
} else if (packet.hasCreateGame()) {
|
||||
@@ -145,13 +155,7 @@ class NetworkManager {
|
||||
} else if (packet.hasUpdateGame()) {
|
||||
gameUpdateSignal.emit(packet.updateGame.changes);
|
||||
} else if (packet.hasGame()) {
|
||||
game.load(packet.game.game);
|
||||
//game.load(packet.game.game);
|
||||
}
|
||||
}
|
||||
|
||||
private function set_game(value:NetworkGame):NetworkGame {
|
||||
this.game = value;
|
||||
connection.send(new Request().setGame(new GameRequest()));
|
||||
return this.game;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,69 @@
|
||||
package ru.m.tankz.view;
|
||||
|
||||
import haxework.view.ButtonView;
|
||||
import haxework.view.LabelView;
|
||||
import haxework.view.frame.FrameSwitcher;
|
||||
import haxework.view.VGroupView;
|
||||
import ru.m.tankz.game.GameState;
|
||||
import ru.m.tankz.network.NetworkManager;
|
||||
import ru.m.tankz.Type.GameType;
|
||||
import ru.m.tankz.view.popup.FontPopup;
|
||||
import ru.m.tankz.view.popup.LoginPopup;
|
||||
|
||||
@:template class StartFrame extends VGroupView {
|
||||
|
||||
public static var ID(default, never):String = "start";
|
||||
|
||||
@:view var username:LabelView;
|
||||
@:view("login") var loginButton:ButtonView;
|
||||
@:view("logout") var logoutButton:ButtonView;
|
||||
@:view("network") var networkButton:ButtonView;
|
||||
|
||||
@:provide var state:GameState;
|
||||
@:provide var switcher:FrameSwitcher;
|
||||
@:provide var network:NetworkManager;
|
||||
|
||||
private var fontPopup:FontPopup;
|
||||
|
||||
public function onShow():Void {
|
||||
onConnectionState(network.state);
|
||||
network.stateSignal.connect(onConnectionState);
|
||||
}
|
||||
|
||||
public function onHide():Void {
|
||||
network.stateSignal.disconnect(onConnectionState);
|
||||
}
|
||||
|
||||
private function onConnectionState(state:ConnectionState):Void {
|
||||
trace("state", state);
|
||||
setUser(switch state {
|
||||
case ONLINE(user): user;
|
||||
case _: null;
|
||||
});
|
||||
}
|
||||
|
||||
private function setUser(value:User):Void {
|
||||
username.text = value == null ? "" : value.name;
|
||||
loginButton.visible = value == null;
|
||||
logoutButton.visible = value != null;
|
||||
networkButton.disabled = value == null;
|
||||
}
|
||||
|
||||
private function startGame(type:GameType):Void {
|
||||
state = new GameState(type);
|
||||
switcher.change(LevelFrame.ID);
|
||||
}
|
||||
|
||||
private function login():Void {
|
||||
LoginPopup.instance.show().then(function(user:User):Void {
|
||||
L.d("Login", 'user: $user');
|
||||
});
|
||||
}
|
||||
|
||||
private function logout():Void {
|
||||
network.logout();
|
||||
}
|
||||
|
||||
private function choiceFont():Void {
|
||||
if (fontPopup == null) {
|
||||
fontPopup = new FontPopup();
|
||||
|
||||
@@ -1,40 +1,63 @@
|
||||
---
|
||||
views:
|
||||
- $type: haxework.view.VGroupView
|
||||
skinId: container
|
||||
layout.margin: 10
|
||||
views:
|
||||
- $type: haxework.view.LabelView
|
||||
text: Tank'z
|
||||
skinId: font
|
||||
fontSize: 100
|
||||
geometry.margin.bottom: 30
|
||||
- $type: haxework.view.ButtonView
|
||||
skinId: button
|
||||
+onPress: $code:startGame('classic')
|
||||
text: Classic
|
||||
- $type: haxework.view.ButtonView
|
||||
skinId: button
|
||||
+onPress: $code:startGame('dota')
|
||||
text: DotA
|
||||
- $type: haxework.view.ButtonView
|
||||
skinId: button
|
||||
+onPress: $code:startGame('death')
|
||||
text: DeathMatch
|
||||
- $type: haxework.view.ButtonView
|
||||
skinId: button
|
||||
+onPress: $code:switcher.change('record')
|
||||
text: Records
|
||||
- $type: haxework.view.HGroupView
|
||||
skinId: panel
|
||||
views:
|
||||
- id: settings
|
||||
$type: haxework.view.ButtonView
|
||||
skinId: button.settings
|
||||
+onPress: $code:switcher.change('settings')
|
||||
- $type: haxework.view.SpriteView
|
||||
geometry.size.width: 100%
|
||||
- $type: haxework.view.LabelView
|
||||
geometry.padding: [20, 5]
|
||||
skinId: text.box
|
||||
text: $r:text:version
|
||||
- $type: haxework.view.VGroupView
|
||||
skinId: container
|
||||
layout.margin: 10
|
||||
views:
|
||||
- $type: haxework.view.LabelView
|
||||
text: Tank'z
|
||||
skinId: font
|
||||
fontSize: 100
|
||||
geometry.margin.bottom: 30
|
||||
- $type: haxework.view.ButtonView
|
||||
skinId: button
|
||||
+onPress: $code:startGame('classic')
|
||||
text: Classic
|
||||
- $type: haxework.view.ButtonView
|
||||
skinId: button
|
||||
+onPress: $code:startGame('dota')
|
||||
text: DotA
|
||||
- $type: haxework.view.ButtonView
|
||||
skinId: button
|
||||
+onPress: $code:startGame('death')
|
||||
text: DeathMatch
|
||||
- $type: haxework.view.ButtonView
|
||||
skinId: button
|
||||
+onPress: $code:switcher.change('record')
|
||||
text: Records
|
||||
- id: network
|
||||
$type: haxework.view.ButtonView
|
||||
skinId: button
|
||||
# +onPress: $code:switcher.change('record')
|
||||
text: Network
|
||||
disabled: true
|
||||
- $type: haxework.view.HGroupView
|
||||
skinId: panel
|
||||
views:
|
||||
- id: settings
|
||||
$type: haxework.view.ButtonView
|
||||
skinId: button.settings
|
||||
+onPress: $code:switcher.change('settings')
|
||||
- $type: haxework.view.SpriteView
|
||||
geometry.size.width: 100%
|
||||
- id: username
|
||||
$type: haxework.view.LabelView
|
||||
skinId: text
|
||||
geometry.margin.right: 10
|
||||
- id: login
|
||||
$type: haxework.view.ButtonView
|
||||
skinId: button.login
|
||||
+onPress: $code:login()
|
||||
- id: logout
|
||||
$type: haxework.view.ButtonView
|
||||
skinId: button.logout
|
||||
+onPress: $code:logout()
|
||||
visible: false
|
||||
- $type: haxework.view.LabelView
|
||||
geometry.hAlign: right
|
||||
geometry.vAlign: top
|
||||
geometry.padding: [20, 5]
|
||||
geometry.position: absolute
|
||||
geometry.margin: [0, 20, 20, 0]
|
||||
skinId: text.box
|
||||
text: $r:text:version
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
---
|
||||
view:
|
||||
$type: haxework.view.VGroupView
|
||||
geometry.size.width: 400
|
||||
geometry.size.height: 80%
|
||||
geometry.padding: 10
|
||||
geometry.hAlign: center
|
||||
geometry.vAlign: middle
|
||||
skinId: dark
|
||||
views:
|
||||
- id: fonts
|
||||
$type: haxework.view.list.VListView
|
||||
geometry.size.stretch: true
|
||||
factory: $this:fontViewFactory
|
||||
+onItemSelect: $code:function(item) close(item.data)
|
||||
scroll:
|
||||
$type: haxework.view.list.VScrollBarView
|
||||
skinId: scroll.vertical
|
||||
- $type: haxework.view.HGroupView
|
||||
geometry.size.width: 100%
|
||||
geometry.margin.top: 10
|
||||
layout.hAlign: right
|
||||
views:
|
||||
- $type: haxework.view.ButtonView
|
||||
skinId: button.simple
|
||||
text: Cancel
|
||||
+onPress: $code:reject('cancel')
|
||||
$type: haxework.view.VGroupView
|
||||
geometry.size.width: 400
|
||||
geometry.size.height: 80%
|
||||
geometry.padding: 10
|
||||
geometry.hAlign: center
|
||||
geometry.vAlign: middle
|
||||
skinId: dark
|
||||
views:
|
||||
- id: fonts
|
||||
$type: haxework.view.list.VListView
|
||||
geometry.size.stretch: true
|
||||
factory: $this:fontViewFactory
|
||||
+onItemSelect: $code:function(item) close(item.data)
|
||||
scroll:
|
||||
$type: haxework.view.list.VScrollBarView
|
||||
skinId: scroll.vertical
|
||||
- $type: haxework.view.HGroupView
|
||||
geometry.size.width: 100%
|
||||
geometry.margin.top: 10
|
||||
layout.hAlign: right
|
||||
views:
|
||||
- $type: haxework.view.ButtonView
|
||||
skinId: button.simple
|
||||
text: Cancel
|
||||
+onPress: $code:reject('cancel')
|
||||
|
||||
@@ -2,34 +2,34 @@
|
||||
layout.hAlign: center
|
||||
layout.vAlign: middle
|
||||
view:
|
||||
$type: haxework.view.VGroupView
|
||||
layout.hAlign: center
|
||||
geometry.size.width: 400
|
||||
geometry.size.height: 400
|
||||
skinId: window
|
||||
views:
|
||||
- $type: haxework.view.HGroupView
|
||||
geometry.size.width: 100%
|
||||
geometry.padding: 10
|
||||
layout.vAlign: middle
|
||||
views:
|
||||
- id: name
|
||||
$type: haxework.view.LabelView
|
||||
geometry.size.width: 100%
|
||||
geometry.margin.left: 10
|
||||
layout.hAlign: left
|
||||
skinId: text
|
||||
- $type: haxework.view.ButtonView
|
||||
skinId: window.close
|
||||
+onPress: $code:reject('close')
|
||||
- $type: haxework.view.SpriteView
|
||||
geometry.size.height: 100%
|
||||
- id: presets
|
||||
$type: haxework.view.DataView
|
||||
factory: $this:presetViewFactory
|
||||
+onDataSelect: $this:onPresetSelect
|
||||
layout:
|
||||
$type: haxework.view.layout.HorizontalLayout
|
||||
hAlign: center
|
||||
margin: 5
|
||||
skinId: panel
|
||||
$type: haxework.view.VGroupView
|
||||
layout.hAlign: center
|
||||
geometry.size.width: 400
|
||||
geometry.size.height: 400
|
||||
skinId: window
|
||||
views:
|
||||
- $type: haxework.view.HGroupView
|
||||
geometry.size.width: 100%
|
||||
geometry.padding: 10
|
||||
layout.vAlign: middle
|
||||
views:
|
||||
- id: name
|
||||
$type: haxework.view.LabelView
|
||||
geometry.size.width: 100%
|
||||
geometry.margin.left: 10
|
||||
layout.hAlign: left
|
||||
skinId: text
|
||||
- $type: haxework.view.ButtonView
|
||||
skinId: window.close
|
||||
+onPress: $code:reject('close')
|
||||
- $type: haxework.view.SpriteView
|
||||
geometry.size.height: 100%
|
||||
- id: presets
|
||||
$type: haxework.view.DataView
|
||||
factory: $this:presetViewFactory
|
||||
+onDataSelect: $this:onPresetSelect
|
||||
layout:
|
||||
$type: haxework.view.layout.HorizontalLayout
|
||||
hAlign: center
|
||||
margin: 5
|
||||
skinId: panel
|
||||
|
||||
47
src/client/haxe/ru/m/tankz/view/popup/LoginPopup.hx
Normal file
47
src/client/haxe/ru/m/tankz/view/popup/LoginPopup.hx
Normal file
@@ -0,0 +1,47 @@
|
||||
package ru.m.tankz.view.popup;
|
||||
|
||||
import haxework.log.BaseLogger.LoggerUtil;
|
||||
import haxework.view.InputView;
|
||||
import haxework.view.popup.PopupView;
|
||||
import haxework.view.TextView;
|
||||
import ru.m.tankz.network.NetworkManager;
|
||||
|
||||
@:template class LoginPopup extends PopupView<User> {
|
||||
|
||||
@:view var username:InputView;
|
||||
@:view var password:InputView;
|
||||
@:view var error:TextView;
|
||||
|
||||
@:provide static var network:NetworkManager;
|
||||
|
||||
override private function onShow():Void {
|
||||
super.onShow();
|
||||
network.stateSignal.connect(onStateChange);
|
||||
}
|
||||
|
||||
override private function onClose():Void {
|
||||
super.onClose();
|
||||
network.stateSignal.disconnect(onStateChange);
|
||||
}
|
||||
|
||||
private function onStateChange(state:ConnectionState):Void {
|
||||
switch state {
|
||||
case ONLINE(user): close(user);
|
||||
case ERROR(error): this.error.text = LoggerUtil.printError(error);
|
||||
case _: this.error.text = null;
|
||||
}
|
||||
}
|
||||
|
||||
private function submit():Void {
|
||||
network.login(username.text);
|
||||
}
|
||||
|
||||
public static var instance(get, null):LoginPopup;
|
||||
|
||||
private static function get_instance():LoginPopup {
|
||||
if (instance == null) {
|
||||
instance = new LoginPopup();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
64
src/client/haxe/ru/m/tankz/view/popup/LoginPopup.yaml
Normal file
64
src/client/haxe/ru/m/tankz/view/popup/LoginPopup.yaml
Normal file
@@ -0,0 +1,64 @@
|
||||
---
|
||||
layout.hAlign: center
|
||||
layout.vAlign: middle
|
||||
view:
|
||||
$type: haxework.view.VGroupView
|
||||
layout.hAlign: center
|
||||
geometry.size.width: 400
|
||||
# geometry.size.height: 400
|
||||
skinId: window
|
||||
views:
|
||||
- $type: haxework.view.HGroupView
|
||||
geometry.size.width: 100%
|
||||
geometry.padding: 10
|
||||
layout.vAlign: middle
|
||||
views:
|
||||
- id: name
|
||||
$type: haxework.view.LabelView
|
||||
geometry.size.width: 100%
|
||||
geometry.margin.left: 10
|
||||
layout.hAlign: left
|
||||
skinId: text
|
||||
text: Login
|
||||
- $type: haxework.view.ButtonView
|
||||
skinId: window.close
|
||||
+onPress: $code:reject('close')
|
||||
- $type: haxework.view.VGroupView
|
||||
geometry.size.width: 100%
|
||||
# geometry.size.stretch: true
|
||||
geometry.padding: 20
|
||||
layout.margin: 5
|
||||
views:
|
||||
- $type: haxework.view.LabelView
|
||||
geometry.size.width: 100%
|
||||
skinId: text
|
||||
text: Username
|
||||
- id: username
|
||||
$type: haxework.view.InputView
|
||||
geometry.size.width: 100%
|
||||
geometry.size.height: 28
|
||||
skinId: text.box
|
||||
- $type: haxework.view.LabelView
|
||||
geometry.size.width: 100%
|
||||
skinId: text
|
||||
text: Password
|
||||
- id: password
|
||||
$type: haxework.view.InputView
|
||||
textField.displayAsPassword: true
|
||||
geometry.size.width: 100%
|
||||
geometry.size.height: 28
|
||||
skinId: text.box
|
||||
- id: error
|
||||
$type: haxework.view.TextView
|
||||
geometry.size.width: 100%
|
||||
skinId: text
|
||||
fill: false
|
||||
- $type: haxework.view.HGroupView
|
||||
layout.hAlign: center
|
||||
layout.margin: 5
|
||||
skinId: panel
|
||||
views:
|
||||
- $type: haxework.view.ButtonView
|
||||
skinId: button.simple
|
||||
text: Submit
|
||||
+onPress: $code:submit()
|
||||
1
src/client/resources/image/icon/sign-in-solid.svg
Normal file
1
src/client/resources/image/icon/sign-in-solid.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="sign-in" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" class="svg-inline--fa fa-sign-in fa-w-16 fa-2x"><path fill="currentColor" d="M137.2 110.3l21.9-21.9c9.3-9.3 24.5-9.4 33.9-.1L344.9 239c9.5 9.4 9.5 24.7 0 34.1L193 423.7c-9.4 9.3-24.5 9.3-33.9-.1l-21.9-21.9c-9.7-9.7-9.3-25.4.8-34.7l77.6-71.1H24c-13.3 0-24-10.7-24-24v-32c0-13.3 10.7-24 24-24h191.5l-77.6-71.1c-10-9.1-10.4-24.9-.7-34.5zM512 352V160c0-53-43-96-96-96h-84c-6.6 0-12 5.4-12 12v40c0 6.6 5.4 12 12 12h84c17.7 0 32 14.3 32 32v192c0 17.7-14.3 32-32 32h-84c-6.6 0-12 5.4-12 12v40c0 6.6 5.4 12 12 12h84c53 0 96-43 96-96z" class=""></path></svg>
|
||||
|
After Width: | Height: | Size: 698 B |
1
src/client/resources/image/icon/sign-out-solid.svg
Normal file
1
src/client/resources/image/icon/sign-out-solid.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="sign-out" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" class="svg-inline--fa fa-sign-out fa-w-16 fa-2x"><path fill="currentColor" d="M180 448H96c-53 0-96-43-96-96V160c0-53 43-96 96-96h84c6.6 0 12 5.4 12 12v40c0 6.6-5.4 12-12 12H96c-17.7 0-32 14.3-32 32v192c0 17.7 14.3 32 32 32h84c6.6 0 12 5.4 12 12v40c0 6.6-5.4 12-12 12zm117.9-303.1l77.6 71.1H184c-13.3 0-24 10.7-24 24v32c0 13.3 10.7 24 24 24h191.5l-77.6 71.1c-10.1 9.2-10.4 25-.8 34.7l21.9 21.9c9.3 9.3 24.5 9.4 33.9.1l152-150.8c9.5-9.4 9.5-24.7 0-34.1L353 88.3c-9.4-9.3-24.5-9.3-33.9.1l-21.9 21.9c-9.7 9.6-9.3 25.4.7 34.6z" class=""></path></svg>
|
||||
|
After Width: | Height: | Size: 695 B |
Reference in New Issue
Block a user