Skip to content

Commit

Permalink
WIP: Added preview panel with link handling
Browse files Browse the repository at this point in the history
TODO: Improve styling
  • Loading branch information
ebkr committed Dec 1, 2024
1 parent 49eb3e0 commit 532162c
Show file tree
Hide file tree
Showing 12 changed files with 623 additions and 189 deletions.
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,13 @@
"fflate": "^0.8.2",
"floating-vue": "^1.0.0-beta.19",
"fs-extra": "^8.1.0",
"github-markdown-css": "^5.7.0",
"glob-parent": "^6.0.2",
"highlight.js": "^10.4.1",
"lodash.debounce": "^4.0.8",
"markdown-it": "^14.1.0",
"markdown-it-table": "^4.1.1",
"modern-normalize": "^3.0.1",
"moment": "^2.29.1",
"node-ipc": "^9.1.1",
"quasar": "^1.14.7",
Expand Down
47 changes: 47 additions & 0 deletions src/components/OnlineRowCard.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<template>
<keep-alive>
<div class='row-card is-shadowless' :class="[{'row-card--expanded': isSelected}]" @click="$emit('click')">
<div class='cursor-pointer'>
<header class='card-header is-shadowless' :id='id'>
<div class='card-header-icon mod-logo' v-if="image !== ''">
<figure class='image is-48x48 image-parent'>
<img :src='image' alt='Mod Logo' class='image-overlap'/>
<img v-if="$store.state.profile.funkyMode" src='../assets/funky_mode.png' alt='Mod Logo' class='image-overlap'/>
</figure>
</div>
<span ref="title" class='card-header-title'><slot name='title'></slot></span>
<slot name='other-icons'></slot>
</header>
</div>
</div>
</keep-alive>
</template>

<script lang='ts'>
import Vue from 'vue';
import { Component, Prop, Watch } from 'vue-property-decorator'
@Component
export default class OnlineRowCard extends Vue {
@Prop()
isSelected: boolean | undefined;
@Prop({default: ''})
image: string | undefined;
@Prop()
id: string | undefined;
async created() {
await this.$store.dispatch('profile/loadModCardSettings');
}
}
</script>


<style lang="scss" scoped>
.card-header-title {
word-break: break-all;
}
</style>
13 changes: 13 additions & 0 deletions src/components/navigation/NavigationLayout.vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,16 @@ import GameRunningModal from '../modals/GameRunningModal.vue';
export default class NavigationLayout extends Vue {}
</script>

<style>
.column {
padding: 0.75rem;
max-height: 100vh;
overflow: hidden;
}
html {
overflow: hidden;
overflow-y: auto;
}
</style>
44 changes: 44 additions & 0 deletions src/components/v2/MarkdownRender.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<script lang="ts" setup>
import { computed } from 'vue';
import markdownit from 'markdown-it';
import { markdownItTable } from 'markdown-it-table';
import LinkProvider from 'src/providers/components/LinkProvider';
interface MarkdownRenderProps {
markdown: string;
}
const props = defineProps<MarkdownRenderProps>();
const markdownToRender = computed(() => {
const md = markdownit("commonmark", {}) as any;
md.use(markdownItTable)
return md.render(props.markdown);
})
function captureClick(e: Event) {
if (e.target && e.target instanceof HTMLElement) {
if (e.target.tagName.toLowerCase() === "a") {
if (e.target.getAttribute("href").startsWith("#")) {
return e;
} else {
LinkProvider.instance.openLink(e.target.getAttribute("href")!);
}
}
}
}
</script>

<template>
<p class="markdown-body" v-html="markdownToRender" @click.prevent="captureClick"></p>
</template>

<style lang="scss" scoped>
@import '~modern-normalize/modern-normalize.css';
@import "~github-markdown-css/github-markdown.css";
.markdown-body {
padding: 1rem;
overflow-y: auto;
}
</style>
93 changes: 93 additions & 0 deletions src/components/v2/OnlinePreviewPanel.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<script lang="ts" setup>
import ThunderstoreMod from 'src/model/ThunderstoreMod';
import { ref, watch } from 'vue';
import GameManager from 'src/model/game/GameManager';
import MarkdownRender from 'components/v2/MarkdownRender.vue';
import { valueToReadableDate } from 'src/utils/DateUtils';
interface ModPreviewPanelProps {
mod: ThunderstoreMod;
}
const activeGame = GameManager.activeGame;
const props = defineProps<ModPreviewPanelProps>();
let readme = ref<string | null>(null);
function fetchReadme(mod: ThunderstoreMod) {
readme.value = null;
fetch(`https://thunderstore.dev/api/experimental/package/${mod.getOwner()}/${mod.getName()}/${mod.getLatestVersion()}/readme/`)
.then(res => {
return res;
})
.then(res => res.json())
.then(res => {
console.log("README:", res)
return res;
})
.then(res => readme.value = res.markdown);
}
fetchReadme(props.mod);
watch(() => props.mod, fetchReadme);
function getReadableDate(date: string): string {
return valueToReadableDate(new Date(date));
}
function getReadableCategories(mod: ThunderstoreMod) {
return mod.getCategories().join(", ");
}
</script>

