Archived
Private
Public Access
1
0

Update 19.11.2022

This commit is contained in:
2022-11-19 14:12:22 +01:00
parent 08ddca2211
commit 771f58073f
322 changed files with 50685 additions and 2 deletions

97
HTML/gcphone/src/App.vue Normal file
View File

@@ -0,0 +1,97 @@
<template>
<div style="height: 100vh; width: 100vw;" @contextmenu="closePhone">
<notification />
<div v-if="show === true && tempoHide === false" :style="{zoom: zoom}" @contextmenu.stop>
<div class="phone_wrapper">
<div v-if="coque" class="phone_coque" :style="{backgroundImage: 'url(/html/static/img/coque/' + coque.value + ')'}"></div>
<div id="app" class="phone_screen">
<router-view></router-view>
</div>
</div>
</div>
</div>
</template>
<script>
import './PhoneBaseStyle.scss'
import './assets/css/font-awesome.min.css'
import { mapGetters, mapActions } from 'vuex'
export default {
name: 'app',
components: {
},
data: function () {
return {
soundCall: null
}
},
methods: {
...mapActions(['loadConfig', 'rejectCall']),
closePhone () {
this.$phoneAPI.closePhone()
}
},
computed: {
...mapGetters(['show', 'zoom', 'coque', 'sonido', 'appelsInfo', 'myPhoneNumber', 'volume', 'tempoHide'])
},
watch: {
appelsInfo (newValue, oldValue) {
if (this.appelsInfo !== null && this.appelsInfo.is_accepts !== true) {
if (this.soundCall !== null) {
this.soundCall.pause()
}
if (this.appelsInfo.initiator === true) {
this.soundCall = new Audio('/html/static/sound/Phone_Call_Sound_Effect.ogg')
} else {
this.soundCall = new Audio('/html/static/sound/' + this.sonido.value)
}
this.soundCall.loop = true
this.soundCall.volume = this.volume
this.soundCall.play()
} else if (this.soundCall !== null) {
this.soundCall.pause()
this.soundCall = null
}
if (newValue === null && oldValue !== null) {
this.$router.push({name: 'home'})
return
}
if (newValue !== null) {
this.$router.push({name: 'appels.active'})
}
},
show () {
if (this.appelsInfo !== null) {
this.$router.push({name: 'appels.active'})
} else {
this.$router.push({name: 'home'})
}
if (this.show === false && this.appelsInfo !== null) {
this.rejectCall()
}
}
},
mounted () {
this.loadConfig()
window.addEventListener('message', (event) => {
if (event.data.keyUp !== undefined) {
this.$bus.$emit('keyUp' + event.data.keyUp)
}
})
window.addEventListener('keyup', (event) => {
const keyValid = ['ArrowRight', 'ArrowLeft', 'ArrowUp', 'ArrowDown', 'Backspace', 'Enter']
if (keyValid.indexOf(event.key) !== -1) {
this.$bus.$emit('keyUp' + event.key)
}
if (event.key === 'Escape') {
this.$phoneAPI.closePhone()
}
})
}
}
</script>
<style lang="scss">
</style>

View File

@@ -0,0 +1,76 @@
<template>
<div class="notifications">
<div
v-for='notif in list'
:key="notif.id"
class="notification"
:style="style(notif)"
>
<div class="title">
<i v-if="notif.icon" class="fa" :class="'fa-' + notif.icon"/> {{notif.title}}
</div>
<div class="message">{{notif.message}}</div>
</div>
</div>
</template>
<script>
import events from './events'
export default {
data () {
return {
currentId: 0,
list: []
}
},
mounted () {
events.$on('add', this.addItem)
},
methods: {
async addItem (event = {}) {
const dataNotif = {
...event,
id: this.currentId ++,
duration: parseInt(event.duration) || 3000
}
this.list.push(dataNotif)
window.setTimeout(() => {
this.destroy(dataNotif.id)
}, dataNotif.duration)
if (event.sound !== null && event.sound !== undefined) {
const audio = new Audio('/html/static/sound/' + event.sound)
audio.addEventListener('ended', () => {
audio.src = null
})
audio.play()
}
},
style (notif) {
return {
backgroundColor: notif.backgroundColor
}
},
destroy (id) {
this.list = this.list.filter(n => n.id !== id)
}
}
}
</script>
<style scoped>
.notification {
width: 450px;
background-color: rgba(29, 161, 242, 0.6);
color: white;
padding: 8px 16px;
margin-bottom: 8px;
border-radius: 6px;
}
.title {
font-size: 18px;
}
.message {
font-size: 16px;
}
</style>

View File

@@ -0,0 +1,2 @@
import Vue from 'vue'
export default new Vue()

View File

@@ -0,0 +1,24 @@
import Vue from 'vue'
import Notification from './Notification'
import events from './events'
const Notify = {
install: function (options) {
if (this.installed) return
this.installed = true
Vue.component('notification', Notification)
const notify = (params) => {
events.$emit('add', params)
}
Vue.notify = notify
Object.defineProperties(Vue.prototype, {
$notify: {
get: () => Vue.notify
}
})
}
}
export default Notify

View File

@@ -0,0 +1,359 @@
import store from '@/store'
import VoiceRTC from './VoiceRCT'
import Vue from 'vue'
import emoji from './emoji.json'
const keyEmoji = Object.keys(emoji)
let USE_VOICE_RTC = false
const BASE_URL = 'http://gcphone/'
/* eslint-disable camelcase */
class PhoneAPI {
constructor () {
window.addEventListener('message', (event) => {
const eventType = event.data.event
if (eventType !== undefined && typeof this['on' + eventType] === 'function') {
this['on' + eventType](event.data)
} else if (event.data.show !== undefined) {
store.commit('SET_PHONE_VISIBILITY', event.data.show)
}
})
this.config = null
this.voiceRTC = null
this.soundList = {}
}
async post (method, data) {
const ndata = data === undefined ? '{}' : JSON.stringify(data)
const response = await window.jQuery.post(BASE_URL + method, ndata)
return JSON.parse(response)
}
async log (...data) {
if (process.env.NODE_ENV === 'production') {
return this.post('log', data)
} else {
return console.log(...data)
}
}
convertEmoji (text) {
for (const e of keyEmoji) {
text = text.replace(new RegExp(`:${e}:`, 'g'), emoji[e])
}
return text
}
// === Gestion des messages
async sendMessage (phoneNumber, message) {
return this.post('sendMessage', {phoneNumber, message})
}
async deleteMessage (id) {
return this.post('deleteMessage', {id})
}
async deleteMessagesNumber (number) {
return this.post('deleteMessageNumber', {number})
}
async deleteAllMessages () {
return this.post('deleteAllMessage')
}
async setMessageRead (number) {
return this.post('setReadMessageNumber', {number})
}
// === Gestion des contacts
async updateContact (id, display, phoneNumber) {
return this.post('updateContact', { id, display, phoneNumber })
}
async addContact (display, phoneNumber) {
return this.post('addContact', { display, phoneNumber })
}
async deleteContact (id) {
return this.post('deleteContact', { id })
}
// == Gestion des appels
async appelsDeleteHistorique (numero) {
return this.post('appelsDeleteHistorique', { numero })
}
async appelsDeleteAllHistorique () {
return this.post('appelsDeleteAllHistorique')
}
// === Autre
async closePhone () {
return this.post('closePhone')
}
async setUseMouse (useMouse) {
return this.post('useMouse', useMouse)
}
async setGPS (x, y) {
return this.post('setGPS', {x, y})
}
async takePhoto () {
store.commit('SET_TEMPO_HIDE', true)
const data = await this.post('takePhoto', { url: this.config.fileUploadService_Url, field: this.config.fileUploadService_Field })
store.commit('SET_TEMPO_HIDE', false)
return data
}
async getReponseText (data) {
if (process.env.NODE_ENV === 'production') {
return this.post('reponseText', data || {})
} else {
return {text: window.prompt()}
}
}
async faketakePhoto () {
return this.post('faketakePhoto')
}
async callEvent (eventName, data) {
return this.post('callEvent', {eventName, data})
}
async deleteALL () {
localStorage.clear()
store.dispatch('tchatReset')
store.dispatch('notesReset')
store.dispatch('resetPhone')
store.dispatch('resetMessage')
store.dispatch('resetContact')
store.dispatch('resetBourse')
store.dispatch('resetAppels')
return this.post('deleteALL')
}
async getConfig () {
if (this.config === null) {
const response = await window.jQuery.get('/html/static/config/config.json')
if (process.env.NODE_ENV === 'production') {
this.config = JSON.parse(response)
} else {
this.config = response
}
if (this.config.useWebRTCVocal === true) {
this.voiceRTC = new VoiceRTC(this.config.RTCConfig)
USE_VOICE_RTC = true
}
// console.log('JS USE RTC', this.config.useWebRTCVocal)
this.notififyUseRTC(this.config.useWebRTCVocal)
}
return this.config
}
async onsetEnableApp (data) {
store.dispatch('setEnableApp', data)
}
async setIgnoreFocus (ignoreFocus) {
this.post('setIgnoreFocus', { ignoreFocus })
}
// === App Tchat
async tchatGetMessagesChannel (channel) {
this.post('tchat_getChannel', { channel })
}
async tchatSendMessage (channel, message) {
this.post('tchat_addMessage', { channel, message })
}
// === App Notes
async notesGetMessagesChannel (channel) {
window.localStorage.setItem('gc_notas_locales', channel)
}
async notesSendMessage (channel, message) {
this.post('notes_addMessage', { channel, message })
}
// ==========================================================================
// Gestion des events
// ==========================================================================
onupdateMyPhoneNumber (data) {
store.commit('SET_MY_PHONE_NUMBER', data.myPhoneNumber)
}
onupdateMessages (data) {
store.commit('SET_MESSAGES', data.messages)
}
onnewMessage (data) {
store.commit('ADD_MESSAGE', data.message)
}
onupdateContacts (data) {
store.commit('SET_CONTACTS', data.contacts)
}
onhistoriqueCall (data) {
store.commit('SET_APPELS_HISTORIQUE', data.historique)
}
onupdateBankbalance (data) {
store.commit('SET_BANK_AMONT', data.banking)
}
onupdateBourse (data) {
store.commit('SET_BOURSE_INFO', data.bourse)
}
// Call
async startCall (numero, extraData = undefined) {
if (USE_VOICE_RTC === true) {
const rtcOffer = await this.voiceRTC.prepareCall()
return this.post('startCall', { numero, rtcOffer, extraData })
} else {
return this.post('startCall', { numero, extraData })
}
}
async acceptCall (infoCall) {
if (USE_VOICE_RTC === true) {
const rtcAnswer = await this.voiceRTC.acceptCall(infoCall)
return this.post('acceptCall', { infoCall, rtcAnswer })
} else {
return this.post('acceptCall', { infoCall })
}
}
async rejectCall (infoCall) {
return this.post('rejectCall', { infoCall })
}
async notififyUseRTC (use) {
return this.post('notififyUseRTC', use)
}
onwaitingCall (data) {
store.commit('SET_APPELS_INFO_IF_EMPTY', {
...data.infoCall,
initiator: data.initiator
})
}
onacceptCall (data) {
if (USE_VOICE_RTC === true) {
if (data.initiator === true) {
this.voiceRTC.onReceiveAnswer(data.infoCall.rtcAnswer)
}
this.voiceRTC.addEventListener('onCandidate', (candidates) => {
this.post('onCandidates', { id: data.infoCall.id, candidates })
})
}
store.commit('SET_APPELS_INFO_IS_ACCEPTS', true)
}
oncandidatesAvailable (data) {
this.voiceRTC.addIceCandidates(data.candidates)
}
onrejectCall (data) {
if (this.voiceRTC !== null) {
this.voiceRTC.close()
}
store.commit('SET_APPELS_INFO', null)
}
// Tchat Event
ontchat_receive (data) {
store.dispatch('tchatAddMessage', data)
}
ontchat_channel (data) {
store.commit('TCHAT_SET_MESSAGES', data)
}
// Notes Event
onnotes_receive (data) {
store.dispatch('notesAddMessage', data)
}
onnotes_channel (data) {
store.commit('NOTES_SET_MESSAGES', data)
}
// =====================
onautoStartCall (data) {
this.startCall(data.number, data.extraData)
}
onautoAcceptCall (data) {
store.commit('SET_APPELS_INFO', data.infoCall)
this.acceptCall(data.infoCall)
}
// === Twitter
twitter_login (username, password) {
this.post('twitter_login', {username, password})
}
twitter_changePassword (username, password, newPassword) {
this.post('twitter_changePassword', {username, password, newPassword})
}
twitter_createAccount (username, password, avatarUrl) {
this.post('twitter_createAccount', {username, password, avatarUrl})
}
twitter_postTweet (username, password, message) {
this.post('twitter_postTweet', { username, password, message })
}
twitter_postTweetImg (username, password, img) {
this.post('twitter_postTweetImg', { username, password, img })
}
twitter_toggleLikeTweet (username, password, tweetId) {
this.post('twitter_toggleLikeTweet', { username, password, tweetId })
}
twitter_setAvatar (username, password, avatarUrl) {
this.post('twitter_setAvatarUrl', { username, password, avatarUrl })
}
twitter_getTweets (username, password) {
this.post('twitter_getTweets', { username, password })
}
twitter_getFavoriteTweets (username, password) {
this.post('twitter_getFavoriteTweets', { username, password })
}
ontwitter_tweets (data) {
store.commit('SET_TWEETS', data)
}
ontwitter_favoritetweets (data) {
store.commit('SET_FAVORITE_TWEETS', data)
}
ontwitter_newTweet (data) {
store.dispatch('addTweet', data.tweet)
}
ontwitter_setAccount (data) {
store.dispatch('setAccount', data)
}
ontwitter_updateTweetLikes (data) {
store.commit('UPDATE_TWEET_LIKE', data)
}
ontwitter_setTweetLikes (data) {
store.commit('UPDATE_TWEET_ISLIKE', data)
}
ontwitter_showError (data) {
Vue.notify({
title: store.getters.IntlString(data.title, ''),
message: store.getters.IntlString(data.message),
icon: 'twitter',
backgroundColor: '#e0245e80'
})
}
ontwitter_showSuccess (data) {
Vue.notify({
title: store.getters.IntlString(data.title, ''),
message: store.getters.IntlString(data.message),
icon: 'twitter'
})
}
onplaySound ({ sound, volume = 1 }) {
if (!sound) return
if (this.soundList[sound] !== undefined) {
this.soundList[sound].volume = volume
} else {
this.soundList[sound] = new Audio('/html/static/sound/' + sound)
this.soundList[sound].loop = true
this.soundList[sound].volume = volume
this.soundList[sound].play()
}
}
onsetSoundVolume ({ sound, volume = 1 }) {
if (this.soundList[sound] !== undefined) {
this.soundList[sound].volume = volume
}
}
onstopSound ({ sound }) {
if (this.soundList[sound] !== undefined) {
this.soundList[sound].pause()
delete this.soundList[sound]
}
}
}
const instance = new PhoneAPI()
export default instance

View File

@@ -0,0 +1,114 @@
body {
font-size: 20px;
margin: 0;
padding: 0;
}
.phone_infoBare {
height: 24px;
}
.phone_app{
height: 739px;
width: 334px;
top: 4px;
display: flex;
flex-direction: column;
}
.phone_title {
font-weight: 300;
padding-left: 24px;
font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
margin-top: 4px;
color: black;
background-color: #e9e9eb;
height: 64px;
line-height: 52px;
font-size: 24px;
padding-left: 64px;
}
.phone_title .btn-back {
width: 42px;
height: 42px;
color: #5A5A5A;
line-height: 42px;
border-radius: 50%;
background-color: transparent;
border: none;
margin: 0;
padding: 0;
position: absolute;
left: 16px;
top: 35px;
outline: none;
text-shadow: none;
text-align: center;
}
.phone_title .btn-back:hover {
background-color: rgba(255,255,255, 0.3);
color: #5A5A5A;
}
.phone_content {
height: 100%;
width: 104%;
overflow-y: auto;
}
.phone_wrapper {
position: absolute;
bottom: 0vh;
right: 0vh;
width: 500px;
height: 1000px;
background-size: cover;
}
.phone_coque{
position: absolute;
z-index: 999;
width: 394px;
height: 800px;
top: 71px;
left: 15px;
pointer-events:none;
background-size: cover;
background-repeat: no-repeat;
}
.phone_screen{
overflow: hidden;
position: absolute;
background-color: white;
width: 326px;
height: 742px;
bottom: 100px;
left: 50px;
right: 50px;
top: 100px;
display: flex;
}
* {
font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
box-sizing: border-box;
font-weight: 100;
font-size: 19px;
user-select: none;
}
*::-webkit-scrollbar-track
{
box-shadow: inset 0 0 6px rgba(0,0,0,0.3);
background-color: #F5F5F5;
}
*::-webkit-scrollbar
{
width: 3px;
background-color: transparent;
}
*::-webkit-scrollbar-thumb
{
background-color: #666;
}

177
HTML/gcphone/src/TimeAgo.js Normal file
View File

@@ -0,0 +1,177 @@
// Original: https://github.com/egoist/vue-timeago
/* eslint-disable */
const MINUTE = 60
const HOUR = MINUTE * 60
const DAY = HOUR * 24
const WEEK = DAY * 7
const MONTH = DAY * 30
const YEAR = DAY * 365
function pluralOrSingular(data, locale) {
if (data === 'just now') {
return locale
}
const count = Math.round(data)
if (Array.isArray(locale)) {
return count > 1
? locale[1].replace(/%s/, count)
: locale[0].replace(/%s/, count)
}
return locale.replace(/%s/, count)
}
function formatTime(time) {
const d = new Date(time)
return d.toLocaleString()
}
const defaultLocales = {
'de_DE': [
"Gerade eben",
["il y a %s seconde", "vor %s Sekunden"],
["il y a %s minute", "vor %s Minuten"],
["il y a %s heure", "vor %s Stunden"],
["il y a %s jour", "vor %s Tagen"],
["il y a %s semaine", "vor %s Wochen"],
["il y a %s mois", "vor %s Monate"],
["il y a %s an", "vor %s Jahre"]
]
}
export default function install(Vue, { name = 'timeago', locale = 'de_DE', locales = defaultLocales} = {} ) {
if (!locales || Object.keys(locales).length === 0) {
throw new TypeError('Expected locales to have at least one locale.')
}
const VueTimeago = {
props: {
since: {
required: true
},
locale: String,
maxTime: Number,
autoUpdate: Number,
format: Function
},
data() {
return {
now: new Date().getTime()
}
},
computed: {
currentLocale() {
if (Vue.prototype.$timeago) {
const locale = VueTimeago.locales[VueTimeago.locale]
if (locale) {
return locale
}
}
return locales['fr_FR']
},
sinceTime() {
return new Date(this.since).getTime()
},
timeForTitle() {
const seconds = this.now / 1000 - this.sinceTime / 1000
if (this.maxTime && seconds > this.maxTime) {
return null
}
return this.format
? this.format(this.sinceTime)
: formatTime(this.sinceTime)
},
timeago() {
const seconds = this.now / 1000 - this.sinceTime / 1000
if (this.maxTime && seconds > this.maxTime) {
clearInterval(this.interval)
return this.format
? this.format(this.sinceTime)
: formatTime(this.sinceTime)
}
const ret =
seconds <= 5
? pluralOrSingular('just now', this.currentLocale[0])
: seconds < MINUTE
? pluralOrSingular(seconds, this.currentLocale[1])
: seconds < HOUR
? pluralOrSingular(seconds / MINUTE, this.currentLocale[2])
: seconds < DAY
? pluralOrSingular(seconds / HOUR, this.currentLocale[3])
: seconds < WEEK
? pluralOrSingular(seconds / DAY, this.currentLocale[4])
: seconds < MONTH
? pluralOrSingular(seconds / WEEK, this.currentLocale[5])
: seconds < YEAR
? pluralOrSingular(
seconds / MONTH,
this.currentLocale[6]
)
: pluralOrSingular(
seconds / YEAR,
this.currentLocale[7]
)
return ret
}
},
mounted() {
if (this.autoUpdate) {
this.update()
}
},
render(h) {
return h(
'time',
{
attrs: {
datetime: new Date(this.since),
title: this.timeForTitle
}
},
this.timeago
)
},
watch: {
autoUpdate(newAutoUpdate) {
this.stopUpdate()
// only update when it's not falsy value
// which means you cans set it to 0 to disable auto-update
if (newAutoUpdate) {
this.update()
}
}
},
methods: {
update() {
const period = this.autoUpdate * 1000
this.interval = setInterval(() => {
this.now = new Date().getTime()
}, period)
},
stopUpdate() {
clearInterval(this.interval)
this.interval = null
}
},
beforeDestroy() {
this.stopUpdate()
}
}
VueTimeago.locale = 'fr_FR'
VueTimeago.locales = {}
Vue.prototype.$timeago = {
setCurrentLocale (locale) {
VueTimeago.locale = locale
},
addLocale (locale, data) {
VueTimeago.locales[locale] = data
}
}
Vue.component(name, VueTimeago)
}

51
HTML/gcphone/src/Utils.js Normal file
View File

@@ -0,0 +1,51 @@
import store from '@/store'
function getRGB (colorStr) {
let match = colorStr.match(/rgba?\((\d{1,3}), ?(\d{1,3}), ?(\d{1,3})\)?(?:, ?(\d(?:\.\d?))\))?/)
if (match !== null) {
return {
red: parseInt(match[1], 10),
green: parseInt(match[2], 10),
blue: parseInt(match[3], 10)
}
}
match = colorStr.match(/^#([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})/)
if (match !== null) {
return {
red: parseInt(match[1], 16),
green: parseInt(match[2], 16),
blue: parseInt(match[3], 16)
}
}
return undefined
}
export function groupBy (xs, key) {
return xs.reduce(function (rv, x) {
(rv[x[key]] = rv[x[key]] || []).push(x)
return rv
}, {})
}
export function generateColorForStr (str) {
if (str.length === 0 || str[0] === '#') {
return '#D32F2F'
}
const h = str.split('').reduce((prevHash, currVal) =>
(((prevHash << 5) - prevHash) + currVal.charCodeAt(0)) | 0
, 0)
return store.getters.colors[Math.abs(h) % store.getters.colors.length]
}
export function getBestFontColor (color) {
const rgb = getRGB(color)
if (rgb === undefined) {
return '#000000'
} else {
if (rgb.red * 0.299 + rgb.green * 0.587 + rgb.blue * 0.114 > 186) {
return 'rgba(0, 0, 0, 0.87)'
} else {
return '#FFFFFF'
}
}
}

View File

@@ -0,0 +1,128 @@
const constraints = {
video: false,
audio: true
}
/* eslint-disable */
class VoiceRTC {
constructor (RTCConfig) {
this.myPeerConnection = null
this.candidates = []
this.listener = {}
this.myCandidates = []
this.audio = new Audio()
this.offer = null
this.answer = null
this.initiator = null
this.RTCConfig = RTCConfig
}
async init () {
await this.close()
this.myPeerConnection = new RTCPeerConnection(this.RTCConfig)
this.stream = await navigator.mediaDevices.getUserMedia(constraints)
}
newConnection () {
this.close()
this.candidates = []
this.myCandidates = []
this.listener = {}
this.offer = null
this.answer = null
this.initiator = null
this.myPeerConnection = new RTCPeerConnection(this.RTCConfig)
this.myPeerConnection.onaddstream = this.onaddstream.bind(this)
}
close () {
if (this.myPeerConnection !== null) {
this.myPeerConnection.close()
}
this.myPeerConnection = null
}
async prepareCall () {
await this.init()
this.newConnection()
this.initiator = true
this.myPeerConnection.addStream(this.stream)
this.myPeerConnection.onicecandidate = this.onicecandidate.bind(this)
this.offer = await this.myPeerConnection.createOffer()
this.myPeerConnection.setLocalDescription(this.offer)
return btoa(JSON.stringify(this.offer))
}
async acceptCall (infoCall) {
const offer = JSON.parse(atob(infoCall.rtcOffer))
this.newConnection()
this.initiator = false
this.stream = await navigator.mediaDevices.getUserMedia(constraints)
this.myPeerConnection.onicecandidate = this.onicecandidate.bind(this)
this.myPeerConnection.addStream(this.stream)
this.offer = new RTCSessionDescription(offer)
this.myPeerConnection.setRemoteDescription(this.offer)
this.answer = await this.myPeerConnection.createAnswer()
this.myPeerConnection.setLocalDescription(this.answer)
return btoa(JSON.stringify(this.answer))
}
async onReceiveAnswer (answerData) {
const answerObj = JSON.parse(atob(answerData))
this.answer = new RTCSessionDescription(answerObj)
this.myPeerConnection.setRemoteDescription(this.answer)
}
onicecandidate (event) {
if (event.candidate !== undefined) {
this.myCandidates.push(event.candidate)
if (this.listener['onCandidate'] !== undefined) {
const candidates = this.getAvailableCandidates()
for (let func of this.listener['onCandidate']) {
func(candidates)
}
}
}
}
getAvailableCandidates() {
const candidates = btoa(JSON.stringify(this.myCandidates))
this.myCandidates = []
return candidates
}
addIceCandidates (candidatesRaw) {
if (this.myPeerConnection !== null) {
const candidates = JSON.parse(atob(candidatesRaw))
candidates.forEach((candidate) => {
if (candidate !== null) {
this.myPeerConnection.addIceCandidate(candidate)
}
})
}
}
addEventListener (eventName, callBack) {
if (eventName === 'onCandidate') {
if (this.listener[eventName] === undefined) {
this.listener[eventName] = []
}
this.listener[eventName].push(callBack)
callBack(this.getAvailableCandidates())
}
}
onaddstream (event) {
this.audio.srcObject = event.stream
this.audio.play()
}
}
/* eslint-disable */
(async function () {
})()
export default VoiceRTC

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 434 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

@@ -0,0 +1,135 @@
<template>
<div class="phone_app">
<PhoneTitle :title="'9 GAG (' + currentSelectPost + ')'" backgroundColor="#000" @back="quit"/>
<div class='phone_content' @click="onClick">
<div class="post" v-if="currentPost !== undefined">
<h1 class="post-title">{{ currentPost.title }}</h1>
<div class="post-content">
<video class="post-video" ref="video" v-if="currentPost.images.image460svwm !== undefined" autoplay loop :src="currentPost.images.image460svwm.url">
</video>
<img class="post-image" v-else :src="currentPost.images.image460.url" alt="">
</div>
</div>
<div v-else class="loading">
<div>CHARGEMENT</div>
</div>
</div>
</div>
</template>
<script>
import PhoneTitle from './../PhoneTitle'
export default {
components: {
PhoneTitle
},
data () {
return {
nextCursor: 'c=10',
currentSelectPost: 0,
posts: []
}
},
computed: {
currentPost () {
if (this.posts && this.posts.length > this.currentSelectPost) {
return this.posts[this.currentSelectPost]
}
this.loadItems()
return undefined
}
},
methods: {
async loadItems () {
let url = 'https://9gag.com/v1/group-posts/group/default/type/hot?' + this.nextCursor
const request = await fetch(url)
const data = await request.json()
this.posts.push(...data.data.posts)
this.nextCursor = data.data.nextCursor
},
previewPost () {
if (this.currentSelectPost === 0) {
return 0
}
this.currentSelectPost -= 1
setTimeout(() => {
if (this.$refs.video !== undefined) {
this.$refs.video.volume = 0.15
}
}, 200)
},
nextPost () {
this.currentSelectPost += 1
setTimeout(() => {
if (this.$refs.video !== undefined) {
this.$refs.video.volume = 0.15
}
}, 200)
},
onClick ($event) {
if ($event.offsetX < 200) {
this.previewPost()
} else {
this.nextPost()
}
},
quit: function () {
this.$router.push({ name: 'home' })
}
},
created: function () {
this.$bus.$on('keyUpArrowLeft', this.previewPost)
this.$bus.$on('keyUpArrowRight', this.nextPost)
this.$bus.$on('keyUpBackspace', this.quit)
},
beforeDestroy: function () {
this.$bus.$off('keyUpArrowLeft', this.previewPost)
this.$bus.$off('keyUpArrowRight', this.nextPost)
this.$bus.$off('keyUpBackspace', this.quit)
}
}
</script>
<style scoped lang="scss">
.post{
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
.post-title {
padding-left: 12px;
font-size: 18px;
height: 18px;
overflow: hidden;
}
.post-content{
display: flex;
width: 390px;
height: 670px;
}
.post-video, .post-image{
object-fit: contain;
max-width: 100%;
max-height: 100%;
width: auto;
height: auto;
}
}
.loading{
height: 100%;
background-color: black;
color: white;
display: flex;
align-items: center;
justify-content: center;
div {
text-align: center;
margin-bottom: 36px;
}
}
</style>

View File

@@ -0,0 +1,147 @@
<template>
<div style="width: 326px; height: 743px;" class="phone_app">
<PhoneTitle :title="IntlString('APP_PHONE_TITLE')" v-on:back="onBackspace" />
<div class="content">
<component :is="subMenu[currentMenuIndex].Comp" />
</div>
<div class="subMenu">
<div
class="subMenu-elem"
:style="getColorItem(i)"
v-for="(Comp, i) of subMenu"
:key="i"
@click="swapMenu(i)"
>
<i class="subMenu-icon fa" :class="['fa-' + Comp.icon]" @click.stop="swapMenu(i)"></i>
<span class="subMenu-name" @click.stop="swapMenu(i)">{{Comp.name}}</span>
</div>
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import PhoneTitle from './../PhoneTitle'
import AppelsFavoris from './AppelsFavoris'
import AppelsContacts from './AppelsContacts'
import AppelsRecents from './AppelsRecents'
export default {
components: {
PhoneTitle
},
data () {
return {
currentMenuIndex: 1
}
},
computed: {
...mapGetters(['IntlString', 'useMouse', 'themeColor']),
subMenu () {
return [{
Comp: AppelsFavoris,
name: this.IntlString('APP_PHONE_MENU_FAVORITES'),
icon: 'star'
}, {
Comp: AppelsRecents,
name: this.IntlString('APP_PHONE_MENU_RECENTS'),
icon: 'clock-o'
}, {
Comp: AppelsContacts,
name: this.IntlString('APP_PHONE_MENU_CONTACTS'),
icon: 'user'
}]
}
},
methods: {
getColorItem (index) {
if (this.currentMenuIndex === index) {
return {
color: this.themeColor
}
}
return {}
},
swapMenu (index) {
this.currentMenuIndex = index
},
onLeft () {
this.currentMenuIndex = Math.max(this.currentMenuIndex - 1, 0)
},
onRight () {
this.currentMenuIndex = Math.min(this.currentMenuIndex + 1, this.subMenu.length - 1)
},
onBackspace: function () {
if (this.ignoreControls === true) return
this.$router.push({ name: 'home' })
}
},
created () {
if (!this.useMouse) {
this.$bus.$on('keyUpBackspace', this.onBackspace)
this.$bus.$on('keyUpArrowLeft', this.onLeft)
this.$bus.$on('keyUpArrowRight', this.onRight)
}
},
beforeDestroy () {
this.$bus.$off('keyUpBackspace', this.onBackspace)
this.$bus.$off('keyUpArrowLeft', this.onLeft)
this.$bus.$off('keyUpArrowRight', this.onRight)
}
}
</script>
<style scoped>
.screen{
position: relative;
left: 0;
top: 0;
width: 100%;
height: 100%;
}
.title{
padding-left: 16px;
height: 34px;
line-height: 34px;
font-weight: 700;
color: white;
background-color: #2c3e50;
}
.content{
height: calc(100% - 68px);
overflow-y: auto;
width: 337px;
}
.subMenu{
border-top: 1px solid rgba(0,0,0,0.24);
display: flex;
height: 56px;
}
.subMenu-elem {
height: 100%;
width: 100%;
text-align: center;
line-height: 56px;
height: 56px;
display: flex;
color: #959595;
flex-direction: column;
}
.subMenu-elem-select, .subMenu-elem:hover {
color: #007aff;
}
.subMenu-icon{
margin-top: 6px;
font-size: 22px;
line-height: 22px;
height: 22px;
}
.subMenu-name{
display: block;
font-size: 14px;
height: 14px;
line-height: 14px;
}
</style>

