Move many things over from Knossos (and other rearrangements) (#102)

This commit is contained in:
Emma Alexia
2023-10-16 21:18:23 -04:00
committed by GitHub
parent 46a6fee81d
commit 8369330053
68 changed files with 852 additions and 342 deletions

View File

@@ -0,0 +1,125 @@
<template>
<Modal ref="modal" :header="title" :noblur="noblur">
<div class="modal-delete">
<div class="markdown-body" v-html="renderString(description)" />
<label v-if="hasToType" for="confirmation" class="confirmation-label">
<span>
<strong>To verify, type</strong>
<em class="confirmation-text">{{ confirmationText }}</em>
<strong>below:</strong>
</span>
</label>
<div class="confirmation-input">
<input
v-if="hasToType"
id="confirmation"
v-model="confirmation_typed"
type="text"
placeholder="Type here..."
@input="type"
/>
</div>
<div class="input-group push-right">
<button class="btn" @click="modal.hide()">
<XIcon />
Cancel
</button>
<button class="btn btn-danger" :disabled="action_disabled" @click="proceed">
<TrashIcon />
{{ proceedLabel }}
</button>
</div>
</div>
</Modal>
</template>
<script setup>
import { Modal, TrashIcon, XIcon, renderString } from '@'
import { ref } from 'vue'
const props = defineProps({
confirmationText: {
type: String,
default: '',
},
hasToType: {
type: Boolean,
default: false,
},
title: {
type: String,
default: 'No title defined',
required: true,
},
description: {
type: String,
default: 'No description defined',
required: true,
},
proceedLabel: {
type: String,
default: 'Proceed',
},
noblur: {
type: Boolean,
default: false,
},
})
const emit = defineEmits(['proceed'])
const modal = ref(null)
const action_disabled = ref(props.hasToType)
const confirmation_typed = ref('')
function proceed() {
modal.value.hide()
emit('proceed')
}
function type() {
if (props.hasToType) {
action_disabled.value =
confirmation_typed.value.toLowerCase() !== props.confirmationText.toLowerCase()
}
}
function show() {
modal.value.show()
}
defineExpose({ show })
</script>
<style scoped lang="scss">
.modal-delete {
padding: var(--gap-lg);
display: flex;
flex-direction: column;
.markdown-body {
margin-bottom: 1rem;
}
.confirmation-label {
margin-bottom: 0.5rem;
}
.confirmation-text {
padding-right: 0.25ch;
margin: 0 0.25rem;
}
.confirmation-input {
input {
width: 20rem;
max-width: 100%;
}
}
.button-group {
margin-left: auto;
margin-top: 1.5rem;
}
}
</style>

View File

@@ -0,0 +1,172 @@
<template>
<div v-if="shown">
<div
:class="{ shown: actuallyShown }"
class="tauri-overlay"
data-tauri-drag-region
@click="() => (closable ? hide() : {})"
/>
<div
:class="{
shown: actuallyShown,
noblur: props.noblur,
}"
class="modal-overlay"
@click="() => (closable ? hide() : {})"
/>
<div class="modal-container" :class="{ shown: actuallyShown }">
<div class="modal-body">
<div v-if="props.header" class="header">
<h1>{{ props.header }}</h1>
<button v-if="closable" class="btn icon-only transparent" @click="hide">
<XIcon />
</button>
</div>
<div class="content">
<slot />
</div>
</div>
</div>
</div>
<div v-else></div>
</template>
<script setup>
import { XIcon } from '@'
import { ref } from 'vue'
const props = defineProps({
header: {
type: String,
default: null,
},
noblur: {
type: Boolean,
default: false,
},
closable: {
type: Boolean,
default: true,
},
})
const shown = ref(false)
const actuallyShown = ref(false)
function show() {
shown.value = true
setTimeout(() => {
actuallyShown.value = true
}, 50)
}
function hide() {
actuallyShown.value = false
setTimeout(() => {
shown.value = false
}, 300)
}
defineExpose({
show,
hide,
})
</script>
<style lang="scss" scoped>
.tauri-overlay {
position: fixed;
visibility: hidden;
top: 0;
left: 0;
width: 100%;
height: 100px;
z-index: 20;
&.shown {
opacity: 1;
visibility: visible;
}
}
.modal-overlay {
visibility: hidden;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 19;
transition: all 0.3s ease-in-out;
&.shown {
opacity: 1;
visibility: visible;
background: hsla(0, 0%, 0%, 0.5);
backdrop-filter: blur(3px);
}
&.noblur {
backdrop-filter: none;
}
}
.modal-container {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
z-index: 21;
visibility: hidden;
pointer-events: none;
&.shown {
visibility: visible;
.modal-body {
opacity: 1;
visibility: visible;
transform: translateY(0);
}
}
.modal-body {
position: fixed;
box-shadow: var(--shadow-raised), var(--shadow-inset);
border-radius: var(--radius-lg);
max-height: calc(100% - 2 * var(--gap-lg));
overflow-y: auto;
width: 600px;
pointer-events: auto;
.header {
display: flex;
justify-content: space-between;
align-items: center;
background-color: var(--color-bg);
padding: var(--gap-md) var(--gap-lg);
h1 {
font-weight: bold;
font-size: 1.25rem;
}
}
.content {
background-color: var(--color-raised-bg);
}
transform: translateY(50vh);
visibility: hidden;
opacity: 0;
transition: all 0.25s ease-in-out;
@media screen and (max-width: 650px) {
width: calc(100% - 2 * var(--gap-lg));
}
}
}
</style>