<template>
<div class="c-preview-panel">
<div class="c-preview-panel__container">
<h1 class="title">{{ mod.getName() }}</h1>
<h2 class="subtitle">By {{ mod.getOwner() }}</h2>
<p class='card-timestamp'><strong>Last updated:</strong> {{getReadableDate(mod.getDateUpdated())}}</p>
<p class='card-timestamp'><strong>Categories:</strong> {{getReadableCategories(mod)}}</p>
<div class="margin-top">
<p class="description">{{ mod.getDescription() }}</p>
</div>
<div class="sticky-top inherit-background-colour sticky-top--no-shadow sticky-top--opaque no-margin sticky-top--no-padding">
<div class="pad pad--top">
<button class="button is-info margin-right">Download</button>
<button class="button">Donate</button>
</div>
<div class="tabs margin-top">
<ul>
<li class="is-active"><a>README</a></li>
<li><a>CHANGELOG</a></li>
<li><a>Dependencies</a></li>
</ul>
</div>
</div>
<template v-if="readme == null">
<p>Loading README</p>
</template>
<template v-else-if="readme.trim().length === 0">
<p>No README content</p>
</template>
<template v-else>
<MarkdownRender :markdown="readme" />
</template>
</div>
</div>
</template>

<style lang="scss" scoped>
.c-preview-panel {
position: sticky;
top: 0;
width: min(50vw, 600px);
height: calc(100vh - 0.75rem);
overflow-y: auto;
&__container {
margin: 1rem;
padding: 1rem;
}
}
</style>
107 changes: 107 additions & 0 deletions src/components/views/OnlineModListWithPanel.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
<template>
<div>
<OnlineRowCard
v-for='(key, index) in pagedModList' :key="`online-${key.getFullName()}-${index}-${settings.getContext().global.expandedCards}`"
:image="getImageUrl(key)"
:id="index"
:is-selected="selectedMod === key"
@click="() => emitCardClick(key)">
<template v-slot:title>
<div>
<span class="selectable">{{key.getName()}}</span>
<div>
<span class="card-byline">{{key.getOwner()}}</span>
</div>
</div>
</template>
<template v-slot:other-icons>
<span class='card-header-icon' v-if="isModDeprecated(key)">
<i class='fas fa-exclamation-triangle' v-tooltip.left="'This mod is potentially broken'"></i>
</span>
<span class='card-header-icon' v-if="key.isPinned() && !readOnly">
<i class='fas fa-map-pin' v-tooltip.left="'Pinned on Thunderstore'"></i>
</span>
<span class='card-header-icon' v-if="key.getDonationLink() && !readOnly">
<Link :url="key.getDonationLink()" target="external" tag="span">
<i class='fas fa-heart' v-tooltip.left="'Donate to the mod author'"></i>
</Link>
</span>
<span class='card-header-icon' v-if="isThunderstoreModInstalled(key) && !readOnly">
<i class='fas fa-check' v-tooltip.left="'Mod already installed'"></i>
</span>
</template>
</OnlineRowCard>
</div>
</template>

<script lang="ts">
import { Prop, Vue } from 'vue-property-decorator';
import Component from 'vue-class-component';
import ThunderstoreMod from '../../model/ThunderstoreMod';
import ManagerSettings from '../../r2mm/manager/ManagerSettings';
import { ExpandableCard, Link } from '../all';
import DownloadModModal from './DownloadModModal.vue';
import ManifestV2 from '../../model/ManifestV2';
import DonateButton from '../../components/buttons/DonateButton.vue';
import CdnProvider from '../../providers/generic/connection/CdnProvider';
import { valueToReadableDate } from '../../utils/DateUtils';
import OnlineRowCard from 'components/OnlineRowCard.vue';
@Component({
components: {
OnlineRowCard,
DonateButton,
DownloadModModal,
ExpandableCard,
Link
}
})
export default class OnlineModListWithPanel extends Vue {
@Prop()
pagedModList!: ThunderstoreMod[];
@Prop()
selectedMod: ThunderstoreMod | null = null;
@Prop({default: false})
readOnly!: boolean;
private funkyMode: boolean = false;
get settings(): ManagerSettings {
return this.$store.getters["settings"];
};
get localModList(): ManifestV2[] {
return this.$store.state.profile.modList;
}
get deprecationMap(): Map<string, boolean> {
return this.$store.state.tsMods.deprecated;
}
isModDeprecated(mod: ThunderstoreMod) {
return this.deprecationMap.get(mod.getFullName()) || false;
}
isThunderstoreModInstalled(mod: ThunderstoreMod) {
return this.localModList.find((local: ManifestV2) => local.getName() === mod.getFullName()) != undefined;
}
getImageUrl(mod: ThunderstoreMod): string {
return CdnProvider.replaceCdnHost(mod.getIcon());
}
emitCardClick(mod: ThunderstoreMod) {
this.$emit("selected-mod", mod);
}
async created() {
this.funkyMode = this.settings.getContext().global.funkyModeEnabled;
}
}
</script>
Loading

0 comments on commit 532162c

Please sign in to comment.