unclean SPARC

This commit is contained in:
STEINNI
2025-08-27 07:03:09 +00:00
commit f308460931
430 changed files with 54426 additions and 0 deletions
+264
View File
@@ -0,0 +1,264 @@
/**
* @category MyEic
* @subcategory Libraries
*/
class ChatModule{
/**
*
* @param {*} appContent
*/
constructor(appContent) {
this.appContent = appContent;
this.unreadChats = {};
this.chatTargets = {};
this.onlineUsers = [];
this.recipientId = null;
this.currentHistory = [];
this.initView();
//TODO: inherit API entry_points
// this.APmeta.xxxx
this.APIEntries = {
'chatTargets' :'/api/chatTargets',
'':'',
};
//TODO: Do this on opening the chat-panel ? => when is it updated ?
this.getConversations()
.then( this.updateChanList.bind(this) )
.then( this.RequestOnlineUsers.bind(this));
this.addMsgBusEvents();
this.RequestUnreadMsgs();
}
addMsgBusEvents(){
app.events.addEvent('MessageBus.Connected', this.RequestUnreadMsgs.bind(this));
app.events.addEvent('MessageBus.Closed', this.updateUnreadBadge.bind(this));
app.events.addEvent('MessageBus.NOTIFS', this.updateUnreadBadge.bind(this));
app.events.addEvent('MessageBus.ISONLINE', this.updateOnlineBadges.bind(this));
app.events.addEvent('MessageBus.CHANHIST', this.replaceHisto.bind(this));
app.events.addEvent('MessageBus.CHATMSG', this.recvBus.bind(this));
}
RequestUnreadMsgs(){
return(
app.MessageBus.requestWssGwAction('NOTIFS', null).then(
(payload) => {
this.unreadChats = payload.unreadChats;
//TODO: share the other stuff with friends... (app.something ??)
this.updateUnreadBadge();
},
(err)=>{ console.warn('MSGBUS error:',err) })
);
}
RequestOnlineUsers(){
let usrlist = Object.keys(this.chatTargets.users);
// Request them now
app.MessageBus.requestWssGwAction('ISONLINE', usrlist);
// Watch them
app.MessageBus.requestWssGwAction('WATCHUSERS', usrlist);
}
getConversations(){
//TODO use app central fetcher ??? => use fake model like in myUser
return(fetch(this.APIEntries.chatTargets)
.then((response) => response.json()));
}
recvBus(e){
let histoDetail = {};
histoDetail[e.detail.msg.histId] = {
'msg' : e.detail.msg.msg,
'sender' : e.detail.msg.sender
};
this.replaceHisto({ 'detail': histoDetail });
}
replaceHisto(e){
let hids = Object.keys(e.detail);
for(let hid of hids){ // Insert without creating dups
if(!(hid in this.currentHistory)) this.currentHistory[hid] = e.detail[hid];
}
let html = ''; let day = '';
let user, acro, dat, timestp;
for(let hid of Object.keys(this.currentHistory).sort()){
user = this.chatTargets.users[this.currentHistory[hid].sender];
acro = user.given_name[0].toUpperCase()+'. '+user.family_name;
dat = new Date(1*hid.substring(0,hid.indexOf('-')));
timestp = dat.toLocaleString('fr').replace('/'+new Date().getFullYear(),''); // hide current year
if((day != '') && (day != dat.getDate())) {
html += `<div class="day-separator">${(dat.getDate()==(new Date()).getDate())?'(today)':''}</div>`;
}
html += `<div class="histo-entry ${(this.currentHistory[hid].sender==app.currentUser.userInfo.sub)?'me':''}">
<span class="sender">${acro} (${timestp})</span><br>
${this.currentHistory[hid].msg.replace(/\n/g, '<br>')}
</div><br>`;
day = dat.getDate();
}
this.chatView.el.querySelector('.history').innerHTML = html;
this.chatView.el.querySelector('.history').scrollTop = this.chatView.el.querySelector('.history').scrollHeight;
}
updateChanList(chatTargets){
this.chatTargets = chatTargets;
for(var uid in chatTargets.users){
if(uid != app.currentUser.userInfo.sub){
this.addUserChannel(uid, chatTargets.users[uid]);
}
}
for(var chid in chatTargets.lobbies){
this.addLobbyChannel(chid, chatTargets.lobbies[chid]);
}
}
addUserChannel(uid, user) {
let acro = user.given_name[0].toUpperCase()+'. '+user.family_name;
this.chanlist.addRow('P:'+uid, [ '<span eicbadge danger class="userled"></span>', acro ]);
}
addLobbyChannel(chid, lobby) {
this.chanlist.addRow('C:'+chid, [ '<span eicbadge info class="lobbyled"></span>', lobby.name]);
}
initView() {
this.chatView = {
el: null,
channels: [],
active: false,
currentChannel: null
};
this.chatView.el = new DropDown(ui.create(`
<div eicdropdown>
<button eicbutton rounded basic small primary class="icon-comment chat-menu"></button>
<menu eicmenu>
<li menuitem>
<div class="eic-chat">
<div class="chanlist"></div>
<div class="lobbyname">Lobby name...</div>
<div class="history"></div>
<textarea eictextarea class="message" placeholder="Type message here..."></textarea>
<button eicbutton small primary class="send" eicicon="icon-send"></button>
</div>
</li>
</menu>
</div>
`)).el;
ui.eicfy(this.chatView.el);
this.appContent.find('header .eic-session').prepend(this.chatView.el);
this.chanlist = new DataGrid(this.appContent.find('.chanlist'), {
headers: [
{label: '', sortable:true },
{label: 'name', filter: 'text', sortable:true},
],
height: '350px',
});
this.chanlist.onRowClick= this.selectChan.bind(this);
this.chatView.el.querySelector('.send').addEventListener('click', this.onSendMessage.bind(this));
this.chatView.el.querySelector('.message').addEventListener("keyup", this.onKeyUp.bind(this));
this.updateUnreadBadge();
this.chatView.tab = new Tab();
}
onKeyUp(event) {
if(this.aftertypeTo) clearTimeout(this.aftertypeTo);
if(event.which == 13 && !event.shiftKey) {
this.onSendMessage(event);
return
}
this.aftertypeTo = setTimeout(() => {
this.chatView.el.querySelector('.message').value = this.emotiAscii2Utf(this.chatView.el.querySelector('.message').value);
}, 900);
}
emotiAscii2Utf(txt) {
let conv = {
':-)':'🙂',':)':'🙂',';-)':'😉',';)':'😉',':-D':'😄','x-D':'😂',':-P':'😛',
':P':'😛',':-|':'😑',':|':'😑',':-(':'🙁',':(':'🙁',":'-)":'😂',":')":'😂',
":'-(":'😢',":'(":'😢','>:(':'😠','>:[':'😡',':-*':'😘',':*':'😘','O:-)':'😇',
'O:)':'😇',':-J':'😏'
}
for(let emoasci of Object.keys(conv)) {
if(emoasci == txt.substring(txt.length-emoasci.length)){
return(txt.substring(0,txt.length-emoasci.length)+conv[emoasci]);
}
}
return(txt);
}
selectChan(e){
let rawid = e.currentTarget.dataset.id;
if((rawid[0]!='P') && (rawid[0]!='C')) return;
this.recipientId = rawid;
for(let el of this.chatView.el.querySelectorAll(`.chanlist li.row`)){
el.style.backgroundColor = '';
el.style.color = '';
}
e.currentTarget.style.backgroundColor = 'var(--eicui-app-toolbar-bg-color)';
e.currentTarget.style.color = 'var(--eicui-base-color-white)';
this.currentHistory = {};
let realRecipientId = rawid.substring(2);
let lobbyname;
if(this.recipientId[0]=='P') {
lobbyname = this.chatTargets.users[realRecipientId].given_name+' '+this.chatTargets.users[realRecipientId].family_name
} else if(this.recipientId[0]=='C') {
lobbyname = this.chatTargets.lobbies[realRecipientId].name;
}
this.chatView.el.querySelector('.lobbyname').innerHTML = lobbyname;
app.MessageBus.requestWssGwAction('STARTCHAT', this.recipientId);
app.MessageBus.requestWssGwAction('CHANHIST', this.recipientId);
}
onSendMessage(e){
let txt = this.chatView.el.querySelector('.message').value;
app.MessageBus.requestWssGwAction('SENDCHAT', {
'recipient' : this.recipientId,
'msg': txt
})
this.chatView.el.querySelector('.message').value = '';
}
updateUnreadBadge(){
//updateUnreadBadge is decoupled from the msgbus via nbUnreadMsgs so you can redraw the UI anytime, not depending on a msgbus request
let totUnRead = 0;
for(let chan in this.unreadChats) totUnRead += this.unreadChats[chan];
if(!app.MessageBus.connected) {
this.chatView.el.querySelector('button.chat-menu').innerHTML = `<span class="icon-warning" warning xsmall></span>`;
} else if(totUnRead>0) {
this.chatView.el.querySelector('button.chat-menu').innerHTML = `<span eicbadge success xxsmall>${totUnRead}</span>`;
} else {
this.chatView.el.querySelector('button.chat-menu').innerHTML = '';
}
}
updateOnlineBadges(event){
this.onlineUsers = event.detail;
let el;
for(el of this.chatView.el.querySelectorAll(`.eic-chat .userled`)){
el.removeAttribute('success');
el.setAttribute('danger','');
}
for(let uid of this.onlineUsers){
el = this.chatView.el.querySelector(`[data-id="P:${uid}"] .userled`);
if(el) {
el.removeAttribute('danger');
el.setAttribute('success','');
}
}
}
}
app.registerClass('ChatModule', ChatModule);
//TODOs
// Lobbies
// Return to send, shift-return to CRLF
// Limit displayed / requested history
// Store my read / unread msg => real unread notifs
// Smileys
Binary file not shown.
+168
View File
@@ -0,0 +1,168 @@
@font-face {
font-family: 'NotoColorEmoji';
src: url('NotoColorEmoji.ttf') format('truetype');
}
.eic-chat {
min-width: 50vw;
padding: var(--eicui-base-spacing-m) !important;
}
.eic-chat section .conversations > div > div {
display: grid;
grid-gap: 10px;
grid-template-rows: auto min-content min-content;
}
.eic-chat .status {
float: right;
color: var(--eicui-base-color-white);
display: grid;
width: 24px;
height: 24px;
background: var(--eicui-base-color-danger-100);
border-radius: 24px;
font-size: 1rem;
align-content: center;
justify-content: center;
}
.eic-chat .status.active {
background: var(--eicui-base-color-success-100);
}
.eic-chat .chanlist {
width: 250px;
font-size: .8em;
border: 1px solid #ccc;
border-radius: 5px;
min-height:350px;
padding: 5px;
grid-area: chanlist;
}
.eic-chat .history{
grid-area: history;
border: 1px solid #ccc;
border-radius: 5px;
min-height: 300px;
width:100%;
text-transform: none;
max-height: 500px;
overflow: scroll;
}
.eic-chat .lobbyname {
grid-area: lobbyname;
background-color: var(--eicui-app-toolbar-bg-color);
color: var(--eicui-base-color-white);
padding: 5px;
text-transform: none;
}
.eic-chat .history .histo-entry{
font-family: sans-serif, 'NotoColorEmoji' !important;
background-color: #E8E8FF;
border-radius: 5px;
margin: 2px 5px;
padding: 2px 5px;
display: inline-block;
max-width: 500px;
float: left;
clear: both;
}
.eic-chat .history .day-separator{
clear: both;
height: 0;
border: 0px none transparent;
border-top: 2px dotted #777;
text-align: center;
line-height: 17px;
font-size: smaller;
}
.eic-chat .history .histo-entry.me{
float: right;
background-color: #D9F2D7;
}
.eic-chat .history .histo-entry .sender{
display: inline-block;
font-size: .85em;
font-weight: bold;
background-color: var(--eicui-app-toolbar-bg-color);
color: #FFF;
padding: 0 2px 2px 2px;
border-radius: 5px;
font-family: monospace;
}
.eic-chat .message{
grid-area: message;
width:550px;
font-family: sans-serif, 'NotoColorEmoji' !important;
}
.eic-chat .send{
grid-area: send;
}
.eic-chat {
display:grid;
grid-template-areas:
'chanlist lobbyname lobbyname'
'chanlist history history'
'chanlist message send';
}
.eic-chat .chanlist li.row{
grid-template-columns: 15px 2fr;
}
.eic-chat .chanlist span.userled{
position: absolute;
left: 5px;
height: calc(var(--eicui-base-spacing-s)*.8);
min-width: calc(var(--eicui-base-spacing-s)*.8);
border: 1px outset #aaa;
}
.eic-chat .chanlist span.lobbyled{
position: absolute;
left: 5px;
height: calc(var(--eicui-base-spacing-s)*.8);
min-width: calc(var(--eicui-base-spacing-s)*.8);
border: 2px outset #00A;
}
.eic-chat .output {
overflow-y: auto;
height: 350px;
background-color: var(--eicui-base-color-white);
width: 100%;
border: 1px solid var(--eicui-base-color-grey);
padding: 10px;
font-size: 0.8rem;
}
.eic-chat .output .emitter {
color: var(--eicui-base-color-primary);
padding-right: 4px;
}
.eic-chat .cols-2.form {
height: 60px;
align-items: flex-end;
}
.eic-chat .cols-2.form textarea {
resize: none;
}
.eic-chat .lobby {
display: grid;
grid-gap: var(--eicui-base-spacing-m);
}
#subchalst{
margin: 0 0 0 0;
padding: 0 0 0 5px;
}
#subchalst li{
font-size:12px;
}
.eic-chat .subscriptions,
.eic-chat .onlineusers{
min-width: 300px;
}
.eic-chat .subscriptions label,
.eic-chat .onlineusers label{
font-weight:bold;
}
+90
View File
@@ -0,0 +1,90 @@
'use strict'
/**
* @category MyEic
* @subcategory Libraries
*/
class MBRendezVous {
/**
*
* @param {*} appctrl
*/
constructor(appctrl){
if(!app.MessageBus) return
this.activeMeetChans = {}
this.myUserNotifChan = app.config.messageBus.userNotifChan.replace(/\{uid\}/g, app.User.identity.uuid)
app.MessageBus.addBusListener(
'rendezVous',
[ this.myUserNotifChan ],
this.onRendezVousRequest.bind(this)
)
}
/**
*
* @param {*} chan
* @param {*} payload
* @param {*} sender
*/
async onRendezVousRequest(chan, payload, sender){
if(! await this.userConfirmation(sender, payload.userMessage)) return
let rdvzchan = payload.chanPrefix + crypto.randomUUID()
//this.activeMeetChans[] = chan
}
/**
*
* @param {*} sender
* @param {*} userMessage
*/
async userConfirmation(sender, userMessage){
let options = {
title: 'Connexion request',
message: `<p>The user <b>${sender}</b> invites you to ${userMessage}.</p>
`,
cancelLabel: 'Refuse',
okLabel: 'Accept',
severity: 'danger',
okPromise: () => new Promise((ok,ko)=>ok())
}
await this.mainCtrl.loadContent('templates/dialogs/ConfirmDialog', options, options)
/*
let result = await this.mainView.confirmDialog({
title: 'Connexion request',
message: `<p>The user <b>${sender}</b> invites you to ${userMessage}.</p>
`,
cancelLabel: 'Refuse',
okLabel: 'Accept',
severity: 'danger',
okPromise: () => new Promise((ok,ko)=>ok())
})
if(result) {
console.log('you said YES')
}
*/
}
/**
*
* @param {*} hisUid
* @param {*} userMessage
* @param {*} timeout
* @returns {Promise}
*/
getRendezVousChan(hisUid, userMessage, timeout){
if(!app.MessageBus) return(
new Promise((resolve, reject) => reject('Cannot rendez-vous without messageBus !'))
)
return(
new Promise((resolve, reject) => {
})
)
}
}
app.registerClass('MBRendezVous', MBRendezVous);