View File

@@ -0,0 +1,123 @@
<template>
<Modal ref="modal" :header="`Report ${itemType}`" :noblur="noblur">
<div class="modal-report universal-labels">
<div class="markdown-body">
<p>
Modding should be safe for everyone, so we take abuse and malicious intent seriously at
Modrinth. We want to hear about harmful content on the site that violates our
<router-link to="/legal/terms">ToS</router-link> and
<router-link to="/legal/rules">Rules</router-link>. Rest assured, we'll keep your
identifying information private.
</p>
<p v-if="itemType === 'project' || itemType === 'version'">
Please <strong>do not</strong> use this to report bugs with the project itself. This form
is only for submitting a report to Modrinth staff. If the project has an Issues link or a
Discord invite, consider reporting it there.
</p>
</div>
<label for="report-type">
<span class="label__title">Reason</span>
</label>
<DropdownSelect
id="report-type"
v-model="reportType"
name="report-type"
:options="reportTypes"
:display-name="capitalizeString"
default-value="Choose report type"
class="multiselect"
/>
<label for="report-body">
<span class="label__title">Additional information</span>
<span class="label__description markdown-body">
Please provide additional context about your report. Include links and images if possible.
<strong>Empty reports will be closed.</strong> This editor supports
<a href="https://docs.modrinth.com/markdown" target="_blank">Markdown formatting</a>.
</span>
</label>
<Chips v-model="bodyViewType" class="separator" :items="['source', 'preview']" />
<div class="text-input textarea-wrapper">
<textarea v-if="bodyViewType === 'source'" id="body" v-model="body" spellcheck="true" />
<div v-else class="preview" v-html="renderString(body)" />
</div>
<div class="input-group push-right">
<Button @click="cancel">
<XIcon />
Cancel
</Button>
<Button color="primary" @click="submitReport">
<CheckIcon />
Report
</Button>
</div>
</div>
</Modal>
</template>
<script setup>
import { Chips, DropdownSelect, Modal, CheckIcon, XIcon, capitalizeString, renderString } from '@'
import { ref } from 'vue'
defineProps({
itemType: {
type: String,
default: '',
},
itemId: {
type: String,
default: '',
},
reportTypes: {
type: Array,
default: () => [],
},
submitReport: {
type: Function,
default: () => {},
},
noblur: {
type: Boolean,
default: false,
},
})
const reportType = ref('')
const body = ref('')
const bodyViewType = ref('source')
const modal = ref(null)
function cancel() {
reportType.value = ''
body.value = ''
bodyViewType.value = 'source'
modal.value.hide()
}
function show() {
modal.value.show()
}
defineExpose({
show,
})
</script>
<style scoped lang="scss">
.modal-report {
padding: var(--gap-lg);
.textarea-wrapper {
height: 10rem;
:first-child {
max-height: 8rem;
transform: translateY(1rem);
}
}
.preview {
overflow-y: auto;
}
}
</style>

View File

@@ -0,0 +1,270 @@
<script setup>
import {
Button,
Modal,
ClipboardCopyIcon,
LinkIcon,
ShareIcon,
MailIcon,
GlobeIcon,
TwitterIcon,
MastodonIcon,
RedditIcon,
} from '@'
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:user@test.com
?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>