View File

@@ -0,0 +1,319 @@
<template>
<div style="width: 326px; height: 743px;" class="phone_app">
<div class="backblur" v-bind:style="{background: 'url(' + backgroundURL +')'}"></div>
<InfoBare />
<div class="num">{{appelsDisplayNumber}}</div>
<div class="contactName">{{appelsDisplayName}}</div>
<div class="time"></div>
<div class="time-display">{{timeDisplay}}</div>
<div
v-if="useMouse && status === 0"
class="ignore"
@click.stop="onIgnoreCall">
{{ IntlString('APP_PHONE_CALL_IGNORE')}}
</div>
<div class="actionbox">
<div class="action raccrocher" :class="{disableTrue: status === 0 && select !== 0}"
@click.stop="raccrocher"
>
<svg viewBox="0 0 24 24" @click.stop="raccrocher">
<g transform="rotate(135, 12, 12)">
<path d="M6.62,10.79C8.06,13.62 10.38,15.94 13.21,17.38L15.41,15.18C15.69,14.9 16.08,14.82 16.43,14.93C17.55,15.3 18.75,15.5 20,15.5A1,1 0 0,1 21,16.5V20A1,1 0 0,1 20,21A17,17 0 0,1 3,4A1,1 0 0,1 4,3H7.5A1,1 0 0,1 8.5,4C8.5,5.25 8.7,6.45 9.07,7.57C9.18,7.92 9.1,8.31 8.82,8.59L6.62,10.79Z"/>
</g>
</svg>
</div>
<div class="action deccrocher" v-if="status === 0" :class="{disableFalse: status === 0 && select !== 1}"
@click.stop="deccrocher"
>
<svg viewBox="0 0 24 24" @click.stop="deccrocher">
<g transform="rotate(0, 12, 12)">
<path d="M6.62,10.79C8.06,13.62 10.38,15.94 13.21,17.38L15.41,15.18C15.69,14.9 16.08,14.82 16.43,14.93C17.55,15.3 18.75,15.5 20,15.5A1,1 0 0,1 21,16.5V20A1,1 0 0,1 20,21A17,17 0 0,1 3,4A1,1 0 0,1 4,3H7.5A1,1 0 0,1 8.5,4C8.5,5.25 8.7,6.45 9.07,7.57C9.18,7.92 9.1,8.31 8.82,8.59L6.62,10.79Z"/>
</g>
</svg>
</div>
</div>
</div>
</template>
<script>
// eslint-disable-next-line
import { mapGetters, mapActions } from 'vuex'
import InfoBare from './../InfoBare'
export default {
components: {
InfoBare
},
data () {
return {
time: -1,
intervalNum: undefined,
select: -1,
status: 0
}
},
methods: {
...mapActions(['acceptCall', 'rejectCall', 'ignoreCall']),
onBackspace () {
if (this.status === 1) {
this.onRejectCall()
} else {
this.onIgnoreCall()
}
},
onEnter () {
if (this.status === 0) {
if (this.select === 0) {
this.onRejectCall()
} else {
this.onAcceptCall()
}
}
},
raccrocher () {
this.onRejectCall()
},
deccrocher () {
if (this.status === 0) {
this.onAcceptCall()
}
},
onLeft () {
if (this.status === 0) {
this.select = 0
}
},
onRight () {
if (this.status === 0) {
this.select = 1
}
},
updateTime () {
this.time += 1
},
onRejectCall () {
this.rejectCall()
this.$phoneAPI.setIgnoreFocus(false)
},
onAcceptCall () {
this.acceptCall()
this.$phoneAPI.setIgnoreFocus(true)
},
onIgnoreCall () {
this.ignoreCall()
this.$phoneAPI.setIgnoreFocus(false)
this.$router.push({ name: 'home' })
},
startTimer () {
if (this.intervalNum === undefined) {
this.time = 0
this.intervalNum = setInterval(this.updateTime, 1000)
}
}
},
watch: {
appelsInfo () {
if (this.appelsInfo === null) return
if (this.appelsInfo.is_accepts === true) {
this.status = 1
this.$phoneAPI.setIgnoreFocus(true)
this.startTimer()
}
}
},
computed: {
...mapGetters(['IntlString', 'backgroundURL', 'useMouse', 'appelsInfo', 'appelsDisplayName', 'appelsDisplayNumber', 'myPhoneNumber']),
timeDisplay () {
if (this.time < 0) {
return '. . .'
}
const min = Math.floor(this.time / 60)
let sec = this.time % 60
if (sec < 10) {
sec = '0' + sec
}
return `${min}:${sec}`
}
},
mounted () {
if (this.appelsInfo !== null && this.appelsInfo.initiator === true) {
this.status = 1
this.$phoneAPI.setIgnoreFocus(true)
}
},
created () {
if (!this.useMouse) {
this.$bus.$on('keyUpEnter', this.onEnter)
this.$bus.$on('keyUpArrowLeft', this.onLeft)
this.$bus.$on('keyUpArrowRight', this.onRight)
}
this.$bus.$on('keyUpBackspace', this.onBackspace)
},
beforeDestroy () {
this.$bus.$off('keyUpBackspace', this.onBackspace)
this.$bus.$off('keyUpEnter', this.onEnter)
this.$bus.$off('keyUpArrowLeft', this.onLeft)
this.$bus.$off('keyUpArrowRight', this.onRight)
if (this.intervalNum !== undefined) {
window.clearInterval(this.intervalNum)
}
this.$phoneAPI.setIgnoreFocus(false)
}
}
</script>
<style scoped>
.backblur{
top: -6px;
left: -6px;
right:-6px;
bottom: -6px;
position: absolute;
background-size: cover !important;
filter: blur(6px);
}
.num{
position: absolute;
text-shadow: 0px 0px 15px black, 0px 0px 15px black;
top: 60px;
left: 0;
right: 0;
color: rgba(255, 255, 255, 0.9);
text-align: center;
font-size: 46px;
}
.contactName{
position: absolute;
text-shadow: 0px 0px 15px black, 0px 0px 15px black;
top: 100px;
left: 0;
right: 0;
color: rgba(255, 255, 255, 0.8);
text-align: center;
margin-top: 16px;
font-size: 26px;
}
.time{
position: relative;
margin: 0 auto;
top: 280px;
left: 0px;
width: 150px;
height: 150px;
border-top: 2px solid white;
border-radius: 50%;
animation: rond 1.8s infinite linear;
}
.time-display{
text-shadow: 0px 0px 15px black, 0px 0px 15px black;
position: relative;
top: 187px;
line-height: 20px;
left: 0px;
width: 150px;
height: 91px;
color: white;
font-size: 36px;
text-align: center;
margin: 0 auto;
}
.actionbox {
position: absolute;
display: flex;
bottom: 70px;
left: 0;
right: 0;
justify-content: space-around;
}
.action {
height: 100px;
width: 100px;
border-radius: 50%;
}
.raccrocher {
background-color: #fd3d2e;
height: 70px;
width: 70px;
}
.raccrocher:hover {
background-color: #ffffff !important;
height: 90px;
width: 90px;
}
.deccrocher {
background-color: #4ddb62;
height: 70px;
width: 70px;
}
.deccrocher:hover {
background-color: #ffffff !important;
height: 90px;
width: 90px;
}
.disableTrue {
background-color: #fd3d2e;
height: 70px;
width: 70px;
}
.disable {
background-color: #4ddb62;
height: 70px;
width: 70px;
}
.action svg{
width: 60px;
height: 60px;
margin: 5px;
fill: #EEE;
}
.ignore {
position: absolute;
display: flex;
bottom: 220px;
height: 40px;
line-height: 40px;
border-radius: 20px;
text-align: center;
left: 0;
right: 0;
justify-content: space-around;
background-color: #4d4d4d;
width: 70%;
left: 15%;
color: #CCC;
}
.ignore:hover {
background-color: #818080;
}
@keyframes rond {
from {
rotate: 0deg
}
to {
rotate: 360deg
}
}
</style>

View File

