Skip to content

Commit

Permalink
Fix merge conflict
Browse files Browse the repository at this point in the history
  • Loading branch information
corrideat committed Dec 18, 2024
2 parents be3d7d7 + 6b1ad58 commit e8f5201
Show file tree
Hide file tree
Showing 19 changed files with 396 additions and 81 deletions.
6 changes: 6 additions & 0 deletions frontend/assets/style/components/_custom-markdowns.scss
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@
position: relative;
padding-left: 1rem;
white-space: pre-line;
margin: 0.25rem 0;

&::before {
content: "";
Expand All @@ -127,6 +128,11 @@
border-radius: 8px;
background-color: $general_0;
}

p:has(+ p, + pre),
pre:has(+ p, + pre) {
margin-bottom: 1rem;
}
}

.link {
Expand Down
21 changes: 5 additions & 16 deletions frontend/controller/actions/identity.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
import { cloneDeep, has, omit } from '@model/contracts/shared/giLodash.js'
import { SETTING_CHELONIA_STATE } from '@model/database.js'
import sbp from '@sbp/sbp'
import { compressImage, imageUpload, objectURLtoBlob } from '@utils/image.js'
import { imageUpload, objectURLtoBlob } from '@utils/image.js'
import { SETTING_CURRENT_USER } from '~/frontend/model/database.js'
import { JOINED_CHATROOM, KV_QUEUE, LOGIN, LOGOUT } from '~/frontend/utils/events.js'
import { GIMessage } from '~/shared/domains/chelonia/GIMessage.js'
Expand Down Expand Up @@ -755,28 +755,17 @@ export default (sbp('sbp/selectors/register', {
const { identityContractID } = sbp('state/vuex/state').loggedIn
try {
const attachmentsData = await Promise.all(attachments.map(async (attachment) => {
const { url, needsImageCompression } = attachment
const { url, compressedBlob } = attachment
// url here is an instance of URL.createObjectURL(), which needs to be converted to a 'Blob'
const attachmentBlob = needsImageCompression
? await compressImage(url)
: await objectURLtoBlob(url)

if (needsImageCompression) {
// Update the attachment details to reflect the compressed image.
const fileNameWithoutExtension = attachment.name.split('.').slice(0, -1).join('.')
const extension = attachmentBlob.type.split('/')[1]

attachment.mimeType = attachmentBlob.type
attachment.name = `${fileNameWithoutExtension}.${extension}`
attachment.size = attachmentBlob.size
}
const attachmentBlob = compressedBlob || await objectURLtoBlob(url)

const response = await sbp('chelonia/fileUpload', attachmentBlob, {
type: attachment.mimeType,
cipher: 'aes256gcm'
}, { billableContractID })
const { delete: token, download: downloadData } = response
return {
attributes: omit(attachment, ['url', 'needsImageCompression']),
attributes: omit(attachment, ['url', 'compressedBlob', 'needsImageCompression']),
downloadData,
deleteData: { token }
}
Expand Down
29 changes: 29 additions & 0 deletions frontend/controller/service-worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,34 @@ sbp('sbp/selectors/register', {
await swRegistration.update()
setInterval(() => sbp('service-worker/update'), HOURS_MILLIS)

// Send a 'ready' message to the SW and wait back for a response
// This way we ensure that Chelonia has been set up
await new Promise((resolve, reject) => {
const messageChannel = new MessageChannel()
messageChannel.port1.onmessage = (event) => {
if (event.data.type === 'ready') {
resolve()
} else {
reject(event.data.error)
}
messageChannel.port1.close()
}
messageChannel.port1.onmessageerror = () => {
reject(new Error('Message error'))
messageChannel.port1.close()
}

navigator.serviceWorker.ready.then((worker) => {
worker.active.postMessage({
type: 'ready',
port: messageChannel.port2
}, [messageChannel.port2])
}).catch((e) => {
reject(e)
messageChannel.port1.close()
})
})

// Keep the service worker alive while the window is open
// The default idle timeout on Chrome and Firefox is 30 seconds. We send
// a ping message every 5 seconds to ensure that the worker remains
Expand Down Expand Up @@ -92,6 +120,7 @@ sbp('sbp/selectors/register', {
})
} catch (e) {
console.error('error setting up service worker:', e)
throw e
}
},
// We call this when the notification permission changes, to create a push
Expand Down
4 changes: 2 additions & 2 deletions frontend/controller/serviceworkers/push.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export default (sbp('sbp/selectors/register', {
const pubsub = sbp('okTurtles.data/get', PUBSUB_INSTANCE)
if (!pubsub) reject(new Error('Missing pubsub instance'))

const readyState = pubsub.socket.readyState
const readyState = pubsub.socket?.readyState
if (readyState !== WebSocket.OPEN) {
reject(new Error('WebSocket connection is not open'))
}
Expand Down Expand Up @@ -71,7 +71,7 @@ export default (sbp('sbp/selectors/register', {
const pubsub = sbp('okTurtles.data/get', PUBSUB_INSTANCE)
if (!pubsub) throw new Error('Missing pubsub instance')

const readyState = pubsub.socket.readyState
const readyState = pubsub.socket?.readyState
if (readyState !== WebSocket.OPEN) {
throw new Error('WebSocket connection is not open')
}
Expand Down
20 changes: 20 additions & 0 deletions frontend/controller/serviceworkers/sw-primary.js
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,26 @@ self.addEventListener('message', function (event) {
case 'event':
sbp('okTurtles.events/emit', event.data.subtype, ...deserializer(event.data.data))
break
case 'ready': {
// The 'ready' message is sent by a client (i.e., a tab or window) to
// ensure that Chelonia has been setup
const port = event.data.port
Promise.race([
setupChelonia(),
new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error('Timed out setting up Chelonia'))
}, 30e3)
})
]).then(() => {
port.postMessage({ type: 'ready' })
}, (e) => {
port.postMessage({ type: 'error', error: e })
}).finally(() => {
port.close()
})
break
}
default:
console.error('[sw] unknown message type:', event.data)
break
Expand Down
2 changes: 1 addition & 1 deletion frontend/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ async function startApp () {
new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error('Timed out setting up service worker'))
}, 16e3)
}, 8e3)
})]
).catch(e => {
console.error('[main] Error setting up service worker', e)
Expand Down
54 changes: 37 additions & 17 deletions frontend/model/notifications/templates.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,17 +145,31 @@ export default ({
}
},
NEW_PROPOSAL (data: { groupID: string, creatorID: string, proposalHash: string, subtype: NewProposalType }) {
const isCreator = data.creatorID === sbp('state/vuex/getters').ourIdentityContractId // notification message is different for creator and non-creator.
const args = {
name: `${CHATROOM_MEMBER_MENTION_SPECIAL_CHAR}${data.creatorID}`,
...LTags('strong')
}

const bodyTemplateMap = {
ADD_MEMBER: L('{strong_}{name}{_strong} proposed to add a member to the group. Vote now!', args),
CHANGE_MINCOME: L('{strong_}{name}{_strong} proposed to change the group mincome. Vote now!', args),
CHANGE_DISTRIBUTION_DATE: L('{strong_}{name}{_strong} proposed to change the group distribution date. Vote now!', args),
CHANGE_VOTING_RULE: L('{strong_}{name}{_strong} proposed to change the group voting system. Vote now!', args),
REMOVE_MEMBER: L('{strong_}{name}{_strong} proposed to remove a member from the group. Vote now!', args),
GENERIC: L('{strong_}{name}{_strong} created a proposal. Vote now!', args)
ADD_MEMBER: isCreator
? L('You proposed to add a member to the group.')
: L('{strong_}{name}{_strong} proposed to add a member to the group. Vote now!', args),
CHANGE_MINCOME: isCreator
? L('You proposed to change the group mincome.')
: L('{strong_}{name}{_strong} proposed to change the group mincome. Vote now!', args),
CHANGE_DISTRIBUTION_DATE: isCreator
? L('You proposed to change the group distribution date.')
: L('{strong_}{name}{_strong} proposed to change the group distribution date. Vote now!', args),
CHANGE_VOTING_RULE: isCreator
? L('You proposed to change the group voting system.')
: L('{strong_}{name}{_strong} proposed to change the group voting system. Vote now!', args),
REMOVE_MEMBER: isCreator
? L('You proposed to remove a member from the group.')
: L('{strong_}{name}{_strong} proposed to remove a member from the group. Vote now!', args),
GENERIC: isCreator
? L('You created a proposal.')
: L('{strong_}{name}{_strong} created a proposal. Vote now!', args)
}

const iconMap = {
Expand Down Expand Up @@ -212,6 +226,7 @@ export default ({
},
PROPOSAL_CLOSED (data: { proposal: Object, proposalHash: string }) {
const { creatorID, status, type, options } = getProposalDetails(data.proposal)
const isCreator = creatorID === sbp('state/vuex/getters').ourIdentityContractId // notification message is different for creator and non-creator

const statusMap = {
[STATUS_PASSED]: { icon: 'check', level: 'success', closedWith: L('accepted') },
Expand All @@ -223,7 +238,7 @@ export default ({
...options,
...LTags('strong'),
closedWith: statusMap[status].closedWith,
name: `${CHATROOM_MEMBER_MENTION_SPECIAL_CHAR}${creatorID}`
name: !isCreator ? `${CHATROOM_MEMBER_MENTION_SPECIAL_CHAR}${creatorID}` : ''
}

if (options.memberID) {
Expand All @@ -233,16 +248,21 @@ export default ({
}

const bodyTemplateMap = {
[PROPOSAL_INVITE_MEMBER]:
L("{strong_}{name}'s{_strong} proposal to add {member} to the group was {strong_}{closedWith}{_strong}.", args),
[PROPOSAL_REMOVE_MEMBER]:
L("{strong_}{name}'s{_strong} proposal to remove {member} from the group was {strong_}{closedWith}{_strong}.", args),
[PROPOSAL_GROUP_SETTING_CHANGE]:
L("{strong_}{name}'s{_strong} proposal to change group's {setting} to {value} was {strong_}{closedWith}{_strong}.", args),
[PROPOSAL_PROPOSAL_SETTING_CHANGE]:
L("{strong_}{name}'s{_strong} proposal to change group's {setting} was {strong_}{closedWith}{_strong}.", args),
[PROPOSAL_GENERIC]:
L('{strong_}{name}\'s{_strong} proposal "{title}" was {strong_}{closedWith}{_strong}.', args)
[PROPOSAL_INVITE_MEMBER]: isCreator
? L('Your proposal to add {member} to the group was {strong_}{closedWith}{_strong}.', args)
: L("{strong_}{name}'s{_strong} proposal to add {member} to the group was {strong_}{closedWith}{_strong}.", args),
[PROPOSAL_REMOVE_MEMBER]: isCreator
? L('Your proposal to remove {member} from the group was {strong_}{closedWith}{_strong}.', args)
: L("{strong_}{name}'s{_strong} proposal to remove {member} from the group was {strong_}{closedWith}{_strong}.", args),
[PROPOSAL_GROUP_SETTING_CHANGE]: isCreator
? L('Your proposal to change group\'s {setting} to {value} was {strong_}{closedWith}{_strong}.', args)
: L("{strong_}{name}'s{_strong} proposal to change group's {setting} to {value} was {strong_}{closedWith}{_strong}.", args),
[PROPOSAL_PROPOSAL_SETTING_CHANGE]: isCreator
? L('Your proposal to change group\'s {setting} was {strong_}{closedWith}{_strong}.', args)
: L("{strong_}{name}'s{_strong} proposal to change group's {setting} was {strong_}{closedWith}{_strong}.", args),
[PROPOSAL_GENERIC]: isCreator
? L('Your proposal "{title}" was {strong_}{closedWith}{_strong}.', args)
: L("{strong_}{name}'s{_strong} proposal \"{title}\" was {strong_}{closedWith}{_strong}.", args)
}

return {
Expand Down
10 changes: 7 additions & 3 deletions frontend/setupChelonia.js
Original file line number Diff line number Diff line change
Expand Up @@ -292,9 +292,12 @@ const setupChelonia = async (): Promise<*> => {
if (cheloniaState.loggedIn?.identityContractID !== identityContractID) return
// it is important we first login before syncing any contracts here since that will load the
// state and the contract sideEffects will sometimes need that state, e.g. loggedIn.identityContractID
await sbp('chelonia/contract/sync', identityContractID)
const contractIDs = groupContractsByType(cheloniaState.contracts)
await syncContractsInOrder(contractIDs)
await sbp('chelonia/contract/sync', identityContractID).then(async () => {
const contractIDs = groupContractsByType(cheloniaState.contracts)
await syncContractsInOrder(contractIDs)
}).catch(e => {
console.error('[setupChelonia] Error syncing identity contract and groups', e)
})
})
}

Expand All @@ -307,6 +310,7 @@ export default ((() => {
return () => {
if (!promise) {
promise = setupChelonia().catch((e) => {
console.error('[setupChelonia] Error during chelonia setup', e)
promise = undefined // Reset on error
throw e // Re-throw the error
})
Expand Down
2 changes: 2 additions & 0 deletions frontend/utils/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ export const LEFT_GROUP = 'left-group'
export const JOINED_CHATROOM = 'joined-chatroom'
export const LEFT_CHATROOM = 'left-chatroom'
export const DELETED_CHATROOM = 'deleted-chatroom'
export const DELETE_ATTACHMENT = 'delete-attachment'
export const DELETE_ATTACHMENT_FEEDBACK = 'delete-attachment-complete'

export const REPLACED_STATE = 'replaced-state'

Expand Down
12 changes: 11 additions & 1 deletion frontend/views/components/ButtonDropdownMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ menu-parent.c-menu(v-on='listeners')
| {{ buttonText }}
i.icon-angle-down.is-suffix

menu-content.c-menu-content
menu-content.c-menu-content(:class='"bound-to-" + boundEdge')
ul
template(v-if='options')
component(
Expand Down Expand Up @@ -42,6 +42,11 @@ export default ({
* - An item to be mapped to a 'menu-header' component:
* { type: 'header', name: string }
*/
},
boundEdge: {
type: String,
default: 'right',
validator: val => ['left', 'right'].includes(val)
}
},
methods: {
Expand Down Expand Up @@ -97,6 +102,11 @@ export default ({
left: unset;
right: 0;
&.bound-to-left {
left: 0;
right: unset;
}
::v-deep .c-header {
font: {
size: $size_5;
Expand Down
20 changes: 15 additions & 5 deletions frontend/views/components/ProfileCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ tooltip(
i18n.button.is-outlined.is-small(
v-if='groupShouldPropose || isGroupCreator'
tag='button'
@click='openModal("RemoveMember", { memberID: contractID })'
@click.stop='onRemoveMemberClick'
data-test='buttonRemoveMember'
) Remove member

Expand All @@ -91,7 +91,7 @@ import UserName from '@components/UserName.vue'
import Tooltip from '@components/Tooltip.vue'
import ModalClose from '@components/modal/ModalClose.vue'
import DMMixin from '@containers/chatroom/DMMixin.js'
import { OPEN_MODAL } from '@utils/events.js'
import { OPEN_MODAL, REPLACE_MODAL } from '@utils/events.js'
import { mapGetters } from 'vuex'
import { PROFILE_STATUS } from '~/frontend/model/contracts/shared/constants.js'
Expand Down Expand Up @@ -159,12 +159,22 @@ export default ({
},
methods: {
openModal (modal, props) {
if (this.deactivated) {
return
}
if (this.deactivated) { return }
this.toggleTooltip()
sbp('okTurtles.events/emit', OPEN_MODAL, modal, props)
},
onRemoveMemberClick () {
if (this.deactivated) { return }
this.toggleTooltip()
sbp(
'okTurtles.events/emit',
this.$route.query?.modal === 'GroupMembersAllModal' ? REPLACE_MODAL : OPEN_MODAL,
'RemoveMember',
{ memberID: this.contractID }
)
},
toggleTooltip () {
this.$refs.tooltip.toggle()
},
Expand Down
2 changes: 2 additions & 0 deletions frontend/views/components/menu/MenuParent.vue
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,15 @@ export default ({
methods: {
handleTrigger () {
this.config.Menu.isActive = true
this.$emit(this.config.Menu.isActive ? 'menu-open' : 'menu-close')
},
handleSelect (itemId) {
this.closeMenu()
this.$emit('select', itemId)
},
closeMenu () {
this.config.Menu.isActive = false
this.$emit('menu-close')
}
}
}: Object)
Expand Down
Loading

0 comments on commit e8f5201

Please sign in to comment.