mirror of
https://gitlab.com/freepascal.org/fpc/pas2js.git
synced 2025-08-29 23:20:13 +02:00
* Add websocket example
This commit is contained in:
parent
208bd1c1ab
commit
1adc178690
35
demo/websockets/README.md
Normal file
35
demo/websockets/README.md
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
# Websocket demo
|
||||||
|
|
||||||
|
This demo is intended to run with the wsserver demo of the FPC websocket
|
||||||
|
support example wsserver. It is a small chat server client.
|
||||||
|
|
||||||
|
# Running the example
|
||||||
|
|
||||||
|
To run it, run the FPC wsserver example application (see fcl-web/examples/websocket/server).
|
||||||
|
```sh
|
||||||
|
wsserver -p 8080
|
||||||
|
```
|
||||||
|
|
||||||
|
Edit the serverconfig.js and set the correct server URL:
|
||||||
|
```json
|
||||||
|
var
|
||||||
|
serverConfig = {
|
||||||
|
"url": "ws://localhost:8080/"
|
||||||
|
};
|
||||||
|
```
|
||||||
|
(change port etc. to match your setup)
|
||||||
|
|
||||||
|
Compile the demowebsocket example using lazarus or pa2js.
|
||||||
|
|
||||||
|
run simpleserver (or any other webserver) in this directory:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
simpleserver -p 3000
|
||||||
|
```
|
||||||
|
|
||||||
|
and point your browser at port 3000:
|
||||||
|
```text
|
||||||
|
http://localhost:3000/
|
||||||
|
```
|
||||||
|
|
||||||
|
|
90
demo/websockets/demowebsocket.lpi
Normal file
90
demo/websockets/demowebsocket.lpi
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<CONFIG>
|
||||||
|
<ProjectOptions>
|
||||||
|
<Version Value="12"/>
|
||||||
|
<General>
|
||||||
|
<Flags>
|
||||||
|
<MainUnitHasCreateFormStatements Value="False"/>
|
||||||
|
<MainUnitHasTitleStatement Value="False"/>
|
||||||
|
<MainUnitHasScaledStatement Value="False"/>
|
||||||
|
<Runnable Value="False"/>
|
||||||
|
</Flags>
|
||||||
|
<SessionStorage Value="InProjectDir"/>
|
||||||
|
<Title Value="demowebsocket"/>
|
||||||
|
<UseAppBundle Value="False"/>
|
||||||
|
<ResourceType Value="res"/>
|
||||||
|
</General>
|
||||||
|
<CustomData Count="4">
|
||||||
|
<Item0 Name="MaintainHTML" Value="1"/>
|
||||||
|
<Item1 Name="PasJSHTMLFile" Value="project1.html"/>
|
||||||
|
<Item2 Name="PasJSPort" Value="0"/>
|
||||||
|
<Item3 Name="PasJSWebBrowserProject" Value="1"/>
|
||||||
|
</CustomData>
|
||||||
|
<BuildModes>
|
||||||
|
<Item Name="Default" Default="True"/>
|
||||||
|
</BuildModes>
|
||||||
|
<PublishOptions>
|
||||||
|
<Version Value="2"/>
|
||||||
|
<UseFileFilters Value="True"/>
|
||||||
|
</PublishOptions>
|
||||||
|
<RunParams>
|
||||||
|
<FormatVersion Value="2"/>
|
||||||
|
</RunParams>
|
||||||
|
<Units>
|
||||||
|
<Unit>
|
||||||
|
<Filename Value="demowebsocket.lpr"/>
|
||||||
|
<IsPartOfProject Value="True"/>
|
||||||
|
</Unit>
|
||||||
|
<Unit>
|
||||||
|
<Filename Value="index.html"/>
|
||||||
|
<IsPartOfProject Value="True"/>
|
||||||
|
<CustomData Count="1">
|
||||||
|
<Item0 Name="PasJSIsProjectHTMLFile" Value="1"/>
|
||||||
|
</CustomData>
|
||||||
|
</Unit>
|
||||||
|
</Units>
|
||||||
|
</ProjectOptions>
|
||||||
|
<CompilerOptions>
|
||||||
|
<Version Value="11"/>
|
||||||
|
<Target FileExt=".js">
|
||||||
|
<Filename Value="demowebsocket"/>
|
||||||
|
</Target>
|
||||||
|
<SearchPaths>
|
||||||
|
<IncludeFiles Value="$(ProjOutDir)"/>
|
||||||
|
<UnitOutputDirectory Value="js"/>
|
||||||
|
</SearchPaths>
|
||||||
|
<Parsing>
|
||||||
|
<SyntaxOptions>
|
||||||
|
<AllowLabel Value="False"/>
|
||||||
|
<CPPInline Value="False"/>
|
||||||
|
<UseAnsiStrings Value="False"/>
|
||||||
|
</SyntaxOptions>
|
||||||
|
</Parsing>
|
||||||
|
<CodeGeneration>
|
||||||
|
<TargetOS Value="browser"/>
|
||||||
|
</CodeGeneration>
|
||||||
|
<Linking>
|
||||||
|
<Debugging>
|
||||||
|
<GenerateDebugInfo Value="False"/>
|
||||||
|
<UseLineInfoUnit Value="False"/>
|
||||||
|
</Debugging>
|
||||||
|
</Linking>
|
||||||
|
<Other>
|
||||||
|
<CustomOptions Value="-Jeutf-8 -Jirtl.js -Jc -Jminclude"/>
|
||||||
|
<CompilerPath Value="$(pas2js)"/>
|
||||||
|
</Other>
|
||||||
|
</CompilerOptions>
|
||||||
|
<Debugging>
|
||||||
|
<Exceptions>
|
||||||
|
<Item>
|
||||||
|
<Name Value="EAbort"/>
|
||||||
|
</Item>
|
||||||
|
<Item>
|
||||||
|
<Name Value="ECodetoolError"/>
|
||||||
|
</Item>
|
||||||
|
<Item>
|
||||||
|
<Name Value="EFOpenError"/>
|
||||||
|
</Item>
|
||||||
|
</Exceptions>
|
||||||
|
</Debugging>
|
||||||
|
</CONFIG>
|
252
demo/websockets/demowebsocket.lpr
Normal file
252
demo/websockets/demowebsocket.lpr
Normal file
@ -0,0 +1,252 @@
|
|||||||
|
program demowebsocket;
|
||||||
|
|
||||||
|
{$mode objfpc}
|
||||||
|
{$modeswitch externalclass}
|
||||||
|
|
||||||
|
uses
|
||||||
|
browserconsole, browserapp, JS, Classes, SysUtils, Web, strutils;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
type
|
||||||
|
TServerConfig = Class External name 'Object' (TJSObject)
|
||||||
|
url : string;
|
||||||
|
end;
|
||||||
|
|
||||||
|
Var
|
||||||
|
ServerConfig : TServerConfig; external name 'serverConfig';
|
||||||
|
|
||||||
|
|
||||||
|
Type
|
||||||
|
{ TMyApplication }
|
||||||
|
TMsgKind = (mkIncoming,mkOutgoing,mkSystem);
|
||||||
|
|
||||||
|
TMyApplication = class(TBrowserApplication)
|
||||||
|
btnSend: TJSHTMLButtonElement;
|
||||||
|
EdtSender : TJSHTMLInputElement;
|
||||||
|
EdtMessage : TJSHTMLInputElement;
|
||||||
|
EdtRecipient : TJSHTMLInputElement;
|
||||||
|
divMessages : TJSHTMLDivElement;
|
||||||
|
btnConnect : TJSHTMLButtonElement;
|
||||||
|
WS : TJSWebSocket;
|
||||||
|
procedure doRun; override;
|
||||||
|
private
|
||||||
|
function AppendIcon(aParent: TJSHTMLElement; aName: String): TJSHTMLElement;
|
||||||
|
function CreateMessageEl(aKind : TMsgKind): TJSHTMLElement;
|
||||||
|
procedure DisplayClose;
|
||||||
|
procedure DisplayOpen;
|
||||||
|
function DoClosed(Event: TEventListenerEvent): boolean;
|
||||||
|
procedure doConnect;
|
||||||
|
function DoIncomingMessage(Event: TEventListenerEvent): boolean;
|
||||||
|
procedure DisplayMessage(Sender, Msg: String; Incoming: Boolean=True);
|
||||||
|
function DoOpen(Event: TEventListenerEvent): boolean;
|
||||||
|
function DoReconnect(aEvent: TJSMouseEvent): boolean;
|
||||||
|
function DoSendClick(aEvent: TJSMouseEvent): boolean;
|
||||||
|
function DoToggleConnectClick(aEvent: TJSMouseEvent): boolean;
|
||||||
|
procedure SendMessage(const aRecipient, aMessage: String);
|
||||||
|
end;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
procedure TMyApplication.doRun;
|
||||||
|
|
||||||
|
begin
|
||||||
|
edtRecipient:=TJSHTMLInputElement(GetHTMLElement('edtRecipient'));
|
||||||
|
edtSender:=TJSHTMLInputElement(GetHTMLElement('edtSender'));
|
||||||
|
edtMessage:=TJSHTMLInputElement(GetHTMLElement('edtMessage'));
|
||||||
|
btnConnect:=TJSHTMLButtonElement(GetHTMLElement('btnConnect'));
|
||||||
|
btnConnect.onclick:=@DoToggleConnectClick;
|
||||||
|
btnSend:=TJSHTMLButtonElement(GetHTMLElement('btnSend'));
|
||||||
|
btnSend.onclick:=@DoSendClick;
|
||||||
|
divMessages:=TJSHTMLDivElement(GetHTMLElement('messages'));
|
||||||
|
DoConnect;
|
||||||
|
Terminate;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TMyApplication.doConnect;
|
||||||
|
|
||||||
|
Var
|
||||||
|
URL,aHost : string;
|
||||||
|
|
||||||
|
begin
|
||||||
|
URL:='';
|
||||||
|
if Assigned(ServerConfig) and isString(serverConfig.URL) then
|
||||||
|
URL:=serverConfig.URL;
|
||||||
|
if URL='' then
|
||||||
|
begin
|
||||||
|
aHost:=window.location.host;
|
||||||
|
aHost:=ExtractWord(1,aHost,[':']);
|
||||||
|
URL:='ws://'+aHost+':8080/';
|
||||||
|
end;
|
||||||
|
try
|
||||||
|
WS:=TJSWebsocket.New(url);
|
||||||
|
WS.onmessage:=@DoIncomingMessage;
|
||||||
|
WS.onclose:=@DoClosed;
|
||||||
|
WS.onopen:=@DoOpen;
|
||||||
|
except
|
||||||
|
on TJSError do
|
||||||
|
Window.Alert('Could not connect to websocket server at '+URL);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TMyApplication.DoOpen(Event: TEventListenerEvent): boolean;
|
||||||
|
begin
|
||||||
|
btnSend.disabled:=False;
|
||||||
|
DisplayOpen;
|
||||||
|
btnConnect.InnerText:='Disconnect';
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TMyApplication.DoClosed(Event: TEventListenerEvent): boolean;
|
||||||
|
begin
|
||||||
|
btnSend.disabled:=true;
|
||||||
|
btnConnect.InnerText:='Connect';
|
||||||
|
DisplayClose;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TMyApplication.DoIncomingMessage(Event: TEventListenerEvent): boolean;
|
||||||
|
|
||||||
|
Var
|
||||||
|
Msg: TJSMessageEvent absolute Event;
|
||||||
|
JS : TJSObject;
|
||||||
|
|
||||||
|
begin
|
||||||
|
if isString(Msg.Data) then
|
||||||
|
begin
|
||||||
|
JS:=TJSJSON.parseObject(String(Msg.Data));
|
||||||
|
DisplayMessage(String(JS['from']),String(JS['msg']),True)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
DisplayMessage('','<<unknown data arrived>>',True);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TMyApplication.CreateMessageEl(aKind : TMsgKind): TJSHTMLElement;
|
||||||
|
|
||||||
|
Var
|
||||||
|
ImgDiv,ImgEl,msgEl : TJSHTMLElement;
|
||||||
|
|
||||||
|
begin
|
||||||
|
MsgEl:=TJSHTMLElement(Document.createElement('DIV'));
|
||||||
|
if aKind=mkSystem then
|
||||||
|
begin
|
||||||
|
MsgEl.className:='text-center my-2';
|
||||||
|
end
|
||||||
|
else
|
||||||
|
begin
|
||||||
|
msgEl.className:='d-flex align-items-center'+Ifthen(aKind=mkIncoming,'',' text-right justify-content-end');
|
||||||
|
ImgDiv:=TJSHTMLElement(Document.createElement('DIV'));
|
||||||
|
ImgDiv.ClassName:='text-left pr-1';
|
||||||
|
ImgEl:=TJSHTMLElement(Document.createElement('IMG'));
|
||||||
|
ImgEl['Src']:=IfThen(aKind=mkIncoming,'guest','you')+'.png';
|
||||||
|
ImgDiv.AppendChild(ImgEl);
|
||||||
|
if aKind=mkIncoming then
|
||||||
|
msgEl.AppendChild(ImgDiv);
|
||||||
|
end;
|
||||||
|
Result:=TJSHTMLElement(Document.createElement(IfThen(aKind=mkSystem,'span','div')));
|
||||||
|
if aKind=mkSystem then
|
||||||
|
Result.className:='between'
|
||||||
|
else
|
||||||
|
Result.className:='pr-2'+IfThen(aKind=mkIncoming,' pl-1','');
|
||||||
|
msgEl.AppendChild(Result);
|
||||||
|
if aKind=mkOutgoing then
|
||||||
|
msgEl.AppendChild(ImgDiv);
|
||||||
|
divMessages.appendChild(msgEl);
|
||||||
|
msgEl.scrollIntoView;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TMyApplication.AppendIcon(aParent: TJSHTMLElement; aName: String
|
||||||
|
): TJSHTMLElement;
|
||||||
|
|
||||||
|
begin
|
||||||
|
Result:=TJSHTMLElement(Document.createElement('i'));
|
||||||
|
Result.className:='fas fa-'+aName+' mr-3';
|
||||||
|
aParent.AppendChild(Result);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TMyApplication.DisplayClose;
|
||||||
|
|
||||||
|
Var
|
||||||
|
iEl,pEl : TJSHTMLElement;
|
||||||
|
|
||||||
|
begin
|
||||||
|
Pel:=CreateMessageEl(mkSystem);
|
||||||
|
iEl:=AppendIcon(Pel,'plug');
|
||||||
|
iEl.onclick:=@DoReconnect;
|
||||||
|
pEl.AppendChild(Document.createTextNode('Connection closed, click icon to reconnect'));
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TMyApplication.DisplayOpen;
|
||||||
|
|
||||||
|
Var
|
||||||
|
pEl : TJSHTMLElement;
|
||||||
|
|
||||||
|
begin
|
||||||
|
Pel:=CreateMessageEl(mkSystem);
|
||||||
|
AppendIcon(Pel,'link');
|
||||||
|
pEl.AppendChild(Document.createTextNode('Connection open, you can start messaging'));
|
||||||
|
end;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
procedure TMyApplication.DisplayMessage(Sender,Msg: String; Incoming: Boolean = True);
|
||||||
|
|
||||||
|
Const
|
||||||
|
kinds : Array[Boolean] of TMsgKind = (mkOutgoing,mkIncoming);
|
||||||
|
|
||||||
|
Var
|
||||||
|
pEl,pEl2 : TJSHTMLElement;
|
||||||
|
|
||||||
|
begin
|
||||||
|
pEl:=CreateMessageEl(Kinds[Incoming]);
|
||||||
|
if (Sender<>'') then
|
||||||
|
begin
|
||||||
|
pEl2:=TJSHTMLElement(Document.createElement('span'));
|
||||||
|
pEl2.className:='name';
|
||||||
|
pEl2.innerText:=Sender;
|
||||||
|
pEl.appendChild(pEl2);
|
||||||
|
end;
|
||||||
|
pEl2:=TJSHTMLElement(Document.createElement('p'));
|
||||||
|
PEL2.className:='msg';
|
||||||
|
PEL2.AppendChild(Document.createTextNode(Msg));
|
||||||
|
pEl.AppendChild(pEL2);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TMyApplication.DoReconnect(aEvent: TJSMouseEvent): boolean;
|
||||||
|
begin
|
||||||
|
DoConnect;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TMyApplication.SendMessage(const aRecipient,aMessage: String);
|
||||||
|
|
||||||
|
Var
|
||||||
|
JS : TJSObject;
|
||||||
|
|
||||||
|
begin
|
||||||
|
JS:=New(['from',EdtSender.Value,'msg',aMessage,'to',aRecipient]);
|
||||||
|
WS.send(TJSJSON.Stringify(JS));
|
||||||
|
DisplayMessage('you',aMessage,False);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TMyApplication.DoSendClick(aEvent: TJSMouseEvent): boolean;
|
||||||
|
begin
|
||||||
|
SendMessage(EDTRecipient.Value, EdtMessage.Value);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TMyApplication.DoToggleConnectClick(aEvent: TJSMouseEvent): boolean;
|
||||||
|
begin
|
||||||
|
if btnConnect.InnerText='Connect' then
|
||||||
|
doConnect
|
||||||
|
else
|
||||||
|
begin
|
||||||
|
WS.close;
|
||||||
|
WS:=nil;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
var
|
||||||
|
Application : TMyApplication;
|
||||||
|
|
||||||
|
begin
|
||||||
|
Application:=TMyApplication.Create(nil);
|
||||||
|
Application.Initialize;
|
||||||
|
Application.Run;
|
||||||
|
end.
|
BIN
demo/websockets/guest.png
Normal file
BIN
demo/websockets/guest.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
74
demo/websockets/index.html
Normal file
74
demo/websockets/index.html
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Websockets Message demo</title>
|
||||||
|
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" >
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css" >
|
||||||
|
<script src="https://code.jquery.com/jquery-3.4.1.js" ></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" ></script>
|
||||||
|
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.js"></script>
|
||||||
|
<script src="serverconfig.js"></script>
|
||||||
|
<script src="demowebsocket.js"></script>
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container-fluid">
|
||||||
|
|
||||||
|
<!-- Your Alias dialog -->
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-12 col-md-6 col-sm-12 col-xs-12">
|
||||||
|
<div class="card p-4 p-md-4 my-4 mx-3 mx-md-0 ">
|
||||||
|
<form onsubmit="event.preventDefault()">
|
||||||
|
<label for="edtSender"> Your Alias </label>
|
||||||
|
<div class="input-group">
|
||||||
|
<input id="edtSender" type="text" class="form-control">
|
||||||
|
<span class="input-group-btn">
|
||||||
|
<button id="btnConnect" type="submit" class="btn btn-primary btn-flat">Connect</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- end of Your Alias dialog -->
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-12 col-md-6 col-sm-12 col-xs-12">
|
||||||
|
<div class="card main">
|
||||||
|
<!-- chat messages -->
|
||||||
|
<div class="px-2 scroll" id="messages">
|
||||||
|
<!-- messages will be appended here -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- end of chat messages -->
|
||||||
|
|
||||||
|
<!-- To and Message Inputs -->
|
||||||
|
<nav class="navbar navbar-expand-sm p-0">
|
||||||
|
<!-- To Input -->
|
||||||
|
<div class="input-group" style="width: 40%;">
|
||||||
|
<div class="input-group-prepend">
|
||||||
|
<span class="input-group-text">To:</span>
|
||||||
|
</div>
|
||||||
|
<input id="edtRecipient" type="text" class="form-control">
|
||||||
|
</div>
|
||||||
|
<!-- Message Input and Send button -->
|
||||||
|
<div class="input-group">
|
||||||
|
<input id="edtMessage" type="text" name="message" placeholder="Type Message ..." class="form-control">
|
||||||
|
<span class="input-group-btn">
|
||||||
|
<button id="btnSend" type="button" class="btn btn-primary btn-flat"><i class="fas fa-paper-plane pr-2"></i>Send</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- end of chat dialog -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
rtl.run();
|
||||||
|
</script>
|
||||||
|
<div id="pasjsconsole"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
5
demo/websockets/serverconfig.js
Normal file
5
demo/websockets/serverconfig.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
var
|
||||||
|
serverConfig = {
|
||||||
|
"url": "ws://localhost:8080/"
|
||||||
|
};
|
||||||
|
|
80
demo/websockets/style.css
Normal file
80
demo/websockets/style.css
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
@import url('https://fonts.googleapis.com/css2?family=Manrope&display=swap');
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
padding: 0px;
|
||||||
|
margin: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: #fff;
|
||||||
|
font-family: 'Manrope', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background: #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: #888;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.scroll {
|
||||||
|
overflow-y: scroll;
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
/* height: 325px */
|
||||||
|
height: 425px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
background-color: #eee;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main {
|
||||||
|
position: relative;
|
||||||
|
padding: 6px 0px 0px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.msg {
|
||||||
|
background-color: #fff;
|
||||||
|
font-size: 16px;
|
||||||
|
padding: 5px 10px;
|
||||||
|
border-radius: 5px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #3e3c3c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.between {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #a09e9e;
|
||||||
|
padding: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar {
|
||||||
|
border-bottom-left-radius: 8px;
|
||||||
|
border-bottom-right-radius: 8px;
|
||||||
|
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19)
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control:focus {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
BIN
demo/websockets/you.png
Normal file
BIN
demo/websockets/you.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.0 KiB |
Loading…
Reference in New Issue
Block a user