Skip to content
This repository has been archived by the owner on Aug 28, 2024. It is now read-only.

Commit

Permalink
Share modal (#74)
Browse files Browse the repository at this point in the history
* Creation of the share modal

* Update package.json

* Addressed comments

* Fixed dependency

* Update ShareModal.vue

* Fixed url

* Update ShareModal.vue

* Adressed changes
  • Loading branch information
CodexAdrian authored Jun 28, 2023
1 parent 227652d commit 42f97f6
Show file tree
Hide file tree
Showing 12 changed files with 345 additions and 2 deletions.
1 change: 1 addition & 0 deletions docs/.vitepress/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export default {
{ text: 'Markdown', link: '/components/markdown' },
{ text: 'Copy Code', link: '/components/copy-code' },
{ text: 'Notifications', link: '/components/notifications' },
{ text: 'Share Modal', link: '/components/share-modal' },
],
},
],
Expand Down
44 changes: 44 additions & 0 deletions docs/components/share-modal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Share Modal

<DemoContainer>
<Button @click="$refs.shareContent.show('This is content')">
<EditIcon/>
Share Content
</Button>
<Button @click="$refs.shareLink.show('https://modrinth.com')">
<GlobeIcon/>
Share Link
</Button>
<ShareModal
ref="shareContent"
share-title="This is the title for the content"
share-text="Share this content"
/>
<ShareModal
ref="shareLink"
share-title="This is the title for the link"
share-text="Share this link"
link
/>
</DemoContainer>

```vue
<ShareModal
ref="shareContent"
share-title="This is the title for the content"
share-text="Share this content"
/>
<ShareModal
ref="shareLink"
share-title="This is the title for the link"
share-text="Share this link"
link
/>
```
You can use ref to open the modal, calling the show method

`content` is what will be shown in the text of the input for sharing
```text
$refs.shareContent.show(content)
```
1 change: 1 addition & 0 deletions lib/assets/external/mastodon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions lib/assets/external/reddit.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions lib/assets/external/twitter.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions lib/assets/icons/mail.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions lib/assets/icons/share.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 5 additions & 1 deletion lib/assets/styles/classes.scss
Original file line number Diff line number Diff line change
Expand Up @@ -623,10 +623,14 @@ a,

