Second commit
This commit is contained in:
41
frontend/app/components/viewer/TopBar.vue
Normal file
41
frontend/app/components/viewer/TopBar.vue
Normal file
@@ -0,0 +1,41 @@
|
||||
<script setup>
|
||||
import TopSearchBar from './topbar/TopSearchBar.vue';
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="top-bar">
|
||||
<div class="left">
|
||||
<span class="top-bar-title"></span>
|
||||
</div>
|
||||
<div class="center">
|
||||
<TopSearchBar></TopSearchBar>
|
||||
</div>
|
||||
<div class="right"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.top-bar {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
min-height: 40px;
|
||||
width: 100%;
|
||||
background-color: var(--top-bar-background-color);
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.left, .right {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.top-bar-title {
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
26
frontend/app/components/viewer/content/Content.vue
Normal file
26
frontend/app/components/viewer/content/Content.vue
Normal file
@@ -0,0 +1,26 @@
|
||||
<script setup>
|
||||
import NoteContainer from './NoteContainer.vue';
|
||||
|
||||
const emitter = useEmitter();
|
||||
|
||||
function hideSearch(){
|
||||
emitter.emit("hide-search-container");
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="content" v-on:click="hideSearch">
|
||||
<NoteContainer></NoteContainer>
|
||||
<!-- PowerMod -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.content {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
</style>
|
||||
171
frontend/app/components/viewer/content/Note.vue
Normal file
171
frontend/app/components/viewer/content/Note.vue
Normal file
@@ -0,0 +1,171 @@
|
||||
<script setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
// import { GetNote, GetContent } from '@/services/Content';
|
||||
const props = defineProps(['text', 'title', 'noteKey']);
|
||||
|
||||
const noteContent = ref(null);
|
||||
|
||||
const emitter = useEmitter();
|
||||
|
||||
function gotoNote(){
|
||||
// emitter.emit('goto-note', props.noteKey);
|
||||
}
|
||||
|
||||
function closeNote(){
|
||||
// emitter.emit('delete-note', props.noteKey);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
onMounted(() => {
|
||||
let content = GetContent();
|
||||
let elements = noteContent.value.getElementsByTagName('a');
|
||||
for(let i = 0, len = elements.length; i < len; i++) {
|
||||
let link = elements[i].pathname.split('/').slice(1).join('');
|
||||
link = decodeURIComponent(link);
|
||||
if(content[link] !== undefined){
|
||||
elements[i].onclick = function (event) {
|
||||
event.preventDefault();
|
||||
|
||||
GetNote(link, (result) => {
|
||||
emitter.emit("push-note", {key: link, text: "<h1>" + result.title + "</h1>" + result.html, title: result.title});
|
||||
});
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
elements[i].classList.add("error-link");
|
||||
elements[i].onclick = function (event) {
|
||||
event.preventDefault();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setTimeout(() => setupCallout(), 0);
|
||||
});
|
||||
|
||||
function closeNote(){
|
||||
emitter.emit('delete-note', props.noteKey);
|
||||
}
|
||||
|
||||
function gotoNote(){
|
||||
// emitter.emit('goto-note', props.noteKey);
|
||||
}
|
||||
|
||||
function toggleCallout() {
|
||||
const outerBlock = this.parentElement;
|
||||
outerBlock.classList.toggle("is-collapsed")
|
||||
const collapsed = outerBlock.classList.contains("is-collapsed")
|
||||
const height = collapsed ? this.scrollHeight : outerBlock.scrollHeight
|
||||
outerBlock.style.maxHeight = height + "px"
|
||||
|
||||
// walk and adjust height of all parents
|
||||
let current = outerBlock
|
||||
let parent = outerBlock.parentElement
|
||||
while (parent) {
|
||||
if (!parent.classList.contains("callout")) {
|
||||
return
|
||||
}
|
||||
|
||||
const collapsed = parent.classList.contains("is-collapsed")
|
||||
const height = collapsed ? parent.scrollHeight : parent.scrollHeight + current.scrollHeight
|
||||
parent.style.maxHeight = height + "px"
|
||||
|
||||
current = parent
|
||||
parent = parent.parentElement
|
||||
}
|
||||
}
|
||||
|
||||
function setupCallout() {
|
||||
const collapsible = noteContent.value.getElementsByClassName(
|
||||
`callout is-collapsible`,
|
||||
);
|
||||
for (const div of collapsible) {
|
||||
const title = div.firstElementChild;
|
||||
|
||||
if (title) {
|
||||
title.addEventListener("click", toggleCallout)
|
||||
|
||||
const collapsed = div.classList.contains("is-collapsed")
|
||||
const height = collapsed ? title.scrollHeight : div.scrollHeight
|
||||
div.style.maxHeight = height + "px"
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="note">
|
||||
<div class="note-stunt" v-on:click="gotoNote">
|
||||
<div class="close-button" v-on:click="closeNote">
|
||||
<img class="icon" src="/icons/Pixelarticons/white/close.svg" alt="My Happy SVG"/>
|
||||
</div>
|
||||
<span>{{ title }}</span>
|
||||
</div>
|
||||
<div class="note-content-container">
|
||||
<div class="note-content" ref="noteContent" v-html="text"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.note-stunt {
|
||||
writing-mode: vertical-lr;
|
||||
position: sticky;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
bottom: 0px;
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.close-button {
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
margin-bottom: 5px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.note {
|
||||
min-width: 700px;
|
||||
max-width: 700px;
|
||||
overflow-y: auto;
|
||||
|
||||
border-color: var(--note-border-color);
|
||||
border-width: 0px;
|
||||
border-right-width: 1px;
|
||||
border-style: solid;
|
||||
display: flex;
|
||||
|
||||
background-color: var(--background-color);
|
||||
position: sticky;
|
||||
top: 0px;
|
||||
}
|
||||
|
||||
/* Contingut de cada nota */
|
||||
.note-content {
|
||||
padding-bottom: 60px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.note-content-container {
|
||||
margin: 20px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
.note-content > h1 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.note-content .katex-display {
|
||||
max-width: 600px;
|
||||
}
|
||||
</style>
|
||||
68
frontend/app/components/viewer/content/NoteContainer.vue
Normal file
68
frontend/app/components/viewer/content/NoteContainer.vue
Normal file
@@ -0,0 +1,68 @@
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import Note from './Note.vue';
|
||||
|
||||
const emitter = useEmitter();
|
||||
|
||||
let noteData = ref([]);
|
||||
|
||||
const noteContainer = ref(null);
|
||||
|
||||
function calculateContainerWidth(){
|
||||
let dom = noteContainer.value;
|
||||
dom.style.width = noteData.value.length * 701 + "px";
|
||||
|
||||
setTimeout(() => {
|
||||
for(let i = 0; i < dom.children.length; i++){
|
||||
let child = dom.children[i];
|
||||
child.style.left = (40 * i) + "px";
|
||||
child.style.right = (40 * (dom.children.length - i) - 700) + "px";
|
||||
child.style.zIndex = i + 1;
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
|
||||
function pushNote(note){
|
||||
noteData.value.push(note);
|
||||
calculateContainerWidth();
|
||||
}
|
||||
|
||||
emitter.on("push-note", (note) => {
|
||||
pushNote(note);
|
||||
})
|
||||
|
||||
emitter.on("delete-note", (key) => {
|
||||
noteData.value = noteData.value.filter((note) => {
|
||||
return note.key !== key;
|
||||
});
|
||||
calculateContainerWidth();
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="note-scrolling-container" id="note-scrolling-container">
|
||||
<div class="note-container" ref="noteContainer" >
|
||||
<Note v-for="element in noteData" :key="element.key" :text="element.text" :title="element.title" :noteKey="element.key"></Note>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.note-scrolling-container {
|
||||
overflow-x: auto;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow-y: none;
|
||||
}
|
||||
|
||||
.note-container {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
38
frontend/app/components/viewer/statusbar/FetchStatus.vue
Normal file
38
frontend/app/components/viewer/statusbar/FetchStatus.vue
Normal file
@@ -0,0 +1,38 @@
|
||||
<script setup>
|
||||
import { ref,onMounted } from 'vue';
|
||||
const emitter = useEmitter();
|
||||
|
||||
const statusIcon = ref(null);
|
||||
const statusMessage = ref(null);
|
||||
|
||||
emitter.on("status-bar-update", (state) => {
|
||||
var preloadImage = new Image();
|
||||
preloadImage.src = '/icons/Pixelarticons/black/' + state.icon + ".svg";
|
||||
preloadImage.onload = function() {
|
||||
statusIcon.value.src = this.src;
|
||||
};
|
||||
statusMessage.value.innerText = state.text;
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
emitter.emit("status-bar-update", {text: "Hola", icon: "check"})
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<span class="status-text">
|
||||
<img class="icon" ref="statusIcon" src=""/>
|
||||
<span class="status-message" ref="statusMessage"></span>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.status-text {
|
||||
margin-left: 10px;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.status-message {
|
||||
margin-left: 5px;
|
||||
}
|
||||
</style>
|
||||
46
frontend/app/components/viewer/statusbar/StatusBar.vue
Normal file
46
frontend/app/components/viewer/statusbar/StatusBar.vue
Normal file
@@ -0,0 +1,46 @@
|
||||
<script setup>
|
||||
import FetchStatus from './FetchStatus.vue';
|
||||
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<div class="status-bar">
|
||||
<div class="left">
|
||||
<FetchStatus></FetchStatus>
|
||||
</div>
|
||||
<div class="center">
|
||||
</div>
|
||||
<div class="right">
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.status-bar {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
min-height: 24px;
|
||||
max-height: 24px;
|
||||
width: 100%;
|
||||
background-color: var(--top-bar-background-color);
|
||||
display: flex;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.left, .right {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.top-bar-title {
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
110
frontend/app/components/viewer/topbar/TopSearchBar.vue
Normal file
110
frontend/app/components/viewer/topbar/TopSearchBar.vue
Normal file
@@ -0,0 +1,110 @@
|
||||
<script setup>
|
||||
import SearchIcon from 'pixelarticons/svg/search.svg'
|
||||
/*
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { GetArrayContent, GetContent } from '@/services/Content';
|
||||
import useEmitter from '@/services/Emitter';
|
||||
|
||||
const emitter = useEmitter();
|
||||
|
||||
import NoteSearchElement from "@/components/topbar/NoteSearchElement.vue";
|
||||
|
||||
let noteLinks = ref([]);
|
||||
let searchContainer = ref(null);
|
||||
|
||||
emitter.on("hide-search-container", () => {
|
||||
searchContainer.value.style.display = "none";
|
||||
})
|
||||
|
||||
function updateList(event){
|
||||
let content = GetArrayContent();
|
||||
let query = "";
|
||||
if(event !== undefined) query = event.target.value;
|
||||
let filter = query.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toUpperCase().trim();
|
||||
|
||||
noteLinks.value = content.filter((noteInfo) => {
|
||||
return noteInfo["title"].normalize("NFD").replace(/[\u0300-\u036f]/g, "").toUpperCase().indexOf(filter) > -1;
|
||||
});
|
||||
}
|
||||
|
||||
function searchFocus(event){
|
||||
if(GetContent() === undefined) return;
|
||||
|
||||
searchContainer.value.style.display = "";
|
||||
updateList(undefined);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
});
|
||||
*/
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="top-search-container">
|
||||
<div class="top-search-box">
|
||||
<img class="icon search-icon" :src="SearchIcon" alt="My Happy SVG"/>
|
||||
<!-- <input type="text" v-on:input="updateList" v-on:focus="searchFocus" class="search-prompt" placeholder="Buscar...">-->
|
||||
<input type="text" class="search-prompt" placeholder="Buscar...">
|
||||
</div>
|
||||
|
||||
<!-- <div class="search-container" ref="searchContainer" v-on:focusout="searchFocusout" style="display: none;">-->
|
||||
<div class="search-container" style="display: none;">
|
||||
<div class="search-container-list">
|
||||
<!--
|
||||
<NoteSearchElement v-for="element in noteLinks" :key="element.key" :title="element.title" :link="element.key"></NoteSearchElement>
|
||||
-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.top-search-container {
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.top-search-box {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
background-color: var(--search-background);
|
||||
padding: 2px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.search-prompt {
|
||||
border: none;
|
||||
padding: 5px;
|
||||
width: 30vw;
|
||||
max-width: 400px;
|
||||
background: none;
|
||||
margin-left: -5px;
|
||||
}
|
||||
|
||||
.search-prompt:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.search-icon {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.search-container {
|
||||
position: absolute;
|
||||
background-color: var(--search-background-container);
|
||||
top: 45px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
max-width: 800px;
|
||||
max-height: 500px;
|
||||
overflow-y: scroll;
|
||||
left: 0;
|
||||
right: 0;
|
||||
backdrop-filter: blur(15px);
|
||||
border-radius: 10px;
|
||||
min-width: 400px;
|
||||
min-height: 500px;
|
||||
z-index: 99999;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user