skillbox/client/src/pages/instrument.vue

215 lines
4.6 KiB
Vue

<template>
<div
class="instrument"
ref="instrumentDiv"
>
<h1
class="instrument__title"
data-cy="instrument-title"
>
{{ instrument.title }}
</h1>
<!-- eslint-disable vue/no-v-html -->
<div
class="instrument__intro intro"
data-cy="instrument-intro"
v-html="instrument.intro"
/>
<content-component
:component="component"
:root="instrument.slug"
:parent="instrument"
:bookmarks="instrument.bookmarks"
:notes="instrument.notes"
:highlights="instrument.highlights"
:edit-mode="false"
v-for="component in instrument.contents"
:key="component.id"
/>
</div>
</template>
<script setup lang="ts">
import { defineAsyncComponent, onUnmounted, ref } from 'vue';
import { useRoute } from 'vue-router';
import { graphql } from '@/__generated__';
import { useQuery } from '@vue/apollo-composable';
import { computed } from '@vue/reactivity';
import { AddHighlightArgument, InstrumentNode } from '@/__generated__/graphql';
import { createHighlightCurry, getSelectionHandler, SelectionHandlerOptions } from '@/helpers/highlight';
import { doUpdateHighlight } from '@/graphql/mutations';
import highlightSidebar from '@/helpers/highlight-sidebar';
const instrumentDiv = ref<HTMLElement | null>(null);
const ContentComponent = defineAsyncComponent(() => import('@/components/content-blocks/ContentComponent.vue'));
graphql(`
fragment InstrumentParts on InstrumentNode {
id
title
intro
slug
language
bookmarks {
uuid
note {
id
text
}
}
type {
id
name
category {
id
name
foreground
background
}
type
}
contents
highlights {
...HighlightParts
}
}
`);
const route = useRoute();
const { result, onResult } = useQuery(
graphql(`
query InstrumentQuery($slug: String!) {
instrument(slug: $slug) {
...InstrumentParts
}
}
`),
{
slug: route.params.slug as string,
}
);
const instrument = computed(() => (result.value?.instrument as InstrumentNode) || {});
const instrumentHighlightsFragment = graphql(`
fragment instrumentHighlightsFragment on InstrumentNode {
id
slug
__typename
highlights {
...HighlightParts
}
}
`);
let selectionHandler;
onResult(() => {
const element = instrumentDiv.value;
const createHighlight = createHighlightCurry({
fragment: instrumentHighlightsFragment,
fragmentName: 'instrumentHighlightsFragment',
cacheSignature: {
slug: instrument.value.slug, // this value is only known onResult
__typename: 'InstrumentNode',
},
isContentHighlight: true,
});
if (element !== null) {
const options: SelectionHandlerOptions = {
el: element,
page: instrument.value,
onChangeColor: (newHighlight: AddHighlightArgument) => {
createHighlight(newHighlight);
},
onCreateNote: (newHighlight: AddHighlightArgument) => {
// todo: the same as the other one in ContentBlock.vue, possible to merge
// we also open the sidebar when clicking on the note icon
createHighlight(newHighlight).then((highlight) => {
highlightSidebar.open({
highlight,
onUpdateText: (text: string) => {
doUpdateHighlight({
input: {
note: text,
id: highlight.id,
},
});
},
});
});
},
};
selectionHandler = getSelectionHandler(options);
element.addEventListener('mouseup', selectionHandler);
}
});
onUnmounted(() => {
const element = instrumentDiv.value;
if (element !== null) {
element.removeEventListener('mouseup', selectionHandler);
}
});
</script>
<style scoped lang="scss">
@import 'styles/helpers';
.instrument {
padding-top: 2 * $large-spacing;
&__title {
font-size: toRem(35px);
margin-bottom: $large-spacing;
line-height: $default-heading-line-height;
overflow-wrap: break-word;
}
& :deep() {
& p {
margin-bottom: $large-spacing;
}
& p:last-child {
margin-bottom: 0;
}
& ul {
@include list-parent;
}
& p + ul {
margin-top: -30px;
}
& li {
@include list-child;
line-height: 1.5;
}
& b {
font-weight: 600;
& mark {
font-weight: 600;
}
}
.brand {
color: $color-brand;
font-weight: 600;
}
.secondary {
color: $secondary-color;
font-weight: 600;
}
}
}
</style>