.resizable-textarea-wrapper {
display: block;
flex-grow: 1;
width: 100%;
margin-bottom: 0;
height: 10rem !important;

textarea {
height: 100%;
border-radius: var(--radius-sm);
min-height: 10rem;
width: calc(100% - var(--gap-xl) - var(--gap-sm));
resize: vertical;
}
Expand Down
270 changes: 270 additions & 0 deletions lib/components/base/ShareModal.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,270 @@
<script setup>
import {
Button,
Modal,
ClipboardCopyIcon,
LinkIcon,
ShareIcon,
MailIcon,
GlobeIcon,
TwitterIcon,
MastodonIcon,
RedditIcon,
} from '@/components'
import { computed, ref, nextTick } from 'vue'
import QrcodeVue from 'qrcode.vue'
const props = defineProps({
header: {
type: String,
default: 'Share',
},
shareTitle: {
type: String,
default: 'Modrinth',
},
shareText: {
type: String,
default: null,
},
link: {
type: Boolean,
default: false,
},
})
const shareModal = ref(null)
const qrCode = ref(null)
const qrImage = ref(null)
const content = ref(null)
const url = ref(null)
const canShare = ref(false)
const share = () => {
navigator.share(
props.link
? {
title: props.shareTitle.toString(),
text: props.shareText,
url: url.value,
}
: {
title: props.shareTitle.toString(),
text: content.value,
}
)
}
const show = async (passedContent) => {
content.value = props.shareText ? `${props.shareText}\n\n${passedContent}` : passedContent
shareModal.value.show()
if (props.link) {
url.value = passedContent
nextTick(() => {
console.log(qrCode.value)
fetch(qrCode.value.getElementsByTagName('canvas')[0].toDataURL('image/png'))
.then((res) => res.blob())
.then((blob) => {
console.log(blob)
qrImage.value = blob
})
})
}
if (navigator.canShare({ title: props.shareTitle.toString(), text: content.value })) {
canShare.value = true
}
}
const copyImage = async () => {
const item = new ClipboardItem({ 'image/png': qrImage.value })
await navigator.clipboard.write([item])
}
const copyText = async () => {
await navigator.clipboard.writeText(url.value ?? content.value)
}
const sendEmail = computed(
() =>
`mailto:[email protected]
?subject=${encodeURIComponent(props.shareTitle)}
&body=` + encodeURIComponent(content.value)
)
const sendTweet = computed(
() => `https://twitter.com/intent/tweet?text=` + encodeURIComponent(content.value)
)
const sendToot = computed(() => `https://tootpick.org/#text=` + encodeURIComponent(content.value))
const postOnReddit = computed(
() =>
`https://www.reddit.com/submit?title=${encodeURIComponent(props.shareTitle)}&text=` +
encodeURIComponent(content.value)
)
defineExpose({
show,
})
</script>
<template>
<Modal ref="shareModal" :header="header">
<div class="share-body">
<div v-if="link" class="qr-wrapper">
<div ref="qrCode">
<QrcodeVue :value="url" class="qr-code" margin="3" />
</div>
<Button v-tooltip="'Copy QR code'" icon-only class="copy-button" @click="copyImage">
<ClipboardCopyIcon />
</Button>
</div>
<div v-else class="resizable-textarea-wrapper">
<textarea v-model="content" />
<Button v-tooltip="'Copy Text'" icon-only class="copy-button transparent" @click="copyText">
<ClipboardCopyIcon />
</Button>
</div>
<div class="all-buttons">
<div v-if="link" class="iconified-input">
<LinkIcon />
<input type="text" :value="url" readonly />
<Button v-tooltip="'Copy Text'" @click="copyText">
<ClipboardCopyIcon />
</Button>
</div>
<div class="button-row">
<Button v-if="canShare" v-tooltip="'Share'" icon-only @click="share">
<ShareIcon />
</Button>
<a v-tooltip="'Send as an email'" class="btn icon-only" target="_blank" :href="sendEmail">
<MailIcon />
</a>
<a
v-if="link"
v-tooltip="'Open link in browser'"
class="btn icon-only"
target="_blank"
:href="url"
>
<GlobeIcon />
</a>
<a
v-tooltip="'Toot about it'"
class="btn mastodon icon-only"
target="_blank"
:href="sendToot"
>
<MastodonIcon />
</a>
<a
v-tooltip="'Tweet about it'"
class="btn twitter icon-only"
target="_blank"
:href="sendTweet"
>
<TwitterIcon />
</a>
<a
v-tooltip="'Share on Reddit'"
class="btn reddit icon-only"
target="_blank"
:href="postOnReddit"
>
<RedditIcon />
</a>
</div>
</div>
</div>
</Modal>
</template>
<style scoped lang="scss">
.share-body {
display: flex;
flex-direction: row;
align-items: center;
flex-wrap: wrap;
gap: var(--gap-sm);
padding: var(--gap-lg);
}
.all-buttons {
display: flex;
flex-direction: column;
gap: var(--gap-sm);
flex-grow: 1;
justify-content: center;
}
.iconified-input {
width: 100%;
input {
flex-basis: auto;
}
}
.button-row {
display: flex;
flex-direction: row;
gap: var(--gap-sm);
.btn {
fill: var(--color-contrast);
color: var(--color-contrast);
&.reddit {
background-color: #ff4500;
}
&.mastodon {
background-color: #563acc;
}
&.twitter {
background-color: #1da1f2;
}
}
}
.qr-wrapper {
position: relative;
margin: 0 auto;
&:hover {
.copy-button {
opacity: 1;
}
}
}
.qr-code {
background-color: white !important;
border-radius: var(--radius-md);
}
.copy-button {
position: absolute;
top: 0;
right: 0;
margin: var(--gap-sm);
transition: all 0.2s ease-in-out;
opacity: 0;
}
.resizable-textarea-wrapper {
position: relative;
height: 100%;
textarea {
width: 100%;
margin: 0;
}
.btn {
opacity: 1;
margin: 0;
}
}
</style>
7 changes: 7 additions & 0 deletions lib/components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export { default as Notifications } from './base/Notifications.vue'
export { default as ModalConfirm } from './base/ModalConfirm.vue'
export { default as Breadcrumbs } from './base/Breadcrumbs.vue'
export { default as DropdownButton } from './base/DropdownButton.vue'
export { default as ShareModal } from './base/ShareModal.vue'

export { default as Categories } from './search/Categories.vue'
export { default as SearchFilter } from './search/SearchFilter.vue'
Expand Down Expand Up @@ -125,5 +126,11 @@ export { default as VersionIcon } from '@/assets/icons/version.svg'
export { default as WikiIcon } from '@/assets/icons/wiki.svg'
export { default as XIcon } from '@/assets/icons/x.svg'
export { default as XCircleIcon } from '@/assets/icons/x-circle.svg'
export { default as MailIcon } from '@/assets/icons/mail.svg'
export { default as ShareIcon } from '@/assets/icons/share.svg'

export { default as MastodonIcon } from '@/assets/external/mastodon.svg'
export { default as RedditIcon } from '@/assets/external/reddit.svg'
export { default as TwitterIcon } from '@/assets/external/twitter.svg'

export { default as ModrinthIcon } from '@/assets/branding/logo.svg'
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "omorphia",
"type": "module",
"version": "0.4.31",
"version": "0.4.32",
"files": [
"dist",
"lib"
Expand Down Expand Up @@ -29,6 +29,7 @@
"floating-vue": "^2.0.0-beta.20",
"highlight.js": "^11.8.0",
"markdown-it": "^13.0.1",
"qrcode.vue": "^3.4.0",
"vue": "^3.3.4",
"vue-router": "^4.2.1",
"vue-select": "^4.0.0-beta.6",
Expand Down
Loading

0 comments on commit 42f97f6

Please sign in to comment.