@@ -0,0 +1,55 @@
<template>
<div style="width: 326px; height: 743px;">
<list :list='contactsList' :showHeader="false" v-on:select="onSelect"></list>
</div>
</template>
<script>
import { mapGetters, mapActions } from 'vuex'
import { generateColorForStr } from '@/Utils'
import List from './../List.vue'
export default {
name: 'Contacts',
components: { List },
data () {
return {
}
},
methods: {
...mapActions(['startCall']),
onSelect (itemSelect) {
if (itemSelect !== undefined) {
if (itemSelect.custom === true) {
this.$router.push({name: 'appels.number'})
} else {
this.startCall({ numero: itemSelect.number })
}
}
}
},
computed: {
...mapGetters(['IntlString', 'contacts']),
contactsList () {
return [{
display: this.IntlString('APP_PHONE_ENTER_NUMBER'),
letter: '#',
backgroundColor: '#D32F2F',
custom: true
}, ...this.contacts.slice(0).map(c => {
c.backgroundColor = generateColorForStr(c.number)
return c
})]
}
},
created () {
},
beforeDestroy () {
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,49 @@
<template>
<div>
<list :list='callList' :showHeader="false" :disable='ignoreControls' v-on:select="onSelect"></list>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import List from './../List.vue'
import Modal from '@/components/Modal/index.js'
export default {
name: 'Favoris',
components: { List },
data () {
return {
ignoreControls: false
}
},
computed: {
...mapGetters(['config']),
callList () {
return this.config.serviceCall || []
}
},
methods: {
onSelect (itemSelect) {
if (this.ignoreControls === true) return
this.ignoreControls = true
Modal.CreateModal({choix: [...itemSelect.subMenu, {title: 'Abbrechen'}]}).then(rep => {
this.ignoreControls = false
if (rep.title === 'Abbrechen') return
this.$phoneAPI.callEvent(rep.eventName, rep.type)
this.$router.push({name: 'home'})
})
}
},
created () {
},
beforeDestroy () {
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,261 @@
<template>
<div class="phone_app">
<PhoneTitle :title="IntlString('APP_PHONE_TITLE')" @back="quit" />
<div class="content">
<div class="number">
{{ numeroFormat }}
<span class="deleteNumber" @click.stop="deleteNumber"></span>
</div>
<div class="keyboard">
<div
class="key"
v-for="(key, i) of keyInfo" :key="key.primary"
:class="{'key-select': i === keySelect, 'keySpe': key.isNotNumber === true}"
@click.stop="onPressKey(key)"
>
<span @click.stop="onPressKey(key)" class="key-primary">{{key.primary}}</span>
<span @click.stop="onPressKey(key)" class="key-secondary">{{key.secondary}}</span>
</div>
</div>
<div class="call">
<div class="call-btn" :class="{'active': keySelect === 12}"
@click.stop="onPressCall">
<svg viewBox="0 0 24 24" @click.stop="onPressCall">
<g transform="rotate(0, 12, 12)">
<path d="M6.62,10.79C8.06,13.62 10.38,15.94 13.21,17.38L15.41,15.18C15.69,14.9 16.08,14.82 16.43,14.93C17.55,15.3 18.75,15.5 20,15.5A1,1 0 0,1 21,16.5V20A1,1 0 0,1 20,21A17,17 0 0,1 3,4A1,1 0 0,1 4,3H7.5A1,1 0 0,1 8.5,4C8.5,5.25 8.7,6.45 9.07,7.57C9.18,7.92 9.1,8.31 8.82,8.59L6.62,10.79Z"/>
</g>
</svg>
</div>
</div>
</div>
</div>
</template>
<script>
import { mapActions, mapGetters } from 'vuex'
import PhoneTitle from './../PhoneTitle'
export default {
components: {
PhoneTitle
},
data () {
return {
numero: '',
keyInfo: [
{primary: '1', secondary: ''},
{primary: '2', secondary: 'abc'},
{primary: '3', secondary: 'def'},
{primary: '4', secondary: 'ghi'},
{primary: '5', secondary: 'jkl'},
{primary: '6', secondary: 'mmo'},
{primary: '7', secondary: 'pqrs'},
{primary: '8', secondary: 'tuv'},
{primary: '9', secondary: 'wxyz'},
{primary: '-', secondary: '', isNotNumber: true},
{primary: '0', secondary: '+'},
{primary: '#', secondary: '', isNotNumber: true}
],
keySelect: 0
}
},
methods: {
...mapActions(['startCall']),
onLeft () {
this.keySelect = Math.max(this.keySelect - 1, 0)
},
onRight () {
this.keySelect = Math.min(this.keySelect + 1, 11)
},
onDown () {
this.keySelect = Math.min(this.keySelect + 3, 12)
},
onUp () {
if (this.keySelect > 2) {
if (this.keySelect === 12) {
this.keySelect = 10
} else {
this.keySelect = this.keySelect - 3
}
}
},
onEnter () {
if (this.keySelect === 12) {
if (this.numero.length > 0) {
this.startCall({ numero: this.numeroFormat })
}
} else {
this.numero += this.keyInfo[this.keySelect].primary
}
},
onBackspace: function () {
if (this.ignoreControls === true) return
if (this.numero.length !== 0) {
this.numero = this.numero.slice(0, -1)
} else {
history.back()
}
},
deleteNumber () {
if (this.numero.length !== 0) {
this.numero = this.numero.slice(0, -1)
}
},
onPressKey (key) {
this.numero = this.numero + key.primary
},
onPressCall () {
this.startCall({ numero: this.numeroFormat })
},
quit () {
history.back()
}
},
computed: {
...mapGetters(['IntlString', 'useMouse', 'useFormatNumberFrance']),
numeroFormat () {
if (this.useFormatNumberFrance === true) {
return this.numero
}
const l = this.numero.startsWith('#') ? 4 : 3
if (this.numero.length > l) {
return this.numero.slice(0, l) + '-' + this.numero.slice(l)
} else {
return this.numero
}
}
},
created () {
if (!this.useMouse) {
this.$bus.$on('keyUpBackspace', this.onBackspace)
this.$bus.$on('keyUpArrowLeft', this.onLeft)
this.$bus.$on('keyUpArrowRight', this.onRight)
this.$bus.$on('keyUpArrowDown', this.onDown)
this.$bus.$on('keyUpArrowUp', this.onUp)
this.$bus.$on('keyUpEnter', this.onEnter)
} else {
this.keySelect = -1
}
},
beforeDestroy () {
this.$bus.$off('keyUpBackspace', this.onBackspace)
this.$bus.$off('keyUpArrowLeft', this.onLeft)
this.$bus.$off('keyUpArrowRight', this.onRight)
this.$bus.$off('keyUpArrowDown', this.onDown)
this.$bus.$off('keyUpArrowUp', this.onUp)
this.$bus.$off('keyUpEnter', this.onEnter)
}
}
</script>
<style scoped>
.number{
margin-top: 140px;
width: 100%;
height: 52px;
font-size: 26px;
line-height: 52px;
text-align: right;
padding-right: 8px;
border-bottom: 1px solid #C0C0C0;
margin-bottom: 8px;
box-shadow: 0px -6px 12px 0px rgba(189,189,189,0.4);
position: relative;
padding-right: 60px;
}
.keyboard {
display: flex;
flex-wrap: wrap;
width: 100%;
}
.key {
position: relative;
flex: 1 1 33.33%;
text-align: center;
height: 96px;
}
.key-select::after, .key:hover::after {
content: '';
position: absolute;
top: calc(50% - 45px);
left: calc(50% - 45px);
display: block;
width: 90px;
height: 90px;
background: radial-gradient(rgba(0, 0, 0, 0.04), rgba(0, 0, 0, 0.16));
border-radius: 50%;
}
.key-primary {
display: block;
font-size: 36px;
color: black;
line-height: 22px;
padding-top: 36px;
}
.keySpe .key-primary {
color: #2c3e50;
line-height: 96px;
padding: 0;
}
.key-secondary {
text-transform: uppercase;
display: block;
font-size: 12px;
color: black;
line-height: 12px;
padding-top: 6px;
}
.call {
margin-top: 18px;
display: flex;
justify-content: center;
}
.call-btn {
margin-top: -29px;
height: 70px;
width: 70px;
border-radius: 50%;
background-color: #52d66a;
}
.call-btn.active, .call-btn:hover {
background-color: #43a047;
}
.call-btn svg {
width: 50px;
height: 50px;
margin: 10px;
fill: #EEE;
}
.deleteNumber {
display: inline-block;
position: absolute;
background: #2C2C2C;
top: 16px;
right: 12px;
height: 18px;
width: 32px;
padding: 0;
}
.deleteNumber:after {
content: '';
position: absolute;
left: -5px;
top:0;
width: 0;
height: 0;
border-style: solid;
border-width: 9px 5px 9px 0;
border-color: transparent #2C2C2C transparent transparent;
}
</style>

View File

@@ -0,0 +1,251 @@
<template>
<div class="phone_app">
<div class="elements">
<div class="element" :class="{'active': selectIndex === key}" v-for='(histo, key) in historique' :key="key"
@click.stop="selectItem(histo)"
>
<!--<div @click.stop="selectItem(histo)" class="elem-pic" :style="stylePuce(histo)">{{histo.letter}}</div>-->
<img style="width: 32px; margin-left: 10px;" src="/html/static/img/icons_app/borrado.png" alt="Logotipo APR2">
<div @click.stop="selectItem(histo)" class="elem-content">
<div style="font-size: 20px; font-weight: 400; margin-top: 20px;" @click.stop="selectItem(histo)" class="elem-content-p">{{histo.display}}</div>
<div @click.stop="selectItem(histo)" class="elem-content-s">
<div
@click.stop="selectItem(histo)"
class="elem-histo-pico"
:class="{'reject': hc.accept === false}"
v-for="(hc, i) in histo.lastCall" :key="i">
<svg @click.stop="selectItem(histo)" v-if="hc.accepts === 1 && hc.incoming === 1" viewBox="0 0 24 24" fill="#c5c5c7">
<path d="M9,5v2h6.59L4,18.59L5.41,20L17,8.41V15h2V5H9z"/>
</svg>
<svg @click.stop="selectItem(histo)" v-else-if="hc.accepts === 1 && hc.incoming === 0" viewBox="0 0 24 24" fill="#c5c5c7">
<path d="M20,5.41L18.59,4L7,15.59V9H5v10h10v-2H8.41L20,5.41z"/>
</svg>
<svg @click.stop="selectItem(histo)" v-else-if="hc.accepts === 0 && hc.incoming === 1" viewBox="0 0 24 24" fill="#c5c5c7">
<path @click.stop="selectItem(histo)" d="M3,8.41l9,9l7-7V15h2V7h-8v2h4.59L12,14.59L4.41,7L3,8.41z"/>
</svg>
<svg @click.stop="selectItem(histo)" v-else-if="hc.accepts === 0 && hc.incoming === 0" viewBox="0 0 24 24" fill="#c5c5c7">
<path d="M19.59,7L12,14.59L6.41,9H11V7H3v8h2v-4.59l7,7l9-9L19.59,7z"/>
</svg>
</div>
<!--<div v-if="histo.lastCall.length !==0" class="lastCall">
<timeago :since='histo.lastCall[0].date' :auto-update="20"></timeago>
</div>-->
</div>
<div style="float: right; margin-top: -43px;" v-if="histo.lastCall.length !==0" class="lastCall">
<timeago class="time" :since='histo.lastCall[0].date' :auto-update="20"></timeago>
</div>
</div>
<!--<div class="elem-icon" @click.stop="selectItem(histo)">
<i class="fa fa-phone" @click.stop="selectItem(histo)"></i>
</div>-->
</div>
</div>
</div>
</template>
<script>
import { mapGetters, mapActions } from 'vuex'
import { groupBy, generateColorForStr } from '@/Utils'
import Modal from '@/components/Modal/index.js'
export default {
name: 'Recents',
components: {},
data () {
return {
ignoreControls: false,
selectIndex: 0
}
},
methods: {
...mapActions(['startCall', 'appelsDeleteHistorique', 'appelsDeleteAllHistorique']),
getContact (num) {
const find = this.contacts.find(e => e.number === num)
return find
},
scrollIntoViewIfNeeded: function () {
this.$nextTick(() => {
this.$el.querySelector('.active').scrollIntoViewIfNeeded()
})
},
onUp () {
if (this.ignoreControls === true) return
this.selectIndex = Math.max(0, this.selectIndex - 1)
this.scrollIntoViewIfNeeded()
},
onDown () {
if (this.ignoreControls === true) return
this.selectIndex = Math.min(this.historique.length - 1, this.selectIndex + 1)
this.scrollIntoViewIfNeeded()
},
async selectItem (item) {
const numero = item.num
const isValid = numero.startsWith('#') === false
this.ignoreControls = true
let choix = [
{id: 1, title: this.IntlString('APP_PHONE_DELETE'), icons: 'fa-trash', color: 'orange'},
{id: 2, title: this.IntlString('APP_PHONE_DELETE_ALL'), icons: 'fa-trash', color: 'red'},
{id: 3, title: this.IntlString('CANCEL'), icons: 'fa-undo'}
]
if (isValid === true) {
choix = [{id: 0, title: this.IntlString('APP_PHONE_CALL'), icons: 'fa-phone'}, ...choix]
}
const rep = await Modal.CreateModal({ choix })
this.ignoreControls = false
switch (rep.id) {
case 0:
this.startCall({ numero })
break
case 1:
this.appelsDeleteHistorique({ numero })
break
case 2 :
this.appelsDeleteAllHistorique()
}
},
async onEnter () {
if (this.ignoreControls === true) return
this.selectItem(this.historique[this.selectIndex])
},
stylePuce (data) {
data = data || {}
if (data.icon !== undefined) {
return {
backgroundImage: `url(${data.icon})`,
backgroundSize: 'cover',
color: 'rgba(0,0,0,0)'
}
}
return {
color: data.color || this.color,
backgroundColor: data.backgroundColor || this.backgroundColor,
borderRadius: '50%'
}
}
},
computed: {
...mapGetters(['IntlString', 'useMouse', 'appelsHistorique', 'contacts']),
historique () {
let grpHist = groupBy(this.appelsHistorique, 'num')
let hist = []
for (let key in grpHist) {
const hg = grpHist[key]
const histoByDate = hg.map(e => {
e.date = new Date(e.time)
return e
}).sort((a, b) => {
return b.date - a.date
}).slice(0, 6)
const contact = this.getContact(key) || { letter: '#' }
hist.push({
num: key,
display: contact.display || key,
lastCall: histoByDate,
letter: contact.letter || contact.display[0],
backgroundColor: contact.backgroundColor || generateColorForStr(key),
icon: contact.icon
})
}
hist.sort((a, b) => {
return b.lastCall[0].time - a.lastCall[0].time
})
return hist
}
},
created () {
if (!this.useMouse) {
this.$bus.$on('keyUpArrowDown', this.onDown)
this.$bus.$on('keyUpArrowUp', this.onUp)
this.$bus.$on('keyUpEnter', this.onEnter)
} else {
this.selectIndex = -1
}
},
beforeDestroy () {
this.$bus.$off('keyUpArrowDown', this.onDown)
this.$bus.$off('keyUpArrowUp', this.onUp)
this.$bus.$off('keyUpEnter', this.onEnter)
}
}
</script>
<style scoped>
.content {
height: 100%;
}
.elements {
overflow-y: auto;
margin-left: -8px
}
.element{
height: 58px;
line-height: 58px;
display: flex;
align-items: center;
position: relative;
margin: 14px 10px;
border-radius: 2px;
}
.active, .element:hover {
background: radial-gradient(rgba(3, 168, 244, 0.14), rgba(3, 169, 244, 0.26));
}
.elem-pic{
margin-left: 12px;
height: 48px;
width: 48px;
text-align: center;
line-height: 48px;
font-weight: 700;
border-radius: 50%;
color: white;
}
.time{
margin-right: 12px;
font-size: 13px;
font-weight: 500;
color: #c8c7cb;
}
.elem-content{
margin-left: 12px;
width: auto;
flex-grow: 1;
margin-top: 13px;
}
.elem-content-p{
font-size: 20px;
line-height: 20px;
width: 153px;
}
.elem-content-s{
font-size: 12px;
line-height: 18px;
width: 137px;
display: flex;
}
.elem-histo-pico {
display: flex;
flex-direction: column;
}
.elem-histo-pico svg {
width: 16px;
height: 16px;
margin-left: 6px;
}
.lastCall {
padding-left: 4px;
}
.elem-icon{
width: 28px;
}
</style>

View File

@@ -0,0 +1,90 @@
<template>
<div class="screen" @click="onBackspace">
<div class='elements'>
<img class="logo_maze" src="/html/static/img/app_bank/logo_mazebank.jpg">
<div class="hr"></div>
<div class='element'>
<div class="element-content">
<span>$ {{ bankAmontFormat }}</span>
</div>
</div>
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
data () {
return {
}
},
computed: {
...mapGetters(['bankAmont']),
bankAmontFormat () {
return Intl.NumberFormat().format(this.bankAmont)
}
},
methods: {
onBackspace () {
this.$router.push({ name: 'home' })
}
},
created () {
this.$bus.$on('keyUpBackspace', this.onBackspace)
},
beforeDestroy () {
this.$bus.$off('keyUpBackspace', this.onBackspace)
}
}
</script>
<style scoped>
.screen{
position: relative;
left: 0;
top: 0;
width: 100%;
height: 100%;
padding: 18px;
background-color: white;
}
.title{
padding-left: 16px;
height: 34px;
line-height: 34px;
font-weight: 700;
color: white;
background-color: rgb(76, 175, 80);
}
.elements{
display: flex;
position: relative;
width: 100%;
flex-direction: column;
height: 100%;
justify-content: center;
}
.hr{
width: 100;
height: 4px;
margin-top: 4px;
background-color: #EB202D;
}
.logo_maze {
width: 100%;
height: auto;
flex-shrink: 0;
}
.element-content{
margin-top: 24px;
display: block;
height: 40px;
width: 100%;
text-align: center;
font-weight: 700;
font-size: 24px;
color: #EB202D;
}
</style>

View File

@@ -0,0 +1,261 @@
<template>
<div style="width: 326px; height: 743px;" class="screen">
<div class='elements'>
<InfoBare style="width: 326px;top: -207px;margin-left: -17px;"/>
<img class="logo_maze" src="/html/static/img/app_bank/fleeca_tar.png">
<div class="num-tarj" >
<span class="moneyTitle">{{ IntlString('APP_BANK_TITLE_BALANCE') }}</span>
<span class="moneyTitle">{{ bankAmontFormat }}$</span>
</div>
<div class="hr"></div>
<div class='element'>
<div class="element-content">
</div>
<div class="element-content" ref="form">
<input style=" border-radius: 23px; font-size: 16px;" v-bind:class="{ select: 0 === currentSelect}" v-autofocus oninput="this.value = this.value.replace(/[^0-9.]/g, ''); this.value = this.value.replace(/(\..*)\./g, '$1');" ref="form0" v-model="id" class="paragonder" placeholder="ID">
</div>
<div class="element-content">
<input style=" border-radius: 23px; font-size: 16px;" v-bind:class="{ select: 1 === currentSelect}" oninput="this.value = this.value.replace(/[^0-9.]/g, ''); this.value = this.value.replace(/(\..*)\./g, '$1');" ref="form1" v-model="paratutar" class="paragonder" placeholder="$">
<button v-bind:class="{ select: 2 === currentSelect}" ref="form2" id="gonder" @click.stop="paragonder" class="buton-transfer">{{ IntlString('APP_BANK_BUTTON_TRANSFER') }}</button><br/>
<button v-bind:class="{ select: 3 === currentSelect}" ref="form3" id="iptal" @click.stop="iptal" class="buton-cancel">{{ IntlString('APP_BANK_BUTTON_CANCEL') }}</button>
</div>
</div>
</div>
<img class="logo_tarj_end" src="/html/static/img/app_bank/tarjetas.png">
</div>
</template>
<script>
import { mapGetters, mapActions } from 'vuex'
import InfoBare from '../InfoBare'
export default {
components: {
InfoBare
},
data () {
return {
id: '',
paratutar: '',
currentSelect: 0
}
},
methods: {
...mapActions(['sendpara']),
scrollIntoViewIfNeeded: function () {
this.$nextTick(() => {
document.querySelector('focus').scrollIntoViewIfNeeded()
})
},
onBackspace () {
this.$router.go(-1)
},
iptal () {
// this.$router.push({path: '/messages'})
this.$router.go(-1)
},
paragonder () {
const paratutar = this.paratutar.trim()
if (paratutar === '') return
this.paratutar = ''
this.sendpara({
id: this.id,
amount: paratutar
})
},
onUp: function () {
if ((this.currentSelect - 1) >= 0) {
this.currentSelect = this.currentSelect - 1
}
this.$refs['form' + this.currentSelect].focus()
console.log(this.currentSelect)
},
onDown () {
if ((this.currentSelect + 1) <= 3) {
this.currentSelect = this.currentSelect + 1
}
this.$refs['form' + this.currentSelect].focus()
console.log(this.currentSelect)
},
onEnter () {
if (this.ignoreControls === true) return
if (this.currentSelect === 2) {
this.paragonder()
} else if (this.currentSelect === 0) {
this.$phoneAPI.getReponseText().then(data => {
let message = data.text.trim()
this.id = message
})
} else if (this.currentSelect === 1) {
this.$phoneAPI.getReponseText().then(data => {
let message = data.text.trim()
this.paratutar = message
})
} else if (this.currentSelect === 3) {
this.iptal()
}
}
},
computed: {
...mapGetters(['bankAmont', 'IntlString']),
bankAmontFormat () {
return Intl.NumberFormat().format(this.bankAmont)
}
},
created () {
this.display = this.$route.params.display
if (!this.useMouse) {
this.$bus.$on('keyUpArrowDown', this.onDown)
this.$bus.$on('keyUpArrowUp', this.onUp)
this.$bus.$on('keyUpEnter', this.onEnter)
}
this.$bus.$on('keyUpBackspace', this.onBackspace)
},
beforeDestroy () {
this.$bus.$off('keyUpArrowDown', this.onDown)
this.$bus.$off('keyUpArrowUp', this.onUp)
this.$bus.$off('keyUpEnter', this.onEnter)
this.$bus.$off('keyUpBackspace', this.onBackspace)
}
}
</script>
<style scoped>
.screen{
position: relative;
left: 0;
top: 0;
width: 100%;
height: 100%;
padding: 18px;
background-color: white;
}
.num-tarj{
margin-top: -88px;
margin-left: 50px
}
.moneyTitle{
font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
font-weight: 200;
color: white;
font-size: 16px;
}
.title{
padding-left: 16px;
height: 34px;
line-height: 34px;
font-weight: 700;
color: white;
background-color: rgb(76, 175, 80);
}
.elements{
display: flex;
position: relative;
width: 100%;
flex-direction: column;
height: 100%;
justify-content: center;
}
.hr{
width: 100px;
height: 4px;
margin-top: 73px;
background-image: linear-gradient(to right, #a9cc2e, #7cb732, #3a5d0d);
}
.logo_maze {
width: 100%;
height: auto;
flex-shrink: 0;
width: 113%;
margin-left: -18px;
margin-top: -207px
}
.logo_tarj_end {
width: 100%;
height: auto;
flex-shrink: 0;
width: 113%;
margin-left: -18px;
margin-top: -57px
}
.element-content{
margin-top: 24px;
display: block;
width: 100%;
text-align: center;
font-weight: 700;
font-size: 24px;
color: black;
}
.paragonder{
display: block;
width: 100%;
height: calc(1.5em + .75rem + 2px);
padding: .375rem .75rem;
font-size: 1rem;
font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
font-weight: 300;
line-height: 1.5;
color: #495057;
background-color: #fff;
background-clip: padding-box;
border: 1px solid #ced4da;
border-radius: .25rem;
transition: border-color .15s ease-in-out,box-shadow .15s ease-in-out;
}
.buton-transfer{
border: none;
width: 220px;
color: #fff;
background-image: linear-gradient(to right, #a9cc2e, #7cb732, #3a5d0d);
padding: .5rem 1rem;
font-size: 17px;
line-height: 1.5;
margin-top: 1.25rem;
font-weight: 300;
margin-bottom: .25rem;
cursor: pointer;
border-radius: 1.3rem;
transition: color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;
text-transform: none;
}
.buton-cancel{
border: none;
width: 220px;
color: #fff;
background-image: linear-gradient(to right, #D3D3D3, #C5C5C5 , #B6B6B6);
padding: .5rem 1rem;
font-size: 17px;
line-height: 1.5;
margin-top: 1.25rem;
font-weight: 300;
margin-bottom: .25rem;
cursor: pointer;
border-radius: 1.3rem;
transition: color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;
text-transform: none;
}
.select{
border: 1px double #7cb732;
}
</style>

View File

@@ -0,0 +1,152 @@
<template>
<div class="phone_app">
<PhoneTitle :title="IntlString('APP_BOURSE_TITLE')" @back="onBackspace"/>
<div class='elements'>
<div class='element'
v-for='(elem, key) in bourseInfo'
v-bind:class="{ select: key === currentSelect}"
v-bind:key="key">
<div class="elem-evo"><i class="fa" :class="classInfo(elem)"></i></div>
<div class="elem-libelle">{{elem.libelle}}</div>
<div class="elem-price" :style="{color: colorBourse(elem)}">{{elem.price}} $ </div>
<div class="elem-difference" :style="{color: colorBourse(elem)}"> <span v-if="elem.difference > 0">+</span>{{elem.difference}}</div>
</div>
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import PhoneTitle from './../PhoneTitle'
export default {
components: {
PhoneTitle
},
data () {
return {
currentSelect: 0
}
},
computed: {
...mapGetters(['IntlString', 'useMouse', 'bourseInfo'])
},
methods: {
scrollIntoViewIfNeeded: function () {
this.$nextTick(() => {
this.$el.querySelector('.select').scrollIntoViewIfNeeded()
})
},
colorBourse (bouseItem) {
if (bouseItem.difference === 0) {
return '#007aff'
} else if (bouseItem.difference < 0) {
return '#2e7d32'
} else {
return '#c62828'
}
},
classInfo (bouseItem) {
if (bouseItem.difference === 0) {
return ['fa-arrow-right', 'iblue']
} else if (bouseItem.difference < 0) {
return ['fa-arrow-up', 'ired']
} else {
return ['fa-arrow-down', 'igreen']
}
},
onBackspace () {
this.$router.push({ name: 'home' })
},
onUp () {
this.currentSelect = this.currentSelect === 0 ? 0 : this.currentSelect - 1
this.scrollIntoViewIfNeeded()
},
onDown () {
this.currentSelect = this.currentSelect === this.bourseInfo.length - 1 ? this.currentSelect : this.currentSelect + 1
this.scrollIntoViewIfNeeded()
}
},
created () {
if (!this.useMouse) {
this.$bus.$on('keyUpArrowDown', this.onDown)
this.$bus.$on('keyUpArrowUp', this.onUp)
}
this.$bus.$on('keyUpBackspace', this.onBackspace)
},
beforeDestroy () {
this.$bus.$off('keyUpArrowDown', this.onDown)
this.$bus.$off('keyUpArrowUp', this.onUp)
this.$bus.$off('keyUpBackspace', this.onBackspace)
}
}
</script>
<style scoped>
.screen{
position: relative;
left: 0;
top: 0;
width: 100%;
height: 100%;
}
.title{
padding-left: 16px;
height: 34px;
line-height: 34px;
font-weight: 700;
color: white;
background-color: rgb(76, 175, 80);
}
.elements{
height: calc(100% - 34px);
overflow-y: auto;
}
.element{
height: 56px;
width: 100%;
line-height: 56px;
display: flex;
position: relative;
}
.element.select{
background-color: #DDD;
}
.element .fa{
color: #2e7d32;
font-size: 18px;
margin-left: 6px;
}
.element .fa-arrow-down{
color: #c62828;
}
.element .fa-arrow-right{
color: #1565c0;
}
.elem-libelle{
padding-left: 6px;
flex: 1;
font-size: 22px;
white-space: nowrap;
font-weight: 100;
font-size: 15px;
}
.elem-price{
text-align: center;
width: 90px;
font-size: 18px;
font-weight: 700;
font-weight: 100;
font-size: 15px;
}
.elem-difference{
text-align: center;
width: 60px;
font-size: 14px;
font-weight: 100;
}
</style>

View File

@@ -0,0 +1,35 @@
<template>
<span>{{time}}</span>
</template>
<script>
export default {
data () {
return {
time: '',
myInterval: 0
}
},
methods: {
updateTime: function () {
var time = new Date()
var minutes = time.getMinutes()
minutes = minutes > 9 ? minutes : '0' + minutes
var heure = time.getHours()
heure = heure > 9 ? heure : '0' + heure
var datestring = heure + ':' + minutes
this.time = datestring
}
},
created: function () {
this.updateTime()
this.myInterval = setInterval(this.updateTime, 1000)
},
beforeDestroy: function () {
clearInterval(this.myInterval)
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,236 @@
<template>
<div style="width: 326px; height: 743px;" class="home" v-bind:style="{background: 'url(' + backgroundURL +')'}">
<InfoBare />
<span class="warningMess" v-if="messages.length >= warningMessageCount">
<div class="warningMess_icon"><i class="fa fa-warning"></i></div>
<span class="warningMess_content">
<span class="warningMess_title">{{ IntlString('PHONE_WARNING_MESSAGE') }}</span><br>
<span class="warningMess_mess">{{messages.length}} / {{warningMessageCount}} {{IntlString('PHONE_WARNING_MESSAGE_MESS')}}</span>
</span>
</span>
<div class="time"></div>
<div class="time-display">{{timeDisplay}}</div>
<div class='home_buttons'>
<button style=" top: 73px; font-family:initial; margin-left: 10px; margin-right: 10px;"
v-for="(but, key) of AppsHome"
v-bind:key="but.name"
v-bind:class="{ select: key === currentSelect}"
v-bind:style="{backgroundImage: 'url(' + but.icons +')'}"
@click="openApp(but)"
>
<!--{{but.intlName}}-->
<span class="puce" v-if="but.puce !== undefined && but.puce !== 0">{{but.puce}}</span>
</button>
<div class="btn_menu_ctn">
<button
class="btn_menu"
:class="{ select: AppsHome.length === currentSelect}"
v-bind:style="{backgroundImage: 'url(' + '/html/static/img/icons_app/menu.png' +')'}"
@click="openApp({routeName: 'menu'})"
>
</button>
</div>
</div>
</div>
</template>
<script>
import { mapGetters, mapActions } from 'vuex'
import InfoBare from './InfoBare'
export default {
components: {
InfoBare
},
data () {
return {
currentSelect: 0
}
},
computed: {
...mapGetters(['IntlString', 'useMouse', 'nbMessagesUnread', 'backgroundURL', 'messages', 'AppsHome', 'warningMessageCount'])
},
methods: {
...mapActions(['closePhone', 'setMessages']),
onLeft () {
this.currentSelect = (this.currentSelect + 1) % (this.AppsHome.length + 1)
},
onRight () {
this.currentSelect = (this.currentSelect + this.AppsHome.length) % (this.AppsHome.length + 1)
},
onUp () {
this.currentSelect = Math.max(this.currentSelect - 4, 0)
},
onDown () {
this.currentSelect = Math.min(this.currentSelect + 4, this.AppsHome.length)
},
openApp (app) {
this.$router.push({ name: app.routeName })
},
onEnter () {
this.openApp(this.AppsHome[this.currentSelect] || {routeName: 'menu'})
},
onBack () {
this.closePhone()
}
},
created () {
if (!this.useMouse) {
this.$bus.$on('keyUpArrowLeft', this.onLeft)
this.$bus.$on('keyUpArrowRight', this.onRight)
this.$bus.$on('keyUpArrowDown', this.onDown)
this.$bus.$on('keyUpArrowUp', this.onUp)
this.$bus.$on('keyUpEnter', this.onEnter)
} else {
this.currentSelect = -1
}
this.$bus.$on('keyUpBackspace', this.onBack)
},
beforeDestroy () {
this.$bus.$off('keyUpArrowLeft', this.onLeft)
this.$bus.$off('keyUpArrowRight', this.onRight)
this.$bus.$off('keyUpArrowDown', this.onDown)
this.$bus.$off('keyUpArrowUp', this.onUp)
this.$bus.$off('keyUpEnter', this.onEnter)
this.$bus.$off('keyUpBackspace', this.onBack)
}
}
</script>
<style scoped="true">
.home{
background-size: cover !important;
background-position: center !important;
position: relative;
left: 0;
top: 0;
width: 100%;
height: 100%;
display: flex;
align-content: center;
justify-content: center;
color: gray;
}
.warningMess{
background-color: white;
position: absolute;
left: 12px;
right: 12px;
top: 34px;
min-height: 64px;
display: flex;
padding: 12px;
border-radius: 4px;
box-shadow: 0 2px 2px 0 rgba(0,0,0,.14), 0 3px 1px -2px rgba(0,0,0,.2), 0 1px 5px 0 rgba(0,0,0,.12);
}
.warningMess .warningMess_icon{
display: flex;
width: 16%;
align-items: center;
justify-content: center;
font-size: 28px;
height: 42px;
width: 42px;
border-radius: 50%;
}
.warningMess .warningMess_icon .fa {
text-align: center;
color: #F94B42;
}
.warningMess .warningMess_content{
padding-left: 12px;
background-color: rgba(255,255,255, 0.2);
}
.warningMess_title {
font-size: 20px;
}
.warningMess_mess {
font-size: 16px;
}
.home_buttons{
display: flex;
padding: 6px;
width: 100%;
bottom:1px;
position: absolute;
align-items: flex-end;
flex-flow: row;
flex-wrap: wrap;
margin-bottom: 3px;
justify-content: space-between;
transition: all 0.5s ease-in-out;
}
button{
position: relative;
margin: 0px;
border: none;
width: 80px;
height: 76px;
color: white;
background-size: 64px 64px;
background-position: center 6px;
background-repeat: no-repeat;
background-color: transparent;
font-size: 14px;
padding-top: 72px;
font-weight: 700;
text-shadow: -1px 0 0 rgba(0,0,0, 0.8),
1px 0 0 rgba(0,0,0, 0.8),
0 -1px 0 rgba(0,0,0, 0.8),
0 1px 0 rgba(0,0,0, 0.8);
text-align: center;
}
button .puce{
position: absolute;
display: block;
background-color: #EE3838;
font-size: 14px;
width: 26px;
height: 26px;
top: -5px;
left: 51px;
font-family: none;
line-height: 28px;
text-align: center;
border-radius: 50%;
font-weight: 400;
font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
bottom: 32px;
right: 12px;
bottom: 32px;
right: 12px;
}
button.select, button:hover{
background-color: rgba(255,255,255, 0.2);
border-radius: 22%;
}
.btn_menu_ctn{
width: 100%;
display: flex;
height: 70px;
justify-content: center;
align-content: center;
border-radius: 24px;
}
.btn_menu {
height: 50px;
}
</style>

View File

@@ -0,0 +1,115 @@
<template>
<div style="width: 326px; top: 4px;" class='phone_infoBare barre-header'>
<span class='reseau'>{{ config.reseau }}</span>
<span class="time">
<current-time style="font-size: 12px; margin-right: 2px;"></current-time>
</span>
<hr class="batterie1">
<hr class="batterie2">
<hr class="barre1">
<hr class="barre2">
<hr class="barre3">
<hr class="barre4">
</div>
</template>
<script>
import {mapGetters} from 'vuex'
import CurrentTime from './CurrentTime'
export default {
computed: mapGetters(['config']),
components: {
CurrentTime
}
}
</script>
<style scoped>
.barre-header {
height: 24px;
font-size: 17px;
line-height: 24px;
padding: 0px 20px 0px 24px;
width: 100%;
color: white;
background-color: rgba(0, 0, 0, 0.3);
position: relative;
}
.barre-header hr {
position: absolute;
display: inline-block;
}
.reseau {
font-size: 12px;
padding-left: 10px;
}
.barre1 {
height: 12px;
width: 3px;
right: 53px;
background-color: rgba(255, 255, 255, 0.6);
color: rgba(255, 255, 255, 0.6);
border: none;
bottom: -1px;
}
.barre2 {
height: 9px;
width: 3px;
right: 58px;
background-color: white;
border: none;
bottom: -1px;
}
.barre3 {
height: 6px;
width: 3px;
right: 63px;
background-color: white;
border: none;
bottom: -1px;
}
.barre4 {
height: 3px;
width: 3px;
right: 68px;
background-color: white;
border: none;
bottom: -1px;
}
.time {
text-align: right;
float: right;
margin-right: -14px;
font-size: 12px;
padding-right: 12px;
}
.batterie1 {
height: 10px;
width: 7px;
right: 78px;
background-color: rgb(255, 255, 255);
color: rgb(255, 255, 255);
border-radius: 0.5px;
border: none;
bottom: -1px;
}
.batterie2 {
height: 13px;
width: 5px;
right: 79px;
bottom: 0px;
background-color: rgba(255, 255, 255, 0.6);
border: 0.5px solid white;
border-radius: 1px;
}
</style>

View File

@@ -0,0 +1,233 @@
<template>
<div class="phone_app">
<PhoneTitle :title="title" :showInfoBare="showInfoBare" v-if="showHeader" @back="back"/>
<!-- <InfoBare v-if="showInfoBare"/>
<div v-if="title !== ''" class="phone_title" v-bind:style="styleTitle()">{{title}}</div>
-->
<div style="width: 324px; height: 595px;" class="phone_content elements">
<div class="element" v-for='(elem, key) in list'
v-bind:key="elem[keyDispay]"
v-bind:class="{ select: key === currentSelect}"
@click.stop="selectItem(elem)"
@contextmenu.prevent="optionItem(elem)"
>
<div class="elem-pic" v-bind:style="stylePuce(elem)" @click.stop="selectItem(elem)">
{{elem.letter || elem[keyDispay][0]}}
</div>
<div @click.stop="selectItem(elem)" v-if="elem.puce !== undefined && elem.puce !== 0" class="elem-puce">{{elem.puce}}</div>
<div @click.stop="selectItem(elem)" v-if="elem.keyDesc === undefined || elem.keyDesc === ''" class="elem-title">{{elem[keyDispay]}}</div>
<div @click.stop="selectItem(elem)" v-if="elem.keyDesc !== undefined && elem.keyDesc !== ''" class="elem-title-has-desc">{{elem[keyDispay]}}</div>
<div @click.stop="selectItem(elem)" v-if="elem.keyDesc !== undefined && elem.keyDesc !== ''" class="elem-description">{{elem.keyDesc}}</div>
</div>
</div>
</div>
</template>
<script>
import PhoneTitle from './PhoneTitle'
import InfoBare from './InfoBare'
import { mapGetters } from 'vuex'
export default {
name: 'hello',
components: {
PhoneTitle, InfoBare
},
data: function () {
return {
currentSelect: 0
}
},
props: {
title: {
type: String,
default: 'Title'
},
showHeader: {
type: Boolean,
default: true
},
showInfoBare: {
type: Boolean,
default: true
},
list: {
type: Array,
required: true
},
color: {
type: String,
default: '#FFFFFF'
},
backgroundColor: {
type: String,
default: '#4CAF50'
},
keyDispay: {
type: String,
default: 'display'
},
disable: {
type: Boolean,
default: false
},
titleBackgroundColor: {
type: String,
default: '#FFFFFF'
}
},
watch: {
list: function () {
this.currentSelect = 0
}
},
computed: {
...mapGetters(['useMouse'])
},
methods: {
styleTitle: function () {
return {
color: this.color,
backgroundColor: this.backgroundColor
}
},
stylePuce (data) {
data = data || {}
if (data.icon !== undefined) {
return {
backgroundImage: `url(${data.icon})`,
backgroundSize: 'cover',
color: 'rgba(0,0,0,0)'
}
}
return {
color: data.color || this.color,
backgroundColor: data.backgroundColor || this.backgroundColor,
borderRadius: '50%'
}
},
scrollIntoViewIfNeeded: function () {
this.$nextTick(() => {
document.querySelector('.select').scrollIntoViewIfNeeded()
})
},
onUp: function () {
if (this.disable === true) return
this.currentSelect = this.currentSelect === 0 ? this.list.length - 1 : this.currentSelect - 1
this.scrollIntoViewIfNeeded()
},
onDown: function () {
if (this.disable === true) return
this.currentSelect = this.currentSelect === this.list.length - 1 ? 0 : this.currentSelect + 1
this.scrollIntoViewIfNeeded()
},
selectItem (item) {
this.$emit('select', item)
},
optionItem (item) {
this.$emit('option', item)
},
back () {
this.$emit('back')
},
onRight: function () {
if (this.disable === true) return
this.$emit('option', this.list[this.currentSelect])
},
onEnter: function () {
if (this.disable === true) return
this.$emit('select', this.list[this.currentSelect])
}
},
created: function () {
if (!this.useMouse) {
this.$bus.$on('keyUpArrowDown', this.onDown)
this.$bus.$on('keyUpArrowUp', this.onUp)
this.$bus.$on('keyUpArrowRight', this.onRight)
this.$bus.$on('keyUpEnter', this.onEnter)
} else {
this.currentSelect = -1
}
},
beforeDestroy: function () {
this.$bus.$off('keyUpArrowDown', this.onDown)
this.$bus.$off('keyUpArrowUp', this.onUp)
this.$bus.$off('keyUpArrowRight', this.onRight)
this.$bus.$off('keyUpEnter', this.onEnter)
}
}
</script>
<style scoped>
.list{
height: 100%;
}
.elements{
overflow-y: auto;
}
.element{
height: 58px;
line-height: 58px;
display: flex;
align-items: center;
position: relative;
font-weight: 300;
font-size: 18px;
}
.element.select, .element:hover {
background-color: #DDD;
}
.elem-pic{
margin-left: 12px;
height: 48px;
width: 48px;
text-align: center;
line-height: 48px;
font-weight: 200;
}
.elem-puce{
background-color: #EE3838;
top: 0px;
color:white;
height: 18px;
width: 18px;
line-height: 18px;
border-radius: 50%;
text-align: center;
font-size: 14px;
margin: 0px;
padding: 0px;
position: absolute;
left: 42px;
z-index: 6;
}
.elem-title{
margin-left: 12px;
font-size: 20px;
font-weight: 400;
}
.elem-title-has-desc {
margin-top:-15px;
margin-left: 12px;
}
.elem-description{
text-align:left;
color:grey;
position:absolute;
display:block;
width:75%;
left:73px;
top:12px;
font-weight: 100px;
font-size:13.5px;
font-style:italic;
overflow:hidden;
text-overflow:ellipsis;
white-space: nowrap;
}
</style>

View File

@@ -0,0 +1,219 @@
<template>
<div style="width: 326px; height: 743px;" class="phone_app">
<div style="width: 342px;
height: 756px;" class="backblur" v-bind:style="{background: 'url(' + backgroundURL +')'}"></div>
<InfoBare class="infobare"/>
<div class="menu" @click="onBack">
<div class="menu_content">
<div class='menu_buttons'>
<button
v-for="(but, key) of Apps"
v-bind:key="but.name"
v-bind:class="{ select: key === currentSelect}"
v-bind:style="{backgroundImage: 'url(' + but.icons +')'}"
@click.stop="openApp(but)"
>
<span class="letra">{{but.intlName}}</span>
<span class="puce" v-if="but.puce !== undefined && but.puce !== 0">{{but.puce}}</span>
</button>
</div>
</div>
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import InfoBare from './InfoBare'
export default {
components: {
InfoBare
},
data: function () {
return {
currentSelect: 0,
nBotonesMenu: 3
}
},
computed: {
...mapGetters(['nbMessagesUnread', 'backgroundURL', 'Apps', 'useMouse'])
},
methods: {
...mapGetters(['closePhone']),
onLeft: function () {
const l = Math.floor(this.currentSelect / this.nBotonesMenu)
const newS = (this.currentSelect + this.nBotonesMenu - 1) % this.nBotonesMenu + l * this.nBotonesMenu
this.currentSelect = Math.min(newS, this.Apps.length - 1)
},
onRight: function () {
const l = Math.floor(this.currentSelect / this.nBotonesMenu)
let newS = (this.currentSelect + 1) % this.nBotonesMenu + l * this.nBotonesMenu
if (newS >= this.Apps.length) {
newS = l * this.nBotonesMenu
}
this.currentSelect = newS
},
onUp: function () {
let newS = this.currentSelect - this.nBotonesMenu
if (newS < 0) {
const r = this.currentSelect % this.nBotonesMenu
newS = Math.floor((this.Apps.length - 1) / this.nBotonesMenu) * this.nBotonesMenu
this.currentSelect = Math.min(newS + r, this.Apps.length - 1)
} else {
this.currentSelect = newS
}
},
onDown: function () {
const r = this.currentSelect % this.nBotonesMenu
let newS = this.currentSelect + this.nBotonesMenu
if (newS >= this.Apps.length) {
newS = r
}
this.currentSelect = newS
},
openApp (app) {
this.$router.push({ name: app.routeName })
},
onEnter () {
this.openApp(this.Apps[this.currentSelect])
},
onBack: function () {
this.$router.push({ name: 'home' })
}
},
mounted () {
},
created () {
if (!this.useMouse) {
this.$bus.$on('keyUpArrowLeft', this.onLeft)
this.$bus.$on('keyUpArrowRight', this.onRight)
this.$bus.$on('keyUpArrowDown', this.onDown)
this.$bus.$on('keyUpArrowUp', this.onUp)
this.$bus.$on('keyUpEnter', this.onEnter)
} else {
this.currentSelect = -1
}
this.$bus.$on('keyUpBackspace', this.onBack)
},
beforeDestroy () {
this.$bus.$off('keyUpArrowLeft', this.onLeft)
this.$bus.$off('keyUpArrowRight', this.onRight)
this.$bus.$off('keyUpArrowDown', this.onDown)
this.$bus.$off('keyUpArrowUp', this.onUp)
this.$bus.$off('keyUpEnter', this.onEnter)
this.$bus.$off('keyUpBackspace', this.onBack)
}
}
</script>
<style scoped>
.menu{
position: relative;
left: 0;
top: 0;
width: 100%;
height: 100%;
padding: 6px 8px;
}
.backblur{
top: -6px;
left: -6px;
right:-6px;
bottom: -6px;
position: absolute;
background-size: cover !important;
background-position: center !important;
filter: blur(6px);
}
.menu_content {
display: flex;
flex-direction: column;
}
.menu_buttons{
margin-top: 24px;
display: flex;
width: 100%;
margin-left: 10px;
align-items: flex-start;
align-content: flex-start;
/* justify-content: space-around; */
flex-flow: row;
flex-wrap: wrap;
margin-bottom: 0px;
transition: all 0.5s ease-in-out;
}
.menu_buttons {
animation-name: up;
animation-duration: 0.6s;
animation-fill-mode: forwards;
}
@keyframes up {
from {transform: translateY(100vh);}
to {transform: translateY(0);}
}
button{
position: relative;
margin: 0px;
border: none;
width: 80px;
height: 110px;
margin: 8px;
color: white;
background-size: 64px 64px;
background-position: center 6px;
background-repeat: no-repeat;
background-color: transparent;
font-size: 14px;
padding-top: 72px;
font-weight: 700;
text-shadow: -1px 0 0 rgba(0,0,0, 0.8),
1px 0 0 rgba(0,0,0, 0.8),
0 -1px 0 rgba(0,0,0, 0.8),
0 1px 0 rgba(0,0,0, 0.8);
text-align: center;
}
button .puce{
position: absolute;
display: block;
background-color: #EE3838;
font-size: 14px;
width: 26px;
height: 26px;
top: -5px;
left: 51px;
font-family: none;
line-height: 28px;
text-align: center;
border-radius: 50%;
font-weight: 400;
font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
bottom: 32px;
right: 12px;
bottom: 32px;
right: 12px;
}
button.select, button:hover{
background-color: rgba(255,255,255, 0.2);
border-radius: 12px;
}
.letra{
font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica;
font-weight: 400;
font-size: 14px;
position: absolute;
left: 50%;
transform: translate(-50%, -10px);
}
</style>

View File

@@ -0,0 +1,150 @@
<template>
<transition name="modal">
<div
class="modal-mask"
@click.stop="cancel">
<div class="modal-container">
<div class="modal-choix"
v-bind:class="{ select: index === currentSelect}"
v-for="(val, index) in choix" :key='index'
v-bind:style="{color: val.color}"
@click.stop="selectItem(val)"
>
<i @click.stop="selectItem(val)" class="fas" :class="val.icons" ></i>{{val.title}}
</div>
</div>
</div>
</transition>
</template>
<script>
import store from './../../store'
import { mapGetters } from 'vuex'
export default {
name: 'Modal',
store: store,
data () {
return {
currentSelect: 0
}
},
props: {
choix: {
type: Array,
default: () => []
}
},
computed: {
...mapGetters(['useMouse'])
},
methods: {
scrollIntoViewIfNeeded () {
this.$nextTick(() => {
document.querySelector('.modal-choix.select').scrollIntoViewIfNeeded()
})
},
onUp () {
this.currentSelect = this.currentSelect === 0 ? 0 : this.currentSelect - 1
this.scrollIntoViewIfNeeded()
},
onDown () {
this.currentSelect = this.currentSelect === this.choix.length - 1 ? this.currentSelect : this.currentSelect + 1
this.scrollIntoViewIfNeeded()
},
selectItem (elem) {
this.$emit('select', elem)
},
onEnter () {
this.$emit('select', this.choix[this.currentSelect])
},
cancel () {
this.$emit('cancel')
}
},
created () {
if (!this.useMouse) {
this.$bus.$on('keyUpArrowDown', this.onDown)
this.$bus.$on('keyUpArrowUp', this.onUp)
this.$bus.$on('keyUpEnter', this.onEnter)
} else {
this.currentSelect = -1
}
this.$bus.$on('keyUpBackspace', this.cancel)
},
beforeDestroy () {
this.$bus.$off('keyUpArrowDown', this.onDown)
this.$bus.$off('keyUpArrowUp', this.onUp)
this.$bus.$off('keyUpEnter', this.onEnter)
this.$bus.$off('keyUpBackspace', this.cancel)
}
}
</script>
<style scoped>
.modal-mask {
position: absolute;
z-index: 99;
top: 0;
left: 0;
width: 334px;
height: 738px;
background-color: rgba(0, 0, 0, .3);
display: flex;
align-items: flex-end;
transition: opacity .3s ease;
}
.modal-container {
width: 100%;
margin: 0;
padding: 0;
background-color: #fff;
border-radius: 2px;
box-shadow: 0 2px 8px rgba(0, 0, 0, .33);
transition: all .3s ease;
padding-bottom: 16px;
max-height: 100%;
overflow-y: auto;
}
.modal-title {
text-align: center;
height: 32px;
line-height: 32px;
color: #42B2DC;
border-bottom: 2px solid #42B2DC;
}
.modal-choix {
font-size: 15px;
height: 56px;
line-height: 56px;
color: gray;
position: relative;
font-weight: 400;
}
.modal-choix .fa, .modal-choix .fas {
font-size: 18px;
line-height: 24px;
margin-left: 12px;
margin-right: 12px;
}
.modal-choix .picto {
z-index: 500;
position: absolute;
width: 42px;
background-size: 100% !important;
background-position-y: 100%;
height: 42px;
}
.modal-choix.select, .modal-choix:hover {
background-color: #E3E3E3;
color: #0079d3
}
</style>

View File

@@ -0,0 +1,179 @@
<template>
<transition name="modal">
<div
class="modal-mask"
>
<div class="modal-container" @click.stop >
<h2 :style="{color}">{{ title }}</h2>
<textarea
class="modal-textarea"
:class="{oneline: limit <= 18}"
ref="textarea"
:style="{borderColor: color}"
v-model="inputText"
:maxlength="limit"
></textarea>
<div class="botton-container">
<button
:style="{color}"
@click="cancel"
>
{{ IntlString('CANCEL') }}
</button>
<button
:style="{color}"
@click="valide"
>
{{ IntlString('OK') }}
</button>
</div>
</div>
</div>
</transition>
</template>
<script>
import store from './../../store'
import { mapGetters } from 'vuex'
export default {
name: 'TextModal',
store: store,
data () {
return {
inputText: ''
}
},
props: {
title: {
type: String,
default: () => ''
},
text: {
type: String,
default: () => ''
},
limit: {
type: Number,
default: 255
}
},
computed: {
...mapGetters(['IntlString', 'themeColor']),
color () {
return this.themeColor || '#2A56C6'
}
},
methods: {
scrollIntoViewIfNeeded () {
this.$nextTick(() => {
document.querySelector('.modal-choix.select').scrollIntoViewIfNeeded()
})
},
onUp () {
this.currentSelect = this.currentSelect === 0 ? 0 : this.currentSelect - 1
this.scrollIntoViewIfNeeded()
},
onDown () {
this.currentSelect = this.currentSelect === this.choix.length - 1 ? this.currentSelect : this.currentSelect + 1
this.scrollIntoViewIfNeeded()
},
selectItem (elem) {
this.$emit('select', elem)
},
onEnter () {
this.$emit('select', this.choix[this.currentSelect])
},
cancel () {
this.$emit('cancel')
},
valide () {
this.$emit('valid', {
text: this.inputText
})
}
},
created () {
this.inputText = this.text
},
mounted () {
this.$nextTick(() => {
this.$refs.textarea.focus()
})
},
beforeDestroy () {
}
}
</script>
<style scoped>
.modal-mask {
position: absolute;
z-index: 99;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, .3);
display: flex;
align-items: center;
justify-content: center;
transition: opacity .3s ease;
}
.modal-container {
width: 100%;
margin: 0;
padding: 0;
background-color: #fff;
border-radius: 2px;
box-shadow: 0 2px 8px rgba(0, 0, 0, .33);
transition: all .3s ease;
padding-bottom: 16px;
max-height: 100%;
width: 90%;
overflow-y: auto;
padding: 15px 20px;
font-size: 18px;
}
h2 {
font-size: 22px;
}
.modal-textarea {
width: 100%;
height: 140px;
border: none;
resize: none;
border-bottom: 3px solid red;
outline: none;
font-size: 18px;
}
.modal-textarea.oneline {
height: 38px;
}
.botton-container {
margin-top: 12px;
display: flex;
justify-content: flex-end;
}
.botton-container button {
background-color: transparent;
border: none;
font-size: 18px;
font-weight: 700;
padding: 6px 12px;
outline: none;
}
.botton-container button:hover {
background-color: rgba(0, 0, 0, .1);
}
</style>

View File

@@ -0,0 +1,49 @@
import Vue from 'vue'
import Modal from './Modal'
import TextModal from './TextModal'
import store from '@/store'
import PhoneAPI from '@/PhoneAPI'
export default {
CreateModal (propsData = {}) {
return new Promise((resolve, reject) => {
let modal = new (Vue.extend(Modal))({
el: document.createElement('div'),
propsData
})
document.querySelector('#app').appendChild(modal.$el)
modal.$on('select', (data) => {
resolve(data)
modal.$el.parentNode.removeChild(modal.$el)
modal.$destroy()
})
modal.$on('cancel', () => {
resolve({title: 'cancel'})
modal.$el.parentNode.removeChild(modal.$el)
modal.$destroy()
})
})
},
CreateTextModal (propsData = {}) {
if (store.getters.useMouse === false) {
return PhoneAPI.getReponseText(propsData)
}
return new Promise((resolve, reject) => {
let modal = new (Vue.extend(TextModal))({
el: document.createElement('div'),
propsData
})
document.querySelector('#app').appendChild(modal.$el)
modal.$on('valid', (data) => {
resolve(data)
modal.$el.parentNode.removeChild(modal.$el)
modal.$destroy()
})
modal.$on('cancel', () => {
reject('UserCancel')
modal.$el.parentNode.removeChild(modal.$el)
modal.$destroy()
})
})
}
}

View File

@@ -0,0 +1,213 @@
<template>
<div style="width: 334px; height: 742px; color: white" class="phone_app">
<PhoneTitle :title="IntlString('APP_NOTES')" backgroundColor="#f8d344" color="white" @back="onBack" />
<div style="backgroundColor: white;" class="elements" @contextmenu.prevent="addChannelOption">
<div
>
<div v-for='(elem, key) in notesChannels'
v-bind:key="elem.channel"
v-bind:class="{ select: key === currentSelect}" class="elem-title">
<h3 style="margin-left: 7px; font-size: 16px; font-weight: 400;"> {{elem.channel}}</h3>
</div>
</div>
</div>
</div>
</template>
<script>
import { mapGetters, mapActions } from 'vuex'
import Modal from '@/components/Modal/index.js'
import PhoneTitle from './../PhoneTitle'
export default {
components: { PhoneTitle },
data: function () {
return {
currentSelect: 0,
ignoreControls: false
}
},
watch: {
list: function () {
this.currentSelect = 0
}
},
computed: {
...mapGetters(['IntlString', 'useMouse', 'notesChannels', 'Apps'])
},
methods: {
...mapActions(['notesAddChannel', 'notesRemoveChannel']),
scrollIntoViewIfNeeded () {
this.$nextTick(() => {
const $select = this.$el.querySelector('.select')
if ($select !== null) {
$select.scrollIntoViewIfNeeded()
}
})
},
onUp () {
if (this.ignoreControls === true) return
this.currentSelect = this.currentSelect === 0 ? 0 : this.currentSelect - 1
this.scrollIntoViewIfNeeded()
},
onDown () {
if (this.ignoreControls === true) return
this.currentSelect = this.currentSelect === this.notesChannels.length - 1 ? this.currentSelect : this.currentSelect + 1
this.scrollIntoViewIfNeeded()
},
async onRight () {
if (this.ignoreControls === true) return
this.ignoreControls = true
let choix = [
{id: 1, title: this.IntlString('APP_DARKTCHAT_NEW_NOTE'), icons: 'fa-plus', color: 'dodgerblue'},
{id: 2, title: this.IntlString('APP_DARKTCHAT_DELETE_NOTE'), icons: 'fa-minus', color: 'tomato'},
{id: 3, title: this.IntlString('APP_DARKTCHAT_CANCEL'), icons: 'fa-undo'}
]
if (this.notesChannels.length === 0) {
choix.splice(1, 1)
}
const rep = await Modal.CreateModal({ choix })
this.ignoreControls = false
switch (rep.id) {
case 1:
this.addChannelOption()
break
case 2:
this.removeChannelOption()
break
case 3 :
}
},
async onEnter () {
if (this.ignoreControls === true) return
if (this.notesChannels.length === 0) {
this.ignoreControls = true
let choix = [
{id: 1, title: this.IntlString('APP_DARKTCHAT_NEW_CHANNEL'), icons: 'fa-plus', color: 'green'},
{id: 3, title: this.IntlString('APP_DARKTCHAT_CANCEL'), icons: 'fa-undo'}
]
const rep = await Modal.CreateModal({ choix })
this.ignoreControls = false
if (rep.id === 1) {
this.addChannelOption()
}
} else {
}
},
showChannel (channel) {
this.$router.push({ name: 'notes.channel.show', params: { channel } })
},
onBack () {
if (this.ignoreControls === true) return
this.$router.push({ name: 'home' })
},
async addChannelOption () {
try {
const rep = await Modal.CreateTextModal({limit: 280, title: this.IntlString('APP_DARKTCHAT_NEW_CHANNEL')})
let channel = (rep || {}).text || ' '
channel
if (channel.length > 0) {
this.currentSelect = 0
this.notesAddChannel({ channel })
}
} catch (e) {}
},
async removeChannelOption () {
const channel = this.notesChannels[this.currentSelect].channel
this.currentSelect = 0
this.notesRemoveChannel({ channel })
}
},
created () {
if (!this.useMouse) {
this.$bus.$on('keyUpArrowDown', this.onDown)
this.$bus.$on('keyUpArrowUp', this.onUp)
this.$bus.$on('keyUpArrowRight', this.onRight)
this.$bus.$on('keyUpEnter', this.onEnter)
this.$bus.$on('keyUpBackspace', this.onBack)
} else {
this.currentSelect = -1
}
},
beforeDestroy () {
this.$bus.$off('keyUpArrowDown', this.onDown)
this.$bus.$off('keyUpArrowUp', this.onUp)
this.$bus.$off('keyUpArrowRight', this.onRight)
this.$bus.$off('keyUpEnter', this.onEnter)
this.$bus.$off('keyUpBackspace', this.onBack)
}
}
</script>
<style scoped>
.list{
height: 100%;
}
.title{
padding-top: 22px;
padding-left: 16px;
height: 54px;
line-height: 34px;
font-weight: 700;
color: white;
}
.elements{
height: calc(100% - 54px);
overflow-y: auto;
background-color: #20201d;
color: #34302f
}
.element{
margin-top: 50px;
height: 42px;
line-height: 42px;
display: flex;
align-items: center;
position: relative;
}
.elem-title{
margin-left: 6px;
width: 300px;
font-size: 20px;
transition: .15s;
font-weight: 200;
color: #34302f;
margin-left: 13px;
border-radius: 13px;
}
.elem-title .diese {
color: #34302f;
font-size: 22px;
font-weight: 700;
line-height: 40px;
}
.elem-title.select, .elem-title:hover{
background-color:rgba(112, 108, 108, 0.1);
color: #34302f;
}
.element.select .elem-title, .element:hover .elem-title {
margin-left: 12px;
}
.element.select .elem-title .diese, .element:hover .elem-title .diese {
color:#f8d344;
}
.elements::-webkit-scrollbar-track
{
box-shadow: inset 0 0 6px rgba(0,0,0,0.3);
background-color: #F5F5F5;
}
.elements::-webkit-scrollbar
{
width: 3px;
background-color: transparent;
}
.elements::-webkit-scrollbar-thumb
{
background-color: white;
}
</style>

View File

@@ -0,0 +1,217 @@
<template>
<div style="width: 334px; height: 742px; background: white" class="phone_app">
<PhoneTitle :title="channelName" backgroundColor="#f8d344" @back="onQuit"/>
<div class="phone_content">
<div class="elements" ref="elementsDiv">
<div class="element" v-for='(elem) in notesMessages'
v-bind:key="elem.id"
>
<div class="time">{{formatTime(elem.time)}}</div>
<div class="message">
{{elem.message}}
</div>
</div>
</div>
<div class='notes_write'>
<input type="text" placeholder="..." v-model="message" @keyup.enter.prevent="sendMessage">
<span class='notes_send' @click="sendMessage">></span>
</div>
</div>
</div>
</template>
<script>
import { mapGetters, mapActions } from 'vuex'
import PhoneTitle from './../PhoneTitle'
export default {
components: { PhoneTitle },
data () {
return {
message: '',
channel: '',
currentSelect: 0
}
},
computed: {
...mapGetters(['notesMessages', 'notesCurrentChannel', 'useMouse']),
channelName () {
return '# ' + this.channel
}
},
watch: {
notesMessages () {
const c = this.$refs.elementsDiv
c.scrollTop = c.scrollHeight
}
},
methods: {
setChannel (channel) {
this.channel = channel
this.notesSetChannel({ channel })
},
...mapActions(['notesSetChannel', 'notesSendMessage']),
scrollIntoViewIfNeeded () {
this.$nextTick(() => {
const $select = this.$el.querySelector('.select')
if ($select !== null) {
$select.scrollIntoViewIfNeeded()
}
})
},
onUp () {
const c = this.$refs.elementsDiv
c.scrollTop = c.scrollTop - 120
},
onDown () {
const c = this.$refs.elementsDiv
c.scrollTop = c.scrollTop + 120
},
async onEnter () {
const rep = await this.$phoneAPI.getReponseText()
if (rep !== undefined && rep.text !== undefined) {
const message = rep.text.trim()
if (message.length !== 0) {
this.notesSendMessage({
channel: this.channel,
message
})
}
}
},
sendMessage () {
const message = this.message.trim()
if (message.length !== 0) {
this.notesSendMessage({
channel: this.channel,
message
})
this.message = ''
}
},
onBack () {
if (this.useMouse === true && document.activeElement.tagName !== 'BODY') return
this.onQuit()
},
onQuit () {
this.$router.push({ name: 'notes.channel' })
},
formatTime (time) {
const d = new Date(time)
return d.toLocaleTimeString()
}
},
created () {
if (!this.useMouse) {
this.$bus.$on('keyUpArrowDown', this.onDown)
this.$bus.$on('keyUpArrowUp', this.onUp)
this.$bus.$on('keyUpEnter', this.onEnter)
} else {
this.currentSelect = -1
}
this.$bus.$on('keyUpBackspace', this.onBack)
this.setChannel(this.$route.params.channel)
},
mounted () {
window.c = this.$refs.elementsDiv
const c = this.$refs.elementsDiv
c.scrollTop = c.scrollHeight
},
beforeDestroy () {
this.$bus.$off('keyUpArrowDown', this.onDown)
this.$bus.$off('keyUpArrowUp', this.onUp)
this.$bus.$off('keyUpEnter', this.onEnter)
this.$bus.$off('keyUpBackspace', this.onBack)
}
}
</script>
<style scoped>
.elements{
height: calc(100% - 56px);
background-color: #dae0e6;
color: white;
display: flex;
flex-direction: column;
padding-bottom: 12px;
overflow-y: auto;
}
.element{
color: #a6a28c;
flex: 0 0 auto;
width: 100%;
display: flex;
/* margin: 9px 12px;
line-height: 18px;
font-size: 18px;
padding-bottom: 6px;
flex-direction: row;
height: 60px; */
}
.time{
padding-right: 10px;
font-size: 10px;
margin-left: 15px;
}
.message{
width: 100%;
color: black;
}
.notes_write{
height: 56px;
widows: 100%;
background: #dae0e6;
display: flex;
justify-content: space-around;
align-items: center;
}
.notes_write input{
width: 75%;
margin-left: 6%;
border: none;
outline: none;
font-size: 16px;
padding: 3px 5px;
float: left;
height: 36px;
background-color: white;
color: black;
}
.notes_write input::placeholder {
color: #ccc;
}
.notes_send{
width: 32px;
height: 32px;
float: right;
border-radius: 50%;
background-color: #f8d344;
margin-right: 12px;
margin-bottom: 2px;
color: white;
line-height: 32px;
text-align: center;
}
.elements::-webkit-scrollbar-track
{
box-shadow: inset 0 0 6px rgba(0,0,0,0.3);
background-color: #a6a28c;
}
.elements::-webkit-scrollbar
{
width: 3px;
background-color: transparent;
}
.elements::-webkit-scrollbar-thumb
{
background-color: #FFC629;
}
</style>

View File

@@ -0,0 +1,48 @@
<template>
<div class="phone_title_content" :style="style" :class="{'hasInfoBare': showInfoBare}" >
<InfoBare v-if="showInfoBare" />
<div class="phone_title" :style="{backgroundColor: backgroundColor}">
<button class="btn-back" @click.stop="back"><i class="fas fa-angle-left" @click.stop="back"></i></button>
{{title}}
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import InfoBare from './InfoBare'
export default {
components: {
InfoBare
},
computed: {
...mapGetters(['themeColorTitle']),
style () {
return {
backgroundColor: this.backgroundColor || this.themeColorTitle,
color: this.color || '#FFF'
}
}
},
methods: {
back () {
this.$emit('back')
}
},
props: {
title: {
type: String,
required: true
},
showInfoBare: {
type: Boolean,
default: true
},
backgroundColor: {
type: String
}
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,8 @@
<script>
import PhoneAPI from './../../PhoneAPI'
export default {
created () {
PhoneAPI.faketakePhoto()
}
}
</script>

View File

@@ -0,0 +1,209 @@
<template>
<div style="width: 334px; height: 742px; color: white" class="phone_app">
<PhoneTitle style="color: white" :title="IntlString('APP_DARKTCHAT_TITLE')" backgroundColor="#ff4500" @back="onBack" />
<div style="backgroundColor: #dae0e6;" class="elements" @contextmenu.prevent="addChannelOption">
<div class="element" v-for='(elem, key) in tchatChannels'
v-bind:key="elem.channel"
v-bind:class="{ select: key === currentSelect}"
@click.stop="showChannel(elem.channel)"
>
<div class="elem-title" @click.stop="showChannel(elem.channel)"><span class="diese">#</span> {{elem.channel}}</div>
</div>
</div>
</div>
</template>
<script>
import { mapGetters, mapActions } from 'vuex'
import Modal from '@/components/Modal/index.js'
import PhoneTitle from './../PhoneTitle'
export default {
components: { PhoneTitle },
data: function () {
return {
currentSelect: 0,
ignoreControls: false
}
},
watch: {
list: function () {
this.currentSelect = 0
}
},
computed: {
...mapGetters(['IntlString', 'useMouse', 'tchatChannels', 'Apps'])
},
methods: {
...mapActions(['tchatAddChannel', 'tchatRemoveChannel']),
scrollIntoViewIfNeeded () {
this.$nextTick(() => {
const $select = this.$el.querySelector('.select')
if ($select !== null) {
$select.scrollIntoViewIfNeeded()
}
})
},
onUp () {
if (this.ignoreControls === true) return
this.currentSelect = this.currentSelect === 0 ? 0 : this.currentSelect - 1
this.scrollIntoViewIfNeeded()
},
onDown () {
if (this.ignoreControls === true) return
this.currentSelect = this.currentSelect === this.tchatChannels.length - 1 ? this.currentSelect : this.currentSelect + 1
this.scrollIntoViewIfNeeded()
},
async onRight () {
if (this.ignoreControls === true) return
this.ignoreControls = true
let choix = [
{id: 1, title: this.IntlString('APP_DARKTCHAT_NEW_CHANNEL'), icons: 'fa-plus', color: 'dodgerblue'},
{id: 2, title: this.IntlString('APP_DARKTCHAT_DELETE_CHANNEL'), icons: 'fa-minus', color: 'tomato'},
{id: 3, title: this.IntlString('APP_DARKTCHAT_CANCEL'), icons: 'fa-undo'}
]
if (this.tchatChannels.length === 0) {
choix.splice(1, 1)
}
const rep = await Modal.CreateModal({ choix })
this.ignoreControls = false
switch (rep.id) {
case 1:
this.addChannelOption()
break
case 2:
this.removeChannelOption()
break
case 3 :
}
},
async onEnter () {
if (this.ignoreControls === true) return
if (this.tchatChannels.length === 0) {
this.ignoreControls = true
let choix = [
{id: 1, title: this.IntlString('APP_DARKTCHAT_NEW_CHANNEL'), icons: 'fa-plus', color: 'green'},
{id: 3, title: this.IntlString('APP_DARKTCHAT_CANCEL'), icons: 'fa-undo'}
]
const rep = await Modal.CreateModal({ choix })
this.ignoreControls = false
if (rep.id === 1) {
this.addChannelOption()
}
} else {
const channel = this.tchatChannels[this.currentSelect].channel
this.showChannel(channel)
}
},
showChannel (channel) {
this.$router.push({ name: 'tchat.channel.show', params: { channel } })
},
onBack () {
if (this.ignoreControls === true) return
this.$router.push({ name: 'home' })
},
async addChannelOption () {
try {
const rep = await Modal.CreateTextModal({limit: 20, title: this.IntlString('APP_DARKTCHAT_NEW_CHANNEL')})
let channel = (rep || {}).text || ''
channel = channel.toLowerCase().replace(/[^a-z]/g, '')
if (channel.length > 0) {
this.currentSelect = 0
this.tchatAddChannel({ channel })
}
} catch (e) {}
},
async removeChannelOption () {
const channel = this.tchatChannels[this.currentSelect].channel
this.currentSelect = 0
this.tchatRemoveChannel({ channel })
}
},
created () {
if (!this.useMouse) {
this.$bus.$on('keyUpArrowDown', this.onDown)
this.$bus.$on('keyUpArrowUp', this.onUp)
this.$bus.$on('keyUpArrowRight', this.onRight)
this.$bus.$on('keyUpEnter', this.onEnter)
this.$bus.$on('keyUpBackspace', this.onBack)
} else {
this.currentSelect = -1
}
},
beforeDestroy () {
this.$bus.$off('keyUpArrowDown', this.onDown)
this.$bus.$off('keyUpArrowUp', this.onUp)
this.$bus.$off('keyUpArrowRight', this.onRight)
this.$bus.$off('keyUpEnter', this.onEnter)
this.$bus.$off('keyUpBackspace', this.onBack)
}
}
</script>
<style scoped>
.list{
height: 100%;
}
.title{
padding-top: 22px;
padding-left: 16px;
height: 54px;
line-height: 34px;
font-weight: 700;
color: white;
}
.elements{
height: calc(100% - 54px);
overflow-y: auto;
background-color: #20201d;
color: #a6a28c
}
.element{
height: 42px;
line-height: 42px;
display: flex;
align-items: center;
position: relative;
}
.elem-title{
margin-left: 6px;
font-size: 20px;
text-transform: capitalize;
transition: .15s;
font-weight: 400;
}
.elem-title .diese {
color: #0079d3;
font-size: 22px;
font-weight: 700;
line-height: 40px;
}
.element.select, .element:hover{
background-color: white;
color: #0079d3;
}
.element.select .elem-title, .element:hover .elem-title {
margin-left: 12px;
}
.element.select .elem-title .diese, .element:hover .elem-title .diese {
color:#0079d3;
}
.elements::-webkit-scrollbar-track
{
box-shadow: inset 0 0 6px rgba(0,0,0,0.3);
background-color: #F5F5F5;
}
.elements::-webkit-scrollbar
{
width: 3px;
background-color: transparent;
}
.elements::-webkit-scrollbar-thumb
{
background-color: #0079d3;
}
</style>

View File

@@ -0,0 +1,217 @@
<template>
<div style="width: 334px; height: 742px; background: white" class="phone_app">
<PhoneTitle :title="channelName" backgroundColor="#ff4500" @back="onQuit"/>
<div class="phone_content">
<div class="elements" ref="elementsDiv">
<div class="element" v-for='(elem) in tchatMessages'
v-bind:key="elem.id"
>
<div class="time">{{formatTime(elem.time)}}</div>
<div class="message">
{{elem.message}}
</div>
</div>
</div>
<div class='tchat_write'>
<input type="text" placeholder="..." v-model="message" @keyup.enter.prevent="sendMessage">
<span class='tchat_send' @click="sendMessage">></span>
</div>
</div>
</div>
</template>
<script>
import { mapGetters, mapActions } from 'vuex'
import PhoneTitle from './../PhoneTitle'
export default {
components: { PhoneTitle },
data () {
return {
message: '',
channel: '',
currentSelect: 0
}
},
computed: {
...mapGetters(['tchatMessages', 'tchatCurrentChannel', 'useMouse']),
channelName () {
return '# ' + this.channel
}
},
watch: {
tchatMessages () {
const c = this.$refs.elementsDiv
c.scrollTop = c.scrollHeight
}
},
methods: {
setChannel (channel) {
this.channel = channel
this.tchatSetChannel({ channel })
},
...mapActions(['tchatSetChannel', 'tchatSendMessage']),
scrollIntoViewIfNeeded () {
this.$nextTick(() => {
const $select = this.$el.querySelector('.select')
if ($select !== null) {
$select.scrollIntoViewIfNeeded()
}
})
},
onUp () {
const c = this.$refs.elementsDiv
c.scrollTop = c.scrollTop - 120
},
onDown () {
const c = this.$refs.elementsDiv
c.scrollTop = c.scrollTop + 120
},
async onEnter () {
const rep = await this.$phoneAPI.getReponseText()
if (rep !== undefined && rep.text !== undefined) {
const message = rep.text.trim()
if (message.length !== 0) {
this.tchatSendMessage({
channel: this.channel,
message
})
}
}
},
sendMessage () {
const message = this.message.trim()
if (message.length !== 0) {
this.tchatSendMessage({
channel: this.channel,
message
})
this.message = ''
}
},
onBack () {
if (this.useMouse === true && document.activeElement.tagName !== 'BODY') return
this.onQuit()
},
onQuit () {
this.$router.push({ name: 'tchat.channel' })
},
formatTime (time) {
const d = new Date(time)
return d.toLocaleTimeString()
}
},
created () {
if (!this.useMouse) {
this.$bus.$on('keyUpArrowDown', this.onDown)
this.$bus.$on('keyUpArrowUp', this.onUp)
this.$bus.$on('keyUpEnter', this.onEnter)
} else {
this.currentSelect = -1
}
this.$bus.$on('keyUpBackspace', this.onBack)
this.setChannel(this.$route.params.channel)
},
mounted () {
window.c = this.$refs.elementsDiv
const c = this.$refs.elementsDiv
c.scrollTop = c.scrollHeight
},
beforeDestroy () {
this.$bus.$off('keyUpArrowDown', this.onDown)
this.$bus.$off('keyUpArrowUp', this.onUp)
this.$bus.$off('keyUpEnter', this.onEnter)
this.$bus.$off('keyUpBackspace', this.onBack)
}
}
</script>
<style scoped>
.elements{
height: calc(100% - 56px);
background-color: #dae0e6;
color: white;
display: flex;
flex-direction: column;
padding-bottom: 12px;
overflow-y: auto;
}
.element{
color: #a6a28c;
flex: 0 0 auto;
width: 100%;
display: flex;
/* margin: 9px 12px;
line-height: 18px;
font-size: 18px;
padding-bottom: 6px;
flex-direction: row;
height: 60px; */
}
.time{
padding-right: 10px;
font-size: 10px;
margin-left: 15px;
}
.message{
width: 100%;
color: black;
}
.tchat_write{
height: 56px;
widows: 100%;
background: #dae0e6;
display: flex;
justify-content: space-around;
align-items: center;
}
.tchat_write input{
width: 75%;
margin-left: 6%;
border: none;
outline: none;
font-size: 16px;
padding: 3px 5px;
float: left;
height: 36px;
background-color: white;
color: black;
}
.tchat_write input::placeholder {
color: #ccc;
}
.tchat_send{
width: 32px;
height: 32px;
float: right;
border-radius: 50%;
background-color: #ff4500;
margin-right: 12px;
margin-bottom: 2px;
color: white;
line-height: 32px;
text-align: center;
}
.elements::-webkit-scrollbar-track
{
box-shadow: inset 0 0 6px rgba(0,0,0,0.3);
background-color: #a6a28c;
}
.elements::-webkit-scrollbar
{
width: 3px;
background-color: transparent;
}
.elements::-webkit-scrollbar-thumb
{
background-color: #FFC629;
}
</style>

View File

@@ -0,0 +1,36 @@
<template>
<div style="width: 334px; height: 678px; background: white" class="splash">
<img src="/html/static/img/app_tchat/reddit.png" alt="">
</div>
</template>
<script>
export default {
created: function () {
setTimeout(() => {
this.$router.push({ name: 'tchat.channel' })
}, 700)
}
}
</script>
<style scoped>
.splash{
width: 100%;
height: 100%;
background-color: #20201d;
display: flex;
justify-content: center;
align-items: center;
}
img {
width: 80px;
animation-name: zoom;
animation-duration: 0.7s;
animation-fill-mode: forwards;
}
@keyframes zoom {
from {width: 80px;}
to {width: 250px;}
}
</style>

View File

@@ -0,0 +1,346 @@
<template>
<div style="width: 326px; height: 743px;" class="phone_app">
<PhoneTitle :title="contact.display" @back="forceCancel"/>
<div class='phone_content content inputText'>
<div class="group select" data-type="text" data-model='display' data-maxlength = '64'>
<input type="text" v-model="contact.display" maxlength="64" v-autofocus>
<span class="highlight"></span>
<span class="bar"></span>
<label>{{ IntlString('APP_CONTACT_LABEL_NAME') }}</label>
</div>
<div class="group inputText" data-type="text" data-model='number' data-maxlength='10'>
<input type="text" v-model="contact.number" maxlength="10">
<span class="highlight"></span>
<span class="bar"></span>
<label>{{ IntlString('APP_CONTACT_LABEL_NUMBER') }}</label>
</div>
<div style="margin-top: 23px; width: 263px; margin-left: 23px; " class="group " data-type="button" data-action='save' @click.stop="save">
<input style="font-weight: 100;" type='button' class="btn btn-green" :value="IntlString('APP_CONTACT_SAVE')" @click.stop="save"/>
</div>
<div style="margin-top: 23px; width: 263px; margin-left: 23px;" class="group" data-type="button" data-action='cancel' @click.stop="forceCancel">
<input style="font-weight: 100;" type='button' class="btn btn-orange" :value="IntlString('APP_CONTACT_CANCEL')" @click.stop="forceCancel"/>
</div>
<div style="margin-top: 23px; width: 263px; margin-left: 23px;" class="group" data-type="button" data-action='deleteC' @click.stop="deleteC">
<input style="font-weight: 100;" type='button' class="btn btn-red" :value="IntlString('APP_CONTACT_DELETE')" @click.stop="deleteC"/>
</div>
</div>
</div>
</template>
<script>
import { mapGetters, mapActions } from 'vuex'
import PhoneTitle from './../PhoneTitle'
import Modal from '@/components/Modal/index.js'
export default {
components: {
PhoneTitle
},
data () {
return {
id: -1,
currentSelect: 0,
ignoreControls: false,
contact: {
display: '',
number: '',
id: -1
}
}
},
computed: {
...mapGetters(['IntlString', 'contacts', 'useMouse'])
},
methods: {
...mapActions(['updateContact', 'addContact']),
onUp () {
if (this.ignoreControls === true) return
let select = document.querySelector('.group.select')
if (select.previousElementSibling !== null) {
document.querySelectorAll('.group').forEach(elem => {
elem.classList.remove('select')
})
select.previousElementSibling.classList.add('select')
let i = select.previousElementSibling.querySelector('input')
if (i !== null) {
i.focus()
}
}
},
onDown () {
if (this.ignoreControls === true) return
let select = document.querySelector('.group.select')
if (select.nextElementSibling !== null) {
document.querySelectorAll('.group').forEach(elem => {
elem.classList.remove('select')
})
select.nextElementSibling.classList.add('select')
let i = select.nextElementSibling.querySelector('input')
if (i !== null) {
i.focus()
}
}
},
onEnter () {
if (this.ignoreControls === true) return
let select = document.querySelector('.group.select')
if (select.dataset.type === 'text') {
let options = {
limit: parseInt(select.dataset.maxlength) || 64,
text: this.contact[select.dataset.model] || ''
}
this.$phoneAPI.getReponseText(options).then(data => {
this.contact[select.dataset.model] = data.text
})
}
if (select.dataset.action && this[select.dataset.action]) {
this[select.dataset.action]()
}
},
save () {
if (this.id === -1 || this.id === 0) {
this.addContact({
display: this.contact.display,
number: this.contact.number
})
} else {
this.updateContact({
id: this.id,
display: this.contact.display,
number: this.contact.number
})
}
history.back()
},
cancel () {
if (this.ignoreControls === true) return
if (this.useMouse === true && document.activeElement.tagName !== 'BODY') return
history.back()
},
forceCancel () {
history.back()
},
deleteC () {
if (this.id !== -1) {
this.ignoreControls = true
let choix = [{title: 'Abbrechen'}, {title: 'Löschen', color: 'red'}]
Modal.CreateModal({choix}).then(reponse => {
this.ignoreControls = false
if (reponse.title === 'Löschen') {
this.$phoneAPI.deleteContact(this.id)
history.back()
}
})
} else {
history.back()
}
}
},
created () {
if (!this.useMouse) {
this.$bus.$on('keyUpArrowDown', this.onDown)
this.$bus.$on('keyUpArrowUp', this.onUp)
this.$bus.$on('keyUpEnter', this.onEnter)
} else {
this.currentSelect = -1
}
this.$bus.$on('keyUpBackspace', this.cancel)
this.id = parseInt(this.$route.params.id)
this.contact.display = this.IntlString('APP_CONTACT_NEW')
this.contact.number = this.$route.params.number
if (this.id !== -1) {
const c = this.contacts.find(e => e.id === this.id)
if (c !== undefined) {
this.contact = {
id: c.id,
display: c.display,
number: c.number
}
}
}
},
beforeDestroy: function () {
this.$bus.$off('keyUpArrowDown', this.onDown)
this.$bus.$off('keyUpArrowUp', this.onUp)
this.$bus.$off('keyUpEnter', this.onEnter)
this.$bus.$off('keyUpBackspace', this.cancel)
}
}
</script>
<style scoped>
.contact{
position: relative;
left: 0;
top: 0;
width: 100%;
height: 100%;
}
.title{
padding-left: 16px;
height: 34px;
line-height: 34px;
font-weight: 700;
background-color: #5264AE;
color: white;
}
.content{
margin: 6px 10px;
margin-top: 28px;
}
.group {
position:relative;
margin-top:24px;
}
.group.inputText {
position:relative;
margin-top:45px;
}
input {
font-size:24px;
display:block;
width:100%;
border:none;
border-bottom:1px solid #e9e9eb;
font-weight: 100;
font-size: 20px;
}
input:focus { outline:none; }
/* LABEL ======================================= */
label {
color:#999;
font-size:18px;
font-weight:normal;
position:absolute;
pointer-events:none;
left:5px;
top:10px;
transition:0.2s ease all;
-moz-transition:0.2s ease all;
-webkit-transition:0.2s ease all;
}
/* active state */
input:focus ~ label, input:valid ~ label {
top:-24px;
font-size:18px;
color:gray;
}
/* BOTTOM BARS ================================= */
.bar { position:relative; display:block; width:100%; }
.bar:before, .bar:after {
content:'';
height:3px;
width:0;
bottom:1px;
position:absolute;
transition:0.2s ease all;
-moz-transition:0.2s ease all;
-webkit-transition:0.2s ease all;
}
.bar:before {
left:50%;
}
.bar:after {
right:50%;
}
/* active state */
input:focus ~ .bar:before, input:focus ~ .bar:after,
.group.select input ~ .bar:before, .group.select input ~ .bar:after{
width:50%;
}
/* HIGHLIGHTER ================================== */
.highlight {
position:absolute;
height:60%;
width:100px;
top:25%;
left:0;
pointer-events:none;
opacity:0.5;
}
/* active state */
input:focus ~ .highlight {
-webkit-animation:inputHighlighter 0.3s ease;
-moz-animation:inputHighlighter 0.3s ease;
animation:inputHighlighter 0.3s ease;
}
.group .btn{
width: 100%;
padding: 0px 0px;
height: 48px;
color: #fff;
border: 0 none;
font-size: 22px;
font-weight: 500;
line-height: 34px;
color: #202129;
background-color: #edeeee;
}
.group.select .btn{
/* border: 6px solid #C0C0C0; */
line-height: 18px;
}
.group .btn.btn-green{
border: 1px solid #0b81ff;
color: #0b81ff;
background-color: white;
font-weight: 500;
border-radius: 28px;
}
.group.select .btn.btn-green, .group:hover .btn.btn-green{
background-image: linear-gradient(to right, #62A3FF, #4994FF , #0b81ff);
color: white;
border: none;
}
.group .btn.btn-orange{
border: 1px solid #B6B6B6;
color: #B6B6B6;
background-color: white;
font-weight: 500;
border-radius: 28px;
}
.group.select .btn.btn-orange, .group:hover .btn.btn-orange{
background-image: linear-gradient(to right, #D3D3D3, #C5C5C5 , #B6B6B6);
color: white;
border: #B6B6B6;
}
.group .btn.btn-red{
border: 1px solid #e74c3c80;
color: #e74c3c;
background-color: white;
font-weight: 500;
border-radius: 28px;
}
.group.select .btn.btn-red, .group:hover .btn.btn-red{
background-image: linear-gradient(to right, #FF5B5B, #FF4B4B , #FE3C3C);
color: white;
border: none;
}
/* ANIMATIONS ================ */
@-webkit-keyframes inputHighlighter {
from { background:#5264AE; }
to { width:0; background:transparent; }
}
@-moz-keyframes inputHighlighter {
from { background:#5264AE; }
to { width:0; background:transparent; }
}
@keyframes inputHighlighter {
from { background:#5264AE; }
to { width:0; background:transparent; }
}
</style>

View File

@@ -0,0 +1,80 @@
<template>
<div style="width: 326px; height: 743px; backgroundColor: white" class="contact">
<list :list='lcontacts' :disable="disableList" :title="IntlString('APP_CONTACT_TITLE')" @back="back" @select='onSelect' @option='onOption'></list>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import { generateColorForStr } from '@/Utils'
import List from './../List.vue'
import Modal from '@/components/Modal/index.js'
export default {
components: {
List
},
data () {
return {
disableList: false
}
},
computed: {
...mapGetters(['IntlString', 'contacts', 'useMouse']),
lcontacts () {
let addContact = {display: this.IntlString('APP_CONTACT_NEW'), letter: '+', num: '', id: -1}
return [addContact, ...this.contacts.map(e => {
e.backgroundColor = e.backgroundColor || generateColorForStr(e.number)
return e
})]
}
},
methods: {
onSelect (contact) {
if (contact.id === -1) {
this.$router.push({ name: 'contacts.view', params: { id: contact.id } })
} else {
this.$router.push({ name: 'messages.view', params: { number: contact.number, display: contact.display } })
}
},
onOption (contact) {
if (contact.id === -1 || contact.id === undefined) return
this.disableList = true
Modal.CreateModal({
choix: [
{id: 1, title: this.IntlString('APP_CONTACT_EDIT'), icons: 'fa-circle-o', color: 'orange'},
{id: 3, title: 'Abbrechen', icons: 'fa-undo'}
]
}).then(rep => {
if (rep.id === 1) {
this.$router.push({path: 'contact/' + contact.id})
}
this.disableList = false
})
},
back () {
if (this.disableList === true) return
this.$router.push({ name: 'home' })
}
},
created () {
if (!this.useMouse) {
this.$bus.$on('keyUpBackspace', this.back)
}
},
beforeDestroy () {
this.$bus.$off('keyUpBackspace', this.back)
}
}
</script>
<style scoped>
.contact{
position: relative;
left: 0;
top: 0;
width: 100%;
height: 100%;
}
</style>

View File

@@ -0,0 +1,76 @@
<template>
<div style="width: 326px; height: 743px; backgroundColor: white" class="contact">
<list :list='lcontacts' :title="IntlString('APP_MESSAGE_CONTACT_TITLE')" v-on:select="onSelect" @back="back"></list>
</div>
</template>
<script>
import List from './../List.vue'
import { mapGetters } from 'vuex'
import Modal from '@/components/Modal/index.js'
export default {
components: {
List
},
data () {
return {
}
},
computed: {
...mapGetters(['IntlString', 'contacts', 'useMouse']),
lcontacts () {
let addContact = {
display: this.IntlString('APP_MESSAGE_CONTRACT_ENTER_NUMBER'),
letter: '+',
backgroundColor: 'orange',
num: -1
}
return [addContact, ...this.contacts]
}
},
methods: {
onSelect (contact) {
if (contact.num === -1) {
Modal.CreateTextModal({
title: this.IntlString('APP_PHONE_ENTER_NUMBER'),
limit: 10
}).then(data => {
let message = data.text.trim()
if (message !== '') {
this.$router.push({
name: 'messages.view',
params: {
number: message,
display: message
}
})
}
})
} else {
this.$router.push({name: 'messages.view', params: contact})
}
},
back () {
history.back()
}
},
created () {
this.$bus.$on('keyUpBackspace', this.back)
},
beforeDestroy () {
this.$bus.$off('keyUpBackspace', this.back)
}
}
</script>
<style scoped>
.contact{
position: relative;
left: 0;
top: 0;
width: 100%;
height: 100%;
}
</style>

View File

@@ -0,0 +1,526 @@
<template>
<!--ESTE HTML ES ACOPLADO DEL VIEJO-->
<div style="width: 330px; height: 743px; backgroundColor: white" class="phone_app messages">
<PhoneTitle :title="displayContact" style="backgroundColor: #F1F1F1; color: black" @back="quit"/> <!--:title="displayContact" :backgroundColor="color" -->
<div class="img-fullscreen" v-if="imgZoom !== undefined" @click.stop="imgZoom = undefined">
<img :src="imgZoom" />
</div>
<textarea ref="copyTextarea" class="copyTextarea"/>
<div style="width: 326px; height: 678px; backgroundColor: white" id='sms_list' @contextmenu.prevent="showOptions">
<div class="sms" v-bind:class="{ select: key === selectMessage}" v-for='(mess, key) in messagesList' v-bind:key="mess.id" @click.stop="onActionMessage(mess)"
>
<div class="sms_message_time">
<h6 v-bind:class="{ sms_me : mess.owner === 1}" class="name_other_sms_me">{{displayContact}}</h6>
<h6 v-bind:class="{ sms_me : mess.owner === 1}" class="name_other_sms_other" @click.stop="onActionMessage(mess)"><timeago style="font-weight: 500" class="sms_time" :since='mess.time' :auto-update="20"></timeago></h6>
</div>
<span class='sms_message sms_me'
@click.stop="onActionMessage(mess)"
v-bind:class="{ sms_other : mess.owner === 0}" >
<img v-if="isSMSImage(mess)" @click.stop="onActionMessage(mess)" class="sms-img" :src="mess.message">
<span v-else @click.stop="onActionMessage(mess)" >{{mess.message}}</span>
<!--<span style="color: white; font-size: 17px; margin: 24px;" @click.stop="onActionMessage(mess)" ><timeago class="sms_time" :since='mess.time' :auto-update="20"></timeago></span>-->
</span>
</div>
</div>
<div style="width: 306px;" id='sms_write' @contextmenu.prevent="showOptions">
<input
type="text"
v-model="message"
:placeholder="IntlString('APP_MESSAGE_PLACEHOLDER_ENTER_MESSAGE')"
v-autofocus
@keyup.enter.prevent="send"
>
<div style=" font-size: 10px;" class="sms_send" @click.stop="send">
<svg height="24" viewBox="0 0 24 24" width="24" @click.stop="send">
<path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/>
<path d="M0 0h24v24H0z" fill="none"/>
</svg>
</div>
</div>
</div>
</template>
<script>
import { mapGetters, mapActions } from 'vuex'
import { generateColorForStr, getBestFontColor } from './../../Utils'
import PhoneTitle from './../PhoneTitle'
import Modal from '@/components/Modal/index.js'
export default {
data () {
return {
ignoreControls: false,
selectMessage: -1,
display: '',
phoneNumber: '',
imgZoom: undefined,
message: ''
}
},
components: {
PhoneTitle
},
methods: {
...mapActions(['setMessageRead', 'sendMessage', 'deleteMessage', 'startCall']),
resetScroll () {
this.$nextTick(() => {
let elem = document.querySelector('#sms_list')
elem.scrollTop = elem.scrollHeight
this.selectMessage = -1
})
},
scrollIntoViewIfNeeded () {
this.$nextTick(() => {
const elem = this.$el.querySelector('.select')
if (elem !== null) {
elem.scrollIntoViewIfNeeded()
}
})
},
quit () {
// this.$router.push({path: '/messages'})
this.$router.go(-1)
},
onUp: function () {
if (this.ignoreControls === true) return
if (this.selectMessage === -1) {
this.selectMessage = this.messagesList.length - 1
} else {
this.selectMessage = this.selectMessage === 0 ? 0 : this.selectMessage - 1
}
this.scrollIntoViewIfNeeded()
},
onDown () {
if (this.ignoreControls === true) return
if (this.selectMessage === -1) {
this.selectMessage = this.messagesList.length - 1
} else {
this.selectMessage = this.selectMessage === this.messagesList.length - 1 ? this.selectMessage : this.selectMessage + 1
}
this.scrollIntoViewIfNeeded()
},
onEnter () {
if (this.ignoreControls === true) return
if (this.selectMessage !== -1) {
this.onActionMessage(this.messagesList[this.selectMessage])
} else {
this.$phoneAPI.getReponseText().then(data => {
let message = data.text.trim()
if (message !== '') {
this.sendMessage({
phoneNumber: this.phoneNumber,
message
})
}
})
}
},
send () {
const message = this.message.trim()
if (message === '') return
this.message = ''
this.sendMessage({
phoneNumber: this.phoneNumber,
message
})
},
isSMSImage (mess) {
return /^https?:\/\/.*\.(png|jpg|jpeg|gif)/.test(mess.message)
},
async onActionMessage (message) {
try {
// let message = this.messagesList[this.selectMessage]
let isGPS = /(-?\d+(\.\d+)?), (-?\d+(\.\d+)?)/.test(message.message)
let hasNumber = /#([0-9]+)/.test(message.message)
let isSMSImage = this.isSMSImage(message)
let choix = [{
id: 'delete',
title: this.IntlString('APP_MESSAGE_DELETE'),
icons: 'fa-trash'
}, {
id: -1,
title: this.IntlString('CANCEL'),
icons: 'fa-undo'
}]
if (isGPS === true) {
choix = [{
id: 'gps',
title: this.IntlString('APP_MESSAGE_SET_GPS'),
icons: 'fa-location-arrow'
}, ...choix]
}
if (hasNumber === true) {
const num = message.message.match(/#([0-9-]*)/)[1]
choix = [{
id: 'num',
title: `${this.IntlString('APP_MESSAGE_MESS_NUMBER')} ${num}`,
number: num,
icons: 'fa-bars'
}, ...choix]
}
if (isSMSImage === true) {
choix = [{
id: 'zoom',
title: this.IntlString('APP_MESSAGE_ZOOM_IMG'),
icons: 'fa-search'
}, ...choix]
}
this.ignoreControls = true
const data = await Modal.CreateModal({choix})
if (data.id === 'delete') {
this.deleteMessage({ id: message.id })
} else if (data.id === 'gps') {
let val = message.message.match(/(-?\d+(\.\d+)?), (-?\d+(\.\d+)?)/)
this.$phoneAPI.setGPS(val[1], val[3])
} else if (data.id === 'num') {
this.$nextTick(() => {
this.onSelectPhoneNumber(data.number)
})
} else if (data.id === 'zoom') {
this.imgZoom = message.message
}
} catch (e) {
} finally {
this.ignoreControls = false
this.selectMessage = -1
}
},
async onSelectPhoneNumber (number) {
try {
this.ignoreControls = true
let choix = [
{
id: 'sms',
title: this.IntlString('APP_MESSAGE_MESS_SMS'),
icons: 'fa-comment'
},
{
id: 'call',
title: this.IntlString('APP_MESSAGE_MESS_CALL'),
icons: 'fa-phone'
}
]
// if (this.useMouse === true) {
choix.push({
id: 'copy',
title: this.IntlString('APP_MESSAGE_MESS_COPY'),
icons: 'fa-copy'
})
// }
choix.push({
id: -1,
title: this.IntlString('CANCEL'),
icons: 'fa-undo'
})
const data = await Modal.CreateModal({ choix })
if (data.id === 'sms') {
this.phoneNumber = number
this.display = undefined
} else if (data.id === 'call') {
this.startCall({ numero: number })
} else if (data.id === 'copy') {
try {
const $copyTextarea = this.$refs.copyTextarea
$copyTextarea.value = number
$copyTextarea.style.height = '20px'
$copyTextarea.focus()
$copyTextarea.select()
await document.execCommand('copy')
$copyTextarea.style.height = '0'
} catch (error) {
}
}
} catch (e) {
} finally {
this.ignoreControls = false
this.selectMessage = -1
}
},
onBackspace () {
if (this.imgZoom !== undefined) {
this.imgZoom = undefined
return
}
if (this.ignoreControls === true) return
if (this.useMouse === true && document.activeElement.tagName !== 'BODY') return
if (this.selectMessage !== -1) {
this.selectMessage = -1
} else {
this.quit()
}
},
async showOptions () {
try {
this.ignoreControls = true
let choix = [
{id: 1, title: this.IntlString('APP_MESSAGE_SEND_GPS'), icons: 'fa-location-arrow'},
{id: -1, title: this.IntlString('CANCEL'), icons: 'fa-undo'}
]
if (this.enableTakePhoto) {
choix = [
{id: 1, title: this.IntlString('APP_MESSAGE_SEND_GPS'), icons: 'fa-location-arrow'},
{id: 2, title: this.IntlString('APP_MESSAGE_SEND_PHOTO'), icons: 'fa-picture-o'},
{id: -1, title: this.IntlString('CANCEL'), icons: 'fa-undo'}
]
}
const data = await Modal.CreateModal({ choix })
if (data.id === 1) {
this.sendMessage({
phoneNumber: this.phoneNumber,
message: '%pos%'
})
}
if (data.id === 2) {
const { url } = await this.$phoneAPI.takePhoto()
if (url !== null && url !== undefined) {
this.sendMessage({
phoneNumber: this.phoneNumber,
message: url
})
}
}
this.ignoreControls = false
} catch (e) {
} finally {
this.ignoreControls = false
}
},
onRight: function () {
if (this.ignoreControls === true) return
if (this.selectMessage === -1) {
this.showOptions()
}
}
},
computed: {
...mapGetters(['IntlString', 'messages', 'contacts', 'useMouse', 'enableTakePhoto']),
messagesList () {
return this.messages.filter(e => e.transmitter === this.phoneNumber).sort((a, b) => a.time - b.time)
},
displayContact () {
if (this.display !== undefined) {
return this.display
}
const c = this.contacts.find(c => c.number === this.phoneNumber)
if (c !== undefined) {
return c.display
}
return this.phoneNumber
},
color () {
return generateColorForStr(this.phoneNumber)
},
colorSmsOwner () {
return [
{
backgroundColor: this.color,
color: getBestFontColor(this.color)
}, {}
]
}
},
watch: {
messagesList () {
this.setMessageRead(this.phoneNumber)
this.resetScroll()
}
},
created () {
this.display = this.$route.params.display
this.phoneNumber = this.$route.params.number
if (!this.useMouse) {
this.$bus.$on('keyUpArrowDown', this.onDown)
this.$bus.$on('keyUpArrowUp', this.onUp)
this.$bus.$on('keyUpEnter', this.onEnter)
this.$bus.$on('keyUpArrowRight', this.onRight)
}
this.$bus.$on('keyUpBackspace', this.onBackspace)
},
beforeDestroy () {
this.$bus.$off('keyUpArrowDown', this.onDown)
this.$bus.$off('keyUpArrowUp', this.onUp)
this.$bus.$off('keyUpEnter', this.onEnter)
this.$bus.$off('keyUpArrowRight', this.onRight)
this.$bus.$off('keyUpBackspace', this.onBackspace)
}
}
</script>
<style scoped>
.messages{
position: absolute;
bottom: 0;
left: 0;
width: 326px;
height: 678px;
right: 0;
height: calc(100% - 20px);
background-color: #DDD;
}
#sms_contact{
background-color: #4CAF50;
color: white;
height: 34px;
line-height: 34px;
padding-left: 5px;
}
#sms_list{
height: calc(100% - 34px - 26px);
overflow-y: auto;
padding-bottom: 8px;
}
.name_other_sms_other{
margin-bottom: -9px;
margin-left: 42px;
font-size: 14px;
font-weight: 500;
color: lightgrey;
}
.name_other_sms_me{
display: none;
}
.name_other_sms_other.sms_me{
display: none;
}
.sms{
overflow: auto;
zoom: 1;
}
.sms-img{
width: 100%;
height: auto;
border-radius: 19px;
}
.img-fullscreen {
position: fixed;
z-index: 999999;
background-color: rgba(20, 20, 20, 0.8);
left: 0;
top: 0;
right: 0;
bottom: 0;
display: flex;
justify-content: center;
align-items: center;
}
.img-fullscreen img {
display: flex;
max-width: 90vw;
max-height: 95vh;
}
.sms_me{
float: right;
background-color: #e9e9eb;
border-radius: 17px;
padding: 5px 10px;
max-width: 90%;
margin-right: 5%;
margin-top: 10px;
}
.sms_other{
background-color: #0b81ff;
border-radius: 17px;
font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
color:white;
float: left;
padding: 5px 10px;
max-width: 90%;
margin-left: 5%;
margin-top: 10px;
}
.sms_time{
display: block;
font-size: 12px;
}
.sms_me .sms_time{
color: #AAA;
margin-left: 4px;
margin-top: -5px;
display: none;
font-size: 9px;
}
.sms_other .sms_time{
color: white;
display: none;
margin-left: 4px;
margin-top: -5px;
font-size: 9px;
}
.messages{
position: relative;
left: 0;
top: 0;
width: 100%;
height: 100%;
}
.sms.select .sms_message, .sms_message:hover{
background-color: #373B3C !important;
color: #E4E3E2 !important;
}
.sms.select .sms_message, .sms_message:hover{
background-color: #373B3C !important;
color: white !important;
}
.sms_message{
word-wrap: break-word;
max-width: 80%;
font-size: 24px;
}
#sms_write{
height: 56px;
margin: 10px;
width: 380px;
background-color: #e9e9eb;
border-radius: 56px;
}
#sms_write input{
height: 56px;
border: none;
outline: none;
font-size: 16px;
margin-left: 14px;
padding: 12px 5px;
background-color: rgba(236, 236, 241, 0)
}
.sms_send{
float: right;
margin-right: 10px;
}
.sms_send svg{
margin: 8px;
width: 36px;
height: 36px;
fill: #C0C0C0;
}
.copyTextarea {
height: 0;
border: 0;
padding: 0;
}
</style>

View File

@@ -0,0 +1,144 @@
<template>
<div style="width: 326px; height: 743px;" class="screen">
<list style="color: black" :list='messagesData' :disable="disableList" :title="IntlString('APP_MESSAGE_TITLE')" @back="back" @select="onSelect" @option='onOption'></list>
</div>
</template>
<script>
import { mapGetters, mapActions } from 'vuex'
import { generateColorForStr } from '@/Utils'
import Modal from '@/components/Modal/index.js'
import List from '@/components/List'
export default {
components: {
List
},
data () {
return {
disableList: false
}
},
methods: {
...mapActions(['deleteMessagesNumber', 'deleteAllMessages', 'startCall']),
onSelect: function (data) {
if (data.id === -1) {
this.$router.push({name: 'messages.selectcontact'})
} else {
this.$router.push({name: 'messages.view', params: data})
}
},
onOption: function (data) {
if (data.number === undefined) return
this.disableList = true
Modal.CreateModal({
choix: [
{id: 4, title: this.IntlString('APP_PHONE_CALL'), icons: 'fa-phone'},
{id: 5, title: this.IntlString('APP_PHONE_CALL_ANONYMOUS'), icons: 'fa-mask'},
{id: 6, title: this.IntlString('APP_MESSAGE_NEW_MESSAGE'), icons: 'fa-sms'},
{id: 1, title: this.IntlString('APP_MESSAGE_ERASE_CONVERSATION'), icons: 'fa-trash', color: 'orange'},
/* {id: 2, title: this.IntlString('APP_MESSAGE_ERASE_ALL_CONVERSATIONS'), icons: 'fa-trash', color: 'red'},
{id: 3, title: this.IntlString('CANCEL'), icons: 'fa-undo'} */
{id: 2, title: this.IntlString('APP_MESSAGE_ERASE_ALL_CONVERSATIONS'), icons: 'fa-trash', color: 'red'}
]
.concat(data.unknowContact ? [{id: 7, title: this.IntlString('APP_MESSAGE_SAVE_CONTACT'), icons: 'fa-save'}] : [])
.concat([{id: 3, title: this.IntlString('CANCEL'), icons: 'fa-undo'}])
}).then(rep => {
if (rep.id === 1) {
this.deleteMessagesNumber({num: data.number})
} else if (rep.id === 2) {
this.deleteAllMessages()
} else if (rep.id === 4) {
this.startCall({ numero: data.number })
} else if (rep.id === 5) {
this.startCall({ numero: '#' + data.number })
} else if (rep.id === 6) {
this.$router.push({name: 'messages.view', params: data})
} else if (rep.id === 7) {
this.$router.push({name: 'contacts.view', params: {id: 0, number: data.number}})
}
this.disableList = false
})
},
back: function () {
if (this.disableList === true) return
this.$router.push({ name: 'home' })
}
},
computed: {
...mapGetters(['IntlString', 'useMouse', 'contacts', 'messages']),
messagesData: function () {
let messages = this.messages
let contacts = this.contacts
let messGroup = messages.reduce((rv, x) => {
if (rv[x['transmitter']] === undefined) {
const data = {
noRead: 0,
lastMessage: 0,
display: x.transmitter
}
let contact = contacts.find(e => e.number === x.transmitter)
data.unknowContact = contact === undefined
if (contact !== undefined) {
data.display = contact.display
data.backgroundColor = contact.backgroundColor || generateColorForStr(x.transmitter)
data.letter = contact.letter
data.icon = contact.icon
} else {
data.backgroundColor = generateColorForStr(x.transmitter)
}
rv[x['transmitter']] = data
}
if (x.isRead === 0) {
rv[x['transmitter']].noRead += 1
}
if (x.time >= rv[x['transmitter']].lastMessage) {
rv[x['transmitter']].lastMessage = x.time
rv[x['transmitter']].keyDesc = x.message
}
return rv
}, {})
let mess = []
Object.keys(messGroup).forEach(key => {
mess.push({
display: messGroup[key].display,
puce: messGroup[key].noRead,
number: key,
lastMessage: messGroup[key].lastMessage,
keyDesc: messGroup[key].keyDesc,
backgroundColor: messGroup[key].backgroundColor,
icon: messGroup[key].icon,
letter: messGroup[key].letter,
unknowContact: messGroup[key].unknowContact
})
})
mess.sort((a, b) => b.lastMessage - a.lastMessage)
return [this.newMessageOption, ...mess]
},
newMessageOption () {
return {
backgroundColor: '#C0C0C0',
display: this.IntlString('APP_MESSAGE_NEW_MESSAGE'),
letter: '+',
id: -1
}
}
},
created () {
this.$bus.$on('keyUpBackspace', this.back)
},
beforeDestroy () {
this.$bus.$off('keyUpBackspace', this.back)
}
}
</script>
<style scoped>
.screen{
position: relative;
left: 0;
top: 0;
width: 100%;
height: 100%;
}
</style>

View File

@@ -0,0 +1,345 @@
<template>
<div class="phone_app">
<PhoneTitle :title="IntlString('APP_CONFIG_TITLE')" @back="onBackspace"/>
<div class='phone_content elements'>
<div class='element'
v-for='(elem, key) in paramList'
v-bind:class="{ select: key === currentSelect}"
v-bind:key="key"
@click.stop="onPressItem(key)"
>
<i class="fa" v-bind:class="elem.icons" v-bind:style="{color: elem.color}" @click.stop="onPressItem(key)"></i>
<div class="element-content" @click.stop="onPressItem(key)">
<span class="element-title" @click.stop="onPressItem(key)">{{elem.title}}</span>
<span v-if="elem.value" class="element-value" @click.stop="onPressItem(key)">{{elem.value}}</span>
</div>
</div>
</div>
</div>
</template>
<script>
import { mapGetters, mapActions } from 'vuex'
import PhoneTitle from './../PhoneTitle'
import Modal from '@/components/Modal/index.js'
export default {
components: {
PhoneTitle
},
data () {
return {
ignoreControls: false,
currentSelect: 0
}
},
computed: {
...mapGetters(['IntlString', 'useMouse', 'myPhoneNumber', 'backgroundLabel', 'coqueLabel', 'sonidoLabel', 'zoom', 'config', 'volume', 'availableLanguages']),
paramList () {
const cancelStr = this.IntlString('CANCEL')
const confirmResetStr = this.IntlString('APP_CONFIG_RESET_CONFIRM')
const cancelOption = {}
const confirmReset = {}
cancelOption[cancelStr] = 'cancel'
confirmReset[confirmResetStr] = 'accept'
return [
{
icons: 'fa-phone',
title: this.IntlString('APP_CONFIG_MY_MUNBER'),
value: this.myPhoneNumber
},
{
icons: 'fa-picture-o',
title: this.IntlString('APP_CONFIG_WALLPAPER'),
value: this.backgroundLabel,
onValid: 'onChangeBackground',
values: this.config.background
},
{
icons: 'fa-mobile',
title: this.IntlString('APP_CONFIG_CASE'),
value: this.coqueLabel,
onValid: 'onChangeCoque',
values: this.config.coque
},
{
icons: 'fa-bell-o',
title: this.IntlString('APP_CONFIG_SOUND'),
value: this.sonidoLabel,
onValid: 'onChangeSonido',
values: this.config.sonido
},
{
icons: 'fa-search',
title: this.IntlString('APP_CONFIG_ZOOM'),
value: this.zoom,
onValid: 'setZoom',
onLeft: this.ajustZoom(-1),
onRight: this.ajustZoom(1),
values: {
'125 %': '125%',
'100 %': '100%',
'80 %': '80%',
'60 %': '60%',
'40 %': '40%',
'20 %': '20%'
}
},
{
icons: 'fa-volume-down',
title: this.IntlString('APP_CONFIG_VOLUME'),
value: this.valumeDisplay,
onValid: 'setPhoneVolume',
onLeft: this.ajustVolume(-0.01),
onRight: this.ajustVolume(0.01),
values: {
'100 %': 1,
'80 %': 0.8,
'60 %': 0.6,
'40 %': 0.4,
'20 %': 0.2,
'0 %': 0
}
},
/* {
icons: 'fa-globe',
title: this.IntlString('APP_CONFIG_LANGUAGE'),
onValid: 'onChangeLanguages',
values: {
...this.availableLanguages,
...cancelOption
}
}, */
{
icons: 'fa-mouse-pointer',
title: this.IntlString('APP_CONFIG_MOUSE_SUPPORT'),
onValid: 'onChangeMouseSupport',
values: {
'Yes': true,
'No': false,
...cancelOption
}
},
{
icons: 'fa-exclamation-triangle',
color: '#ee3838',
title: this.IntlString('APP_CONFIG_RESET'),
onValid: 'resetPhone',
values: {
...confirmReset,
...cancelOption
}
}
]
},
valumeDisplay () {
return `${Math.floor(this.volume * 100)} %`
}
},
methods: {
...mapActions(['getIntlString', 'setZoon', 'setBackground', 'setCoque', 'setSonido', 'setVolume', 'setLanguage', 'setMouseSupport']),
scrollIntoViewIfNeeded: function () {
this.$nextTick(() => {
document.querySelector('.select').scrollIntoViewIfNeeded()
})
},
onBackspace () {
if (this.ignoreControls === true) return
this.$router.push({ name: 'home' })
},
onUp: function () {
if (this.ignoreControls === true) return
this.currentSelect = this.currentSelect === 0 ? 0 : this.currentSelect - 1
this.scrollIntoViewIfNeeded()
},
onDown: function () {
if (this.ignoreControls === true) return
this.currentSelect = this.currentSelect === this.paramList.length - 1 ? this.currentSelect : this.currentSelect + 1
this.scrollIntoViewIfNeeded()
},
onRight () {
if (this.ignoreControls === true) return
let param = this.paramList[this.currentSelect]
if (param.onRight !== undefined) {
param.onRight(param)
}
},
onLeft () {
if (this.ignoreControls === true) return
let param = this.paramList[this.currentSelect]
if (param.onLeft !== undefined) {
param.onLeft(param)
}
},
actionItem (param) {
if (param.values !== undefined) {
this.ignoreControls = true
let choix = Object.keys(param.values).map(key => {
return {title: key, value: param.values[key], picto: param.values[key]}
})
Modal.CreateModal({choix}).then(reponse => {
this.ignoreControls = false
if (reponse.title === 'cancel') return
this[param.onValid](param, reponse)
})
}
},
onPressItem (index) {
this.actionItem(this.paramList[index])
},
onEnter () {
if (this.ignoreControls === true) return
this.actionItem(this.paramList[this.currentSelect])
},
async onChangeBackground (param, data) {
let val = data.value
if (val === 'URL') {
this.ignoreControls = true
Modal.CreateTextModal({
text: 'https://i.imgur.com/'
}).then(valueText => {
if (valueText.text !== '' && valueText.text !== undefined && valueText.text !== null && valueText.text !== 'https://i.imgur.com/') {
this.setBackground({
label: 'Custom',
value: valueText.text
})
}
}).finally(() => {
this.ignoreControls = false
})
} else {
this.setBackground({
label: data.title,
value: data.value
})
}
},
onChangeCoque: function (param, data) {
this.setCoque({
label: data.title,
value: data.value
})
},
onChangeSonido: function (param, data) {
this.setSonido({
label: data.title,
value: data.value
})
},
setZoom: function (param, data) {
this.setZoon(data.value)
},
ajustZoom (inc) {
return () => {
const percent = Math.max(10, (parseInt(this.zoom) || 100) + inc)
this.setZoon(`${percent}%`)
}
},
setPhoneVolume (param, data) {
this.setVolume(data.value)
},
ajustVolume (inc) {
return () => {
const newVolume = Math.max(0, Math.min(1, parseFloat(this.volume) + inc))
this.setVolume(newVolume)
}
},
onChangeLanguages (param, data) {
if (data.value !== 'cancel') {
this.setLanguage(data.value)
}
},
onChangeMouseSupport (param, data) {
if (data.value !== 'cancel') {
this.setMouseSupport(data.value)
this.onBackspace()
}
},
resetPhone: function (param, data) {
if (data.value !== 'cancel') {
this.ignoreControls = true
const cancelStr = this.IntlString('CANCEL')
const confirmResetStr = this.IntlString('APP_CONFIG_RESET_CONFIRM')
let choix = [{title: cancelStr}, {title: cancelStr}, {title: confirmResetStr, color: 'red', reset: true}, {title: cancelStr}, {title: cancelStr}]
Modal.CreateModal({choix}).then(reponse => {
this.ignoreControls = false
if (reponse.reset === true) {
this.$phoneAPI.deleteALL()
}
})
}
}
},
created () {
if (!this.useMouse) {
this.$bus.$on('keyUpArrowRight', this.onRight)
this.$bus.$on('keyUpArrowLeft', this.onLeft)
this.$bus.$on('keyUpArrowDown', this.onDown)
this.$bus.$on('keyUpArrowUp', this.onUp)
this.$bus.$on('keyUpEnter', this.onEnter)
} else {
this.currentSelect = -1
}
this.$bus.$on('keyUpBackspace', this.onBackspace)
},
beforeDestroy () {
this.$bus.$off('keyUpArrowRight', this.onRight)
this.$bus.$off('keyUpArrowLeft', this.onLeft)
this.$bus.$off('keyUpArrowDown', this.onDown)
this.$bus.$off('keyUpArrowUp', this.onUp)
this.$bus.$off('keyUpEnter', this.onEnter)
this.$bus.$off('keyUpBackspace', this.onBackspace)
}
}
</script>
<style scoped>
.element{
height: 58px;
line-height: 58px;
display: flex;
align-items: center;
position: relative;
}
.element .fa{
color: #0b81ff;
margin-left: 6px;
height: 52px;
width: 52px;
text-align: center;
line-height: 52px;
}
.element-content{
display: block;
height: 58px;
width: 100%;
margin-left: 6px;
display: flex;
flex-flow: column;
justify-content: center;
}
.element-title{
display: block;
margin-top: 4px;
height: 22px;
line-height: 22px;
font-size: 20px;
font-weight: 300;
font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
}
.element-value{
display: block;
line-height: 16px;
height: 8px;
font-size: 14px;
font-weight: 100;
color: #808080;
}
.element.select, .element:hover{
background-color: #DDD;
}
</style>

View File

@@ -0,0 +1,587 @@
<template>
<div style="width: 314px; height: 577px;" class='phone_content content inputText'>
<template v-if="state === STATES.MENU">
<template v-if="!isLogin">
<div class="group" data-type="button" @click.stop="state = STATES.LOGIN">
<input type='button' class="btn btn-blue" @click.stop="state = STATES.LOGIN" :value="IntlString('APP_TWITTER_ACCOUNT_LOGIN')"/>
</div>
<div class="group" data-type="button" @click.stop="state = STATES.NOTIFICATION">
<input type='button' class="btn btn-blue" @click.stop="state = STATES.NOTIFICATION" :value="IntlString('APP_TWITTER_NOTIFICATION')" />
</div>
<div class="group bottom" data-type="button" @click.stop="state = STATES.NEW_ACCOUNT">
<input type='button' class="btn btn-red" @click.stop="state = STATES.NEW_ACCOUNT" :value="IntlString('APP_TWITTER_ACCOUNT_NEW')" />
</div>
</template>
<template v-if="isLogin">
<img :src="twitterAvatarUrl" height="128" width="128" style="align-self: center;">
<div class="group" data-type="button" @click.stop="state = STATES.ACCOUNT">
<input type='button' class="btn btn-blue" @click.stop="state = STATES.ACCOUNT" :value="IntlString('APP_TWITTER_ACCOUNT_PARAM')" />
</div>
<div class="group" data-type="button" @click.stop="state = STATES.NOTIFICATION">
<input type='button' class="btn btn-blue" @click.stop="state = STATES.NOTIFICATION" :value="IntlString('APP_TWITTER_NOTIFICATION')" />
</div>
<div class="group bottom" data-type="button" @click.stop="logout">
<input type='button' class="btn btn-red" @click.stop="logout" :value="IntlString('APP_TWITTER_ACCOUNT_LOGOUT')" />
</div>
</template>
</template>
<template v-else-if="state === STATES.LOGIN">
<div class="group inputText" data-type="text" data-maxlength='64' :data-defaultValue="localAccount.username">
<input type="text" :value="localAccount.username" @change="setLocalAccount($event, 'username')">
<span class="highlight"></span>
<span class="bar"></span>
<label>{{ IntlString('APP_TWITTER_ACCOUNT_USERNAME') }}</label>
</div>
<div class="group inputText" data-type="text" data-model='password' data-maxlength='30'>
<input autocomplete="new-password" type="password" :value="localAccount.password" @change="setLocalAccount($event, 'password')">
<span class="highlight"></span>
<span class="bar"></span>
<label>{{ IntlString('APP_TWITTER_ACCOUNT_PASSWORD') }}</label>
</div>
<div class="group" data-type="button" @click.stop="login">
<input type='button' class="btn btn-blue" @click.stop="login" :value="IntlString('APP_TWITTER_ACCOUNT_LOGIN')" />
</div>
</template>
<template v-else-if="state === STATES.NOTIFICATION">
<div class="groupCheckBoxTitle">
<label>{{ IntlString('APP_TWITTER_NOTIFICATION_WHEN') }}</label>
</div>
<label class="group checkbox" data-type="button" @click.prevent.stop="setNotification(2)">
<input type="checkbox" :checked="twitterNotification === 2" @click.prevent.stop="setNotification(2)">
{{ IntlString('APP_TWITTER_NOTIFICATION_ALL') }}
</label>
<label class="group checkbox" data-type="button" @click.prevent.stop="setNotification(1)">
<input type="checkbox" :checked="twitterNotification === 1" @click.prevent.stop="setNotification(1)">
{{ IntlString('APP_TWITTER_NOTIFICATION_MENTION') }}
</label>
<label class="group checkbox" data-type="button" @click.prevent.stop="setNotification(0)">
<input type="checkbox" :checked="twitterNotification === 0" @click.prevent.stop="setNotification(0)">
{{ IntlString('APP_TWITTER_NOTIFICATION_NEVER') }}
</label>
<div class="groupCheckBoxTitle">
<label>{{ IntlString('APP_TWITTER_NOTIFICATION_SOUND') }}</label>
</div>
<label class="group checkbox" data-type="button" @click.prevent.stop="setNotificationSound(true)">
<input type="checkbox" :checked="twitterNotificationSound" @click.prevent.stop="setNotificationSound(true)">
{{ IntlString('APP_TWITTER_NOTIFICATION_SOUND_YES') }}
</label>
<label class="group checkbox" data-type="button" @click.prevent.stop="setNotificationSound(false)">
<input type="checkbox" :checked="!twitterNotificationSound" @click.prevent.stop="setNotificationSound(false)">
{{ IntlString('APP_TWITTER_NOTIFICATION_SOUND_NO') }}
</label>
</template>
<template v-else-if="state === STATES.ACCOUNT">
<div style="margin-top: 42px; margin-bottom: 42px;" class="group img" data-type="button" @click.stop="onPressChangeAvartar">
<img :src="twitterAvatarUrl" height="128" width="128" @click.stop="onPressChangeAvartar">
<input type='button' class="btn btn-blue" :value="IntlString('APP_TWITTER_ACCOUNT_AVATAR')" @click.stop="onPressChangeAvartar" />
</div>
<div class="group" data-type="button" @click.stop="changePassword">
<input type='button' class="btn btn-red" :value="IntlString('APP_TWITTER_ACCOUNT_CHANGE_PASSWORD')" @click.stop="changePassword"/>
</div>
</template>
<template v-else-if="state === STATES.NEW_ACCOUNT">
<div class="group inputText" data-type="text" data-maxlength='64' data-defaultValue="">
<input type="text" :value="localAccount.username" @change="setLocalAccount($event, 'username')">
<span class="highlight"></span>
<span class="bar"></span>
<label>{{ IntlString('APP_TWITTER_NEW_ACCOUNT_USERNAME') }}</label>
</div>
<div class="group inputText" data-type="text" data-model='password' data-maxlength='30'>
<input autocomplete="new-password" type="password" :value="localAccount.password" @change="setLocalAccount($event, 'password')">
<span class="highlight"></span>
<span class="bar"></span>
<label>{{ IntlString('APP_TWITTER_NEW_ACCOUNT_PASSWORD') }}</label>
</div>
<div class="group inputText" data-type="text" data-model='password' data-maxlength='30'>
<input autocomplete="new-password" type="password" :value="localAccount.passwordConfirm" @change="setLocalAccount($event, 'passwordConfirm')">
<span class="highlight"></span>
<span class="bar"></span>
<label>{{ IntlString('APP_TWITTER_NEW_ACCOUNT_PASSWORD_CONFIRM') }}</label>
</div>
<div style="margin-top: 42px; margin-bottom: 42px;" class="group img" data-type="button" @click.stop="setLocalAccountAvartar($event)">
<img :src="localAccount.avatarUrl" height="128" width="128" @click.stop="setLocalAccountAvartar($event)">
<input type='button' class="btn btn-blue" :value="IntlString('APP_TWITTER_NEW_ACCOUNT_AVATAR')" @click.stop="setLocalAccountAvartar($event)"/>
</div>
<div class="group" data-type="button" @click.stop="createAccount">
<input type='button' class="btn" :class="validAccount ? 'btn-blue' : 'btn-gray'" :value="IntlString('APP_TWIITER_ACCOUNT_CREATE')" @click.stop="createAccount"/>
</div>
</template>
</div>
</template>
<script>
import { mapGetters, mapActions } from 'vuex'
import Modal from '@/components/Modal'
const STATES = Object.freeze({
MENU: 0,
NEW_ACCOUNT: 1,
LOGIN: 2,
ACCOUNT: 3,
NOTIFICATION: 4
})
export default {
components: {
},
data () {
return {
STATES,
state: STATES.MENU,
localAccount: {
username: '',
password: '',
passwordConfirm: '',
avatarUrl: null
},
notification: 0,
notificationSound: false
}
},
computed: {
...mapGetters(['IntlString', 'useMouse', 'twitterUsername', 'twitterPassword', 'twitterAvatarUrl', 'twitterNotification', 'twitterNotificationSound']),
isLogin () {
return this.twitterUsername !== undefined && this.twitterUsername !== ''
},
validAccount () {
return this.localAccount.username.length >= 4 && this.localAccount.password.length >= 6 && this.localAccount.password === this.localAccount.passwordConfirm
}
},
methods: {
...mapActions(['twitterLogin', 'twitterChangePassword', 'twitterLogout', 'twitterSetAvatar', 'twitterCreateNewAccount', 'setTwitterNotification', 'setTwitterNotificationSound']),
onUp: function () {
if (this.ignoreControls === true) return
let select = document.querySelector('.group.select')
if (select === null) {
select = document.querySelector('.group')
select.classList.add('select')
return
}
while (select.previousElementSibling !== null) {
if (select.previousElementSibling.classList.contains('group')) {
break
}
select = select.previousElementSibling
}
if (select.previousElementSibling !== null) {
document.querySelectorAll('.group').forEach(elem => {
elem.classList.remove('select')
})
select.previousElementSibling.classList.add('select')
let i = select.previousElementSibling.querySelector('input')
if (i !== null) {
i.focus()
}
}
},
onDown: function () {
if (this.ignoreControls === true) return
let select = document.querySelector('.group.select')
if (select === null) {
select = document.querySelector('.group')
select.classList.add('select')
return
}
while (select.nextElementSibling !== null) {
if (select.nextElementSibling.classList.contains('group')) {
break
}
select = select.nextElementSibling
}
if (select.nextElementSibling !== null) {
document.querySelectorAll('.group').forEach(elem => {
elem.classList.remove('select')
})
select.nextElementSibling.classList.add('select')
let i = select.nextElementSibling.querySelector('input')
if (i !== null) {
i.focus()
}
}
},
onEnter: function () {
if (this.ignoreControls === true) return
let select = document.querySelector('.group.select')
if (select === null) return
if (select.dataset !== null) {
if (select.dataset.type === 'text') {
const $input = select.querySelector('input')
let options = {
limit: parseInt(select.dataset.maxlength) || 64,
text: select.dataset.defaultValue || ''
}
this.$phoneAPI.getReponseText(options).then(data => {
$input.value = data.text
$input.dispatchEvent(new window.Event('change'))
})
}
if (select.dataset.type === 'button') {
select.click()
}
}
},
onBack () {
if (this.state !== this.STATES.MENU) {
this.state = this.STATES.MENU
} else {
this.$bus.$emit('twitterHome')
}
},
setLocalAccount ($event, key) {
this.localAccount[key] = $event.target.value
},
async setLocalAccountAvartar ($event) {
try {
const data = await Modal.CreateTextModal({
text: this.twitterAvatarUrl || 'https://i.imgur.com/'
})
this.localAccount.avatarUrl = data.text
} catch (e) {}
},
async onPressChangeAvartar () {
try {
const data = await Modal.CreateTextModal({
text: this.twitterAvatarUrl || 'https://i.imgur.com/'
})
this.twitterSetAvatar({avatarUrl: data.text})
} catch (e) {}
},
login () {
this.twitterLogin({
username: this.localAccount.username,
password: this.localAccount.password
})
this.state = STATES.MENU
},
logout () {
this.twitterLogout()
},
createAccount () {
if (this.validAccount === true) {
this.twitterCreateNewAccount(this.localAccount)
this.localAccount = {
username: '',
password: '',
passwordConfirm: '',
avatarUrl: null
}
this.state = this.STATES.MENU
}
},
cancel () {
this.state = STATES.MENU
},
setNotification (value) {
this.setTwitterNotification(value)
},
setNotificationSound (value) {
this.setTwitterNotificationSound(value)
},
async changePassword (value) {
try {
const password1 = await Modal.CreateTextModal({limit: 30})
if (password1.text === '') return
const password2 = await Modal.CreateTextModal({limit: 30})
if (password2.text === '') return
if (password2.text !== password1.text) {
this.$notify({
title: this.IntlString('APP_TWITTER_NAME'),
message: this.IntlString('APP_TWITTER_NOTIF_NEW_PASSWORD_MISS_MATCH'),
icon: 'twitter',
backgroundColor: '#e0245e80'
})
return
} else if (password2.text.length < 6) {
this.$notify({
title: this.IntlString('APP_TWITTER_NAME'),
message: this.IntlString('APP_TWITTER_NOTIF_NEW_PASSWORD_LENGTH_ERROR'),
icon: 'twitter',
backgroundColor: '#e0245e80'
})
return
}
this.twitterChangePassword(password2.text)
} catch (e) {
console.error(e)
}
}
},
created () {
if (!this.useMouse) {
this.$bus.$on('keyUpArrowDown', this.onDown)
this.$bus.$on('keyUpArrowUp', this.onUp)
this.$bus.$on('keyUpEnter', this.onEnter)
this.$bus.$on('keyUpBackspace', this.onBack)
}
},
beforeDestroy () {
this.$bus.$off('keyUpArrowDown', this.onDown)
this.$bus.$off('keyUpArrowUp', this.onUp)
this.$bus.$off('keyUpEnter', this.onEnter)
this.$bus.$off('keyUpBackspace', this.onBack)
}
}
</script>
<style scoped>
.content{
margin: 6px 10px;
margin-top: 28px;
height: calc(100% - 48px);
display: flex;
flex-direction: column;
}
.group {
position:relative;
margin-top:24px;
}
.group.inputText {
position:relative;
margin-top:45px;
}
.group.bottom {
margin-top: auto;
}
.group.img {
display: flex;
flex-direction: row;
align-items: center;
}
.group.img img{
display: flex;
flex-direction: row;
flex-grow: 0;
flex: 0 0 128px;
height: 128px;
margin-right: 24px;
}
input {
font-size:24px;
display:block;
width: 314px;
border:none;
border-bottom:1px solid #757575;
}
input:focus { outline:none; }
/* LABEL ======================================= */
.group.inputText label {
color:#999;
font-size:18px;
font-weight:normal;
position:absolute;
pointer-events:none;
left:5px;
top:10px;
transition:0.2s ease all;
-moz-transition:0.2s ease all;
-webkit-transition:0.2s ease all;
}
.checkbox {
display: flex;
height: 42px;
line-height: 42px;
align-items: center;
color: #007aff;
font-weight: 200;
border-radius: 6px;
padding-left: 12px;
margin-top: 4px;
}
.checkbox input {
width: 24px;
height: 0px;
opacity: 1;
}
.checkbox input::after {
box-sizing: border-box;
content: '';
opacity: 1;
position: absolute;
left: 6px;
margin-top: -10px;
width: 15px;
height: 15px;
background-color: white;
border: 3px #007aff solid;
border-radius: 50%;
}
.checkbox input:checked::after {
background-color: #007aff;
}
.checkbox.select {
border: 1px solid #007aff;
background-color: #007aff;
color: white;
}
.groupCheckBoxTitle {
font-weight: 700;
margin-top: 12px;
}
/* active state */
.group.inputText input:focus ~ label, .group.inputText input:valid ~ label {
top:-24px;
font-size:18px;
color:#007aff;
}
/* BOTTOM BARS ================================= */
.bar { position:relative; display:block; width:100%; }
.bar:before, .bar:after {
content:'';
height:2px;
width:0;
bottom:1px;
position:absolute;
background:#007aff;
transition:0.2s ease all;
-moz-transition:0.2s ease all;
-webkit-transition:0.2s ease all;
}
.bar:before {
left:50%;
}
.bar:after {
right:50%;
}
/* active state */
input:focus ~ .bar:before, input:focus ~ .bar:after,
.group.select input ~ .bar:before, .group.select input ~ .bar:after{
width:50%;
}
/* HIGHLIGHTER ================================== */
.highlight {
position:absolute;
height:60%;
width:100px;
top:25%;
left:0;
pointer-events:none;
opacity:0.5;
}
/* active state */
input:focus ~ .highlight {
-webkit-animation:inputHighlighter 0.3s ease;
-moz-animation:inputHighlighter 0.3s ease;
animation:inputHighlighter 0.3s ease;
}
.group .btn{
width: 100%;
padding: 0px 0px;
height: 48px;
color: #fff;
border: 0 none;
font-size: 22px;
font-weight: 500;
line-height: 34px;
color: #202129;
background-color: #edeeee;
}
.group.select .btn{
/* border: 6px solid #C0C0C0; */
line-height: 18px;
}
.group .btn.btn-blue{
width: 293px;
margin-left: 6px;
border: 1px solid #007aff;
color: #007aff;
background-color: white;
font-weight: 500;
border-radius: 10px;
font-weight: 300;
font-size: 19px;
}
.group.select .btn.btn-blue, .group:hover .btn.btn-blue{
background-color: #007aff;
color: white;
border: none;
}
.group .btn.btn-red{
border: 1px solid #ee3838;
color: #ee3838;
background-color: white;
font-weight: 200;
border-radius: 10px;
width: 193px;
margin: 0 auto;
margin-bottom: 11px;
}
.group.select .btn.btn-red, .group:hover .btn.btn-red{
background-color: #ee3838;
color: white;
border: none;
}
.group .btn.btn-gray{
border: none;
color: #222;
background-color: #AAA;
font-weight: 500;
border-radius: 10px;
}
.group.select .btn.btn-gray, .group:hover .btn.btn-gray{
background-color: #757575;
color: white;
border: none;
}
/* ANIMATIONS ================ */
@-webkit-keyframes inputHighlighter {
from { background:#007aff; }
to { width:0; background:transparent; }
}
@-moz-keyframes inputHighlighter {
from { background:#007aff; }
to { width:0; background:transparent; }
}
@keyframes inputHighlighter {
from { background:#007aff; }
to { width:0; background:transparent; }
}
</style>

View File

@@ -0,0 +1,120 @@
<template>
<div style="width: 326px; height: 743px;" class="phone_content">
<div class='tweet_write'>
<textarea
class="textarea-input"
v-model.trim="message"
v-autofocus
:placeholder="IntlString('APP_TWITTER_PLACEHOLDER_MESSAGE')"
></textarea>
<span class='tweet_send' @click="tweeter">{{ IntlString('APP_TWITTER_BUTTON_ACTION_TWEETER') }}</span>
</div>
</div>
</template>
<script>
import { mapActions, mapGetters } from 'vuex'
export default {
components: {},
data () {
return {
message: ''
}
},
computed: {
...mapGetters(['IntlString', 'useMouse'])
},
watch: {
},
methods: {
...mapActions(['twitterPostTweet']),
async onEnter () {
try {
const rep = await this.$phoneAPI.getReponseText({
// text: 'https://i.imgur.com/axLm3p6.png'
})
if (rep !== undefined && rep.text !== undefined) {
const message = rep.text.trim()
if (message.length !== 0) {
this.twitterPostTweet({ message })
}
}
} catch (e) {}
},
async tweeter () {
if (this.message === '') return
await this.twitterPostTweet({ message: this.message })
this.message = ''
},
onBack () {
if (this.useMouse === true && document.activeElement.tagName !== 'BODY') return
this.$bus.$emit('twitterHome')
}
},
created () {
if (!this.useMouse) {
this.$bus.$on('keyUpEnter', this.onEnter)
}
this.$bus.$on('keyUpBackspace', this.onBack)
},
async mounted () {
},
beforeDestroy () {
this.$bus.$off('keyUpBackspace', this.onBack)
this.$bus.$off('keyUpEnter', this.onEnter)
}
}
</script>
<style scoped>
.phone_content {
background: #DBF0F4;
}
.tweet_write{
widows: 100%;
display: flex;
flex-direction: column;
justify-content: space-around;
align-items: flex-end;
}
.tweet_write .textarea-input{
align-self: center;
width: 90%;
margin-top: 20px;
border: none;
outline: none;
font-size: 16px;
padding: 13px 16px;
height: 336px;
background-color: #ffffff;
color: white;
border-radius: 16px;
resize: none;
color: #222;
font-size: 18px;
}
.tweet_send{
align-self: flex-end;
width: 120px;
height: 32px;
float: right;
border-radius: 16px;
background-color: rgb(29, 161, 242);
margin-right: 12px;
margin-bottom: 2px;
color: white;
line-height: 32px;
text-align: center;
margin: 26px 20px;
font-size: 16px;
}
.tweet_send:hover {
cursor: pointer;
background-color: #0084b4;
}
</style>

View File

@@ -0,0 +1,128 @@
<template>
<div style="width: 326px; height: 743px;" class="phone_app">
<PhoneTitle :title="currentScreen.title" backgroundColor="white" v-on:back="quit"/>
<div class="phone_content">
<component v-bind:is="currentScreen.component"/>
</div>
<div class="twitter_menu">
<div
v-for="(s, i) in screen"
:key="i"
class="twitter_menu-item"
:class="{select: i === currentScreenIndex}"
@click.stop="openMenu(i)">
<i class="fa" :class="s.icon" @click.stop="openMenu(i)"></i>
</div>
</div>
</div>
</template>
<script>
import PhoneTitle from './../PhoneTitle'
import TwitterView from './TwitterView'
import TwitterPostTweet from './TwitterPostTweet'
import TwitterAccount from './TwitterAccount'
import TwitterTopTweet from './TwitterTopTweet'
import { mapGetters } from 'vuex'
export default {
components: {
PhoneTitle
},
data () {
return {
currentScreenIndex: 0
}
},
computed: {
...mapGetters(['IntlString', 'useMouse']),
screen () {
return [
{
title: this.IntlString('APP_TWITTER_VIEW_TWITTER'),
component: TwitterView,
icon: 'fa-home'
},
{
title: this.IntlString('APP_TWITTER_VIEW_TOP_TWEETS'),
component: TwitterTopTweet,
icon: 'fa-heart'
},
{
title: this.IntlString('APP_TWITTER_VIEW_TWEETER'),
component: TwitterPostTweet,
icon: ' fa-comment-o'
},
{
title: this.IntlString('APP_TWITTER_VIEW_SETTING'),
component: TwitterAccount,
icon: 'fa-cog'
}
]
},
currentScreen () {
return this.screen[this.currentScreenIndex]
}
},
watch: {
},
methods: {
onLeft () {
this.currentScreenIndex = Math.max(0, this.currentScreenIndex - 1)
},
onRight () {
this.currentScreenIndex = Math.min(this.screen.length - 1, this.currentScreenIndex + 1)
},
home () {
this.currentScreenIndex = 0
},
openMenu (index) {
this.currentScreenIndex = index
},
quit () {
if (this.currentScreenIndex === 0) {
this.$router.push({ name: 'home' })
} else {
this.currentScreenIndex = 0
}
}
},
created () {
if (!this.useMouse) {
this.$bus.$on('keyUpArrowLeft', this.onLeft)
this.$bus.$on('keyUpArrowRight', this.onRight)
}
this.$bus.$on('twitterHome', this.home)
},
mounted () {
},
beforeDestroy () {
this.$bus.$off('keyUpArrowLeft', this.onLeft)
this.$bus.$off('keyUpArrowRight', this.onRight)
this.$bus.$off('twitterHome', this.home)
}
}
</script>
<style scoped>
.twitter_menu {
border-top: 1px solid #CCC;
height: 56px;
display: flex;
width: 100%;
}
.twitter_menu-item {
flex-grow: 1;
flex-basis: 0;
display: flex;
justify-content: center;
align-items: center;
color: #959595;
}
.twitter_menu-item.select {
color: #1da1f2;
}
.twitter_menu-item:hover {
color: #1da1f2;
}
</style>

View File

@@ -0,0 +1,36 @@
<template>
<div style="width: 326px; height: 743px;" class="splash">
<img src="/html/static/img/twitter/bird.png">
</div>
</template>
<script>
export default {
created: function () {
setTimeout(() => {
this.$router.push({ name: 'twitter.screen' })
}, 500)
}
}
</script>
<style scoped>
.splash{
width: 100%;
height: 100%;
background-color: white; /*#1da1f2;*/
display: flex;
justify-content: center;
align-items: center;
}
img {
width: 80px;
animation-name: zoom;
animation-duration: 0.35s;
animation-fill-mode: forwards;
}
@keyframes zoom {
from {width: 180px;}
to {width: 250px;}
}
</style>

View File

@@ -0,0 +1,379 @@
<template>
<div style="width: 326px; height: 743px;" class="phone_content">
<div class="img-fullscreen" v-if="imgZoom !== undefined" @click.stop="imgZoom = undefined">
<img :src="imgZoom" />
</div>
<div class="tweets-wrapper" ref="elementsDiv">
<div class="tweet" v-for='(tweet, key) in tweets'
v-bind:key="tweet.id"
v-bind:class="{ select: key === selectMessage}"
>
<div class="tweet-img">
<img :src="tweet.authorIcon || 'html/static/img/twitter/default_profile.png'" width="48" height="48"/>
</div>
<div class="tweet-content">
<div class="tweet-head">
<div class="tweet-head-author">{{ tweet.author }}</div>
<div class="tweet-head-time">{{formatTime(tweet.time)}}</div>
</div>
<div class="tweet-message">
<template v-if="!isImage(tweet.message)">{{ tweet.message }}</template>
<img v-else :src="tweet.message" class="tweet-attachement-img" @click.stop="imgZoom = tweet.message">
</div>
<div class="tweet-like">
<div class="item svgreply" @click.stop="reply(tweet)">
<svg @click.stop="reply(tweet)" xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H6l-2 2V4h16v12z"/></svg>
</div>
<div class="item">
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24"><path d="M0 0h24v24H0z" fill="none"/><path d="M7 7h10v3l4-4-4-4v3H5v6h2V7zm10 10H7v-3l-4 4 4 4v-3h12v-6h-2v4z"/></svg>
</div>
<div v-if="tweet.isLikes" class="item svgdislike" @click.stop="twitterToogleLike({ tweetId: tweet.id })">
<svg @click.stop="twitterToogleLike({ tweetId: tweet.id })" xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24"><path d="M0 0h24v24H0z" fill="none"/><path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"/></svg>
<span @click.stop="twitterToogleLike({ tweetId: tweet.id })">{{ tweet.likes }}</span>
</div>
<div v-else class="svglike" @click.stop="twitterToogleLike({ tweetId: tweet.id })">
<svg @click.stop="twitterToogleLike({ tweetId: tweet.id })" xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24"><path d="M0 0h24v24H0z" fill="none"/><path d="M16.5 3c-1.74 0-3.41.81-4.5 2.09C10.91 3.81 9.24 3 7.5 3 4.42 3 2 5.42 2 8.5c0 3.78 3.4 6.86 8.55 11.54L12 21.35l1.45-1.32C18.6 15.36 22 12.28 22 8.5 22 5.42 19.58 3 16.5 3zm-4.4 15.55l-.1.1-.1-.1C7.14 14.24 4 11.39 4 8.5 4 6.5 5.5 5 7.5 5c1.54 0 3.04.99 3.57 2.36h1.87C13.46 5.99 14.96 5 16.5 5c2 0 3.5 1.5 3.5 3.5 0 2.89-3.14 5.74-7.9 10.05z"/></svg>
<span @click.stop="twitterToogleLike({ tweetId: tweet.id })">{{ tweet.likes }}</span>
</div>
<div class="item">
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7c.05-.23.09-.46.09-.7s-.04-.47-.09-.7l7.05-4.11c.54.5 1.25.81 2.04.81 1.66 0 3-1.34 3-3s-1.34-3-3-3-3 1.34-3 3c0 .24.04.47.09.7L8.04 9.81C7.5 9.31 6.79 9 6 9c-1.66 0-3 1.34-3 3s1.34 3 3 3c.79 0 1.5-.31 2.04-.81l7.12 4.16c-.05.21-.08.43-.08.65 0 1.61 1.31 2.92 2.92 2.92s2.92-1.31 2.92-2.92-1.31-2.92-2.92-2.92z"/></svg>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { mapGetters, mapActions } from 'vuex'
import Modal from '@/components/Modal/index.js'
export default {
components: {},
data () {
return {
selectMessage: -1,
ignoreControls: false,
imgZoom: undefined
}
},
computed: {
...mapGetters(['favoriteTweets', 'IntlString', 'useMouse']),
tweets () {
return this.favoriteTweets
}
},
watch: {
},
methods: {
...mapActions(['twitterLogin', 'twitterPostTweet', 'twitterToogleLike', 'fetchFavoriteTweets']),
async showOption () {
this.ignoreControls = true
const tweet = this.tweets[this.selectMessage]
let optionsChoix = [{
id: 1,
title: 'Like / Unlike',
icons: 'fa-heart'
}, {
id: 2,
title: 'Répondre',
icons: 'fa-reply'
}, {
id: -1,
title: this.IntlString('CANCEL'),
icons: 'fa-undo'
}]
if (this.isImage(tweet.message)) {
optionsChoix = [{
id: 3,
title: this.IntlString('APP_MESSAGE_ZOOM_IMG'),
icons: 'fa-search'
}, ...optionsChoix]
}
const choix = await Modal.CreateModal({ choix: optionsChoix })
this.ignoreControls = false
switch (choix.id) {
case 1:
this.twitterToogleLike({ tweetId: tweet.id })
break
case 2:
this.reply(tweet)
break
case 3:
this.imgZoom = tweet.message
break
}
},
isImage (mess) {
return /^https?:\/\/.*\.(png|jpg|jpeg|gif)/.test(mess)
},
async reply (tweet) {
const authorName = tweet.author
try {
this.ignoreControls = true
const rep = await Modal.CreateTextModal({
title: 'Répondre',
text: `@${authorName} `
})
if (rep !== undefined && rep.text !== undefined) {
const message = rep.text.trim()
if (message.length !== 0) {
this.twitterPostTweet({ message })
}
}
} catch (e) {
} finally {
this.ignoreControls = false
}
},
resetScroll () {
this.$nextTick(() => {
let elem = document.querySelector('#tweets')
elem.scrollTop = elem.scrollHeight
this.selectMessage = -1
})
},
scrollIntoViewIfNeeded () {
this.$nextTick(() => {
const elem = this.$el.querySelector('.select')
if (elem !== null) {
elem.scrollIntoViewIfNeeded()
}
})
},
onUp: function () {
if (this.ignoreControls === true) return
if (this.selectMessage === -1) {
this.selectMessage = 0
} else {
this.selectMessage = this.selectMessage === 0 ? 0 : this.selectMessage - 1
}
this.scrollIntoViewIfNeeded()
},
onDown () {
if (this.ignoreControls === true) return
if (this.selectMessage === -1) {
this.selectMessage = 0
} else {
this.selectMessage = this.selectMessage === this.tweets.length - 1 ? this.selectMessage : this.selectMessage + 1
}
this.scrollIntoViewIfNeeded()
},
async onEnter () {
if (this.ignoreControls === true) return
if (this.selectMessage === -1) {
this.newTweet()
} else {
this.showOption()
}
},
onBack () {
if (this.imgZoom !== undefined) {
this.imgZoom = undefined
return
}
if (this.ignoreControls === true) return
if (this.selectMessage !== -1) {
this.selectMessage = -1
} else {
this.$bus.$emit('twitterHome')
}
},
formatTime (time) {
const d = new Date(time)
return d.toLocaleTimeString()
}
},
created () {
if (!this.useMouse) {
this.$bus.$on('keyUpArrowDown', this.onDown)
this.$bus.$on('keyUpArrowUp', this.onUp)
this.$bus.$on('keyUpEnter', this.onEnter)
}
this.$bus.$on('keyUpBackspace', this.onBack)
},
mounted () {
this.fetchFavoriteTweets()
},
beforeDestroy () {
this.$bus.$off('keyUpArrowDown', this.onDown)
this.$bus.$off('keyUpArrowUp', this.onUp)
this.$bus.$off('keyUpEnter', this.onEnter)
this.$bus.$off('keyUpBackspace', this.onBack)
}
}
</script>
<style scoped>
.svgreply:hover {
cursor: pointer;
fill: #1da1f2;
color: #1da1f2;
}
.svglike:hover {
cursor: pointer;
fill: red;
color: red;
}
.svgdislike {
fill: red;
color: red;
}
.svgdislike:hover {
cursor: pointer;
fill: #C0C0C0;
color: #C0C0C0;
}
.img-fullscreen {
position: fixed;
z-index: 999999;
background-color: rgba(20, 20, 20, 0.8);
left: 0;
top: 0;
right: 0;
bottom: 0;
display: flex;
justify-content: center;
align-items: center;
}
.img-fullscreen img {
display: flex;
max-width: 90vw;
max-height: 95vh;
}
.tweets-wrapper{
height: 100%;
background-color: #DBF0F4;
color: black;
display: flex;
flex-direction: column;
overflow-y: auto;
}
.tweet{
background-color: white;
flex: 0 0 auto;
width: 100%;
display: flex;
flex-direction: row;
border-bottom: #CCC 1px solid;
padding-top: 6px;
}
.tweet.select {
background-color: #c0deed;
}
.tweet-img {
width: 322px;
display: flex;
justify-content: center;
}
.tweet-img img{
border-radius: 50%;
}
.tweet-content {
width: 260px;
}
.tweet-head {
padding-bottom: 4px;
font-size: 14px;
display: flex;
flex-direction: row;
font-weight: 700;
}
.tweet-head-author {
width: 100%;
}
.tweet-head-time {
font-size: 12px;
text-align: right;
padding-right: 6px;
color: #888;
}
.tweet-message{
font-size: 14px;
color: 000;
min-height: 36px;
word-break: break-word;
}
.tweet-attachement-img {
width: 96%;
}
.tweet-like {
margin-top: 6px;
display: flex;
width: 100%;
height: 24px;
font-size: 12px;
line-height: 24px;
font-weight: 700;
}
.tweet-like div {
width: 80px;
}
.tweet_write{
height: 56px;
widows: 100%;
background: #c0deed;
display: flex;
justify-content: space-around;
align-items: center;
}
.tweet_write input{
width: 75%;
margin-left: 6%;
border: none;
outline: none;
font-size: 16px;
padding: 3px 12px;
float: left;
height: 36px;
background-color: #ffffff;
color: white;
border-radius: 16px;
}
.tweet_write input::placeholder {
color: #888;
}
.tweet_send{
width: 32px;
height: 32px;
float: right;
border-radius: 50%;
background-color: #0084b4;
margin-right: 12px;
margin-bottom: 2px;
color: white;
line-height: 32px;
text-align: center;
}
.elements::-webkit-scrollbar-track
{
box-shadow: inset 0 0 6px rgba(0,0,0,0.3);
background-color: #a6a28c;
}
.elements::-webkit-scrollbar
{
width: 3px;
background-color: transparent;
}
.elements::-webkit-scrollbar-thumb
{
background-color: #1da1f2;
}
</style>

View File

@@ -0,0 +1,376 @@
<template>
<div style="width: 326px; height: 743px;" class="phone_content">
<div class="img-fullscreen" v-if="imgZoom !== undefined" @click.stop="imgZoom = undefined">
<img :src="imgZoom" />
</div>
<div class="tweets-wrapper" ref="elementsDiv">
<div class="tweet" v-for='(tweet, key) in tweets'
v-bind:key="tweet.id"
v-bind:class="{ select: key === selectMessage}"
>
<div class="tweet-img">
<img :src="tweet.authorIcon || 'html/static/img/twitter/default_profile.png'" width="48" height="48"/>
</div>
<div class="tweet-content">
<div class="tweet-head">
<div class="tweet-head-author">{{ tweet.author }}</div>
<div class="tweet-head-time">{{formatTime(tweet.time)}}</div>
</div>
<div class="tweet-message">
<template v-if="!isImage(tweet.message)">{{ tweet.message }}</template>
<img v-else :src="tweet.message" class="tweet-attachement-img" @click.stop="imgZoom = tweet.message">
</div>
<div class="tweet-like">
<div class="item svgreply" @click.stop="reply(tweet)">
<svg @click.stop="reply(tweet)" xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H6l-2 2V4h16v12z"/></svg>
</div>
<div class="item">
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24"><path d="M0 0h24v24H0z" fill="none"/><path d="M7 7h10v3l4-4-4-4v3H5v6h2V7zm10 10H7v-3l-4 4 4 4v-3h12v-6h-2v4z"/></svg>
</div>
<div v-if="tweet.isLikes" class="item svgdislike" @click.stop="twitterToogleLike({ tweetId: tweet.id })">
<svg @click.stop="twitterToogleLike({ tweetId: tweet.id })" xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24"><path d="M0 0h24v24H0z" fill="none"/><path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"/></svg>
<span @click.stop="twitterToogleLike({ tweetId: tweet.id })">{{ tweet.likes }}</span>
</div>
<div v-else class="svglike" @click.stop="twitterToogleLike({ tweetId: tweet.id })">
<svg @click.stop="twitterToogleLike({ tweetId: tweet.id })" xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24"><path d="M0 0h24v24H0z" fill="none"/><path d="M16.5 3c-1.74 0-3.41.81-4.5 2.09C10.91 3.81 9.24 3 7.5 3 4.42 3 2 5.42 2 8.5c0 3.78 3.4 6.86 8.55 11.54L12 21.35l1.45-1.32C18.6 15.36 22 12.28 22 8.5 22 5.42 19.58 3 16.5 3zm-4.4 15.55l-.1.1-.1-.1C7.14 14.24 4 11.39 4 8.5 4 6.5 5.5 5 7.5 5c1.54 0 3.04.99 3.57 2.36h1.87C13.46 5.99 14.96 5 16.5 5c2 0 3.5 1.5 3.5 3.5 0 2.89-3.14 5.74-7.9 10.05z"/></svg>
<span @click.stop="twitterToogleLike({ tweetId: tweet.id })">{{ tweet.likes }}</span>
</div>
<div class="item">
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7c.05-.23.09-.46.09-.7s-.04-.47-.09-.7l7.05-4.11c.54.5 1.25.81 2.04.81 1.66 0 3-1.34 3-3s-1.34-3-3-3-3 1.34-3 3c0 .24.04.47.09.7L8.04 9.81C7.5 9.31 6.79 9 6 9c-1.66 0-3 1.34-3 3s1.34 3 3 3c.79 0 1.5-.31 2.04-.81l7.12 4.16c-.05.21-.08.43-.08.65 0 1.61 1.31 2.92 2.92 2.92s2.92-1.31 2.92-2.92-1.31-2.92-2.92-2.92z"/></svg>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { mapGetters, mapActions } from 'vuex'
import Modal from '@/components/Modal/index.js'
export default {
components: {},
data () {
return {
selectMessage: -1,
ignoreControls: false,
imgZoom: undefined
}
},
computed: {
...mapGetters(['tweets', 'IntlString', 'useMouse'])
},
watch: {
},
methods: {
...mapActions(['twitterLogin', 'twitterPostTweet', 'twitterToogleLike', 'fetchTweets']),
async showOption () {
this.ignoreControls = true
const tweet = this.tweets[this.selectMessage]
let optionsChoix = [{
id: 1,
title: 'Like / Unlike',
icons: 'fa-heart'
}, {
id: 2,
title: 'Répondre',
icons: 'fa-reply'
}, {
id: -1,
title: this.IntlString('CANCEL'),
icons: 'fa-undo'
}]
if (this.isImage(tweet.message)) {
optionsChoix = [{
id: 3,
title: this.IntlString('APP_MESSAGE_ZOOM_IMG'),
icons: 'fa-search'
}, ...optionsChoix]
}
const choix = await Modal.CreateModal({ choix: optionsChoix })
this.ignoreControls = false
switch (choix.id) {
case 1:
this.twitterToogleLike({ tweetId: tweet.id })
break
case 2:
this.reply(tweet)
break
case 3:
this.imgZoom = tweet.message
break
}
},
isImage (mess) {
return /^https?:\/\/.*\.(png|jpg|jpeg|gif)/.test(mess)
},
async reply (tweet) {
const authorName = tweet.author
try {
this.ignoreControls = true
const rep = await Modal.CreateTextModal({
title: 'Répondre',
text: `@${authorName} `
})
if (rep !== undefined && rep.text !== undefined) {
const message = rep.text.trim()
if (message.length !== 0) {
this.twitterPostTweet({ message })
}
}
} catch (e) {
} finally {
this.ignoreControls = false
}
},
resetScroll () {
this.$nextTick(() => {
let elem = document.querySelector('#tweets')
elem.scrollTop = elem.scrollHeight
this.selectMessage = -1
})
},
scrollIntoViewIfNeeded () {
this.$nextTick(() => {
const elem = this.$el.querySelector('.select')
if (elem !== null) {
elem.scrollIntoViewIfNeeded()
}
})
},
onUp () {
if (this.ignoreControls === true) return
if (this.selectMessage === -1) {
this.selectMessage = 0
} else {
this.selectMessage = this.selectMessage === 0 ? 0 : this.selectMessage - 1
}
this.scrollIntoViewIfNeeded()
},
onDown () {
if (this.ignoreControls === true) return
if (this.selectMessage === -1) {
this.selectMessage = 0
} else {
this.selectMessage = this.selectMessage === this.tweets.length - 1 ? this.selectMessage : this.selectMessage + 1
}
this.scrollIntoViewIfNeeded()
},
async onEnter () {
if (this.ignoreControls === true) return
if (this.selectMessage === -1) {
this.newTweet()
} else {
this.showOption()
}
},
onBack () {
if (this.imgZoom !== undefined) {
this.imgZoom = undefined
return
}
if (this.ignoreControls === true) return
if (this.selectMessage !== -1) {
this.selectMessage = -1
} else {
this.$router.push({ name: 'home' })
}
},
formatTime (time) {
const d = new Date(time)
return d.toLocaleTimeString()
}
},
created () {
if (!this.useMouse) {
this.$bus.$on('keyUpArrowDown', this.onDown)
this.$bus.$on('keyUpArrowUp', this.onUp)
this.$bus.$on('keyUpEnter', this.onEnter)
}
this.$bus.$on('keyUpBackspace', this.onBack)
},
mounted () {
this.fetchTweets()
},
beforeDestroy () {
this.$bus.$off('keyUpArrowDown', this.onDown)
this.$bus.$off('keyUpArrowUp', this.onUp)
this.$bus.$off('keyUpEnter', this.onEnter)
this.$bus.$off('keyUpBackspace', this.onBack)
}
}
</script>
<style scoped>
.svgreply:hover {
cursor: pointer;
fill: #1da1f2;
color: #1da1f2;
}
.svglike:hover {
cursor: pointer;
fill: red;
color: red;
}
.svgdislike {
fill: red;
color: red;
}
.svgdislike:hover {
cursor: pointer;
fill: #C0C0C0;
color: #C0C0C0;
}
.img-fullscreen {
position: fixed;
z-index: 999999;
background-color: rgba(20, 20, 20, 0.8);
left: 0;
top: 0;
right: 0;
bottom: 0;
display: flex;
justify-content: center;
align-items: center;
}
.img-fullscreen img {
display: flex;
max-width: 90vw;
max-height: 95vh;
}
.tweets-wrapper{
height: 100%;
background-color: #DBF0F4;
color: black;
display: flex;
flex-direction: column;
overflow-y: auto;
}
.tweet{
background-color: white;
flex: 0 0 auto;
width: 100%;
display: flex;
flex-direction: row;
border-bottom: #CCC 1px solid;
padding-top: 6px;
}
.tweet.select {
background-color: #c0deed;
}
.tweet-img {
width: 322px;
display: flex;
justify-content: center;
}
.tweet-img img{
border-radius: 50%;
}
.tweet-content {
width: 260px;
}
.tweet-head {
padding-bottom: 4px;
font-size: 14px;
display: flex;
flex-direction: row;
font-weight: 700;
}
.tweet-head-author {
width: 100%;
}
.tweet-head-time {
font-size: 12px;
text-align: right;
padding-right: 6px;
color: #888;
}
.tweet-message{
font-size: 14px;
color: 000;
min-height: 36px;
word-break: break-word;
}
.tweet-attachement-img {
width: 96%;
}
.tweet-like {
margin-top: 6px;
display: flex;
width: 100%;
height: 24px;
font-size: 12px;
line-height: 24px;
font-weight: 700;
}
.tweet-like div {
width: 80px;
}
.tweet_write{
height: 56px;
widows: 100%;
background: #c0deed;
display: flex;
justify-content: space-around;
align-items: center;
}
.tweet_write input{
width: 75%;
margin-left: 6%;
border: none;
outline: none;
font-size: 16px;
padding: 3px 12px;
float: left;
height: 36px;
background-color: #ffffff;
color: white;
border-radius: 16px;
}
.tweet_write input::placeholder {
color: #888;
}
.tweet_send{
width: 32px;
height: 32px;
float: right;
border-radius: 50%;
background-color: #0084b4;
margin-right: 12px;
margin-bottom: 2px;
color: white;
line-height: 32px;
text-align: center;
}
.elements::-webkit-scrollbar-track
{
box-shadow: inset 0 0 6px rgba(0,0,0,0.3);
background-color: #a6a28c;
}
.elements::-webkit-scrollbar
{
width: 3px;
background-color: transparent;
}
.elements::-webkit-scrollbar-thumb
{
background-color: #1da1f2;
}
</style>

View File

@@ -0,0 +1,7 @@
const directive = {
inserted (el) {
el.focus()
}
}
export default directive

202
HTML/gcphone/src/emoji.json Normal file
View File

@@ -0,0 +1,202 @@
{
"100":"💯",
"1234":"🔢",
"grinning":"😀",
"grimacing":"😬",
"grin":"😁",
"joy":"😂",
"rofl":"🤣",
"partying":"🥳",
"smiley":"😃",
"smile":"😄",
"sweat_smile":"😅",
"laughing":"😆",
"innocent":"😇",
"wink":"😉",
"blush":"😊",
"slightly_smiling_face":"🙂",
"upside_down_face":"🙃",
"relaxed":"☺️",
"yum":"😋",
"relieved":"😌",
"heart_eyes":"😍",
"smiling_face_with_three_hearts":"🥰",
"kissing_heart":"😘",
"kissing":"😗",
"kissing_smiling_eyes":"😙",
"kissing_closed_eyes":"😚",
"stuck_out_tongue_winking_eye":"😜",
"zany":"🤪",
"raised_eyebrow":"🤨",
"monocle":"🧐",
"stuck_out_tongue_closed_eyes":"😝",
"stuck_out_tongue":"😛",
"money_mouth_face":"🤑",
"nerd_face":"🤓",
"sunglasses":"😎",
"star_struck":"🤩",
"clown_face":"🤡",
"cowboy_hat_face":"🤠",
"hugs":"🤗",
"smirk":"😏",
"no_mouth":"😶",
"neutral_face":"😐",
"expressionless":"😑",
"unamused":"😒",
"roll_eyes":"🙄",
"thinking":"🤔",
"lying_face":"🤥",
"hand_over_mouth":"🤭",
"shushing":"🤫",
"symbols_over_mouth":"🤬",
"exploding_head":"🤯",
"flushed":"😳",
"disappointed":"😞",
"worried":"😟",
"angry":"😠",
"rage":"😡",
"pensive":"😔",
"confused":"😕",
"slightly_frowning_face":"🙁",
"frowning_face":"☹",
"persevere":"😣",
"confounded":"😖",
"tired_face":"😫",
"weary":"😩",
"pleading":"🥺",
"triumph":"😤",
"open_mouth":"😮",
"scream":"😱",
"fearful":"😨",
"cold_sweat":"😰",
"hushed":"😯",
"frowning":"😦",
"anguished":"😧",
"cry":"😢",
"disappointed_relieved":"😥",
"drooling_face":"🤤",
"sleepy":"😪",
"sweat":"😓",
"hot":"🥵",
"cold":"🥶",
"sob":"😭",
"dizzy_face":"😵",
"astonished":"😲",
"zipper_mouth_face":"🤐",
"nauseated_face":"🤢",
"sneezing_face":"🤧",
"vomiting":"🤮",
"mask":"😷",
"face_with_thermometer":"🤒",
"face_with_head_bandage":"🤕",
"woozy":"🥴",
"sleeping":"😴",
"zzz":"💤",
"poop":"💩",
"smiling_imp":"😈",
"imp":"👿",
"japanese_ogre":"👹",
"japanese_goblin":"👺",
"skull":"💀",
"ghost":"👻",
"alien":"👽",
"robot":"🤖",
"smiley_cat":"😺",
"smile_cat":"😸",
"joy_cat":"😹",
"heart_eyes_cat":"😻",
"smirk_cat":"😼",
"kissing_cat":"😽",
"scream_cat":"🙀",
"crying_cat_face":"😿",
"pouting_cat":"😾",
"palms_up":"🤲",
"raised_hands":"🙌",
"clap":"👏",
"wave":"👋",
"call_me_hand":"🤙",
"\\+1":"👍",
"-1":"👎",
"facepunch":"👊",
"fist":"✊",
"fist_left":"🤛",
"fist_right":"🤜",
"v":"✌",
"ok_hand":"👌",
"raised_hand":"✋",
"raised_back_of_hand":"🤚",
"open_hands":"👐",
"muscle":"💪",
"pray":"🙏",
"foot":"🦶",
"leg":"🦵",
"handshake":"🤝",
"point_up":"☝",
"point_up_2":"👆",
"point_down":"👇",
"point_left":"👈",
"point_right":"👉",
"fu":"🖕",
"raised_hand_with_fingers_splayed":"🖐",
"love_you":"🤟",
"metal":"🤘",
"crossed_fingers":"🤞",
"vulcan_salute":"🖖",
"writing_hand":"✍",
"selfie":"🤳",
"nail_care":"💅",
"lips":"👄",
"tooth":"🦷",
"tongue":"👅",
"ear":"👂",
"nose":"👃",
"eye":"👁",
"eyes":"👀",
"brain":"🧠",
"bust_in_silhouette":"👤",
"busts_in_silhouette":"👥",
"speaking_head":"🗣",
"baby":"👶",
"child":"🧒",
"boy":"👦",
"girl":"👧",
"adult":"🧑",
"man":"👨",
"woman":"👩",
"blonde_woman":"👱‍♀️",
"blonde_man":"👱",
"bearded_person":"🧔",
"older_adult":"🧓",
"older_man":"👴",
"older_woman":"👵",
"man_with_gua_pi_mao":"👲",
"woman_with_headscarf":"🧕",
"woman_with_turban":"👳‍♀️",
"man_with_turban":"👳",
"policewoman":"👮‍♀️",
"policeman":"👮",
"construction_worker_woman":"👷‍♀️",
"construction_worker_man":"👷",
"guardswoman":"💂‍♀️",
"guardsman":"💂",
"female_detective":"🕵️‍♀️",
"male_detective":"🕵",
"woman_health_worker":"👩‍⚕️",
"man_health_worker":"👨‍⚕️",
"woman_farmer":"👩‍🌾",
"man_farmer":"👨‍🌾",
"woman_cook":"👩‍🍳",
"man_cook":"👨‍🍳",
"woman_student":"👩‍🎓",
"man_student":"👨‍🎓",
"woman_singer":"👩‍🎤",
"man_singer":"👨‍🎤",
"woman_teacher":"👩‍🏫",
"man_teacher":"👨‍🏫",
"woman_factory_worker":"👩‍🏭",
"man_factory_worker":"👨‍🏭",
"woman_technologist":"👩‍💻",
"man_technologist":"👨‍💻",
"woman_office_worker":"👩‍💼",
"man_office_worker":"👨‍💼"
}

30
HTML/gcphone/src/main.js Normal file
View File

@@ -0,0 +1,30 @@
import Vue from 'vue'
import App from './App'
import router from './router'
import store from './store'
import VueTimeago from './TimeAgo'
import PhoneAPI from './PhoneAPI'
import Notification from './Notification'
import AutoFocus from './directives/autofocus'
Vue.use(VueTimeago)
Vue.use(Notification)
Vue.config.productionTip = false
Vue.prototype.$bus = new Vue()
Vue.prototype.$phoneAPI = PhoneAPI
window.VueTimeago = VueTimeago
window.Vue = Vue
window.store = store
Vue.directive('autofocus', AutoFocus)
/* eslint-disable no-new */
window.APP = new Vue({
el: '#app',
store,
router,
render: h => h(App)
})

View File

@@ -0,0 +1,137 @@
import Vue from 'vue'
import Router from 'vue-router'
import Home from '@/components/Home'
import Menu from '@/components/Menu'
import Contacts from '@/components/contacts/Contacts'
import Contact from '@/components/contacts/Contact'
import MessagesList from '@/components/messages/MessagesList'
import Messages from '@/components/messages/Messages'
import MessageContactsSelect from '@/components/messages/MessageContactsSelect'
import Appels from '@/components/Appels/Appels'
import AppelsActive from '@/components/Appels/AppelsActive'
import AppelsNumber from '@/components/Appels/AppelsNumber'
import TchatSplashScreen from '@/components/Tchat/TchatSplashScreen'
import TchatChannel from '@/components/Tchat/TchatChannel'
import TchatMessage from '@/components/Tchat/TchatMessage'
import NotesChannel from '@/components/Notes/NotesChannel'
import NotesMessage from '@/components/Notes/NotesMessage'
import TwitterSpashScreen from '@/components/twitter/TwitterSpashScreen'
import TwitterScreen from '@/components/twitter/TwitterScreen'
import Parametre from '@/components/parametre/Parametre'
import Bank from '@/components/Bank/Bank'
import Bourse from '@/components/Bourse/Bourse'
import Photo from '@/components/Photo/Photo'
import App9GAG from '@/components/App9GAG'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
name: 'home',
component: Home
},
{
path: '/menu',
name: 'menu',
component: Menu
},
{
path: '/contacts',
name: 'contacts',
component: Contacts
},
{
path: '/contact/:id/:number?',
name: 'contacts.view',
component: Contact
},
{
path: '/messages',
name: 'messages',
component: MessagesList
},
{
path: '/messages/select',
name: 'messages.selectcontact',
component: MessageContactsSelect
},
{
path: '/messages/:number/:display',
name: 'messages.view',
component: Messages
}, {
path: '/bourse',
name: 'bourse',
component: Bourse
}, {
path: '/bank',
name: 'bank',
component: Bank
}, {
path: '/photo',
name: 'photo',
component: Photo
}, {
path: '/paramtre',
name: 'parametre',
component: Parametre
}, {
path: '/appels',
name: 'appels',
component: Appels
}, {
path: '/appelsactive',
name: 'appels.active',
component: AppelsActive
}, {
path: '/appelsNumber',
name: 'appels.number',
component: AppelsNumber
}, {
path: '/tchatsplash',
name: 'tchat',
component: TchatSplashScreen
}, {
path: '/tchat',
name: 'tchat.channel',
component: TchatChannel
}, {
path: '/tchat/:channel',
name: 'tchat.channel.show',
component: TchatMessage
}, {
path: '/notes',
name: 'notes',
component: NotesChannel
}, {
path: '/notes/:channel',
name: 'notes.channel.show',
component: NotesMessage
}, {
path: '/twitter/splash',
name: 'twitter.splash',
component: TwitterSpashScreen
}, {
path: '/twitter/view',
name: 'twitter.screen',
component: TwitterScreen
}, {
path: '/9gag',
name: '9gag',
component: App9GAG
}, {
path: '*',
redirect: '/'
}
]
})

View File

@@ -0,0 +1,29 @@
import Vue from 'vue'
import Vuex from 'vuex'
import phone from './modules/phone'
import contacts from './modules/contacts'
import messages from './modules/messages'
import appels from './modules/appels'
import bank from './modules/bank'
import notes from './modules/notes'
import bourse from './modules/bourse'
import tchat from './modules/tchat'
import twitter from './modules/twitter'
Vue.use(Vuex)
export default new Vuex.Store({
modules: {
phone,
contacts,
messages,
appels,
bank,
bourse,
notes,
tchat,
twitter
},
strict: true
})

View File

@@ -0,0 +1,116 @@
import PhoneAPI from './../../PhoneAPI'
const state = {
appelsHistorique: [],
appelsInfo: null
}
const getters = {
appelsHistorique: ({ appelsHistorique }) => appelsHistorique,
appelsInfo: ({ appelsInfo }) => appelsInfo,
appelsDisplayName (state, getters) {
if (state.appelsInfo === null) {
return 'ERROR'
}
if (state.appelsInfo.hidden === true) {
return getters.IntlString('APP_PHONE_NUMBER_HIDDEN')
}
const num = getters.appelsDisplayNumber
const contact = getters.contacts.find(e => e.number === num) || {}
return contact.display || getters.IntlString('APP_PHONE_NUMBER_UNKNOWN')
},
appelsDisplayNumber (state, getters) {
if (state.appelsInfo === null) {
return 'ERROR'
}
if (getters.isInitiatorCall === true) {
return state.appelsInfo.receiver_num
}
if (state.appelsInfo.hidden === true) {
return '###-####'
}
return state.appelsInfo.transmitter_num
},
isInitiatorCall (state, getters) {
if (state.appelsInfo === null) {
return false
}
return state.appelsInfo.initiator === true
}
}
const actions = {
startCall ({ commit }, { numero }) {
PhoneAPI.startCall(numero)
},
acceptCall ({ state }) {
PhoneAPI.acceptCall(state.appelsInfo)
},
rejectCall ({ state }) {
PhoneAPI.rejectCall(state.appelsInfo)
},
ignoreCall ({ commit }) {
commit('SET_APPELS_INFO', null)
// PhoneAPI.ignoreCall(state.appelsInfo)
},
appelsDeleteHistorique ({ commit, state }, { numero }) {
PhoneAPI.appelsDeleteHistorique(numero)
commit('SET_APPELS_HISTORIQUE', state.appelsHistorique.filter(h => {
return h.num !== numero
}))
},
appelsDeleteAllHistorique ({ commit }) {
PhoneAPI.appelsDeleteAllHistorique()
commit('SET_APPELS_HISTORIQUE', [])
},
resetAppels ({ commit }) {
commit('SET_APPELS_HISTORIQUE', [])
commit('SET_APPELS_INFO', null)
}
}
const mutations = {
SET_APPELS_HISTORIQUE (state, appelsHistorique) {
state.appelsHistorique = appelsHistorique
},
SET_APPELS_INFO_IF_EMPTY (state, appelsInfo) {
if (state.appelsInfo === null) {
state.appelsInfo = appelsInfo
}
},
SET_APPELS_INFO (state, appelsInfo) {
state.appelsInfo = appelsInfo
},
SET_APPELS_INFO_IS_ACCEPTS (state, isAccepts) {
if (state.appelsInfo !== null) {
state.appelsInfo = Object.assign({}, state.appelsInfo, {
is_accepts: isAccepts
})
}
}
}
export default {
state,
getters,
actions,
mutations
}
if (process.env.NODE_ENV !== 'production') {
// eslint-disable-next-line
state.appelsHistorique = [{"id":1,"incoming":0,"num":"336-4557","owner":"336-4557","accepts":0,"time":1528374759000},{"id":2,"incoming":0,"num":"police","owner":"336-4557","accepts":1,"time":1528374787000},{"id":3,"incoming":1,"num":"555-5555","owner":"336-4557","accepts":1,"time":1528374566000},{"id":4,"incoming":1,"num":"555-5555","owner":"336-4557","accepts":0,"time":1528371227000}]
state.appelsInfo = {
initiator: false,
id: 5,
transmitter_src: 5,
// transmitter_num: '###-####',
transmitter_num: '336-4557',
receiver_src: undefined,
// receiver_num: '336-4557',
receiver_num: '###-####',
is_valid: 0,
is_accepts: 0,
hidden: 0
}
}

View File

@@ -0,0 +1,29 @@
import PhoneAPI from './../../PhoneAPI'
const state = {
bankAmont: '0'
}
const getters = {
bankAmont: ({ bankAmont }) => bankAmont
}
const actions = {
sendpara ({ state }, { id, amount }) {
PhoneAPI.callEvent('gcphone:bankTransfer', {id, amount})
}
}
const mutations = {
SET_BANK_AMONT (state, bankAmont) {
state.bankAmont = bankAmont
}
}
export default {
state,
getters,
actions,
mutations
}

View File

@@ -0,0 +1,43 @@
const state = {
bourseInfo: []
}
const getters = {
bourseInfo: ({ bourseInfo }) => bourseInfo
}
const actions = {
resetBourse ({ commit }) {
commit('SET_BOURSE_INFO', [])
}
}
const mutations = {
SET_BOURSE_INFO (state, bourseInfo) {
state.bourseInfo = bourseInfo
}
}
export default {
state,
getters,
actions,
mutations
}
if (process.env.NODE_ENV !== 'production') {
// eslint-disable-next-line
state.bourseInfo = [{
difference: 0,
libelle: 'Diamante',
price: 1540.2
}, {
difference: 20,
libelle: 'Hierro',
price: 54.2
}, {
difference: -20.5,
libelle: 'Cobre',
price: 254.2
}]
}

View File

@@ -0,0 +1,55 @@
import PhoneAPI from './../../PhoneAPI'
const state = {
contacts: [],
defaultContacts: []
}
const getters = {
contacts: ({ contacts, defaultContacts }) => [...contacts, ...defaultContacts]
}
const actions = {
updateContact (context, {id, display, number}) {
PhoneAPI.updateContact(id, display, number)
},
addContact (context, {display, number}) {
PhoneAPI.addContact(display, number)
},
deleteContact (context, {id}) {
PhoneAPI.deleteContact(id)
},
resetContact ({ commit }) {
commit('SET_CONTACTS', [])
}
}
const mutations = {
SET_CONTACTS (state, contacts) {
state.contacts = contacts.sort((a, b) => a.display.localeCompare(b.display))
},
SET_DEFAULT_CONTACTS (state, contacts) {
state.defaultContacts = contacts
}
}
export default {
state,
getters,
actions,
mutations
}
if (process.env.NODE_ENV !== 'production') {
// eslint-disable-next-line
state.contacts = [{
id: 2,
number: '336-4557',
display: 'John doe'
},
{
id: 4,
number: '336-4553',
display: 'Nop user'
}]
}

View File

@@ -0,0 +1,104 @@
import PhoneAPI from './../../PhoneAPI'
const state = {
messages: []
}
const getters = {
messages: ({ messages }) => messages,
nbMessagesUnread: ({ messages }) => {
return messages.filter(e => e.isRead !== 1).length
}
}
const actions = {
setMessages ({ commit }, messages) {
commit('SET_MESSAGES', messages)
},
sendMessage ({ commit }, {phoneNumber, message}) {
PhoneAPI.sendMessage(phoneNumber, message)
},
deleteMessage ({ commit }, { id }) {
PhoneAPI.deleteMessage(id)
},
deleteMessagesNumber ({ commit, state }, { num }) {
PhoneAPI.deleteMessagesNumber(num)
commit('SET_MESSAGES', state.messages.filter(mess => {
return mess.transmitter !== num
}))
},
deleteAllMessages ({ commit }) {
PhoneAPI.deleteAllMessages()
commit('SET_MESSAGES', [])
},
setMessageRead ({ commit }, num) {
PhoneAPI.setMessageRead(num)
commit('SET_MESSAGES_READ', { num })
},
resetMessage ({ commit }) {
commit('SET_MESSAGES', [])
}
}
const mutations = {
SET_MESSAGES (state, messages) {
state.messages = messages
},
ADD_MESSAGE (state, message) {
state.messages.push(message)
},
SET_MESSAGES_READ (state, { num }) {
for (let i = 0; i < state.messages.length; i += 1) {
if (state.messages[i].transmitter === num && state.messages[i].isRead !== 1) {
state.messages[i].isRead = 1
}
}
}
}
export default {
state,
getters,
actions,
mutations
}
if (process.env.NODE_ENV !== 'production') {
const time = new Date().getTime()
const numRandom = '' + Math.floor(Math.random() * 10000000)
state.messages = [
{id: 0, transmitter: '0000', receiver: '06', time: time - 160, message: '#666-123', isRead: 1, owner: 0},
{id: 1, transmitter: numRandom, receiver: '06', time: time - 160, message: 'Salut sa va ?!!!', isRead: 1, owner: 0},
{id: 2, transmitter: numRandom, time, message: 'Tu fait quoi?', isRead: 1, owner: 0},
{id: 3, transmitter: numRandom, time, message: 'Oui est toi ?', isRead: 1, owner: 1},
{id: 4, transmitter: numRandom, time, message: 'GPS : 244 - 123', isRead: 1, owner: 0},
{id: 2, transmitter: numRandom, time, message: 'Tu fait quoi?', isRead: 1, owner: 0},
{id: 3, transmitter: numRandom, time, message: 'Oui est toi ?', isRead: 1, owner: 1},
{id: 4, transmitter: numRandom, time, message: 'GPS : 244 - 123', isRead: 1, owner: 0},
{id: 2, transmitter: numRandom, time, message: 'Tu fait quoi?', isRead: 1, owner: 0},
{id: 3, transmitter: numRandom, time, message: 'Oui est toi ?', isRead: 1, owner: 1},
{id: 4, transmitter: numRandom, time, message: 'GPS : 244 - 123', isRead: 1, owner: 0},
{id: 2, transmitter: numRandom, time, message: 'Tu fait quoi?', isRead: 1, owner: 0},
{id: 3, transmitter: numRandom, time, message: 'Oui est toi ?', isRead: 1, owner: 1},
{id: 4, transmitter: numRandom, time, message: 'GPS : 244 - 123', isRead: 1, owner: 0},
{id: 2, transmitter: numRandom, time, message: 'Tu fait quoi?', isRead: 1, owner: 0},
{id: 3, transmitter: numRandom, time, message: 'Oui est toi ?', isRead: 1, owner: 1},
{id: 4, transmitter: numRandom, time, message: 'GPS : 244 - 123', isRead: 1, owner: 0},
{id: 2, transmitter: numRandom, time, message: 'Tu fait quoi?', isRead: 1, owner: 0},
{id: 3, transmitter: numRandom, time, message: 'Oui est toi ?', isRead: 1, owner: 1},
{id: 4, transmitter: numRandom, time, message: 'GPS : 244 - 123', isRead: 1, owner: 0},
{id: 2, transmitter: numRandom, time, message: 'Tu fait quoi?', isRead: 1, owner: 0},
{id: 3, transmitter: numRandom, time, message: 'Oui est toi ?', isRead: 1, owner: 1},
{id: 4, transmitter: numRandom, time, message: 'GPS: 244.21, -123.15', isRead: 1, owner: 0},
{id: 5, transmitter: 'police', time, message: 'Tu fait quoi?', isRead: 1, owner: 1},
{id: 6, transmitter: 'ambulance', time, message: 'Oui est toi ?', isRead: 1, owner: 1},
{id: 7, transmitter: '01', time, message: 'Salut sa va ?', isRead: 1, owner: 0},
{id: 8, transmitter: '01', time, message: 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', isRead: 0, owner: 1},
{id: 9, transmitter: '01', time, message: 'GPS: -1034.5810546875, -2734.1027832031', isRead: 1, owner: 0},
{id: 44, transmitter: '01', time, message: 'https://i.imgur.com/gthahbs.png', isRead: 1, owner: 0},
{id: 10, transmitter: '02', time, message: 'Salut sa va ?', isRead: 1, owner: 0},
{id: 11, transmitter: '04', time, message: 'Salut sa va ?', isRead: 1, owner: 0},
{id: 12, transmitter: '04', time, message: 'Salut sa va ?', isRead: 1, owner: 0},
{id: 13, transmitter: '09', time, message: 'Tu sais pas !', isRead: 1, owner: 0}
]
}

View File

@@ -0,0 +1,116 @@
import PhoneAPI from './../../PhoneAPI'
const LOCAL_NAME = 'gc_notes_channels'
let NotesAudio = null
const state = {
channels: JSON.parse(localStorage[LOCAL_NAME] || null) || [],
currentChannel: null,
messagesChannel: []
}
const getters = {
notesChannels: ({ channels }) => channels,
notesCurrentChannel: ({ currentChannel }) => currentChannel,
notesMessages: ({ messagesChannel }) => messagesChannel
}
const actions = {
notesReset ({commit}) {
commit('NOTES_SET_MESSAGES', { messages: [] })
commit('NOTES_SET_CHANNEL', { channel: null })
commit('NOTES_REMOVES_ALL_CHANNELS')
},
notesSetChannel ({ state, commit, dispatch }, { channel }) {
if (state.currentChannel !== channel) {
commit('NOTES_SET_MESSAGES', { messages: [] })
commit('NOTES_SET_CHANNEL', { channel })
dispatch('notesGetMessagesChannel', { channel })
}
},
notesAddMessage ({ state, commit, getters }, { message }) {
const channel = message.channel
if (state.channels.find(e => e.channel === channel) !== undefined) {
if (NotesAudio !== null) {
NotesAudio.pause()
NotesAudio = null
}
NotesAudio = new Audio('/html/static/sound/tchatNotification.ogg')
NotesAudio.volume = getters.volume
NotesAudio.play()
}
commit('NOTES_ADD_MESSAGES', { message })
},
notesAddChannel ({ commit }, { channel }) {
commit('NOTES_ADD_CHANNELS', { channel })
},
notesRemoveChannel ({ commit }, { channel }) {
commit('NOTES_REMOVES_CHANNELS', { channel })
},
notesGetMessagesChannel ({ commit }, { channel }) {
PhoneAPI.notesGetMessagesChannel(channel)
},
notesSendMessage (state, { channel, message }) {
PhoneAPI.notesSendMessage(channel, message)
}
}
const mutations = {
NOTES_SET_CHANNEL (state, { channel }) {
state.currentChannel = channel
},
NOTES_ADD_CHANNELS (state, { channel }) {
state.channels.push({
channel
})
localStorage[LOCAL_NAME] = JSON.stringify(state.channels)
},
NOTES_REMOVES_CHANNELS (state, { channel }) {
state.channels = state.channels.filter(c => {
return c.channel !== channel
})
localStorage[LOCAL_NAME] = JSON.stringify(state.channels)
},
NOTES_REMOVES_ALL_CHANNELS (state) {
state.channels = []
localStorage[LOCAL_NAME] = JSON.stringify(state.channels)
},
NOTES_ADD_MESSAGES (state, { message }) {
if (message.channel === state.currentChannel) {
state.messagesChannel.push(message)
}
},
NOTES_SET_MESSAGES (state, { messages }) {
state.messagesChannel = messages
}
}
export default {
state,
getters,
actions,
mutations
}
if (process.env.NODE_ENV !== 'production') {
state.currentChannel = 'debug'
state.messagesChannel = JSON.parse('[{"channel":"teste","message":"teste","id":6,"time":1528671680000},{"channel":"teste","message":"Hop","id":5,"time":1528671153000}]')
for (let i = 0; i < 200; i++) {
state.messagesChannel.push(Object.assign({}, state.messagesChannel[0], { id: 100 + i, message: 'mess ' + i }))
}
state.messagesChannel.push({
message: 'Message sur plusieur ligne car il faut bien !!! Ok !',
id: 5000,
time: new Date().getTime()
})
state.messagesChannel.push({
message: 'Message sur plusieur ligne car il faut bien !!! Ok !',
id: 5000,
time: new Date().getTime()
})
state.messagesChannel.push({
message: 'Message sur plusieur ligne car il faut bien !!! Ok !',
id: 5000,
time: new Date(4567845).getTime()
})
}

View File

@@ -0,0 +1,229 @@
import Vue from 'vue'
import PhoneAPI from './../../PhoneAPI'
const state = {
show: process.env.NODE_ENV !== 'production',
tempoHide: false,
myPhoneNumber: '###-####',
background: JSON.parse(window.localStorage['gc_background'] || null),
coque: JSON.parse(window.localStorage['gc_coque'] || null),
sonido: JSON.parse(window.localStorage['gc_sonido'] || null),
zoom: window.localStorage['gc_zoom'] || '100%',
volume: parseFloat(window.localStorage['gc_volume']) || 1,
mouse: window.localStorage['gc_mouse'] === 'true',
lang: window.localStorage['gc_language'],
config: {
reseau: 'ElBichop',
useFormatNumberFrance: false,
apps: [],
themeColor: '#2A56C6',
colors: ['#2A56C6'],
language: {}
}
}
PhoneAPI.setUseMouse(state.mouse)
const getters = {
show: ({ show }) => show,
tempoHide: ({ tempoHide }) => tempoHide,
myPhoneNumber: ({ myPhoneNumber }) => myPhoneNumber,
volume: ({ volume }) => volume,
enableTakePhoto: ({ config }) => config.enableTakePhoto === true,
background: ({ background, config }) => {
if (background === null) {
if (config.background_default !== undefined) {
return config.background_default
}
return {
label: 'Default',
value: 'default.jpg'
}
}
return background
},
backgroundLabel: (state, getters) => getters.background.label,
backgroundURL: (state, getters) => {
if (getters.background.value.startsWith('http') === true) {
return getters.background.value
}
return '/html/static/img/background/' + getters.background.value
},
coque: ({ coque, config }) => {
if (coque === null) {
if (config && config.coque_default !== undefined) {
return config.coque_default
}
return {
label: 'base',
value: 'base.jpg'
}
}
return coque
},
sonido: ({ sonido, config }) => {
if (sonido === null) {
if (config && config.sonido_default !== undefined) {
return config.sonido_default
}
return {
label: 'Panters',
value: 'ring.ogg'
}
}
return sonido
},
coqueLabel: (state, getters) => getters.coque.label,
sonidoLabel: (state, getters) => getters.sonido.label,
zoom: ({ zoom }) => zoom,
useMouse: ({ mouse }) => mouse,
config: ({ config }) => config,
warningMessageCount: ({ config }) => config.warningMessageCount || 250,
useFormatNumberFrance: ({ config }) => config.useFormatNumberFrance,
themeColor: ({ config }) => config.themeColor,
colors: ({ config }) => config.colors,
Apps: ({ config, lang }, getters) => config.apps
.filter(app => app.enabled !== false)
.map(app => {
if (app.puceRef !== undefined) {
app.puce = getters[app.puceRef]
}
const keyName = `${lang}__name`
app.intlName = app[keyName] || app.name
return app
}),
AppsHome: (state, getters) => getters.Apps.filter(app => app.inHomePage === true),
availableLanguages ({ config }) {
const langKey = Object.keys(config.language)
const AvailableLanguage = {}
for (const key of langKey) {
AvailableLanguage[config.language[key].NAME] = key
}
return AvailableLanguage
},
IntlString ({ config, lang }) {
lang = lang || config.defaultLanguage
if (config.language[lang] === undefined) {
return (LABEL) => LABEL
}
return (LABEL, defaultValue) => {
return config.language[lang][LABEL] || defaultValue || LABEL
}
}
}
const actions = {
async loadConfig ({ commit, state }) {
const config = await PhoneAPI.getConfig()
const keyLang = Object.keys(config.language)
for (const key of keyLang) {
const timeAgoConf = config.language[key].TIMEAGO
if (timeAgoConf !== undefined) {
Vue.prototype.$timeago.addLocale(key, timeAgoConf)
}
}
Vue.prototype.$timeago.setCurrentLocale(state.lang)
if (config.defaultContacts !== undefined) {
commit('SET_DEFAULT_CONTACTS', config.defaultContacts)
}
commit('SET_CONFIG', config)
},
setEnableApp ({ commit, state }, { appName, enable = true }) {
commit('SET_APP_ENABLE', { appName, enable })
},
setVisibility ({ commit }, show) {
commit('SET_PHONE_VISIBILITY', show)
},
setZoon ({ commit }, zoom) {
window.localStorage['gc_zoom'] = zoom
commit('SET_ZOOM', zoom)
},
setBackground ({ commit }, background) {
window.localStorage['gc_background'] = JSON.stringify(background)
commit('SET_BACKGROUND', background)
},
setCoque ({ commit }, coque) {
window.localStorage['gc_coque'] = JSON.stringify(coque)
commit('SET_COQUE', coque)
},
setSonido ({ commit }, sonido) {
window.localStorage['gc_sonido'] = JSON.stringify(sonido)
commit('SET_SONIDO', sonido)
},
setVolume ({ commit }, volume) {
window.localStorage['gc_volume'] = volume
commit('SET_VOLUME', volume)
},
setLanguage ({ commit }, lang) {
window.localStorage['gc_language'] = lang
Vue.prototype.$timeago.setCurrentLocale(lang)
commit('SET_LANGUAGE', lang)
},
setMouseSupport ({ commit }, value) {
window.localStorage['gc_mouse'] = value
PhoneAPI.setUseMouse(value)
commit('SET_MOUSE_SUPPORT', value)
},
closePhone () {
PhoneAPI.closePhone()
},
resetPhone ({ dispatch, getters }) {
dispatch('setZoon', '100%')
dispatch('setVolume', 1)
dispatch('setBackground', getters.config.background_default)
dispatch('setCoque', getters.config.coque_default)
dispatch('setSonido', getters.config.sonido_default)
dispatch('setLanguage', 'fr_FR')
}
}
const mutations = {
SET_CONFIG (state, config) {
state.config = config
},
SET_APP_ENABLE (state, {appName, enable}) {
const appIndex = state.config.apps.findIndex(app => app.name === appName)
if (appIndex !== -1) {
Vue.set(state.config.apps[appIndex], 'enabled', enable)
}
},
SET_PHONE_VISIBILITY (state, show) {
state.show = show
state.tempoHide = false
},
SET_TEMPO_HIDE (state, hide) {
state.tempoHide = hide
},
SET_MY_PHONE_NUMBER (state, myPhoneNumber) {
state.myPhoneNumber = myPhoneNumber
},
SET_BACKGROUND (state, background) {
state.background = background
},
SET_COQUE (state, coque) {
state.coque = coque
},
SET_SONIDO (state, sonido) {
state.sonido = sonido
},
SET_ZOOM (state, zoom) {
state.zoom = zoom
},
SET_VOLUME (state, volume) {
state.volume = volume
},
SET_LANGUAGE (state, lang) {
state.lang = lang
},
SET_MOUSE_SUPPORT (state, value) {
state.mouse = value
}
}
export default {
state,
getters,
actions,
mutations
}

View File

@@ -0,0 +1,116 @@
import PhoneAPI from './../../PhoneAPI'
const LOCAL_NAME = 'gc_tchat_channels'
let TchatAudio = null
const state = {
channels: JSON.parse(localStorage[LOCAL_NAME] || null) || [],
currentChannel: null,
messagesChannel: []
}
const getters = {
tchatChannels: ({ channels }) => channels,
tchatCurrentChannel: ({ currentChannel }) => currentChannel,
tchatMessages: ({ messagesChannel }) => messagesChannel
}
const actions = {
tchatReset ({commit}) {
commit('TCHAT_SET_MESSAGES', { messages: [] })
commit('TCHAT_SET_CHANNEL', { channel: null })
commit('TCHAT_REMOVES_ALL_CHANNELS')
},
tchatSetChannel ({ state, commit, dispatch }, { channel }) {
if (state.currentChannel !== channel) {
commit('TCHAT_SET_MESSAGES', { messages: [] })
commit('TCHAT_SET_CHANNEL', { channel })
dispatch('tchatGetMessagesChannel', { channel })
}
},
tchatAddMessage ({ state, commit, getters }, { message }) {
const channel = message.channel
if (state.channels.find(e => e.channel === channel) !== undefined) {
if (TchatAudio !== null) {
TchatAudio.pause()
TchatAudio = null
}
TchatAudio = new Audio('/html/static/sound/tchatNotification.ogg')
TchatAudio.volume = getters.volume
TchatAudio.play()
}
commit('TCHAT_ADD_MESSAGES', { message })
},
tchatAddChannel ({ commit }, { channel }) {
commit('TCHAT_ADD_CHANNELS', { channel })
},
tchatRemoveChannel ({ commit }, { channel }) {
commit('TCHAT_REMOVES_CHANNELS', { channel })
},
tchatGetMessagesChannel ({ commit }, { channel }) {
PhoneAPI.tchatGetMessagesChannel(channel)
},
tchatSendMessage (state, { channel, message }) {
PhoneAPI.tchatSendMessage(channel, message)
}
}
const mutations = {
TCHAT_SET_CHANNEL (state, { channel }) {
state.currentChannel = channel
},
TCHAT_ADD_CHANNELS (state, { channel }) {
state.channels.push({
channel
})
localStorage[LOCAL_NAME] = JSON.stringify(state.channels)
},
TCHAT_REMOVES_CHANNELS (state, { channel }) {
state.channels = state.channels.filter(c => {
return c.channel !== channel
})
localStorage[LOCAL_NAME] = JSON.stringify(state.channels)
},
TCHAT_REMOVES_ALL_CHANNELS (state) {
state.channels = []
localStorage[LOCAL_NAME] = JSON.stringify(state.channels)
},
TCHAT_ADD_MESSAGES (state, { message }) {
if (message.channel === state.currentChannel) {
state.messagesChannel.push(message)
}
},
TCHAT_SET_MESSAGES (state, { messages }) {
state.messagesChannel = messages
}
}
export default {
state,
getters,
actions,
mutations
}
if (process.env.NODE_ENV !== 'production') {
state.currentChannel = 'debug'
state.messagesChannel = JSON.parse('[{"channel":"teste","message":"teste","id":6,"time":1528671680000},{"channel":"teste","message":"Hop","id":5,"time":1528671153000}]')
for (let i = 0; i < 200; i++) {
state.messagesChannel.push(Object.assign({}, state.messagesChannel[0], { id: 100 + i, message: 'mess ' + i }))
}
state.messagesChannel.push({
message: 'Message sur plusieur ligne car il faut bien !!! Ok !',
id: 5000,
time: new Date().getTime()
})
state.messagesChannel.push({
message: 'Message sur plusieur ligne car il faut bien !!! Ok !',
id: 5000,
time: new Date().getTime()
})
state.messagesChannel.push({
message: 'Message sur plusieur ligne car il faut bien !!! Ok !',
id: 5000,
time: new Date(4567845).getTime()
})
}

View File

@@ -0,0 +1,205 @@
import PhoneAPI from './../../PhoneAPI'
import Vue from 'vue'
const state = {
twitterUsername: localStorage['gcphone_twitter_username'],
twitterPassword: localStorage['gcphone_twitter_password'],
twitterAvatarUrl: localStorage['gcphone_twitter_avatarUrl'],
twitterNotification: localStorage['gcphone_twitter_notif'] ? parseInt(localStorage['gcphone_twitter_notif']) : 1,
twitterNotificationSound: localStorage['gcphone_twitter_notif_sound'] !== 'false',
tweets: [],
favoriteTweets: []
}
const getters = {
twitterUsername: ({ twitterUsername }) => twitterUsername,
twitterPassword: ({ twitterPassword }) => twitterPassword,
twitterAvatarUrl: ({ twitterAvatarUrl }) => twitterAvatarUrl,
twitterNotification: ({ twitterNotification }) => twitterNotification,
twitterNotificationSound: ({ twitterNotificationSound }) => twitterNotificationSound,
tweets: ({ tweets }) => tweets,
favoriteTweets: ({ favoriteTweets }) => favoriteTweets
}
const actions = {
twitterCreateNewAccount (_, {username, password, avatarUrl}) {
PhoneAPI.twitter_createAccount(username, password, avatarUrl)
},
twitterLogin ({ commit }, { username, password }) {
PhoneAPI.twitter_login(username, password)
},
twitterChangePassword ({ state }, newPassword) {
PhoneAPI.twitter_changePassword(state.twitterUsername, state.twitterPassword, newPassword)
},
twitterLogout ({ commit }) {
localStorage.removeItem('gcphone_twitter_username')
localStorage.removeItem('gcphone_twitter_password')
localStorage.removeItem('gcphone_twitter_avatarUrl')
commit('UPDATE_ACCOUNT', {
username: undefined,
password: undefined,
avatarUrl: undefined
})
},
twitterSetAvatar ({ state }, { avatarUrl }) {
PhoneAPI.twitter_setAvatar(state.twitterUsername, state.twitterPassword, avatarUrl)
},
twitterPostTweet ({ state, commit }, { message }) {
if (/^https?:\/\/.*\.(png|jpg|jpeg|gif)$/.test(message)) {
PhoneAPI.twitter_postTweetImg(state.twitterUsername, state.twitterPassword, message)
} else {
PhoneAPI.twitter_postTweet(state.twitterUsername, state.twitterPassword, PhoneAPI.convertEmoji(message))
}
},
twitterToogleLike ({ state }, { tweetId }) {
PhoneAPI.twitter_toggleLikeTweet(state.twitterUsername, state.twitterPassword, tweetId)
},
setAccount ({ commit }, data) {
localStorage['gcphone_twitter_username'] = data.username
localStorage['gcphone_twitter_password'] = data.password
localStorage['gcphone_twitter_avatarUrl'] = data.avatarUrl
commit('UPDATE_ACCOUNT', data)
},
addTweet ({ commit, state }, tweet) {
let notif = state.twitterNotification === 2
if (state.twitterNotification === 1) {
notif = tweet.message && tweet.message.toLowerCase().indexOf(state.twitterUsername.toLowerCase()) !== -1
}
if (notif === true) {
Vue.notify({
message: tweet.message,
title: tweet.author + ' :',
icon: 'twitter',
sound: state.twitterNotificationSound ? 'Twitter_Sound_Effect.ogg' : undefined
})
}
commit('ADD_TWEET', { tweet })
},
fetchTweets ({ state }) {
PhoneAPI.twitter_getTweets(state.twitterUsername, state.twitterPassword)
},
fetchFavoriteTweets ({ state }) {
PhoneAPI.twitter_getFavoriteTweets(state.twitterUsername, state.twitterPassword)
},
setTwitterNotification ({ commit }, value) {
localStorage['gcphone_twitter_notif'] = value
commit('SET_TWITTER_NOTIFICATION', { notification: value })
},
setTwitterNotificationSound ({ commit }, value) {
localStorage['gcphone_twitter_notif_sound'] = value
commit('SET_TWITTER_NOTIFICATION_SOUND', { notificationSound: value })
}
}
const mutations = {
SET_TWITTER_NOTIFICATION (state, { notification }) {
state.twitterNotification = notification
},
SET_TWITTER_NOTIFICATION_SOUND (state, { notificationSound }) {
state.twitterNotificationSound = notificationSound
},
UPDATE_ACCOUNT (state, { username, password, avatarUrl }) {
state.twitterUsername = username
state.twitterPassword = password
state.twitterAvatarUrl = avatarUrl
},
SET_TWEETS (state, { tweets }) {
state.tweets = tweets
},
SET_FAVORITE_TWEETS (state, { tweets }) {
state.favoriteTweets = tweets
},
ADD_TWEET (state, { tweet }) {
state.tweets = [tweet, ...state.tweets]
},
UPDATE_TWEET_LIKE (state, { tweetId, likes }) {
const tweetIndex = state.tweets.findIndex(t => t.id === tweetId)
if (tweetIndex !== -1) {
state.tweets[tweetIndex].likes = likes
}
const tweetIndexFav = state.favoriteTweets.findIndex(t => t.id === tweetId)
if (tweetIndexFav !== -1) {
state.favoriteTweets[tweetIndexFav].likes = likes
}
},
UPDATE_TWEET_ISLIKE (state, { tweetId, isLikes }) {
const tweetIndex = state.tweets.findIndex(t => t.id === tweetId)
if (tweetIndex !== -1) {
Vue.set(state.tweets[tweetIndex], 'isLikes', isLikes)
}
const tweetIndexFav = state.favoriteTweets.findIndex(t => t.id === tweetId)
if (tweetIndexFav !== -1) {
Vue.set(state.favoriteTweets[tweetIndexFav], 'isLikes', isLikes)
}
}
}
export default {
state,
getters,
actions,
mutations
}
if (process.env.NODE_ENV !== 'production') {
state.favoriteTweets = [{
id: 1,
message: 'https://pbs.twimg.com/profile_images/702982240184107008/tUKxvkcs_400x400.jpg',
author: 'Gannon',
time: new Date(),
likes: 3,
isLikes: 60
}, {
id: 2,
message: 'Borderlands 3 arrives on Xbox One, PS4, and PC on September 13, 2019! Tune in to the Gameplay Reveal Event on May 1st, where well debut the first hands-on looks! Pre-order now to get the Gold Weapon Skins Pack! ➜ https://borderlands.com ',
author: 'Gearbox Official',
authorIcon: 'https://pbs.twimg.com/profile_images/702982240184107008/tUKxvkcs_400x400.jpg',
time: new Date(),
likes: 65
}, {
id: 3,
message: '',
img: 'https://cdn.discordapp.com/attachments/563443658192322576/563473765569396746/samurai-background-hd-1920x1200-45462.jpg',
author: 'Gannon',
time: new Date()
}, {
id: 4,
message: 'Super Message de la mort.',
author: 'Gannon',
authorIcon: 'https://pbs.twimg.com/profile_images/986085090684960768/AcD9lOLw_bigger.jpg',
likes: 0,
time: new Date()
},
{
id: 5,
message: 'Super Message de la mort.',
author: 'Gannon',
authorIcon: 'https://pbs.twimg.com/profile_images/986085090684960768/AcD9lOLw_bigger.jpg',
likes: 0,
time: new Date()
},
{
id: 6,
message: 'Super Message de la mort.',
author: 'Gannon',
authorIcon: 'https://pbs.twimg.com/profile_images/986085090684960768/AcD9lOLw_bigger.jpg',
likes: 0,
time: new Date()
},
{
id: 7,
message: 'Super Message de la mort.',
author: 'Gannon',
authorIcon: 'https://pbs.twimg.com/profile_images/986085090684960768/AcD9lOLw_bigger.jpg',
likes: 0,
time: new Date()
},
{
id: 8,
message: 'Super Message de la mort.',
author: 'Gannon',
authorIcon: 'https://pbs.twimg.com/profile_images/986085090684960768/AcD9lOLw_bigger.jpg',
likes: 0,
time: new Date()
}]
}