This commit is contained in:
Elvanos 2021-03-17 22:26:08 +01:00
parent bf4b787a6d
commit b01246f78d
63 changed files with 3080 additions and 382 deletions

View file

@ -215,7 +215,10 @@ module.exports = configure(function (ctx) {
builder: {
// https://www.electron.build/configuration/configuration
appId: "fantasiaarchive"
appId: "fantasiaarchive",
win: {
icon: 'src-electron/icons/icon.ico'
}
},
// More info: https://quasar.dev/quasar-cli/developing-electron-apps/node-integration

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 116 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 44 KiB

View file

@ -7,9 +7,12 @@
<script lang="ts">
import BaseClass from "src/BaseClass"
import { Component } from "vue-property-decorator"
import { Component, Watch } from "vue-property-decorator"
import { defaultKeybinds } from "src/scripts/appSettings/defaultKeybinds"
import appWindowButtons from "src/components/appHeader/AppWindowButtons.vue"
import PouchDB from "pouchdb"
import { OptionsStateInteface } from "./store/module-options/state"
import { colors } from "quasar"
@Component({
components: {
@ -27,7 +30,7 @@ export default class App extends BaseClass {
}
}
this.registerDefaultKeybinds()
this.loadSettings().catch(e => console.log(e))
window.addEventListener("keydown", this.triggerKeyPush)
}
@ -44,7 +47,7 @@ export default class App extends BaseClass {
altKey: e.altKey,
ctrlKey: e.ctrlKey,
shiftKey: e.shiftKey,
keyCode: e.keyCode
which: e.which
}
this.SSET_updatePressedKey(ouputKeycombo)
@ -54,6 +57,7 @@ export default class App extends BaseClass {
destroyed () {
window.removeEventListener("auxclick", this.reactToMiddleClick)
this.deregisterCustomKeybinds()
this.deregisterDefaultKeybinds()
window.removeEventListener("keydown", this.triggerKeyPush)
}
@ -71,5 +75,42 @@ export default class App extends BaseClass {
deregisterDefaultKeybinds () {
defaultKeybinds.forEach(e => this.SSET_deregisterDefaultKeybind(e))
}
registerCustomKeybinds () {
setTimeout(() => {
this.SGET_options.userKeybindList.forEach(e => this.SSET_registerUserKeybind(e))
}, 1000)
}
deregisterCustomKeybinds () {
defaultKeybinds.forEach(e => this.SSET_deregisterUserKeybind(e))
}
async loadSettings () {
const SettingsDB = new PouchDB("fa-settings")
const settingsData = await SettingsDB.allDocs({ include_docs: true })
const settings = settingsData?.rows[0]?.doc as unknown as OptionsStateInteface
if (settings) {
this.SSET_options(settings)
}
this.registerDefaultKeybinds()
this.registerCustomKeybinds()
}
@Watch("SGET_options", { deep: true })
onSettingsChange () {
const options = this.SGET_options
this.$q.dark.set(options.darkMode)
if (options.darkMode) {
colors.setBrand("dark", "#1b333e")
colors.setBrand("primary", "#ffd673")
}
else {
colors.setBrand("dark", "#18303a")
colors.setBrand("primary", "#d7ac47")
}
}
}
</script>

View file

@ -1,3 +1,4 @@
import { OptionsStateInteface } from "./store/module-options/state"
import { KeyManagementInterface } from "./store/module-keybinds/state"
import { I_OpenedDocument, I_ShortenedDocument } from "./interfaces/I_OpenedDocument"
import { Component, Vue } from "vue-property-decorator"
@ -7,10 +8,13 @@ import { I_NewObjectTrigger } from "src/interfaces/I_NewObjectTrigger"
import { uid, colors } from "quasar"
import { I_FieldRelationship } from "src/interfaces/I_FieldRelationship"
import { I_KeyPressObject } from "src/interfaces/I_KeypressObject"
import PouchDB from "pouchdb"
const Blueprints = namespace("blueprintsModule")
const OpenedDocuments = namespace("openedDocumentsModule")
const Keybinds = namespace("keybindsModule")
const Options = namespace("optionsModule")
const Dialogs = namespace("dialogsModule")
@Component
export default class BaseClass extends Vue {
@ -37,6 +41,10 @@ export default class BaseClass extends Vue {
retrieveKeybindString (keybind: I_KeyPressObject): string {
let keybindString = ""
if (!keybind) {
return keybindString
}
if (keybind.ctrlKey) {
keybindString += "CTRL + "
}
@ -49,33 +57,102 @@ export default class BaseClass extends Vue {
keybindString += "SHIFT + "
}
const keybinds = [37, 38, 39, 40, 9, 32, 13]
const keybinds = [37, 38, 39, 40, 9, 32, 13, 192, 189, 187, 219, 221, 220, 186, 222, 188, 190, 191, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123]
if (keybinds.includes(keybind.keyCode)) {
if (keybind.keyCode === 13) {
if (keybinds.includes(keybind.which)) {
if (keybind.which === 13) {
keybindString += "ENTER"
}
if (keybind.keyCode === 9) {
if (keybind.which === 9) {
keybindString += "TAB"
}
if (keybind.keyCode === 32) {
if (keybind.which === 32) {
keybindString += "SPACE"
}
if (keybind.keyCode === 37) {
if (keybind.which === 37) {
keybindString += "LEFT ARROW"
}
if (keybind.keyCode === 38) {
if (keybind.which === 38) {
keybindString += "UP ARROW"
}
if (keybind.keyCode === 39) {
if (keybind.which === 39) {
keybindString += "RIGHT ARROW"
}
if (keybind.keyCode === 40) {
if (keybind.which === 40) {
keybindString += "DOWN ARROW"
}
if (keybind.which === 192) {
keybindString += "`"
}
if (keybind.which === 189) {
keybindString += "-"
}
if (keybind.which === 187) {
keybindString += "+"
}
if (keybind.which === 219) {
keybindString += "["
}
if (keybind.which === 221) {
keybindString += "]"
}
if (keybind.which === 220) {
keybindString += "\\"
}
if (keybind.which === 186) {
keybindString += ";"
}
if (keybind.which === 222) {
keybindString += "'"
}
if (keybind.which === 188) {
keybindString += ","
}
if (keybind.which === 190) {
keybindString += "."
}
if (keybind.which === 191) {
keybindString += "/"
}
if (keybind.which === 112) {
keybindString += "F1"
}
if (keybind.which === 113) {
keybindString += "F2"
}
if (keybind.which === 114) {
keybindString += "F3"
}
if (keybind.which === 115) {
keybindString += "F4"
}
if (keybind.which === 116) {
keybindString += "F5"
}
if (keybind.which === 117) {
keybindString += "F6"
}
if (keybind.which === 118) {
keybindString += "F7"
}
if (keybind.which === 119) {
keybindString += "F8"
}
if (keybind.which === 120) {
keybindString += "F9"
}
if (keybind.which === 121) {
keybindString += "F10"
}
if (keybind.which === 122) {
keybindString += "F11"
}
if (keybind.which === 123) {
keybindString += "F12"
}
}
else {
keybindString += String.fromCharCode(keybind.keyCode)
keybindString += String.fromCharCode(keybind.which)
}
if (keybind.note) {
@ -107,7 +184,7 @@ export default class BaseClass extends Vue {
currentKeyPress.altKey === fieldToCheck.altKey &&
currentKeyPress.ctrlKey === fieldToCheck.ctrlKey &&
currentKeyPress.shiftKey === fieldToCheck.shiftKey &&
currentKeyPress.keyCode === fieldToCheck.keyCode
currentKeyPress.which === fieldToCheck.which
) {
return true
}
@ -159,6 +236,14 @@ export default class BaseClass extends Vue {
})
}
/****************************************************************/
// Option management
/****************************************************************/
@Options.Getter("getOptions") SGET_options!: OptionsStateInteface
@Options.Action("setOptions") SSET_options!: (input: OptionsStateInteface) => void
/****************************************************************/
// Open documents management
/****************************************************************/
@ -237,10 +322,16 @@ export default class BaseClass extends Vue {
return fieldValue.length
}
@Dialogs.Getter("getDialogsState") SGET_getDialogsState!: boolean
/**
* Refreshes the route
*/
refreshRoute () {
if (this.SGET_options.disableCloseAftertSelectQuickSearch && this.SGET_getDialogsState) {
return
}
const remainingDocuments = this.SGET_allOpenedDocuments.docs
const lastIndex = this.SGET_allOpenedDocuments.lastRemovedIndex
@ -314,6 +405,40 @@ export default class BaseClass extends Vue {
return hierarchicalString
}
async retrieveAllDocuments () {
let allDocs = [] as I_ShortenedDocument[]
for (const blueprint of this.SGET_allBlueprints) {
const CurrentObjectDB = new PouchDB(blueprint._id)
const dbRows = await CurrentObjectDB.allDocs({ include_docs: true })
const dbDocuments = dbRows.rows.map(d => d.doc)
const formattedDocuments: I_ShortenedDocument[] = []
for (const singleDocument of dbDocuments) {
const doc = singleDocument as unknown as I_ShortenedDocument
const pushValue = {
label: doc.extraFields.find(e => e.id === "name")?.value,
icon: doc.icon,
id: doc._id,
url: doc.url,
type: doc.type,
extraFields: doc.extraFields,
// @ts-ignore
hierarchicalPath: this.getDocumentHieararchicalPath(doc, dbDocuments),
tags: doc.extraFields.find(e => e.id === "tags")?.value,
color: doc.extraFields.find(e => e.id === "documentColor")?.value,
isCategory: doc.extraFields.find(e => e.id === "categorySwitch")?.value
} as unknown as I_ShortenedDocument
formattedDocuments.push(pushValue)
}
const sortedDocuments = formattedDocuments.sort((a, b) => a.label.localeCompare(b.label))
// @ts-ignore
allDocs = [...allDocs, ...sortedDocuments]
}
return allDocs
}
retrieveIconColor (document: I_ShortenedDocument): string {
// @ts-ignore
return (document.activeTypeSearch) ? colors.getBrand("primary") : document.color

BIN
src/assets/appLogo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

View file

@ -32,7 +32,7 @@
@trigger-dialog-close="keybindsDialogClose"
/>
<q-page-sticky position="top-right" class="documentControl">
<q-page-sticky position="top-right" class="documentControl bg-dark" v-if="!disableDocumentControlBar">
<div class="documentControl__blocker"></div>
@ -40,37 +40,39 @@
<div class="documentControl__left">
<q-btn
icon="mdi-keyboard-settings"
color="primary"
outline
@click="keybindsDialogAssignUID"
>
<q-tooltip
:delay="500"
anchor="bottom middle"
self="top middle"
<template v-if="!disableDocumentControlBarGuides">
<q-btn
icon="mdi-keyboard-settings"
color="primary"
outline
@click="keybindsDialogAssignUID"
>
Open keybinds cheatsheet
</q-tooltip>
</q-btn>
<q-tooltip
:delay="500"
anchor="bottom middle"
self="top middle"
>
Open keybinds cheatsheet
</q-tooltip>
</q-btn>
<q-btn
icon="mdi-file-question"
color="primary"
outline
@click="advancedSearchGuideAssignUID"
>
<q-tooltip
:delay="500"
anchor="bottom middle"
self="top middle"
<q-btn
icon="mdi-file-question"
color="primary"
outline
@click="advancedSearchGuideAssignUID"
>
Open advanced search guide
</q-tooltip>
</q-btn>
<q-tooltip
:delay="500"
anchor="bottom middle"
self="top middle"
>
Open advanced search guide
</q-tooltip>
</q-btn>
<q-separator vertical inset color="accent" />
<q-separator vertical inset color="accent" />
</template>
<q-btn
icon="mdi-package-variant-closed"
@ -232,6 +234,16 @@ export default class DocumentControl extends BaseClass {
projectExists: undefined | string | boolean = false
projectName = ""
disableDocumentControlBar = false
disableDocumentControlBarGuides = false
@Watch("SGET_options", { immediate: true, deep: true })
onSettingsChange () {
const options = this.SGET_options
this.disableDocumentControlBar = options.disableDocumentControlBar
this.disableDocumentControlBarGuides = options.disableDocumentControlBarGuides
}
async created () {
this.projectName = await retrieveCurrentProjectName()
this.projectExists = !!(await retrieveCurrentProjectName())
@ -245,31 +257,37 @@ export default class DocumentControl extends BaseClass {
* Local keybinds
*/
@Watch("SGET_getCurrentKeyBindData", { deep: true })
processKeyPush () {
async processKeyPush () {
// Quick new document
if (this.determineKeyBind("quickNewDocument")) {
if (this.determineKeyBind("quickNewDocument") && !this.SGET_getDialogsState) {
this.newObjectAssignUID()
}
// Quick open existing document
if (this.determineKeyBind("quickExistingDocument")) {
if (this.determineKeyBind("quickExistingDocument") && !this.SGET_getDialogsState) {
this.existingObjectAssignUID()
}
// Delete dialog - CTRL + D
if (this.determineKeyBind("deleteDocument") && !this.currentlyNew && this.SGET_allOpenedDocuments.docs.length > 0) {
if (this.determineKeyBind("deleteDocument") && !this.currentlyNew && this.SGET_allOpenedDocuments.docs.length > 0 && !this.SGET_getDialogsState) {
this.deleteObjectAssignUID()
}
// Edit document - CTRL + E
if (this.determineKeyBind("editDocument") && this.currentyEditable && this.SGET_allOpenedDocuments.docs.length > 0) {
if (this.determineKeyBind("editDocument") && this.currentyEditable && this.SGET_allOpenedDocuments.docs.length > 0 && !this.SGET_getDialogsState) {
this.toggleEditMode()
}
// Save document - CTRL + S
if (this.determineKeyBind("saveDocument") && !this.currentyEditable && this.SGET_allOpenedDocuments.docs.length > 0) {
if (this.determineKeyBind("saveDocument") && !this.currentyEditable && this.SGET_allOpenedDocuments.docs.length > 0 && !this.SGET_getDialogsState) {
this.saveCurrentDocument().catch(e => console.log(e))
}
// Add new under parent - CTRL + SHIFT + N
if (this.determineKeyBind("addUnderParent") && !this.currentlyNew && this.SGET_allOpenedDocuments.docs.length > 0 && !this.SGET_getDialogsState) {
await this.sleep(100)
this.addNewUnderParent()
}
}
/****************************************************************/
@ -472,8 +490,7 @@ export default class DocumentControl extends BaseClass {
<style lang="scss">
.documentControl {
z-index: 999;
background-color: $dark;
width: calc(100vw - 375px);
width: calc(100vw - 380px);
margin-top: 2.5px;
&__blocker {
@ -487,10 +504,21 @@ export default class DocumentControl extends BaseClass {
}
&__wrapper {
width: calc(100vw - 375px);
width: calc(100vw - 385px);
padding: 8.5px 15px;
display: flex;
justify-content: space-between;
position: relative;
&::after {
content: " ";
bottom: 1px;
right: -5px;
left: -5px;
position: absolute;
height: 1px;
background-color: rgba($accent, 0.2);
}
}
&__left,

View file

@ -1,19 +1,28 @@
<template>
<span>
<q-page-sticky position="top-left" class="treeSearchWrapper">
<div
class="treeSearchWrapper"
:class="{'fullWidth': disableDocumentControlBar}"
>
<q-input
ref="treeFilter"
filled
dark
debounce="200"
v-model="treeFilter"
label="Filter document tree..."
>
<template v-slot:append>
<q-icon name="mdi-text-search" />
</template>
<template v-slot:prepend>
<q-icon v-if="treeFilter !== ''" name="clear" class="cursor-pointer text-secondary" @click="resetTreeFilter" />
</template>
</q-input>
</div>
<q-input ref="treeFilter" dark filled v-model="treeFilter" label="Filter document tree...">
<template v-slot:append>
<q-icon name="mdi-text-search" />
</template>
<template v-slot:prepend>
<q-icon v-if="treeFilter !== ''" name="clear" class="cursor-pointer text-secondary" @click="resetTreeFilter" />
</template>
</q-input>
</q-page-sticky>
<h6 class="projectTitle text-cultured">
<h6 class="projectTitle text-cultured" v-if="!noProjectName">
<span>
{{projectName}}
<q-tooltip
@ -26,6 +35,7 @@
<q-tree
class="objectTree q-pa-sm"
:class="{'hasTextShadow': textShadow}"
:nodes="hierarchicalTree"
node-key="key"
no-connectors
@ -54,7 +64,7 @@
{{ prop.node.label }}
<span
class="text-primary text-weight-medium q-ml-xs"
v-if="prop.node.isRoot">
v-if="prop.node.isRoot || prop.node.isTag">
({{prop.node.allCount}})
<q-tooltip
:delay="1000"
@ -100,7 +110,7 @@
</q-btn>
<q-btn
tabindex="-1"
v-if="(!prop.node.specialLabel && !prop.node.isRoot) || (prop.node.isRoot && !prop.node.isTag)"
v-if="(!prop.node.specialLabel && !prop.node.isRoot) && !prop.node.isTag"
round
flat
dense
@ -173,13 +183,13 @@ export default class ObjectTree extends BaseClass {
@Watch("SGET_getCurrentKeyBindData", { deep: true })
processKeyPush () {
// Focus left tree search
if (this.determineKeyBind("focusHierarchicalTree")) {
if (this.determineKeyBind("focusHierarchicalTree") && !this.SGET_getDialogsState) {
const treeFilterDOM = this.$refs.treeFilter as unknown as HTMLInputElement
treeFilterDOM.focus()
}
// Clear input in the left tree search
if (this.determineKeyBind("clearInputHierarchicalTree")) {
if (this.determineKeyBind("clearInputHierarchicalTree") && !this.SGET_getDialogsState) {
this.resetTreeFilter()
}
}
@ -201,6 +211,28 @@ export default class ObjectTree extends BaseClass {
// Unfuck the rendering by giving the app some time to load first
await this.$nextTick()
}
tagsAtTop = false
compactTags = false
noTags = false
noProjectName = false
invertTreeSorting = false
doNotcollaseTreeOptions = false
disableDocumentControlBar = false
textShadow = false
@Watch("SGET_options", { immediate: true, deep: true })
onSettingsChange () {
const options = this.SGET_options
this.tagsAtTop = options.tagsAtTop
this.compactTags = options.compactTags
this.noTags = options.noTags
this.noProjectName = options.noProjectName
this.invertTreeSorting = options.invertTreeSorting
this.doNotcollaseTreeOptions = options.doNotcollaseTreeOptions
this.disableDocumentControlBar = options.disableDocumentControlBar
this.textShadow = options.textShadow
this.buildCurrentObjectTree().catch((e) => {
console.log(e)
@ -289,7 +321,7 @@ export default class ObjectTree extends BaseClass {
/**
* Holds all currently expanded notes
*/
expandedTreeNodes = []
expandedTreeNodes: string[] = []
/**
* Filter model for the tree
@ -323,8 +355,17 @@ export default class ObjectTree extends BaseClass {
// Sort by custom order
.sort((a, b) => {
const order1 = a.extraFields.find(e => e.id === "order")?.value
const order2 = b.extraFields.find(e => e.id === "order")?.value
let order1 = 0
let order2 = 0
if (!this.invertTreeSorting) {
order1 = a.extraFields.find(e => e.id === "order")?.value
order2 = b.extraFields.find(e => e.id === "order")?.value
}
else {
order2 = a.extraFields.find(e => e.id === "order")?.value
order1 = b.extraFields.find(e => e.id === "order")?.value
}
if (order1 > order2) {
return 1
@ -393,7 +434,7 @@ export default class ObjectTree extends BaseClass {
*/
async buildCurrentObjectTree () {
const allBlueprings = this.SGET_allBlueprints
const treeObject: any[] = []
let treeObject: any[] = []
let allTreeDocuments: I_ShortenedDocument[] = []
// Process all documents, build hieararchy out of the and sort them via name and custom order
@ -481,41 +522,78 @@ export default class ObjectTree extends BaseClass {
return 0
})
const tagList = await tagListBuildFromBlueprints(this.SGET_allBlueprints)
if (!this.noTags) {
const tagList = await tagListBuildFromBlueprints(this.SGET_allBlueprints)
tagList.forEach((tag: string) => {
const tagDocs = allTreeDocuments
.filter(doc => {
const docTags = doc.extraFields.find(e => e.id === "tags")?.value as unknown as string[]
return (docTags && docTags.includes(tag))
})
.map((doc:I_ShortenedDocument) => {
let allTags = 0
let allTagsCategories = 0
let allTagsDocuments = 0
let tagNodeList = tagList.map((tag: string) => {
const tagDocs = allTreeDocuments
.filter(doc => {
const docTags = doc.extraFields.find(e => e.id === "tags")?.value as unknown as string[]
return (docTags && docTags.includes(tag))
})
.map((doc:I_ShortenedDocument) => {
// @ts-ignore
doc.key = `${tag}${doc._id}`
// @ts-ignore
doc.isTag = true
return doc
})
.sort((a, b) => a.label.localeCompare(b.label))
doc.key = `${tag}${doc._id}`
// @ts-ignore
// doc.isTag = true
return doc
})
.sort((a, b) => a.label.localeCompare(b.label))
const documentCount = tagDocs.filter(e => !e.isCategory).length
const categoryCount = tagDocs.filter(e => e.isCategory).length
const allCount = tagDocs.length
const documentCount = tagDocs.filter(e => !e.isCategory).length
const categoryCount = tagDocs.filter(e => e.isCategory).length
const allCount = tagDocs.length
const tagObject = {
label: `${tag}`,
icon: "mdi-tag",
_id: `tag-${tag}`,
key: `tag-${tag}`,
allCount: allCount,
documentCount: documentCount,
categoryCount: categoryCount,
isRoot: true,
isTag: true,
children: tagDocs
allTags += allCount
allTagsCategories += categoryCount
allTagsDocuments += documentCount
return {
label: `${tag}`,
icon: "mdi-tag",
_id: `tag-${tag}`,
key: `tag-${tag}`,
allCount: allCount,
documentCount: documentCount,
categoryCount: categoryCount,
isRoot: true,
isTag: true,
children: tagDocs
}
})
if (this.compactTags && tagNodeList.length > 0) {
tagNodeList = [
{
label: "Tags",
icon: "mdi-tag",
_id: "tagsList",
key: "tagList",
isRoot: true,
allCount: allTags,
documentCount: allTagsDocuments,
categoryCount: allTagsCategories,
isTag: true,
// @ts-ignore
children: tagNodeList.map(e => {
e.isRoot = false
return e
})
}
]
}
treeObject.push(tagObject)
})
if (this.tagsAtTop) {
treeObject = [...tagNodeList, ...treeObject]
}
else {
treeObject = [...treeObject, ...tagNodeList]
}
}
// Assign the finished object to the render model
this.hierarchicalTree = treeObject
@ -548,8 +626,10 @@ export default class ObjectTree extends BaseClass {
buildTreeExpands (newDocs: I_OpenedDocument[]) {
const expandIDs: string[] = []
const newDocsSnapshot: I_OpenedDocument[] = extend(true, [], newDocs)
// Check for parent changes
newDocs.forEach(s => {
newDocsSnapshot.forEach((s, index) => {
const oldParentDoc = this.lastDocsSnapShot.find(doc => doc._id === s._id)
// Fizzle if the parent doesn't exist in the old version
if (!oldParentDoc) {
@ -570,13 +650,19 @@ export default class ObjectTree extends BaseClass {
})
// Process top level documents
newDocs.forEach(s => {
newDocsSnapshot.forEach(s => {
const newParentDocField = this.retrieveFieldValue(s, "parentDoc")
const oldParentDoc = this.lastDocsSnapShot.find(doc => doc._id === s._id)
// @ts-ignore
const oldParentDocField = this.retrieveFieldValue(oldParentDoc, "parentDoc")
// @ts-ignore
const oldParentDocID = (oldParentDocField?.value) ? oldParentDocField.value.value : false
// @ts-ignore
const newParentDocID = (newParentDocField?.value) ? newParentDocField.value.value : false
if (!newParentDocID) {
if (!newParentDocID && oldParentDocID !== newParentDocID) {
expandIDs.push(s.type)
}
})
@ -657,19 +743,38 @@ export default class ObjectTree extends BaseClass {
}
}
expandeCollapseNode (node: {key: string}) {
expandeCollapseNode (node: {key: string, children: []}) {
const treeDOM = this.$refs.tree as unknown as {
setExpanded: (key:string, state: boolean)=> void,
isExpanded: (key:string)=> boolean
}
const isExpanded = treeDOM.isExpanded(node.key)
treeDOM.setExpanded(node.key, !isExpanded)
if (isExpanded) {
this.collapseAllNodes(node)
}
else {
treeDOM.setExpanded(node.key, true)
}
}
determineNodeColor (node: {color: string, isTag: boolean, isRoot: boolean}) {
// @ts-ignore
return (node?.isTag && node?.isRoot) ? colors.getBrand("primary") : node.color
return (node?.isTag) ? colors.getBrand("primary") : node.color
}
collapseAllNodes (node: {key: string, children: []}) {
if (node.children && !this.doNotcollaseTreeOptions) {
for (const child of node.children) {
if (this.expandedTreeNodes.includes(node.key)) {
this.collapseAllNodes(child)
}
}
}
if (this.expandedTreeNodes.includes(node.key)) {
this.expandedTreeNodes = this.expandedTreeNodes.filter(n => n !== node.key)
}
}
}
</script>
@ -678,10 +783,29 @@ export default class ObjectTree extends BaseClass {
.projectTitle {
margin: 0 0 -5px 0;
padding: 65px 10px 0;
padding: 10px 10px 0;
}
.objectTree {
&.hasTextShadow {
.documentLabel {
font-weight: 500;
$shadowColorOutline: #000;
$shadowColorSurround: #000;
filter: drop-shadow(0 0 4px #000);
text-shadow:
-2px -2px 0 $shadowColorSurround,
2px -2px 0 $shadowColorSurround,
-2px 2px 0 $shadowColorSurround,
2px 2px 0 $shadowColorSurround,
-1px -1px 0 $shadowColorOutline,
1px -1px 0 $shadowColorOutline,
-1px 1px 0 $shadowColorOutline,
1px 1px 0 $shadowColorOutline;
}
}
> .q-tree__node {
padding-left: 0 !important;
}
@ -758,12 +882,17 @@ export default class ObjectTree extends BaseClass {
}
.treeSearchWrapper {
top: -40px;
left: -375px;
top: -55px;
left: 0;
position: fixed;
width: 375px;
z-index: 555;
background-color: $dark;
&.fullWidth {
width: 100%;
}
> div {
width: 100%;
}
@ -793,4 +922,16 @@ export default class ObjectTree extends BaseClass {
}
}
}
body.body--dark {
.objectTree {
.documentLabel {
color: #dcdcdc;
}
}
.projectTitle {
color: #dcdcdc;
}
}
</style>

View file

@ -3,6 +3,19 @@
<div
:class="{'AppControl': isProject}"
>
<!-- New document dialog -->
<newDocumentDialog
:dialog-trigger="newObjectDialogTrigger"
@trigger-dialog-close="newObjectDialogClose"
/>
<!-- Existing document dialog -->
<existingDocumentDialog
:dialog-trigger="existingObjectDialogTrigger"
@trigger-dialog-close="existingObjectDialogClose"
/>
<!-- Project close dialog -->
<projectCloseCheckDialog
:dialog-trigger="projectCloseCheckDialogTrigger"
@ -40,6 +53,12 @@
@trigger-dialog-close="changeLogDialogClose"
/>
<!-- Program settings dialog -->
<programSettingsDialog
:dialog-trigger="programSettingsDialogTrigger"
@trigger-dialog-close="programSettingsDialogClose"
/>
<!-- Advanced search guide dialog -->
<advancedSearchGuideDialog
:dialog-trigger="advancedSearchGuideDialogTrigger"
@ -54,15 +73,22 @@
<!-- Options button -->
<q-btn
flat
v-if="true === false"
:ripple="false"
dark
size='md'
no-caps
@click="programSettingsDialogAssignUID"
>
Options
</q-btn>
<q-img
:src="appLogo"
style="height: 26px; width: 26px; margin: 0 -9px;"
/>
<q-tooltip anchor="center right" self="center left" :delay="500">
Program settings
</q-tooltip>
</q-btn>
<q-separator color="primary" vertical dark style="opacity: 0.1;" />
<!-- Project button-->
<q-btn
flat
@ -82,6 +108,38 @@
>
<q-list class="bg-gunmetal-light" dark>
<q-item
v-close-popup
clickable
active
active-class="bg-gunmetal-light text-cultured"
class="noHigh"
@click="newObjectAssignUID"
:disable="!projectExists || isFrontpage"
>
<q-item-section>Quick-add new document</q-item-section>
<q-item-section avatar>
<q-icon name="mdi-text-box-plus-outline" />
</q-item-section>
</q-item>
<q-item
v-close-popup
clickable
active
active-class="bg-gunmetal-light text-cultured"
class="noHigh"
@click="existingObjectAssignUID"
:disable="!projectExists || isFrontpage"
>
<q-item-section>Quick-search existing document</q-item-section>
<q-item-section avatar>
<q-icon name="mdi-database-search" />
</q-item-section>
</q-item>
<q-separator dark />
<q-item
v-close-popup
clickable
@ -91,6 +149,9 @@
@click="newProjectAssignUID"
>
<q-item-section>New project</q-item-section>
<q-item-section avatar>
<q-icon name="mdi-plus" />
</q-item-section>
</q-item>
<q-separator dark />
@ -105,6 +166,9 @@
:disable="!projectExists"
>
<q-item-section>Export current project</q-item-section>
<q-item-section avatar>
<q-icon name="mdi-package-variant-closed" />
</q-item-section>
</q-item>
<q-item
@ -116,6 +180,9 @@
@click="importProjectAssignUID"
>
<q-item-section>Import existing project</q-item-section>
<q-item-section avatar>
<q-icon name="mdi-package-variant" />
</q-item-section>
</q-item>
<q-separator dark />
@ -130,6 +197,9 @@
:disable="!projectExists || isProjectPage"
>
<q-item-section>Resume project</q-item-section>
<q-item-section avatar>
<q-icon name="mdi-folder-open-outline" />
</q-item-section>
</q-item>
<q-item
@ -142,6 +212,9 @@
:disable="!projectExists || isFrontpage"
>
<q-item-section>Close project</q-item-section>
<q-item-section avatar>
<q-icon name="mdi-exit-to-app" />
</q-item-section>
</q-item>
</q-list>
</q-menu>
@ -155,7 +228,7 @@
size='md'
no-caps
>
Help & Info
Help, Settings & Info
<q-menu
anchor="bottom left"
class="bg-gunmetal-light"
@ -163,6 +236,22 @@
square
>
<q-list class="bg-gunmetal-light" dark>
<q-item
@click="programSettingsDialogAssignUID"
v-close-popup
clickable
active
active-class="bg-gunmetal-light text-cultured"
class="noHigh"
>
<q-item-section>Program settings</q-item-section>
<q-item-section avatar>
<q-icon name="mdi-tune" />
</q-item-section>
</q-item>
<q-separator dark />
<q-item
@click="keybindsDialogAssignUID"
v-close-popup
@ -172,6 +261,9 @@
class="noHigh"
>
<q-item-section>Show keybind cheatsheet</q-item-section>
<q-item-section avatar>
<q-icon name="mdi-keyboard-settings" />
</q-item-section>
</q-item>
<q-item
@ -183,6 +275,9 @@
class="noHigh"
>
<q-item-section>Advanced search guide</q-item-section>
<q-item-section avatar>
<q-icon name="mdi-file-question" />
</q-item-section>
</q-item>
<q-separator dark />
@ -196,6 +291,9 @@
class="noHigh"
>
<q-item-section>Changelog</q-item-section>
<q-item-section avatar>
<q-icon name="mdi-clipboard-text" />
</q-item-section>
</q-item>
<q-item
@ -207,6 +305,9 @@
class="noHigh"
>
<q-item-section>About Fantasia Archive</q-item-section>
<q-item-section avatar>
<q-icon name="mdi-information-variant" />
</q-item-section>
</q-item>
<q-separator dark />
@ -220,6 +321,9 @@
class="noHigh"
>
<q-item-section>Toggle developer tools</q-item-section>
<q-item-section avatar>
<q-icon name="mdi-code-tags" />
</q-item-section>
</q-item>
</q-list>
@ -243,13 +347,19 @@ import importProjectCheckDialog from "src/components/dialogs/ImportProjectCheck.
import newProjectCheckDialog from "src/components/dialogs/NewProjectCheck.vue"
import aboutAppDialog from "src/components/dialogs/AboutApp.vue"
import changeLogDialog from "src/components/dialogs/ChangeLog.vue"
import programSettingsDialog from "src/components/dialogs/ProgramSettings.vue"
import advancedSearchGuideDialog from "src/components/dialogs/AdvancedSearchGuide.vue"
import newDocumentDialog from "src/components/dialogs/NewDocument.vue"
import existingDocumentDialog from "src/components/dialogs/ExistingDocument.vue"
import { Loading, QSpinnerGears } from "quasar"
import { retrieveCurrentProjectName, exportProject } from "src/scripts/projectManagement/projectManagent"
import { toggleDevTools } from "src/scripts/utilities/devTools"
import appLogo from "src/assets/appLogo.png"
@Component({
components: {
projectCloseCheckDialog,
@ -258,7 +368,10 @@ import { toggleDevTools } from "src/scripts/utilities/devTools"
newProjectCheckDialog,
aboutAppDialog,
changeLogDialog,
advancedSearchGuideDialog
advancedSearchGuideDialog,
programSettingsDialog,
newDocumentDialog,
existingDocumentDialog
}
})
export default class AppControl extends BaseClass {
@ -271,6 +384,8 @@ export default class AppControl extends BaseClass {
isProjectPage = true
projectName = ""
appLogo = appLogo
created () {
this.checkProjectStatus().catch(e => console.log(e))
}
@ -337,9 +452,14 @@ export default class AppControl extends BaseClass {
@Watch("SGET_getCurrentKeyBindData", { deep: true })
processKeyPush () {
// Keybind cheatsheet
if (this.determineKeyBind("openKeybindsCheatsheet")) {
if (this.determineKeyBind("openKeybindsCheatsheet") && !this.SGET_getDialogsState) {
this.keybindsDialogAssignUID()
}
// App options
if (this.determineKeyBind("openAppOptions") && !this.SGET_getDialogsState) {
this.programSettingsDialogAssignUID()
}
}
/****************************************************************/
@ -381,6 +501,19 @@ export default class AppControl extends BaseClass {
this.changeLogDialogTrigger = this.generateUID()
}
/****************************************************************/
// Program settings dialog
/****************************************************************/
programSettingsDialogTrigger: string | false = false
programSettingsDialogClose () {
this.programSettingsDialogTrigger = false
}
programSettingsDialogAssignUID () {
this.programSettingsDialogTrigger = this.generateUID()
}
/****************************************************************/
// Advanced search guide dialog
/****************************************************************/
@ -393,6 +526,32 @@ export default class AppControl extends BaseClass {
advancedSearchGuideAssignUID () {
this.advancedSearchGuideDialogTrigger = this.generateUID()
}
/****************************************************************/
// New document dialog
/****************************************************************/
newObjectDialogTrigger: string | false = false
newObjectDialogClose () {
this.newObjectDialogTrigger = false
}
newObjectAssignUID () {
this.newObjectDialogTrigger = this.generateUID()
}
/****************************************************************/
// Existing document dialog
/****************************************************************/
existingObjectDialogTrigger: string | false = false
existingObjectDialogClose () {
this.existingObjectDialogTrigger = false
}
existingObjectAssignUID () {
this.existingObjectDialogTrigger = this.generateUID()
}
}
</script>

View file

@ -10,10 +10,11 @@
<q-tabs
v-if="localDocuments.length > 0"
:class="{'hasTextShadow': textShadow}"
align="left"
inline-label
outside-arrows
mobile-arrows
mobile-arrows
class="tabsWrapper"
dense
no-caps>
@ -78,6 +79,14 @@ import closeDocumentCheckDialog from "src/components/dialogs/CloseDocumentCheck.
components: { closeDocumentCheckDialog }
})
export default class TopTabs extends BaseClass {
textShadow = false
@Watch("SGET_options", { immediate: true, deep: true })
onSettingsChange () {
const options = this.SGET_options
this.textShadow = options.textShadow
}
@Watch("SGET_allOpenedDocuments", { deep: true })
reactToDocumentListChange (val: {docs: I_OpenedDocument[]}, oldVal: {docs: I_OpenedDocument[]}) {
this.localDocuments = []
@ -108,18 +117,18 @@ export default class TopTabs extends BaseClass {
*/
@Watch("SGET_getCurrentKeyBindData", { deep: true })
processKeyPush () {
// Delete dialog
if (this.determineKeyBind("closeTab") && this.localDocuments.length > 0) {
// Close tab dialog
if (this.determineKeyBind("closeTab") && this.localDocuments.length > 0 && !this.SGET_getDialogsState) {
this.tryCloseTab()
}
// Next tab
if (this.determineKeyBind("nextTab") && this.localDocuments.length > 0) {
if (this.determineKeyBind("nextTab") && this.localDocuments.length > 0 && !this.SGET_getDialogsState) {
this.goToNextTab()
}
// Previous tab
if (this.determineKeyBind("previousTab") && this.localDocuments.length > 0) {
if (this.determineKeyBind("previousTab") && this.localDocuments.length > 0 && !this.SGET_getDialogsState) {
this.goToPreviousTab()
}
}
@ -202,6 +211,25 @@ export default class TopTabs extends BaseClass {
.tabsWrapper {
-webkit-app-region: no-drag;
&.hasTextShadow {
.q-tab__label,
.q-tab__icon {
$shadowColorOutline: #000;
$shadowColorSurround: #000;
filter: drop-shadow(0 0 4px #000);
text-shadow:
-2px -2px 0 $shadowColorSurround,
2px -2px 0 $shadowColorSurround,
-2px 2px 0 $shadowColorSurround,
2px 2px 0 $shadowColorSurround,
-1px -1px 0 $shadowColorOutline,
1px -1px 0 $shadowColorOutline,
-1px 1px 0 $shadowColorOutline,
1px 1px 0 $shadowColorOutline;
}
}
.q-tabs__arrow {
text-shadow: none !important;
}
@ -248,4 +276,12 @@ export default class TopTabs extends BaseClass {
color: $primary;
}
}
body.body--dark {
.topTabs {
.q-tab {
color: #dcdcdc;
}
}
}
</style>

View file

@ -18,7 +18,6 @@
outline
label="Discard changes"
color="secondary"
v-close-popup
@click="closeDocument(dialogDocument)" />
</q-card-actions>
</q-card>
@ -61,6 +60,9 @@ export default class CloseDocumentCheckDialog extends DialogBase {
closeDocument (input: I_OpenedDocument) {
const dataPass = { doc: input, treeAction: false }
this.dialogModel = false
this.SSET_setDialogState(false)
this.SSET_removeOpenedDocument(dataPass)
}
}

View file

@ -30,7 +30,6 @@
outline
label="Delete document"
color="secondary"
v-close-popup
@click="deleteDocument()" />
</q-card-actions>
</q-card>
@ -71,6 +70,10 @@ export default class DeleteDocumentCheckDialog extends DialogBase {
await CurrentObjectDB.remove(this.currentDocument)
const dataPass = { doc: this.currentDocument, treeAction: true }
this.dialogModel = false
this.SSET_setDialogState(false)
this.SSET_removeOpenedDocument(dataPass)
}
}

View file

@ -1,5 +1,6 @@
<template>
<q-dialog
no-route-dismiss
v-model="dialogModel"
@hide="triggerDialogClose"
>
@ -14,7 +15,7 @@
<q-card-section class="column items-center">
<div class="q-mb-lg">
<q-checkbox dark v-model="includeCategories" label="Include categories in the list?" />
<q-checkbox dark color="primary" v-model="includeCategories" label="Include categories in the list?" />
</div>
<q-select
style="width: 400px;"
@ -26,14 +27,16 @@
menu-self="top middle"
:options="filteredExistingInput"
use-input
outlined
input-debounce="0"
multiple
filled
input-debounce="200"
v-model="existingDocumentModel"
@filter="filterExistingSelect"
@input="openExistingInput"
>
<template v-slot:option="{ itemProps, itemEvents, opt }">
<q-item
:class="{'hasTextShadow': textShadow}"
v-bind="itemProps"
v-on="itemEvents"
:style="`color: ${opt.color}`"
@ -96,11 +99,12 @@
import { Component, Watch } from "vue-property-decorator"
import { I_ShortenedDocument } from "src/interfaces/I_OpenedDocument"
import PouchDB from "pouchdb"
import { advancedDocumentFilter } from "src/scripts/utilities/advancedDocumentFilter"
import { extend } from "quasar"
import PouchDB from "pouchdb"
import DialogBase from "src/components/dialogs/_DialogBase"
import { I_Blueprint } from "src/interfaces/I_Blueprint"
@Component({
components: { }
@ -112,46 +116,23 @@ export default class ExistingDocumentDialog extends DialogBase {
if (this.SGET_getDialogsState) {
return
}
this.isCloseAbleViaKeybind = false
this.SSET_setDialogState(true)
this.dialogModel = true
this.reloadOptions()
this.populateExistingObjectDialog().catch(e => console.log(e))
}
}
existingObjectsBackupList = [] as I_ShortenedDocument[]
existingObjectList = [] as I_ShortenedDocument[]
allDocumentBluePrints = [] as I_Blueprint[]
async populateExistingObjectDialog () {
let allDocs = [] as I_ShortenedDocument[]
for (const blueprint of this.SGET_allBlueprints) {
const CurrentObjectDB = new PouchDB(blueprint._id)
this.allDocumentBluePrints = this.SGET_allBlueprints
const dbRows = await CurrentObjectDB.allDocs({ include_docs: true })
const dbDocuments = dbRows.rows.map(d => d.doc)
const formattedDocuments: I_ShortenedDocument[] = []
for (const singleDocument of dbDocuments) {
const doc = singleDocument as unknown as I_ShortenedDocument
const pushValue = {
label: doc.extraFields.find(e => e.id === "name")?.value,
icon: doc.icon,
id: doc._id,
url: doc.url,
type: doc.type,
// @ts-ignore
hierarchicalPath: this.getDocumentHieararchicalPath(doc, dbDocuments),
tags: doc.extraFields.find(e => e.id === "tags")?.value,
color: doc.extraFields.find(e => e.id === "documentColor")?.value,
isCategory: doc.extraFields.find(e => e.id === "categorySwitch")?.value
} as unknown as I_ShortenedDocument
formattedDocuments.push(pushValue)
}
const sortedDocuments = formattedDocuments.sort((a, b) => a.label.localeCompare(b.label))
// @ts-ignore
allDocs = [...allDocs, ...sortedDocuments]
}
const allDocs = await this.retrieveAllDocuments()
this.existingObjectsBackupList = allDocs
this.filterDocuments()
@ -164,9 +145,10 @@ export default class ExistingDocumentDialog extends DialogBase {
this.$refs.ref_existingDocument.focus()
/* eslint-enable */
}
this.isCloseAbleViaKeybind = true
}
existingDocumentModel = null
existingDocumentModel = []
filteredExistingInput = null as unknown as I_ShortenedDocument[]
@ -194,7 +176,7 @@ export default class ExistingDocumentDialog extends DialogBase {
update(() => {
const needle = val.toLowerCase()
const listCopy : I_ShortenedDocument[] = extend(true, [], this.existingObjectList)
this.filteredExistingInput = advancedDocumentFilter(needle, listCopy)
this.filteredExistingInput = advancedDocumentFilter(needle, listCopy, this.allDocumentBluePrints, this.existingObjectsBackupList)
if (this.$refs.ref_existingDocument && this.filteredExistingInput.length > 0) {
this.refocusSelect().catch(e => console.log(e))
@ -202,11 +184,29 @@ export default class ExistingDocumentDialog extends DialogBase {
})
}
openExistingInput (e: I_ShortenedDocument) {
this.dialogModel = false
// @ts-ignore
this.openExistingDocumentRoute(e)
this.existingDocumentModel = null
async openExistingInput (e: I_ShortenedDocument[]) {
if (!this.disableCloseAftertSelectQuickSearch) {
this.dialogModel = false
// @ts-ignore
this.openExistingDocumentRoute(e[0])
this.existingDocumentModel = []
}
else {
// @ts-ignore
this.existingDocumentModel = []
const CurrentObjectDB = new PouchDB(e[0].type)
// @ts-ignore
const retrievedObject = await CurrentObjectDB.get(e[0].id)
const dataPass = {
doc: retrievedObject,
treeAction: false
}
// @ts-ignore
this.SSET_addOpenedDocument(dataPass)
}
}
addNewItemUnderSelected (parent: any) {
@ -218,6 +218,23 @@ export default class ExistingDocumentDialog extends DialogBase {
this.addNewObjectRoute(routeObject)
}
disableCloseAftertSelectQuickSearch = false
closeWithSameClick = false
textShadow = false
@Watch("SGET_options", { immediate: true, deep: true })
onSettingsChange () {
this.reloadOptions()
}
reloadOptions () {
const options = this.SGET_options
this.closeWithSameClick = options.allowQuickPopupSameKeyClose
this.disableCloseAftertSelectQuickSearch = options.disableCloseAftertSelectQuickSearch
this.includeCategories = !options.disableQuickSearchCategoryPrecheck
this.textShadow = options.textShadow
}
includeCategories = true
@Watch("includeCategories")
@ -228,6 +245,22 @@ export default class ExistingDocumentDialog extends DialogBase {
filterDocuments () {
this.existingObjectList = this.existingObjectsBackupList.filter(e => !((!this.includeCategories && e.isCategory)))
}
isCloseAbleViaKeybind = false
/**
* Local keybinds
*/
@Watch("SGET_getCurrentKeyBindData", { deep: true })
processKeyPush () {
// Keybind cheatsheet
if (this.determineKeyBind("quickExistingDocument") && this.dialogModel && this.closeWithSameClick && this.isCloseAbleViaKeybind && this.SGET_getDialogsState) {
this.dialogModel = false
this.SSET_setDialogState(false)
// @ts-ignore
this.existingDocumentModel = null
}
}
}
</script>

View file

@ -31,7 +31,7 @@
</tr>
</thead>
<tbody>
<tr v-for="keybind in SGET_getCurrentKeyBindData.defaults" :key="keybind.id">
<tr v-for="keybind in localCheatSheet" :key="keybind.id">
<td class="text-left" v-html="keybind.tooltip"/>
<td class="text-left" v-html="retrieveKeybindString(keybind)"/>
</tr>
@ -54,6 +54,7 @@
import { Component, Watch } from "vue-property-decorator"
import DialogBase from "src/components/dialogs/_DialogBase"
import { I_KeyPressObject } from "src/interfaces/I_KeypressObject"
@Component({
components: { }
})
@ -66,9 +67,34 @@ export default class KeybindCheatsheet extends DialogBase {
}
this.SSET_setDialogState(true)
this.dialogModel = true
this.localCheatSheet = this.SGET_getCurrentKeyBindData.defaults.map((bind, index) => {
const mappedKeybind = (this.SGET_getCurrentKeyBindData.userKeybinds[index] && this.SGET_getCurrentKeyBindData.userKeybinds[index].which)
? {
altKey: this.SGET_getCurrentKeyBindData.userKeybinds[index].altKey,
ctrlKey: this.SGET_getCurrentKeyBindData.userKeybinds[index].ctrlKey,
shiftKey: this.SGET_getCurrentKeyBindData.userKeybinds[index].shiftKey,
which: this.SGET_getCurrentKeyBindData.userKeybinds[index].which,
id: bind.id,
tooltip: bind.tooltip,
note: bind.note
}
: {
altKey: bind.altKey,
ctrlKey: bind.ctrlKey,
shiftKey: bind.shiftKey,
which: bind.which,
id: bind.id,
tooltip: bind.tooltip,
note: bind.note
}
return mappedKeybind
})
}
}
localCheatSheet: I_KeyPressObject[] = []
thumbStyle ={
right: "-40px",
borderRadius: "5px",

View file

@ -23,7 +23,7 @@
class="newDocumentSelect"
:options="filteredNewInput"
use-input
outlined
filled
input-debounce="0"
v-model="newDocumentModel"
@filter="filterNewSelect"
@ -31,6 +31,7 @@
>
<template v-slot:option="{ itemProps, itemEvents, opt }">
<q-item
:class="{'hasTextShadow': textShadow}"
v-bind="itemProps"
v-on="itemEvents"
>
@ -76,6 +77,7 @@ export default class NewDocumentDialog extends DialogBase {
if (this.SGET_getDialogsState) {
return
}
this.isCloseAbleViaKeybind = false
this.SSET_setDialogState(true)
this.dialogModel = true
this.populateNewObjectDialog().catch(e => console.log(e))
@ -104,6 +106,8 @@ export default class NewDocumentDialog extends DialogBase {
// @ts-ignore
this.$refs.ref_newDocument.focus()
/* eslint-enable */
this.isCloseAbleViaKeybind = true
}
filteredNewInput = null as unknown as NewObjectDocument[]
@ -143,6 +147,35 @@ export default class NewDocumentDialog extends DialogBase {
this.addNewObjectRoute(e)
this.newDocumentModel = null
}
isCloseAbleViaKeybind = false
closeWithSameClick = false
textShadow = false
@Watch("SGET_options", { immediate: true, deep: true })
onSettingsChange () {
this.reloadOptions()
}
reloadOptions () {
const options = this.SGET_options
this.closeWithSameClick = options.allowQuickPopupSameKeyClose
this.textShadow = options.textShadow
}
/**
* Local keybinds
*/
@Watch("SGET_getCurrentKeyBindData", { deep: true })
processKeyPush () {
// Keybind cheatsheet
if (this.determineKeyBind("quickNewDocument") && this.dialogModel && this.closeWithSameClick && this.isCloseAbleViaKeybind && this.SGET_getDialogsState) {
this.dialogModel = false
this.SSET_setDialogState(false)
// @ts-ignore
this.existingDocumentModel = null
}
}
}
</script>

View file

@ -0,0 +1,952 @@
<template>
<q-dialog
v-model="dialogModel"
persistent
@hide="triggerDialogClose"
>
<q-card
class="programSettingsDialog"
dark
>
<h4 class="programSettingsTitle">Fantasia Archive Settings</h4>
<q-card-section horizontal class="programSettingsTabs">
<q-tabs
v-model="activeTab"
class="text-accent"
active-color="primary"
indicator-color="primary"
vertical
style="width: 100%;"
>
<q-tab name="uiSettings" label="Visuals & Functionality" />
<q-separator dark />
<q-tab name="keybinds" label="Keybinds" />
</q-tabs>
</q-card-section>
<q-separator vertical dark />
<q-card-section horizontal class="programSettingsTabContent">
<q-tab-panels
dark
v-model="activeTab"
animated
style="width: 100%;"
vertical
transition-prev="jump-up"
transition-next="jump-down"
>
<q-tab-panel name="uiSettings" dark class="q-pt-sm">
<q-scroll-area
class="programSettingsScrollArea"
visible
dark
:thumb-style="thumbStyle"
>
<div class="row justify-start">
<div class="col-12">
<div class="text-h6">
Program looks & Accessibility
</div>
</div>
<div class="col-3 optionWrapper">
<div class="optionTitle">
Dark mode
<q-icon name="mdi-help-circle" size="16px" class="q-ml-md">
<q-tooltip :delay="500">
Turn between light and dark mode of the app
</q-tooltip>
</q-icon>
</div>
<q-toggle
v-model="options.darkMode"
/>
</div>
<div class="col-3 optionWrapper">
<div class="optionTitle">
Accessibility - Text shadow
<q-icon name="mdi-help-circle" size="16px" class="q-ml-md">
<q-tooltip :delay="500">
This setting toggles text shadows in the hieratchical tree, relationship search popups and in tabs; allowing for most "standout" looks of the text from the background.
</q-tooltip>
</q-icon>
</div>
<q-toggle
v-model="options.textShadow"
/>
</div>
<div class="col-12">
<div class="text-h6 q-mt-lg">
Document view settings
</div>
</div>
<div class="col-3 optionWrapper">
<div class="optionTitle">
Disable document control bar
<q-icon name="mdi-help-circle" size="16px" class="q-ml-md">
<q-tooltip :delay="500">
In case you wish to maximize you working space on the document, you can disable the top button bar with the setting.
<br>
The necesarry control buttons will be moved to the top of the main document body while the rest of the functionality will be accesible via keybinds or thought the app menu on the top left.
</q-tooltip>
</q-icon>
</div>
<q-toggle
v-model="options.disableDocumentControlBar"
/>
</div>
<div class="col-3 optionWrapper">
<div class="optionTitle">
Disable document guides
<q-icon name="mdi-help-circle" size="16px" class="q-ml-md">
<q-tooltip :delay="500">
Toggles the newbie-friendly guides on/off from the document control bar
</q-tooltip>
</q-icon>
</div>
<q-toggle
v-model="options.disableDocumentControlBarGuides"
/>
</div>
<div class="col-3 optionWrapper">
<div class="optionTitle">
Disable document tooltips
<q-icon name="mdi-help-circle" size="16px" class="q-ml-md">
<q-tooltip :delay="500">
If you dislike the document-view tooltips, you can turn them off globally here
</q-tooltip>
</q-icon>
</div>
<q-toggle
v-model="options.disableDocumentToolTips"
/>
</div>
<div class="col-3 optionWrapper">
<div class="optionTitle">
Hide empty fields
<q-icon name="mdi-help-circle" size="16px" class="q-ml-md">
<q-tooltip :delay="500">
Hides fields without any filled in value in view (non-edit) mode.
<br>
Please note that this may result into a relatively wild layout shifts which might make the document look unruly in some cases.
</q-tooltip>
</q-icon>
</div>
<q-toggle
v-model="options.hideEmptyFields"
/>
</div>
<div class="col-12">
<div class="text-h6 q-mt-lg">
Quick-search & Quick-add popups
</div>
</div>
<div class="col-4 optionWrapper">
<div class="optionTitle">
Stop quick-search close after selection
<q-icon name="mdi-help-circle" size="16px" class="q-ml-md">
<q-tooltip :delay="500">
Normally the quick-search closes after an item is selected from it.
<br>
Turning this feature on prevents this behavior; allowing for opening multiple search results one after the other.
</q-tooltip>
</q-icon>
</div>
<q-toggle
v-model="options.disableCloseAftertSelectQuickSearch"
/>
</div>
<div class="col-4 optionWrapper">
<div class="optionTitle">
Don't precheck category filter
<q-icon name="mdi-help-circle" size="16px" class="q-ml-md">
<q-tooltip :delay="500">
Normally, the categories are included in the quick-search.
<br>
Enabling this option reverses the behavior.
</q-tooltip>
</q-icon>
</div>
<q-toggle
v-model="options.disableQuickSearchCategoryPrecheck"
/>
</div>
<div class="col-4 optionWrapper">
<div class="optionTitle">
Close quick popups with same key
<q-icon name="mdi-help-circle" size="16px" class="q-ml-md">
<q-tooltip :delay="500">
This allows for closing of the quick-search and quick-add popups with the same key combination that was used to open them to begin with.
</q-tooltip>
</q-icon>
</div>
<q-toggle
v-model="options.allowQuickPopupSameKeyClose"
/>
</div>
<div class="col-12">
<div class="text-h6 q-mt-lg">
Hierarchy tree
</div>
</div>
<div class="col-4 optionWrapper">
<div class="optionTitle">
Stop sublevel collapse in tree
<q-icon name="mdi-help-circle" size="16px" class="q-ml-md">
<q-tooltip :delay="500">
This option prevents sub-category closing in the hierarchical tree upon parent category closure
</q-tooltip>
</q-icon>
</div>
<q-toggle
v-model="options.doNotcollaseTreeOptions"
/>
</div>
<div class="col-4 optionWrapper">
<div class="optionTitle">
Hide project name in tree
<q-icon name="mdi-help-circle" size="16px" class="q-ml-md">
<q-tooltip :delay="500">
Determines if the the project name shows in the hierarchical tree at all
</q-tooltip>
</q-icon>
</div>
<q-toggle
v-model="options.noProjectName"
/>
</div>
<div class="col-4 optionWrapper">
<div class="optionTitle">
Invert tree custom order sorting
<q-icon name="mdi-help-circle" size="16px" class="q-ml-md">
<q-tooltip :delay="500">
Sorts the documents in the hiearachical tree the other way around than normally:
<br>
From highest to lowest
</q-tooltip>
</q-icon>
</div>
<q-toggle
v-model="options.invertTreeSorting"
/>
</div>
<div class="col-4 optionWrapper">
<div class="optionTitle">
Hide tags in tree
<q-icon name="mdi-help-circle" size="16px" class="q-ml-md">
<q-tooltip :delay="500">
Determines if the tags show in the hierarchical tree at all
</q-tooltip>
</q-icon>
</div>
<q-toggle
v-model="options.noTags"
/>
</div>
<div class="col-4 optionWrapper">
<div class="optionTitle">
Top tags in tree
<q-icon name="mdi-help-circle" size="16px" class="q-ml-md">
<q-tooltip :delay="500">
Show tags at the top of the hierarchical tree
</q-tooltip>
</q-icon>
</div>
<q-toggle
v-model="options.tagsAtTop"
/>
</div>
<div class="col-4 optionWrapper">
<div class="optionTitle">
Compact tags
<q-icon name="mdi-help-circle" size="16px" class="q-ml-md">
<q-tooltip :delay="500">
Determine if the tags should be shown as individual categories or they should show as one big category with each tag as a subcategory.
</q-tooltip>
</q-icon>
</div>
<q-toggle
v-model="options.compactTags"
/>
</div>
</div>
</q-scroll-area>
</q-tab-panel>
<q-tab-panel name="keybinds" dark>
<q-table
class="keybindsTable"
virtual-scroll
flat
title="Keybinds"
:filter="filter"
hide-bottom
:pagination.sync="pagination"
:rows-per-page-options="[0]"
:virtual-scroll-sticky-size-start="48"
row-key="index"
:data="keybindList"
:columns="keybindListCollums"
>
<template v-slot:top-right>
<q-input clearable dense debounce="300" v-model="filter" placeholder="Filter the keybinds">
<template v-slot:prepend>
<q-icon name="search" />
</template>
</q-input>
</template>
<template v-slot:body="props">
<q-tr :props="props">
<q-td
key="name"
:props="props"
v-html="props.row.name"
>
</q-td>
<q-td
key="userKeybinds"
:props="props"
>
<template
v-if="props.row.editable"
>
<q-btn
:color="(props.row.userKeybind && props.row.userKeybind.which) ? 'accent' : 'primary'"
size="12px"
outline
>
{{(props.row.userKeybind && props.row.userKeybind.which) ? retrieveKeybindString(props.row.userKeybind) : 'Add New'}}
<q-popup-edit
content-class="darkBg"
v-model="props.row.userKeybind"
@before-show="prepareKeybindSetting(props.row)"
@before-hide="processKeybindSetting()"
>
<q-btn
color="dark"
round
dense
flat
v-close-popup
class="keybindPopupCloseButton"
size="md"
icon="close"
/>
<div class="keybindUpdateField">
<div class="text-center q-mt-xs q-mb-lg text-dark darkBg__title">{{currentRowData.name}}</div>
<q-field filled readonly
dense
class="q-ml-lg"
:dark="false"
:label="(tempKeybindString.length === 0)? '> Type your keybind <' : ''"
:error="keybindError"
:error-message="keybindErrorMessage">
<template v-slot:after>
<q-icon name="mdi-help-circle" size="23px" class="keybindsTooltipIcon">
<q-tooltip :delay="500" content-class="keybindToolTip">
Allow keys combination and modifiers:
<br>
- CTRL + <span class="text-italic text-caption">YOUR KEYBIND HERE</span>
<br>
- ALT + <span class="text-italic text-caption">YOUR KEYBIND HERE</span>
<br>
- CTRL + ALT + <span class="text-italic text-caption">YOUR KEYBIND HERE</span>
<br>
- CTRL + SHIFT + <span class="text-italic text-caption">YOUR KEYBIND HERE</span>
<br>
- ALT + SHIFT + <span class="text-italic text-caption">YOUR KEYBIND HERE</span>
<br>
- CTRL + ALT + SHIFT + <span class="text-italic text-caption">YOUR KEYBIND HERE</span>
<br>
<br>
- Your desired keybind can contain any symbol of the alphanumerical part of the keyboard along with all of the F keys and arrow keys.
</q-tooltip>
</q-icon>
</template>
<template v-slot:control>
<div class="self-center full-width no-outline">{{tempKeybindString}}</div>
</template>
</q-field>
<div class="flex justify-around q-mt-md">
<q-btn
label="Clear keybind"
color="secondary"
size="14px"
v-close-popup
@click="resetKeybind"
:disable="!(tempKeybindString && tempKeybindString.length > 0)"
/>
<q-btn
label="Set keybind"
color="dark"
size="14px"
v-close-popup
@click="setKeybind"
:disable="!tempHasChange || (!tempKeybindString || tempKeybindString.length === 0)"
/>
</div>
</div>
</q-popup-edit>
</q-btn>
</template>
<template
v-if="!props.row.editable"
>
<span class="text-secondary text-bold">
Built-in & uneditable functionality
</span>
</template>
</q-td>
<q-td
key="defaultKeybinds"
:class="{'text-blue-grey-6':props.row.userKeybind && props.row.userKeybind.which}"
:props="props"
v-html="retrieveKeybindString(props.row.defaultKeybind)"
>
</q-td>
</q-tr>
</template>
</q-table>
</q-tab-panel>
</q-tab-panels>
</q-card-section>
<q-card-actions align="right" class="q-mb-lg q-mt-md closeButton">
<q-btn flat label="Close without saving" color="accent" class="q-mr-xl" v-close-popup />
<q-btn label="Save settings" color="primary" class="q-mx-lg" outline v-close-popup @click="saveSettings" />
</q-card-actions>
</q-card>
</q-dialog>
</template>
<script lang="ts">
import { Component, Watch } from "vue-property-decorator"
import DialogBase from "src/components/dialogs/_DialogBase"
import { extend } from "quasar"
import { OptionsStateInteface } from "src/store/module-options/state"
@Component({
components: { }
})
export default class ProgramSettings extends DialogBase {
/****************************************************************/
// DIALOG CONTROL
/****************************************************************/
@Watch("dialogTrigger")
openDialog (val: string|false) {
if (val) {
if (this.SGET_getDialogsState) {
return
}
this.SSET_setDialogState(true)
this.dialogModel = true
this.activeTab = "uiSettings"
this.retrieveOptions()
this.mapKeybinds()
}
}
thumbStyle ={
right: "-40px",
borderRadius: "5px",
backgroundColor: "#61a2bd",
width: "5px",
opacity: 1
}
activeTab = "uiSettings"
/****************************************************************/
// OPTIONS TAB
/****************************************************************/
options: OptionsStateInteface = {
_id: "settings",
darkMode: false,
textShadow: false,
tagsAtTop: false,
noTags: false,
compactTags: false,
noProjectName: false,
invertTreeSorting: false,
hideEmptyFields: false,
disableDocumentToolTips: false,
doNotcollaseTreeOptions: false,
disableDocumentControlBar: false,
disableDocumentControlBarGuides: false,
disableCloseAftertSelectQuickSearch: false,
disableQuickSearchCategoryPrecheck: false,
allowQuickPopupSameKeyClose: false,
userKeybindList: []
}
retrieveOptions () {
const optionsSnapShot: OptionsStateInteface = extend(true, {}, this.SGET_options)
this.options = optionsSnapShot
}
saveSettings () {
const optionsSnapShot: OptionsStateInteface = extend(true, {}, this.options)
optionsSnapShot.userKeybindList = []
this.keybindList.forEach(e => {
this.SSET_deregisterUserKeybind({
id: e.id,
altKey: e.defaultKeybind.altKey,
ctrlKey: e.defaultKeybind.ctrlKey,
shiftKey: e.defaultKeybind.shiftKey,
which: e.defaultKeybind.which
})
if (e.userKeybind && e.userKeybind.which) {
const tempkey = {
id: e.id,
altKey: e.userKeybind.altKey,
ctrlKey: e.userKeybind.ctrlKey,
shiftKey: e.userKeybind.shiftKey,
which: e.userKeybind.which
}
optionsSnapShot.userKeybindList.push(tempkey)
this.SSET_registerUserKeybind(tempkey)
}
})
this.SSET_options(optionsSnapShot)
}
/****************************************************************/
// KEYBDINS MANAGEMENT
/****************************************************************/
pagination = {
rowsPerPage: 0
}
keybindListCollums = [
{
name: "name",
required: true,
label: "Action",
align: "left",
field: (row: {name: string}) => row.name,
format: (val: string) => `${val}`,
sortable: true
},
{
name: "userKeybinds",
align: "left",
label: "User Keybinds",
field: "userKeybind"
},
{
name: "defaultKeybinds",
align: "left",
label: "Default keybinds",
field: "defaultKeybind"
}
]
filter = ""
tempKeybindString = ""
tempKeybindData = null as any
tempHasChange = false
keybindList: any[] = []
keybindError = false
keybindErrorMessage = ""
currentRowData = {} as any
async resetKeybind () {
this.tempKeybindString = ""
this.tempKeybindData = null
this.keybindError = false
this.currentRowData.userKeybind = ""
const temp: any[] = extend(true, [], this.keybindList)
this.keybindList = []
await this.$nextTick()
this.keybindList = temp
}
async setKeybind () {
this.currentRowData.userKeybind = this.tempKeybindData
const temp: any[] = extend(true, [], this.keybindList)
this.keybindList = []
await this.$nextTick()
this.keybindList = temp
}
processKeybindSetting () {
window.removeEventListener("keydown", this.triggerKeyPush)
}
prepareKeybindSetting (row: any) {
this.keybindError = false
this.tempHasChange = false
this.tempKeybindData = (row.userKeybind && row.userKeybind.which) ? this.tempKeybindData : null
this.tempKeybindString = (row.userKeybind && row.userKeybind.which) ? this.retrieveKeybindString(row.userKeybind) : ""
this.currentRowData = row
window.addEventListener("keydown", this.triggerKeyPush)
}
triggerKeyPush (e:any) {
this.keybindError = false
const ignoredKeys = [16, 17, 18, 27]
const allowedKeys = [
186,
187,
188,
189,
190,
191,
192,
219,
220,
221,
222,
112,
113,
114,
115,
116,
117,
118,
119,
120,
121,
122,
123,
37,
38,
39,
40,
48,
49,
50,
51,
52,
53,
54,
55,
56,
57,
65,
66,
67,
68,
69,
70,
71,
72,
73,
74,
75,
76,
77,
78,
79,
80,
81,
82,
83,
84,
85,
86,
87,
88,
89,
90
]
// Prevent all non-permitted key presses
if ((e?.altKey || e?.ctrlKey) && e?.keyCode && !ignoredKeys.includes(e.which)) {
if (allowedKeys.includes(e.which)) {
// Check for duplicates already existing in the list
const compareList = this.keybindList
.filter(e => e.id !== this.currentRowData.id)
.map(bind => {
const mappedKeybind = (bind.userKeybind && bind.userKeybind.which)
? {
altKey: bind.userKeybind.altKey,
ctrlKey: bind.userKeybind.ctrlKey,
shiftKey: bind.userKeybind.shiftKey,
which: bind.userKeybind.which,
id: this.currentRowData.id
}
: {
altKey: bind.defaultKeybind.altKey,
ctrlKey: bind.defaultKeybind.ctrlKey,
shiftKey: bind.defaultKeybind.shiftKey,
which: bind.defaultKeybind.which,
id: this.currentRowData.id
}
return mappedKeybind
})
let duplicate = false
compareList.forEach(bind => {
if (
bind.altKey === e.altKey &&
bind.ctrlKey === e.ctrlKey &&
bind.shiftKey === e.shiftKey &&
bind.which === e.which
) {
duplicate = true
}
})
if (duplicate) {
this.keybindError = true
this.keybindErrorMessage = "This keybind is already present among the existing ones. Please chose a different one."
return
}
// Continue the script if no duplicates were found
this.tempHasChange = true
this.keybindError = false
const keybindString = this.retrieveKeybindString(e)
this.tempKeybindString = keybindString
this.tempKeybindData = {
altKey: e.altKey,
ctrlKey: e.ctrlKey,
shiftKey: e.shiftKey,
which: e.which,
id: this.currentRowData.id
}
}
else {
this.keybindError = true
this.keybindErrorMessage = "Only alphanumerical keys, arrows keys and F keys are allowed for keybinds."
}
}
else if (!ignoredKeys.includes(e.keyCode)) {
this.keybindError = true
this.keybindErrorMessage = "Only combination containing ALT and/or CTRL allowed."
}
}
mapKeybinds () {
this.keybindList = this.SGET_getCurrentKeyBindData.defaults.map((keybind, index) => {
return {
name: keybind.tooltip,
id: keybind.id,
editable: keybind.editable,
defaultKeybind: keybind,
userKeybind: (this.options.userKeybindList[index]) || ""
}
})
}
}
</script>
<style lang="scss">
.keybindsTooltipIcon {
right: 0;
margin-left: 10px;
top: 4px;
}
.keybindToolTip {
background-color: white !important;
border: 1px solid $dark;
}
.keybindPopupCloseButton {
position: absolute;
right: 10px;
top: 10px;
}
.keybindUpdateField {
width: 550px;
padding: 40px;
.q-field__label {
text-align: center;
margin-top: 7px;
}
.q-field__native > div {
text-align: center;
}
.q-field__append {
position: absolute;
right: 0;
top: 5px;
}
}
.darkBg {
background: $accent;
.darkBg__title {
font-weight: 500;
font-size: 16px;
}
}
.keybindsTable {
max-height: calc(100vh - 340px);
height: calc(100vh - 340px);
margin-top: -25px;
.q-table__control,
.q-table__control label {
width: 200px;
}
th,
td {
white-space: normal;
}
.q-table__top,
.q-table__bottom,
thead tr:first-child th {
background-color: var(--q-color-dark);
}
thead tr th {
position: sticky;
z-index: 1;
}
thead tr:last-child th {
top: 48px;
}
thead tr:first-child th {
top: 0;
}
}
body.body--dark {
.programSettingsDialog {
.optionTitle,
.text-h5,
h4 {
color: #dcdcdc;
}
}
}
.programSettingsDialog {
width: 1400px;
max-width: calc(100vw - 100px) !important;
max-height: calc(100vh - 85px);
display: flex;
flex-wrap: wrap;
.closeButton {
width: 100%;
}
.optionWrapper {
display: flex;
flex-direction: column;
justify-content: flex-start;
.q-toggle__inner {
max-width: 150px;
}
}
.optionTitle {
margin-top: 16px;
margin-bottom: 8px;
font-weight: 500;
align-items: center;
justify-content: flex-start;
display: flex;
flex-wrap: wrap;
margin-left: 10px;
}
.programSettingsTabs {
width: 250px;
}
.programSettingsTabContent {
width: calc(100% - 270px);
}
.programSettingsTitle {
width: 100%;
text-align: center;
}
.programSettingsScrollArea {
max-height: calc(100vh - 357px);
margin: auto;
height: 740px;
width: calc(100% - 80px);
}
h6 {
display: block;
}
}
</style>

View file

@ -3,7 +3,7 @@
<div class="flex justify-start items-center text-weight-bolder q-mb-sm q-mt-md">
<q-icon v-if="inputIcon" :name="inputIcon" :size="inputIcon.includes('fas')? '15px': '20px'" class="q-mr-md" min="1"/>
{{inputDataBluePrint.name}}
<q-icon v-if="toolTip" name="mdi-help-circle" size="16px" class="q-ml-md">
<q-icon v-if="toolTip && !disableDocumentToolTips" name="mdi-help-circle" size="16px" class="q-ml-md">
<q-tooltip :delay="500">
<span v-html="toolTip"/>
</q-tooltip>
@ -32,7 +32,8 @@
v-model.number="localInput"
type="text"
@keyup="signalInput"
outlined
:outlined="!isDarkMode"
:filled="isDarkMode"
dense
>
<template v-slot:prepend>
@ -74,6 +75,16 @@ export default class Field_ColorPicker extends BaseClass {
@Prop() readonly editMode!: boolean
@Prop() readonly isNew!: boolean
isDarkMode = false
disableDocumentToolTips = false
@Watch("SGET_options", { immediate: true, deep: true })
onSettingsChange () {
const options = this.SGET_options
this.isDarkMode = options.darkMode
this.disableDocumentToolTips = options.disableDocumentToolTips
}
changedInput = false
localInput = ""

View file

@ -3,7 +3,7 @@
<div class="flex justify-start items-center text-weight-bolder q-mb-sm q-mt-md">
<q-icon v-if="inputIcon" :name="inputIcon" :size="inputIcon.includes('fas')? '15px': '20px'" class="q-mr-md"/>
{{inputDataBluePrint.name}}
<q-icon v-if="toolTip" name="mdi-help-circle" size="16px" class="q-ml-md">
<q-icon v-if="toolTip && !disableDocumentToolTips" name="mdi-help-circle" size="16px" class="q-ml-md">
<q-tooltip :delay="500">
<span v-html="toolTip"/>
</q-tooltip>
@ -16,8 +16,8 @@
dense>
<q-item v-for="(input,index) in localInput" :key="index">
<q-item-section
class="fieldList_itemDot"
side>
class="fieldList_itemDot"
side>
<q-icon
color="primary"
name="mdi-menu-right"
@ -27,7 +27,7 @@
<span class="text-weight-medium">
{{input.value}}
</span>
<span v-if="localInput[index].affix" class="inline-block q-ml-xs text-italic">
<span v-if="localInput[index].affix" class="inline-block q-ml-xs text-italic listNote">
({{localInput[index].affix}})
</span>
</q-item-section>
@ -42,9 +42,11 @@
<div class="col">
<q-input
v-model="localInput[index].value"
:class="`listField_input${index}_${inputDataBluePrint.id}`"
dense
@keyup="signalInput"
outlined
:outlined="!isDarkMode"
:filled="isDarkMode"
>
</q-input>
</div>
@ -59,8 +61,9 @@
:options="localExtraInput"
use-input
:hide-dropdown-icon="!editMode"
:outlined="editMode"
:outlined="editMode && !isDarkMode"
:borderless="!editMode"
:filled="editMode && isDarkMode"
:readonly="!editMode"
input-debounce="0"
new-value-mode="add"
@ -75,6 +78,7 @@
<q-btn
v-if="editMode"
color="secondary"
:outline="isDarkMode"
@click="removeFromList(index)"
label="Remove" />
</div>
@ -82,7 +86,11 @@
<div class="row q-mt-xs" v-if="editMode">
<div class="col justify-start flex">
<q-btn color="primary" :label="`Add new`" @click="addNewInput" />
<q-btn
color="primary"
:outline="isDarkMode"
label="Add new"
@click="addNewInput" />
</div>
</div>
</div>
@ -122,6 +130,16 @@ export default class Field_List extends BaseClass {
@Prop() readonly editMode!: boolean
isDarkMode = false
disableDocumentToolTips = false
@Watch("SGET_options", { immediate: true, deep: true })
onSettingsChange () {
const options = this.SGET_options
this.isDarkMode = options.darkMode
this.disableDocumentToolTips = options.disableDocumentToolTips
}
changedInput = false
localInput = [] as {
value: string
@ -178,11 +196,22 @@ export default class Field_List extends BaseClass {
return returnValue
}
addNewInput () {
async addNewInput () {
this.localInput.push({
value: "",
affix: ""
})
const targetRefStringNamer = `.listField_input${this.localInput.length - 1}_${this.inputDataBluePrint.id}`
await this.$nextTick()
const newInput = document.querySelector(targetRefStringNamer) as HTMLInputElement
if (newInput) {
newInput.focus()
}
this.signalInput()
}
}

View file

@ -3,14 +3,14 @@
<div class="flex justify-start items-center text-weight-bolder q-mb-sm q-mt-md">
<q-icon v-if="inputIcon" :name="inputIcon" :size="inputIcon.includes('fas')? '15px': '20px'" class="q-mr-md"/>
{{inputDataBluePrint.name}}
<q-icon v-if="toolTip" name="mdi-help-circle" size="16px" class="q-ml-md">
<q-icon v-if="toolTip && !disableDocumentToolTips" name="mdi-help-circle" size="16px" class="q-ml-md">
<q-tooltip :delay="500">
<span v-html="toolTip"/>
</q-tooltip>
</q-icon>
<q-icon v-if="isOneWayRelationship" name="mdi-arrow-right-bold" size="16px" class="q-ml-md">
<q-tooltip :delay="500">
This is a one-way relationship. <br> Editing this value <span class="text-secondary">WILL NOT</span> have effect on the connected document/s.
<q-tooltip :delay="500" v-if="!disableDocumentToolTips">
This is a one-way relationship. <br> Editing this value <span class="text-secondary">WILL NOT</span> have any effect on the connected document/s.
<br>
<br>
Left-clicking the linked document in non-edit mode will open it in new tab and focuses on it.
@ -19,8 +19,8 @@
</q-tooltip>
</q-icon>
<q-icon v-if="!isOneWayRelationship" name="mdi-arrow-left-right-bold" size="16px" class="q-ml-md">
<q-tooltip :delay="500">
This is a two-way relationship. <br> Editing this value <span class="text-secondary">WILL</span> also effect the connected document/s.
<q-tooltip :delay="500" v-if="!disableDocumentToolTips">
This is a two-way relationship. <br> Editing this value <span class="text-secondary">WILL</span> also affect the connected document/s.
<br>
<br>
Left-clicking the linked document in non-edit mode will open it in new tab and focuses on it.
@ -79,13 +79,15 @@
dark
style="flex-grow: 1;"
dense
@popup-show="reloadAllDocuments"
:ref="`multieRelationshipField${this.inputDataBluePrint.id}`"
:options="filteredInput"
use-input
outlined
:outlined="!isDarkMode"
:filled="isDarkMode"
use-chips
multiple
input-debounce="0"
input-debounce="200"
v-model="localInput"
@filter="filterSelect"
@input="signalInput(false)"
@ -105,6 +107,7 @@
</template>
<template v-slot:option="{ itemProps, itemEvents, opt }">
<q-item
:class="{'hasTextShadow': textShadow}"
v-bind="itemProps"
v-on="itemEvents"
>
@ -152,7 +155,8 @@
v-model="singleNote.value"
dense
@keyup="signalInput(false)"
outlined
:outlined="!isDarkMode"
:filled="isDarkMode"
>
</q-input>
</td>
@ -200,6 +204,17 @@ export default class Field_SingleRelationship extends BaseClass {
localInput = [] as unknown as I_FieldRelationship[]
isDarkMode = false
disableDocumentToolTips = false
textShadow = false
@Watch("SGET_options", { immediate: true, deep: true })
onSettingsChange () {
const options = this.SGET_options
this.isDarkMode = options.darkMode
this.disableDocumentToolTips = options.disableDocumentToolTips
this.textShadow = options.textShadow
}
@Watch("inputDataValue", { deep: true, immediate: true })
reactToInputChanges () {
this.localInput = (this.inputDataValue?.value) ? this.inputDataValue.value : []
@ -248,6 +263,12 @@ export default class Field_SingleRelationship extends BaseClass {
/* eslint-enable */
}
allDocuments: I_ShortenedDocument[] = []
async reloadAllDocuments () {
this.allDocuments = await this.retrieveAllDocuments()
}
filterSelect (val: string, update: (e: () => void) => void) {
if (val === "") {
update(() => {
@ -264,7 +285,7 @@ export default class Field_SingleRelationship extends BaseClass {
const listCopy : I_ShortenedDocument[] = extend(true, [], this.extraInput)
// @ts-ignore
this.filteredInput = advancedDocumentFilter(needle, listCopy)
this.filteredInput = advancedDocumentFilter(needle, listCopy, this.SGET_allBlueprints, this.allDocuments)
/*eslint-disable */
if(this.$refs[`multiRelationshipField${this.inputDataBluePrint.id}`] && this.filteredInput.length > 0){
@ -321,6 +342,7 @@ export default class Field_SingleRelationship extends BaseClass {
value: objectDoc._id,
type: objectDoc.type,
disable: isDisabled,
extraFields: objectDoc.extraFields,
url: `/project/display-content/${objectDoc.type}/${objectDoc._id}`,
label: objectDoc.extraFields.find(e => e.id === "name")?.value,
isCategory: objectDoc.extraFields.find(e => e.id === "categorySwitch")?.value,

View file

@ -3,7 +3,7 @@
<div class="flex justify-start items-center text-weight-bolder q-mb-sm q-mt-md">
<q-icon v-if="inputIcon" :name="inputIcon" :size="inputIcon.includes('fas')? '15px': '20px'" class="q-mr-md"/>
{{inputDataBluePrint.name}}
<q-icon v-if="toolTip" name="mdi-help-circle" size="16px" class="q-ml-md">
<q-icon v-if="toolTip && !disableDocumentToolTips" name="mdi-help-circle" size="16px" class="q-ml-md">
<q-tooltip :delay="500">
<span v-html="toolTip"/>
</q-tooltip>
@ -43,7 +43,8 @@
class="multiSelect"
:options="extraInput"
use-input
outlined
:outlined="!isDarkMode"
:filled="isDarkMode"
use-chips
@filter="filterFn"
input-debounce="0"
@ -89,7 +90,6 @@ import { I_ExtraFields } from "src/interfaces/I_Blueprint"
})
export default class Field_MultiSelect extends BaseClass {
@Prop({ default: [] }) readonly inputDataBluePrint!: I_ExtraFields
@Prop({
default: () => {
return []
@ -97,9 +97,18 @@ export default class Field_MultiSelect extends BaseClass {
}) readonly inputDataValue!: []
@Prop() readonly isNew!: boolean
@Prop() readonly editMode!: boolean
isDarkMode = false
disableDocumentToolTips = false
@Watch("SGET_options", { immediate: true, deep: true })
onSettingsChange () {
const options = this.SGET_options
this.isDarkMode = options.darkMode
this.disableDocumentToolTips = options.disableDocumentToolTips
}
changedInput = false
localInput = []

View file

@ -3,7 +3,7 @@
<div class="flex justify-start items-center text-weight-bolder q-mb-sm q-mt-md">
<q-icon v-if="inputIcon" :name="inputIcon" :size="inputIcon.includes('fas')? '15px': '20px'" class="q-mr-md" min="1"/>
{{inputDataBluePrint.name}}
<q-icon v-if="toolTip" name="mdi-help-circle" size="16px" class="q-ml-md">
<q-icon v-if="toolTip && !disableDocumentToolTips" name="mdi-help-circle" size="16px" class="q-ml-md">
<q-tooltip :delay="500">
<span v-html="toolTip"/>
</q-tooltip>
@ -28,7 +28,8 @@
v-model.number="localInput"
type="number"
@keyup="signalInput"
outlined
:outlined="!isDarkMode"
:filled="isDarkMode"
dense
/>
@ -58,6 +59,16 @@ export default class Field_Number extends BaseClass {
changedInput = false
localInput: null|number = null
isDarkMode = false
disableDocumentToolTips = false
@Watch("SGET_options", { immediate: true, deep: true })
onSettingsChange () {
const options = this.SGET_options
this.isDarkMode = options.darkMode
this.disableDocumentToolTips = options.disableDocumentToolTips
}
@Emit()
signalInput () {
this.changedInput = true

View file

@ -3,14 +3,14 @@
<div class="flex justify-start items-center text-weight-bolder q-mb-sm q-mt-md">
<q-icon v-if="inputIcon" :name="inputIcon" :size="inputIcon.includes('fas')? '15px': '20px'" class="q-mr-md"/>
{{inputDataBluePrint.name}}
<q-icon v-if="toolTip" name="mdi-help-circle" size="16px" class="q-ml-md">
<q-icon v-if="toolTip && !disableDocumentToolTips" name="mdi-help-circle" size="16px" class="q-ml-md">
<q-tooltip :delay="500">
<span v-html="toolTip"/>
</q-tooltip>
</q-icon>
<q-icon v-if="isOneWayRelationship" name="mdi-arrow-right-bold" size="16px" class="q-ml-md">
<q-tooltip :delay="500">
This is a one-way relationship. <br> Editing this value <span class="text-secondary">WILL NOT</span> have effect on the connected document/s.
<q-tooltip :delay="500" v-if="!disableDocumentToolTips">
This is a one-way relationship. <br> Editing this value <span class="text-secondary">WILL NOT</span> have any effect on the connected document/s.
<br>
<br>
Left-clicking the linked document in non-edit mode will open it in new tab and focuses on it.
@ -19,8 +19,8 @@
</q-tooltip>
</q-icon>
<q-icon v-if="!isOneWayRelationship" name="mdi-arrow-left-right-bold" size="16px" class="q-ml-md">
<q-tooltip :delay="500">
This is a two-way relationship. <br> Editing this value <span class="text-secondary">WILL</span> also effect the connected document/s.
<q-tooltip :delay="500" v-if="!disableDocumentToolTips">
This is a two-way relationship. <br> Editing this value <span class="text-secondary">WILL</span> also affect the connected document/s.
<br>
<br>
Left-clicking the linked document in non-edit mode will open it in new tab and focuses on it.
@ -79,8 +79,10 @@
:ref="`singleRelationshipField${inputDataBluePrint.id}`"
:options="filteredInput"
use-input
outlined
input-debounce="0"
@popup-show="reloadAllDocuments"
:outlined="!isDarkMode"
:filled="isDarkMode"
input-debounce="200"
v-model="localInput"
@filter="filterSelect"
@input="signalInput(false)"
@ -102,6 +104,7 @@
<template v-slot:option="{ itemProps, itemEvents, opt }">
<q-item
:class="{'hasTextShadow': textShadow}"
v-bind="itemProps"
v-on="itemEvents"
>
@ -146,7 +149,8 @@
v-model="inputNote.value"
dense
@keyup="signalInput(false)"
outlined
:outlined="!isDarkMode"
:filled="isDarkMode"
>
</q-input>
</td>
@ -197,6 +201,17 @@ export default class Field_SingleRelationship extends BaseClass {
value: ""
}
isDarkMode = false
disableDocumentToolTips = false
textShadow = false
@Watch("SGET_options", { immediate: true, deep: true })
onSettingsChange () {
const options = this.SGET_options
this.isDarkMode = options.darkMode
this.disableDocumentToolTips = options.disableDocumentToolTips
this.textShadow = options.textShadow
}
@Watch("inputDataValue", { deep: true, immediate: true })
reactToInputChanges () {
// @ts-ignore
@ -232,6 +247,12 @@ export default class Field_SingleRelationship extends BaseClass {
/* eslint-enable */
}
allDocuments: I_ShortenedDocument[] = []
async reloadAllDocuments () {
this.allDocuments = await this.retrieveAllDocuments()
}
filterSelect (val: string, update: (e: () => void) => void) {
if (val === "") {
update(() => {
@ -248,7 +269,7 @@ export default class Field_SingleRelationship extends BaseClass {
const needle = val.toLowerCase()
const listCopy : I_ShortenedDocument[] = extend(true, [], this.extraInput)
// @ts-ignore
this.filteredInput = advancedDocumentFilter(needle, listCopy)
this.filteredInput = advancedDocumentFilter(needle, listCopy, this.SGET_allBlueprints, this.allDocuments)
if (this.$refs[`singleRelationshipField${this.inputDataBluePrint.id}`] && this.filteredInput.length > 0) {
this.refocusSelect().catch(e => console.log(e))
@ -316,6 +337,7 @@ export default class Field_SingleRelationship extends BaseClass {
type: objectDoc.type,
icon: objectDoc.icon,
disable: isDisabled,
extraFields: objectDoc.extraFields,
url: `/project/display-content/${objectDoc.type}/${objectDoc._id}`,
label: objectDoc.extraFields.find(e => e.id === "name")?.value,
color: objectDoc.extraFields.find(e => e.id === "documentColor")?.value,

View file

@ -3,7 +3,7 @@
<div class="flex justify-start items-center text-weight-bolder q-mb-sm q-mt-md">
<q-icon v-if="inputIcon" :name="inputIcon" :size="inputIcon.includes('fas')? '15px': '20px'" class="q-mr-md"/>
{{inputDataBluePrint.name}}
<q-icon v-if="toolTip" name="mdi-help-circle" size="16px" class="q-ml-md">
<q-icon v-if="toolTip && !disableDocumentToolTips" name="mdi-help-circle" size="16px" class="q-ml-md">
<q-tooltip :delay="500">
<span v-html="toolTip"/>
</q-tooltip>
@ -32,7 +32,8 @@
class="singleSelect"
:options="extraInput"
use-input
outlined
:outlined="!isDarkMode"
:filled="isDarkMode"
@filter="filterFn"
input-debounce="0"
new-value-mode="add"
@ -77,13 +78,20 @@ import { I_ExtraFields } from "src/interfaces/I_Blueprint"
})
export default class Field_SingleSelect extends BaseClass {
@Prop({ default: [] }) readonly inputDataBluePrint!: I_ExtraFields
@Prop({ default: "" }) readonly inputDataValue!: ""
@Prop() readonly isNew!: boolean
@Prop() readonly editMode!: boolean
isDarkMode = false
disableDocumentToolTips = false
@Watch("SGET_options", { immediate: true, deep: true })
onSettingsChange () {
const options = this.SGET_options
this.isDarkMode = options.darkMode
this.disableDocumentToolTips = options.disableDocumentToolTips
}
changedInput = false
localInput = ""

View file

@ -3,7 +3,7 @@
<div class="flex justify-start items-center text-weight-bolder q-mb-sm q-mt-md">
<q-icon v-if="inputIcon" :name="inputIcon" :size="inputIcon.includes('fas')? '15px': '20px'" class="q-mr-md" min="1"/>
{{inputDataBluePrint.name}}
<q-icon v-if="toolTip" name="mdi-help-circle" size="16px" class="q-ml-md">
<q-icon v-if="toolTip && !disableDocumentToolTips" name="mdi-help-circle" size="16px" class="q-ml-md">
<q-tooltip :delay="500">
<span v-html="toolTip"/>
</q-tooltip>
@ -42,6 +42,16 @@ export default class Field_Switch extends BaseClass {
changedInput = false
localInput: null|boolean = null
isDarkMode = false
disableDocumentToolTips = false
@Watch("SGET_options", { immediate: true, deep: true })
onSettingsChange () {
const options = this.SGET_options
this.isDarkMode = options.darkMode
this.disableDocumentToolTips = options.disableDocumentToolTips
}
@Emit()
signalInput () {
this.changedInput = true

View file

@ -3,7 +3,7 @@
<div class="flex justify-start items-center text-weight-bolder q-mb-sm q-mt-md">
<q-icon v-if="inputIcon" :name="inputIcon" :size="inputIcon.includes('fas')? '15px': '20px'" class="q-mr-md"/>
{{inputDataBluePrint.name}}
<q-icon v-if="toolTip" name="mdi-help-circle" size="16px" class="q-ml-md">
<q-icon v-if="toolTip && !disableDocumentToolTips" name="mdi-help-circle" size="16px" class="q-ml-md">
<q-tooltip :delay="500">
<span v-html="toolTip"/>
</q-tooltip>
@ -15,7 +15,9 @@
>
<q-chip
v-for="(input,index) in localInput" :key="index"
color="gunmetal-light" text-color="satin-sheen-gold-light" class="text-weight-medium">
:color="(isDarkMode) ? 'accent' : 'gunmetal-light'"
:text-color="(isDarkMode) ? 'dark' :'satin-sheen-gold-light'"
:class="(isDarkMode) ? 'text-weight-bold':'text-weight-medium'">
{{input}}
</q-chip>
</div>
@ -31,7 +33,8 @@
class="tagSelect"
:options="filteredTags"
use-input
outlined
:outlined="!isDarkMode"
:filled="isDarkMode"
use-chips
@filter="filterFn"
input-debounce="0"
@ -90,6 +93,16 @@ export default class Field_Tags extends BaseClass {
@Prop() readonly editMode!: boolean
isDarkMode = false
disableDocumentToolTips = false
@Watch("SGET_options", { immediate: true, deep: true })
onSettingsChange () {
const options = this.SGET_options
this.isDarkMode = options.darkMode
this.disableDocumentToolTips = options.disableDocumentToolTips
}
changedInput = false
localInput: string[] = []

View file

@ -3,7 +3,7 @@
<div class="flex justify-start items-center text-weight-bolder q-mb-sm q-mt-md">
<q-icon v-if="inputIcon" :name="inputIcon" :size="inputIcon.includes('fas')? '15px': '20px'" class="q-mr-md"/>
{{inputDataBluePrint.name}}
<q-icon v-if="toolTip" name="mdi-help-circle" size="16px" class="q-ml-md">
<q-icon v-if="toolTip && !disableDocumentToolTips" name="mdi-help-circle" size="16px" class="q-ml-md">
<q-tooltip :delay="500">
<span v-html="toolTip"/>
</q-tooltip>
@ -27,7 +27,8 @@
v-if="editMode"
v-model="localInput"
@keyup="signalInput"
outlined
:outlined="!isDarkMode"
:filled="isDarkMode"
dense
:ref="`textField${this.inputDataBluePrint.id}`"
>
@ -60,6 +61,16 @@ export default class Field_Text extends BaseClass {
@Prop() readonly editMode!: boolean
@Prop() readonly isNew!: boolean
isDarkMode = false
disableDocumentToolTips = false
@Watch("SGET_options", { immediate: true, deep: true })
onSettingsChange () {
const options = this.SGET_options
this.isDarkMode = options.darkMode
this.disableDocumentToolTips = options.disableDocumentToolTips
}
changedInput = false
localInput = ""

View file

@ -1,9 +1,9 @@
<template>
<div>
<div class="flex justify-start items-center text-weight-bolder q-mb-sm q-mt-md">
<div class="flex justify-start items-center text-weight-bolder q-mb-sm q-mt-md fieldWysiwygTitle">
<q-icon v-if="inputIcon" :name="inputIcon" :size="inputIcon.includes('fas')? '15px': '20px'" class="q-mr-md"/>
{{inputDataBluePrint.name}}
<q-icon v-if="toolTip" name="mdi-help-circle" size="16px" class="q-ml-md">
<q-icon v-if="toolTip && !disableDocumentToolTips" name="mdi-help-circle" size="16px" class="q-ml-md">
<q-tooltip :delay="500">
<span v-html="toolTip"/>
</q-tooltip>
@ -11,19 +11,22 @@
</div>
<div
v-if="!editMode"
class="fieldWysiwyg"
v-html="localInput">
v-if="!editMode"
class="fieldWysiwyg"
v-html="localInput">
</div>
<q-editor
v-model="localInput"
:ref="`wysiwygField${this.inputDataBluePrint.id}`"
@paste.native="evt => pasteCapture(evt)"
:toolbar="wysiwygOptions"
:fonts="wysiwygFonts"
@input="signalInput"
:flat="isDarkMode"
v-if="editMode"
:definitions="definitions"
min-height="250px"
min-height="350px"
/>
<div class="separatorWrapper">
@ -53,6 +56,16 @@ export default class Field_Wysiwyg extends BaseClass {
changedInput = false
localInput = ""
isDarkMode = false
disableDocumentToolTips = false
@Watch("SGET_options", { immediate: true, deep: true })
onSettingsChange () {
const options = this.SGET_options
this.isDarkMode = options.darkMode
this.disableDocumentToolTips = options.disableDocumentToolTips
}
@Emit()
signalInput () {
this.changedInput = true
@ -72,6 +85,47 @@ export default class Field_Wysiwyg extends BaseClass {
this.localInput = this.inputDataValue
}
@Watch("editMode")
turnOffFullScreen () {
if (!this.editMode && this.$refs[`wysiwygField${this.inputDataBluePrint.id}`]) {
/*eslint-disable */
// @ts-ignore
this.$refs[`wysiwygField${this.inputDataBluePrint.id}`].exitFullscreen()
/* eslint-enable */
}
}
pasteCapture (evt: any) {
/*eslint-disable */
// Let inputs do their thing, so we don't break pasting of links.
if (evt.target.nodeName === "INPUT") {
return
}
let text, onPasteStripFormattingIEPaste
evt.preventDefault()
if (evt.originalEvent && evt.originalEvent.clipboardData.getData) {
text = evt.originalEvent.clipboardData.getData("text/plain")
// @ts-ignore
this.$refs[`wysiwygField${this.inputDataBluePrint.id}`].runCmd("insertText", text)
}
else if (evt.clipboardData && evt.clipboardData.getData) {
text = evt.clipboardData.getData("text/plain")
// @ts-ignore
this.$refs[`wysiwygField${this.inputDataBluePrint.id}`].runCmd("insertText", text)
}
// @ts-ignore
else if (window.clipboardData && window.clipboardData.getData) {
if (!onPasteStripFormattingIEPaste) {
onPasteStripFormattingIEPaste = true
// @ts-ignore
this.$refs[`wysiwygField${this.inputDataBluePrint.id}`].runCmd("ms-pasteTextOnly", text)
}
onPasteStripFormattingIEPaste = false
}
/* eslint-enable */
}
definitions = {
fullscreen: { label: "Fullscreen" }
}
@ -150,7 +204,7 @@ export default class Field_Wysiwyg extends BaseClass {
<style lang='scss'>
.fieldWysiwyg {
padding-right: 10px;
padding-left: 10px;
padding-top: 15px;
padding-bottom: 15px;
}
</style>

View file

@ -3,15 +3,17 @@
@import './app/customColors.scss';
* {
letter-spacing: 0.025em;
/* width */
::-webkit-scrollbar {
width: 5px;
height: 5px;
}
/* Track */
::-webkit-scrollbar-track {
background: $dark;
background: var(--q-color-dark);
}
/* Handle */
@ -37,14 +39,145 @@ body {
/* WebKit/Blink Browsers */
::selection {
background: lighten($dark, 30);
color: white;
color: lighten($primary, 25);
background: lighten($secondary, 10);
}
/* Gecko Browsers */
::-moz-selection {
background: lighten($dark, 30);
color: white;
color: lighten($primary, 25);
background: lighten($secondary, 10);
}
&.body--dark {
background: #303742;
/* WebKit/Blink Browsers */
::selection {
color: lighten($primary, 25);
background: lighten($secondary, 7);
}
/* Gecko Browsers */
::-moz-selection {
color: lighten($primary, 25);
background: lighten($secondary, 7);
}
.q-editor {
border: none !important;
background-color: #303742;
color: #dcdcdc;
}
.q-field__control {
background-color: transparent;
}
.q-markdown {
color: #d4d0c9;
}
.existingDocumentSelect,
.newDocumentSelect {
.q-field__input,
.q-icon,
.q-field__native span {
color: #d4d0c9 !important;
}
}
.q-editor__content {
background-color: #4c515a;
box-shadow: none;
}
.q-editor__toolbars-container {
background-color: rgba(#000, 0.3);
}
}
}
.q-editor {
background-color: rgba(250, 250, 250, 1);
padding-bottom: 15px;
}
.fieldWysiwyg,
.q-editor__content,
.fieldWysiwygTitle,
.q-editor__toolbars-container {
max-width: 1040px;
margin-left: auto;
margin-right: auto;
width: 100%;
}
.fieldWysiwyg {
font-size: 16px;
text-align: justify;
> div {
margin: 12.5px 0;
}
}
.q-editor__content {
padding: 35px 50px;
line-height: 1.7;
background-color: #fff;
box-shadow: 2px 2px 5px 0 rgba(0, 0, 0, 0.25);
border-radius: 2px;
font-size: 16px;
text-align: justify;
* {
letter-spacing: 0.02em;
}
> div {
margin: 12.5px 0;
}
}
.q-editor__toolbars-container {
padding: 5px;
max-width: 1200px;
margin-bottom: 10px;
margin-top: 10px;
background-color: rgba($dark, 0.05);
}
.q-editor.fullscreen {
top: 40px !important;
height: calc(100vh - 40px) !important;
padding: 10px 0 15px;
.q-editor__content {
padding: 35px 60px;
}
}
.q-editor__toolbar {
border: none;
}
.q-item {
&.hasTextShadow {
$shadowColorOutline: #000;
$shadowColorSurround: #000;
filter: drop-shadow(0 0 4px #000);
font-weight: 500;
text-shadow:
-2px -2px 0 $shadowColorSurround,
2px -2px 0 $shadowColorSurround,
-2px 2px 0 $shadowColorSurround,
2px 2px 0 $shadowColorSurround,
-1px -1px 0 $shadowColorOutline,
1px -1px 0 $shadowColorOutline,
-1px 1px 0 $shadowColorOutline,
1px 1px 0 $shadowColorOutline;
}
}
@ -78,6 +211,14 @@ body {
left: calc(100% - 30px);
}
.existingDocumentPopup,
.newDocumentPopup {
.q-checkbox__bg {
border-color: #d4d0c9 !important;
background: transparent !important;
}
}
.tagSelect,
.singleSelect,
.multiSelect,
@ -120,6 +261,15 @@ body {
}
}
.existingDocumentSelect,
.newDocumentSelect {
.q-field__input,
.q-icon,
.q-field__native span {
color: #d4d0c9 !important;
}
}
.q-menu .q-item {
transition: none !important;
}
@ -151,11 +301,6 @@ body {
padding-top: 6px;
}
.q-editor.fullscreen {
top: 40px !important;
height: calc(100vh - 40px) !important;
}
body .q-item--active:not(.noHigh) {
color: inherit;
@ -192,6 +337,13 @@ body .q-tooltip {
.q-markdown {
line-height: 1.75;
.q-markdown--token {
font-weight: 600;
padding: 0 5px;
background-color: $accent !important;
color: $dark !important;
}
strong {
font-weight: 500;
}
@ -211,3 +363,15 @@ body .q-tooltip {
margin-bottom: 5px;
}
}
.q-splitter__before {
z-index: 2;
}
.q-splitter__after {
z-index: 1;
}
.q-splitter__separator {
z-index: 3;
}

View file

@ -13,7 +13,7 @@
// Tip: Use the "Theme Builder" on Quasar's documentation website.
$primary : #d7ac47;
$secondary : #f72e43;
$secondary : #f75746;
$accent : #dde4e4;
$dark : #18303a;
@ -26,9 +26,10 @@ $warning : #f2c037;
$customColors: (
'gunmetal': #18303a,
'gunmetal-light': lighten(#18303a, 7.5),
'gunmetal-lighter': lighten(#18303a, 10),
'gunmetal-bright': lighten(#18303a, 50),
'gunmetal-shine': lighten(#18303a, 60),
'ruby-red': #f72e43,
'ruby-red': #f75746,
'satin-sheen-gold': #d7ac47,
'satin-sheen-gold-dark': darken(#d7ac47, 12.5),
'satin-sheen-gold-light': lighten(#d7ac47, 7.5),

View file

@ -36,6 +36,9 @@ The search itself works the following: You can search any amount of words and th
Except for the advanced search functionality, Fantasia Archive also offers instant filtering via multiple attributes for further narrowing search results.
- **NOTE: All of the following filter values (including the Full-search filtering in the next section) support matching any part of the search-text with any part of the search-term**
- Example: `>nada` will match with `Continent > North America > Canada > Toronto`
### The filtering works in the following ways and follows these rules
- **Any of the following filter terms will not conflict with the normal word search**
@ -49,4 +52,32 @@ Except for the advanced search functionality, Fantasia Archive also offers insta
- `$` - Symbol for document type search
- `#` - Symbol for tag search
- `>` - Symbol for hierarchical path search
## Full-search filtering
This feature is meant mostly for those in need of full-scale search that can crawl through any field in any document to match any value field in almost anywhere. Full-search filtering allows the user to narrow down the search marginally by digging through the whole document database and pinpointing exactly what is needed.
### A few words of caution
**The full-search is a very powerful, but also demanding tool - the more your project will grow, the more demanding it will become. This means that if you for example have 2000+ documents in your project and the search algorithm will have to go crawl through all of them, then the full-search might take a few second to reload your search results - please keep this in mind when using this feature: It can potentially be A LOT of data.**
### The filtering works in the following ways and follows these rules
- **The full-search can be used in combination with any other filters and/or normal search terms**
- **It is possible to have only a single instance of the full-search present in the search at once**
- **The filter is case-insensitive, which means that you can type everything in UPPER or lower case, it won't matter**
- **In the case of lists and multi-relationships, all the entered values get converted to one big text-line for the sake of searching**
- Example with a field called `Local currencies`:
- Original values: `Canadian Dollar` `American Dollar` `Euro` `Klingon Darsek`
- Converted values: `canadian-dollar-american-dollar-euro-klingon-darsek`
- **The following filter terms must be used inside of the search term**
- `%` - Symbol for the beginning full-search
- `:` - Symbol for the division between the field-name and field value
- **If your filter-term contained whitespaces, replace them with the `-` symbol**
- Example: You wish to search for a field called `Local Currencies` that contains `Canadian Dollars` as value, to fully match this tag, you will need to type `%local-currencies:canadian-dollars`
- **A list of fields/field types the full-search doesn't work with:**
- The `Break` field type (these are the big titles present throughout the document)
- The `Tags` field type (this one is covered with a more sophisticated tag filter)
- The `Switch` field type (this one doesn't contain any text values to even filter)
- The `Name` field (this one is the main concern of the search and the normal search is far more advanced for searching through this one)
- The `Belongs under` field (this one is covered by a much more advanced hierarchical path search)

View file

@ -3,6 +3,67 @@
---
## 0.1.4
### Known issues
- When creating a brand new project, Fantasia Archive sometimes doesn't load the default categories in the left hierarchical tree. A temporary workaround before the issue is fixed is restarting the program - the project stays intact, can be normally edited no data loss occurs.
### Bugfixes
- Fixed a bug that was preventing the text editor field from closing the full-screen view upon saving via the CTRL+S shortcut while in the full-screen mode.
- Fixed a bug that was causing top level documents to randomly expand their respect document type when opened in the active tab list
- Fixed a small bug causing newly created documents to "bounce around" or scroll roughly to the half of the document on their own
- Fixed a bug with filter via the document type that was cuasing the filter to search by document type ID instead of the actual name
- Fixed a bug where the big text editor field was also copying input text styles (colors, backgrounds, fonts)
- Globally changed a typo in "Connected to myths. legends and stories" field
- Globally changed a typo in the tooltip of "Tags" field
- Globally changed a typo in the tooltip of "Scientifical" to "Scientific"
- Reworded and fixed typos in the Single and Mutli relationship field tooltips
- Fixed horizontal scrollbar looks and functionality
- Fixed a bug that was causing keybinds to register and affect the UI even if a popup was opened over it
### New features
- Added a custom keybind support to the app
- Added a resizeable hieararchical tree for all your categorical needs
- The app also remembers the tree-size on restart so your prefered width gets transfered between your world-building sessions
- Added dark mode
- Restyled and pimped-up text editor field to replace most of your MS-word needs (obviously supports both light and dark modes properly)!
- Added a specific field/value support for the relationship and quick-search popups
- This also means added suppoirt for filtering by document color
- Added automatic sub-category closure in the hierarchical tree when closing the parent category
- Added new App option keybind
- Added App options
- Added option: Dark mode
- Added option: Accessibility - Text shadow
- Added option: Disable document control bar
- Added option: Disable document guides
- Added option: Disable document tooltips
- Added option: Hide empty fields
- Added option: Stop quick-search close after selection
- Added option: Don't precheck category filter
- Added option: Close quick popups with same key
- Added option: Stop sublevel collapse in tree
- Added option: Hide project name in tree
- Added option: Invert tree custom order sorting
- Added option: Hide tags in tree
- Added option: Top tags in tree
- Added option: Compact tags
### QoL adjustments
- Globally renamed "Color" field to "Text Color" in order to allow better filtering via field-search for future addition of background color support
- Added a more contrasting text-select colors for dark mode
- Added Quick add/search popup functionality to the Project menu
- Added icons to the app menus
- Added a small debounce timer to the relationship searches in order to reduce the of lag it was causing
- Lightly touched up on the color scheme
- Increased readability of highlit bits of the Advanced search guide
- Added an auto-select of the newly added field upon adding new text items in the list field-type
---
## 0.1.3
### Known issues

5
src/globals.d.ts vendored
View file

@ -2,3 +2,8 @@ declare module "*.md"{
const content: string
export default content
}
declare module "*.png"{
const content: string
export default content
}

View file

@ -2,7 +2,7 @@ export interface I_KeyPressObject {
altKey: boolean
ctrlKey: boolean
shiftKey: boolean
keyCode: number
which: number
editable?: boolean
id?: string
tooltip?: string

View file

@ -1,48 +1,62 @@
<template>
<q-layout view="hHh LpR lfr">
<!-- Left drawer -->
<q-drawer
content-class="bg-dark text-cultured sideWrapper"
v-model="leftDrawerOpen"
side="left"
:width=375
show-if-above
>
<objectTree/>
</q-drawer>
<!-- Header -->
<appHeader
:is-project="true"
/>
<q-page-container>
<documentControl/>
<transition
enter-active-class="animated fadeIn"
leave-active-class="animated fadeOut"
appear
:duration="150"
<q-splitter
v-model="splitterModel"
unit="px"
emit-immediately
:class="splitterClass"
@input="onChange"
:limits="[374, Infinity]"
class="pageSplitter"
>
<router-view :key="$route.path" />
</transition>
</q-page-container>
<template #before>
<!-- Left drawer -->
<q-drawer
content-class="bg-dark text-cultured sideWrapper"
v-model="leftDrawerOpen"
side="left"
:width="drawerWidth"
:breakpoint="0"
show-if-above
>
<objectTree/>
</q-drawer>
</template>
<template #after>
<q-page-container :style="compPadding">
<documentControl/>
<transition
enter-active-class="animated fadeIn"
leave-active-class="animated fadeOut"
appear
:duration="150"
>
<router-view :key="$route.path" />
</transition>
</q-page-container>
</template>
</q-splitter>
</q-layout>
</template>
<script lang="ts">
import { Component } from "vue-property-decorator"
import { Component, Watch } from "vue-property-decorator"
import BaseClass from "src/BaseClass"
import objectTree from "src/components/ObjectTree.vue"
import appHeader from "src/components/AppHeader.vue"
import documentControl from "src/components/DocumentControl.vue"
import { extend } from "quasar"
import { OptionsStateInteface } from "src/store/module-options/state"
@Component({
components: {
objectTree,
@ -59,7 +73,48 @@ export default class DocumentLayout extends BaseClass {
* Model for the left drawer of the app containing the hierarchical tree
*/
leftDrawerOpen = true
splitterModel = 374
disableDocumentToolTips = false
@Watch("SGET_options", { immediate: true, deep: true })
onSettingsChange () {
const options = this.SGET_options
if (options.treeWidth) {
this.splitterModel = options.treeWidth
}
}
get drawerWidth () {
return this.splitterModel + 1
}
get splitterClass () {
return !this.leftDrawerOpen ? "splitt" : ""
}
get compPadding () {
return this.leftDrawerOpen ? { paddingLeft: "0px" } : ""
}
pullTimer = null as any
onChange (value: number) {
this.leftDrawerOpen = value > 0
const optionsSnapShot: OptionsStateInteface = extend(true, {}, this.SGET_options)
optionsSnapShot.treeWidth = this.splitterModel
clearTimeout(this.pullTimer)
this.pullTimer = setTimeout(() => {
this.SSET_options(optionsSnapShot)
}, 500)
}
}
</script>
<style lang="scss">
@ -67,4 +122,25 @@ export default class DocumentLayout extends BaseClass {
outline: none !important;
}
.splitt {
.q-splitter__before {
transition: width 0.2s ease-out;
width: 0 !important;
}
}
.pageSplitter {
aside {
height: calc(100% - 55px) !important;
margin-top: 55px !important;
}
.q-splitter__separator {
background-color: transparent;
height: calc(100vh - 95px);
bottom: 0;
top: 95px;
}
}
</style>

View file

@ -1,13 +1,100 @@
<template>
<q-page class="q-pa-xl"
<q-page
class="documentDisplay"
:class="{
'q-pb-xl q-pl-xl q-pr-xl': disableDocumentControlBar,
'q-pa-xl': !disableDocumentControlBar,
'hiddenFields': hideEmptyFields
}"
v-if="bluePrintData"
>
<!-- Delele document dialog -->
<deleteDocumentCheckDialog
:dialog-trigger="deleteObjectDialogTrigger"
@trigger-dialog-close="deleteObjectDialogClose"
/>
<div class="row justify-start q-col-gutter-x-xl">
<div
class="flex justify-end localControlRow"
v-if="disableDocumentControlBar"
>
<q-btn
:color="(hasEdits) ? 'teal-14' : 'primary'"
icon="mdi-content-save"
@click="saveCurrentDocument"
:outline="isDarkMode"
class="q-mr-md"
v-if="editMode"
>
<q-tooltip
:delay="500"
anchor="bottom middle"
self="top middle"
>
Save current document
</q-tooltip>
</q-btn>
<q-btn
color="primary"
icon="mdi-file-document-edit"
@click="toggleEditMode"
:outline="isDarkMode"
class="q-mr-md"
v-if="!editMode"
>
<q-tooltip
:delay="500"
anchor="bottom middle"
self="top middle"
>
Edit current document
</q-tooltip>
</q-btn>
<q-btn
color="primary"
icon="mdi-file-tree"
@click="addNewUnderParent"
:outline="isDarkMode"
class="q-mr-md"
v-if="!currentData.isNew"
>
<q-tooltip
:delay="500"
anchor="bottom middle"
self="top middle"
>
Add new document with the current one as parent
</q-tooltip>
</q-btn>
<q-btn
color="secondary"
icon="mdi-text-box-remove-outline"
:outline="isDarkMode"
@click="deleteObjectAssignUID"
v-if="!currentData.isNew"
>
<q-tooltip
:delay="500"
anchor="bottom left"
self="top middle"
>
Delete current document
</q-tooltip>
</q-btn>
</div>
<div
:class="`col-${field.sizing} q-mb-md`"
v-for="field in bluePrintData.extraFields"
:key="field.id"
v-show="determineFieldValue(field) || editMode"
>
<Field_Break
@ -149,6 +236,7 @@ import PouchDB from "pouchdb"
import { extend } from "quasar"
import { saveDocument } from "src/scripts/databaseManager/documentManager"
import deleteDocumentCheckDialog from "src/components/dialogs/DeleteDocumentCheck.vue"
import Field_Break from "src/components/fields/Field_Break.vue"
import Field_Text from "src/components/fields/Field_Text.vue"
@ -176,7 +264,9 @@ import Field_Tags from "src/components/fields/Field_Tags.vue"
Field_SingleRelationship,
Field_MultiRelationship,
Field_Wysiwyg,
Field_Tags
Field_Tags,
deleteDocumentCheckDialog
}
})
@ -185,15 +275,35 @@ export default class PageDocumentDisplay extends BaseClass {
* Watches on changes of the route in order to load proper blueprint and object data
*/
@Watch("$route", { immediate: true, deep: true })
onUrlChange () {
this.reloadLocalContent().catch(e => console.log(e))
async onUrlChange () {
await this.sleep(50)
window.scrollTo({ top: 0, behavior: "auto" })
await this.reloadLocalContent().catch(e => console.log(e))
window.scrollTo({ top: 0, behavior: "auto" })
}
hasEdits = false
checkHasEdits () {
const currentDocument = this.findRequestedOrActiveDocument()
if (currentDocument && currentDocument.hasEdits) {
this.hasEdits = true
}
else {
this.hasEdits = false
}
}
/**
* Watches on changes of the opened documents in order to load proper blueprint and object data
*/
@Watch("SGET_allOpenedDocuments", { deep: true })
@Watch("SGET_allOpenedDocuments", { immediate: true, deep: true })
async onDocChange () {
this.checkHasEdits()
await this.sleep(300)
const matchingDoc = this.findRequestedOrActiveDocument()
@ -202,6 +312,18 @@ export default class PageDocumentDisplay extends BaseClass {
}
}
disableDocumentControlBar = false
isDarkMode = false
hideEmptyFields = false
@Watch("SGET_options", { immediate: true, deep: true })
onSettingsChange () {
const options = this.SGET_options
this.disableDocumentControlBar = options.disableDocumentControlBar
this.isDarkMode = options.darkMode
this.hideEmptyFields = options.hideEmptyFields
}
async reloadLocalContent () {
// Determine the type and retrieve the right blueprint
this.bluePrintData = this.retrieveDocumentBlueprint()
@ -545,9 +667,100 @@ export default class PageDocumentDisplay extends BaseClass {
fieldLimiter (currentFieldID: string) {
const isCategory = this.retrieveFieldValue(this.currentData, "categorySwitch")
const ignoredList = ["breakBasic", "name", "documentColor", "parentDoc", "order", "categorySwitch"]
const ignoredList = ["breakBasic", "name", "documentColor", "parentDoc", "order", "categorySwitch", "tags"]
return (!isCategory || ignoredList.includes(currentFieldID))
}
/****************************************************************/
// Delete dialog
/****************************************************************/
deleteObjectDialogTrigger: string | false = false
deleteObjectDialogClose () {
this.deleteObjectDialogTrigger = false
}
deleteObjectAssignUID () {
this.deleteObjectDialogTrigger = this.generateUID()
}
/****************************************************************/
// Add new document under parent
/****************************************************************/
addNewUnderParent () {
const currentDoc = this.findRequestedOrActiveDocument()
if (currentDoc) {
const routeObject = {
_id: currentDoc.type,
parent: currentDoc._id
}
// @ts-ignore
this.addNewObjectRoute(routeObject)
}
}
/****************************************************************/
// Toggle edit mode & Save document
/****************************************************************/
toggleEditMode () {
const currentDoc = this.findRequestedOrActiveDocument()
if (currentDoc && !currentDoc.editMode) {
const dataCopy: I_OpenedDocument = extend(true, {}, currentDoc)
dataCopy.editMode = true
const dataPass = { doc: dataCopy, treeAction: false }
this.SSET_updateOpenedDocument(dataPass)
}
}
async saveCurrentDocument () {
if (document.activeElement) {
(document.activeElement as HTMLElement).blur()
}
const currentDoc = this.findRequestedOrActiveDocument()
const allDocuments = this.SGET_allOpenedDocuments
const docCopy: I_OpenedDocument[] = extend(true, [], allDocuments.docs)
if (currentDoc) {
// @ts-ignore
const savedDocument: {
documentCopy: I_OpenedDocument,
allOpenedDocuments: I_OpenedDocument[]
} = await saveDocument(currentDoc, docCopy)
// Update the opened document
const dataPass = { doc: savedDocument.documentCopy, treeAction: true }
this.SSET_updateOpenedDocument(dataPass)
// Update all others
for (const doc of savedDocument.allOpenedDocuments) {
// Update the opened document
const dataPass = { doc: doc, treeAction: true }
this.SSET_updateOpenedDocument(dataPass)
}
}
}
determineFieldValue (field: any) {
if (!this.hideEmptyFields) {
return true
}
const value = this.retrieveFieldValue(this.currentData, field.id)
if (!value ||
(Array.isArray(value) && value.length === 0) ||
// @ts-ignore
(value?.value && value.value.length === 0) ||
// @ts-ignore
(value.value === null)) {
return false
}
return true
}
}
</script>
@ -573,4 +786,96 @@ export default class PageDocumentDisplay extends BaseClass {
.q-field {
max-width: 100%;
}
.documentDisplay {
&.hiddenFields {
padding-top: 105px;
}
.localControlRow {
position: absolute;
right: 48px;
top: 50px;
}
/* WebKit/Blink Browsers */
::selection {
background: lighten($dark, 30);
color: white;
}
/* Gecko Browsers */
::-moz-selection {
background: lighten($dark, 30);
color: white;
}
}
body.body--dark {
.documentDisplay {
/* WebKit/Blink Browsers */
::selection {
color: lighten($primary, 25);
background: lighten($secondary, 7);
}
/* Gecko Browsers */
::-moz-selection {
color: lighten($primary, 25);
background: lighten($secondary, 7);
}
$darkModeText: #dcdcdc;
color: $darkModeText;
.connectionList .connectionNote,
.listNote {
color: $darkModeText;
opacity: 0.9;
}
.q-list--dark,
.q-item--dark,
.q-field--dark .q-field__native,
.q-field--dark .q-field__prefix,
.q-field--dark .q-field__suffix,
.q-field--dark .q-field__input {
color: $darkModeText;
}
.q-separator {
opacity: 0.85;
background-color: $primary !important;
}
.q-field--dark .q-field__control::before {
background-color: rgba(255, 255, 255, 0.1);
opacity: 0.6;
border: none;
}
.tagSelect,
.singleSelect,
.multiSelect,
.singleRelashionshipSelect,
.multiRelashionshipSelect,
.existingDocumentSelect,
.newDocumentSelect {
&.q-field--dark .q-field__control::before {
border: none;
}
.q-field__input,
.q-icon,
.q-field__native span {
color: $darkModeText !important;
&.q-chip__icon--remove {
color: #000 !important;
}
}
}
}
}
</style>

View file

@ -22,6 +22,7 @@
v-if="projectExists"
color="primary"
size="md"
:outline="isDarkMode"
class="q-px-xl q-py-xs"
to="/project"
>
@ -33,6 +34,7 @@
<q-btn
color="primary"
size="md"
:outline="isDarkMode"
class="q-px-xl q-py-xs"
@click="newProjectAssignUID"
>
@ -43,6 +45,7 @@
<div class="col-12 q-mb-lg">
<q-btn
color="primary"
:outline="isDarkMode"
size="md"
class="q-px-xl q-py-xs"
@click="importProjectAssignUID()"
@ -55,7 +58,7 @@
</template>
<script lang="ts">
import { Component } from "vue-property-decorator"
import { Component, Watch } from "vue-property-decorator"
import BaseClass from "src/BaseClass"
import importProjectCheckDialog from "src/components/dialogs/ImportProjectCheck.vue"
@ -71,6 +74,14 @@ import { retrieveCurrentProjectName } from "src/scripts/projectManagement/projec
}
})
export default class WelcomeScreen extends BaseClass {
isDarkMode = false
@Watch("SGET_options", { immediate: true, deep: true })
onSettingsChange () {
const options = this.SGET_options
this.isDarkMode = options.darkMode
}
projectExists: undefined | string | boolean = false
newProjectName = ""
newProjectDialog = false

View file

@ -5,18 +5,29 @@ export const defaultKeybinds = [
altKey: true,
ctrlKey: true,
shiftKey: false,
keyCode: 75,
which: 75,
editable: true,
id: "openKeybindsCheatsheet",
tooltip: "Open keybind cheatsheet"
},
// Open app options - CTRL + ALT + J
{
altKey: true,
ctrlKey: true,
shiftKey: false,
which: 74,
editable: true,
id: "openAppOptions",
tooltip: "Open Fantasia Archive options"
},
// Quick new document - CTRL + N
{
altKey: false,
ctrlKey: true,
shiftKey: false,
keyCode: 78,
which: 78,
editable: true,
id: "quickNewDocument",
tooltip: "Quick-add new document"
@ -27,7 +38,7 @@ export const defaultKeybinds = [
altKey: false,
ctrlKey: true,
shiftKey: false,
keyCode: 81,
which: 81,
editable: true,
id: "quickExistingDocument",
tooltip: "Quick-search existing document"
@ -38,7 +49,7 @@ export const defaultKeybinds = [
altKey: false,
ctrlKey: true,
shiftKey: true,
keyCode: 81,
which: 81,
editable: true,
id: "focusHierarchicalTree",
tooltip: "Focus search field in the left hierarchical tree"
@ -49,10 +60,10 @@ export const defaultKeybinds = [
altKey: false,
ctrlKey: true,
shiftKey: true,
keyCode: 87,
which: 87,
editable: true,
id: "clearInputHierarchicalTree",
tooltip: "Clears any input in the search field in the left hierarchical tree"
tooltip: "Clear any input in the search field in the left hierarchical tree"
},
// Close tab - CTRL + W
@ -60,7 +71,7 @@ export const defaultKeybinds = [
altKey: false,
ctrlKey: true,
shiftKey: false,
keyCode: 87,
which: 87,
editable: true,
id: "closeTab",
tooltip: "Close active document"
@ -71,7 +82,7 @@ export const defaultKeybinds = [
altKey: true,
ctrlKey: false,
shiftKey: false,
keyCode: 39,
which: 39,
editable: true,
id: "nextTab",
tooltip: "Next tab"
@ -82,7 +93,7 @@ export const defaultKeybinds = [
altKey: true,
ctrlKey: false,
shiftKey: false,
keyCode: 37,
which: 37,
editable: true,
id: "previousTab",
tooltip: "Previous tab"
@ -93,7 +104,7 @@ export const defaultKeybinds = [
altKey: false,
ctrlKey: true,
shiftKey: false,
keyCode: 83,
which: 83,
editable: true,
id: "saveDocument",
tooltip: "Save active document"
@ -104,18 +115,29 @@ export const defaultKeybinds = [
altKey: false,
ctrlKey: true,
shiftKey: false,
keyCode: 69,
which: 69,
editable: true,
id: "editDocument",
tooltip: "Edit active document"
},
// Edit document - CTRL + SHIFT + N
{
altKey: false,
ctrlKey: true,
shiftKey: true,
which: 78,
editable: true,
id: "addUnderParent",
tooltip: "Add a new document with currently opened one as parent"
},
// Delete document - CTRL + D
{
altKey: false,
ctrlKey: true,
shiftKey: false,
keyCode: 68,
which: 68,
editable: true,
id: "deleteDocument",
tooltip: "Delete active document"
@ -126,7 +148,7 @@ export const defaultKeybinds = [
altKey: false,
ctrlKey: false,
shiftKey: false,
keyCode: 9,
which: 9,
editable: false,
id: "nextFocus",
tooltip: "Focuses next input field/input element/hierarchical tree node",
@ -138,7 +160,7 @@ export const defaultKeybinds = [
altKey: false,
ctrlKey: false,
shiftKey: true,
keyCode: 9,
which: 9,
editable: false,
id: "previousFocus",
tooltip: "Focuses previous input field/input element/hierarchical tree node",
@ -150,7 +172,7 @@ export const defaultKeybinds = [
altKey: false,
ctrlKey: false,
shiftKey: false,
keyCode: 13,
which: 13,
editable: false,
id: "openTreeNode",
tooltip: "Open the focused document in the left hierarchical tree",
@ -162,7 +184,7 @@ export const defaultKeybinds = [
altKey: false,
ctrlKey: false,
shiftKey: false,
keyCode: 32,
which: 32,
editable: false,
id: "collapseExpandeTreeNode",
tooltip: "Collapse or open the focused category in the left hierarchical tree",

View file

@ -21,7 +21,7 @@ export const chaptersBlueprint: I_Blueprint = {
},
{
id: "documentColor",
name: "Color",
name: "Text Color",
type: "colorPicker",
icon: "mdi-eyedropper",
tooltip:
@ -80,7 +80,7 @@ export const chaptersBlueprint: I_Blueprint = {
<br>
This limitation also applies to any variation of lower or upper case iterations of the same tag.
<br>
Example: A tag called "Player Party" will be consider the same tag as "player party", "PlAyER PaRtY" or any similar.
Example: A tag called "Player Party" will be considered the same tag as "player party", "PlAyER PaRtY" or anything similar.
`,
sizing: 12
},

View file

@ -21,7 +21,7 @@ export const charactersBlueprint: I_Blueprint = {
},
{
id: "documentColor",
name: "Color",
name: "Text Color",
type: "colorPicker",
icon: "mdi-eyedropper",
tooltip:
@ -80,7 +80,7 @@ export const charactersBlueprint: I_Blueprint = {
<br>
This limitation also applies to any variation of lower or upper case iterations of the same tag.
<br>
Example: A tag called "Player Party" will be consider the same tag as "player party", "PlAyER PaRtY" or any similar.
Example: A tag called "Player Party" will be considered the same tag as "player party", "PlAyER PaRtY" or anything similar.
`,
sizing: 12
},
@ -1199,7 +1199,7 @@ export const charactersBlueprint: I_Blueprint = {
},
{
id: "pairedConnectionTechGroup",
name: "Connected to scientifical/technological groups",
name: "Connected to scientific/technological groups",
type: "manyToManyRelationship",
icon: "fas fa-wrench",
sizing: 6,
@ -1210,7 +1210,7 @@ export const charactersBlueprint: I_Blueprint = {
},
{
id: "pairedBelongingTechGroup",
name: "Member of scientifical/technological groups",
name: "Member of scientific/technological groups",
type: "manyToManyRelationship",
icon: "fas fa-wrench",
sizing: 6,
@ -1221,7 +1221,7 @@ export const charactersBlueprint: I_Blueprint = {
},
{
id: "pairedAllyTechGroup",
name: "Ally of scientifical/technological groups",
name: "Ally of scientific/technological groups",
type: "manyToManyRelationship",
icon: "fas fa-wrench",
sizing: 6,
@ -1232,7 +1232,7 @@ export const charactersBlueprint: I_Blueprint = {
},
{
id: "pairedEnemyTechGroup",
name: "Enemy of scientifical/technological groups",
name: "Enemy of scientific/technological groups",
type: "manyToManyRelationship",
icon: "fas fa-wrench",
sizing: 6,
@ -1271,7 +1271,7 @@ export const charactersBlueprint: I_Blueprint = {
},
{
id: "pairedConnectedMyths",
name: "Connected to myths. legends and stories",
name: "Connected to myths, legends and stories",
type: "manyToManyRelationship",
icon: "fas fa-journal-whills",
sizing: 6,

View file

@ -21,7 +21,7 @@ export const currenciesBlueprint: I_Blueprint = {
},
{
id: "documentColor",
name: "Color",
name: "Text Color",
type: "colorPicker",
icon: "mdi-eyedropper",
tooltip:
@ -80,7 +80,7 @@ export const currenciesBlueprint: I_Blueprint = {
<br>
This limitation also applies to any variation of lower or upper case iterations of the same tag.
<br>
Example: A tag called "Player Party" will be consider the same tag as "player party", "PlAyER PaRtY" or any similar.
Example: A tag called "Player Party" will be considered the same tag as "player party", "PlAyER PaRtY" or anything similar.
`,
sizing: 12
},

View file

@ -21,7 +21,7 @@ export const eventsBlueprint: I_Blueprint = {
},
{
id: "documentColor",
name: "Color",
name: "Text Color",
type: "colorPicker",
icon: "mdi-eyedropper",
tooltip:
@ -80,7 +80,7 @@ export const eventsBlueprint: I_Blueprint = {
<br>
This limitation also applies to any variation of lower or upper case iterations of the same tag.
<br>
Example: A tag called "Player Party" will be consider the same tag as "player party", "PlAyER PaRtY" or any similar.
Example: A tag called "Player Party" will be considered the same tag as "player party", "PlAyER PaRtY" or anything similar.
`,
sizing: 12
},
@ -251,7 +251,7 @@ export const eventsBlueprint: I_Blueprint = {
},
{
id: "connectedTech",
name: "Connected tech/scientifical groups",
name: "Connected tech/scientific groups",
type: "manyToManyRelationship",
icon: "fas fa-wrench",
sizing: 6,

View file

@ -21,7 +21,7 @@ export const itemsBlueprint: I_Blueprint = {
},
{
id: "documentColor",
name: "Color",
name: "Text Color",
type: "colorPicker",
icon: "mdi-eyedropper",
tooltip:
@ -80,7 +80,7 @@ export const itemsBlueprint: I_Blueprint = {
<br>
This limitation also applies to any variation of lower or upper case iterations of the same tag.
<br>
Example: A tag called "Player Party" will be consider the same tag as "player party", "PlAyER PaRtY" or any similar.
Example: A tag called "Player Party" will be considered the same tag as "player party", "PlAyER PaRtY" or anything similar.
`,
sizing: 12
},

View file

@ -21,7 +21,7 @@ export const languagesBlueprint: I_Blueprint = {
},
{
id: "documentColor",
name: "Color",
name: "Text Color",
type: "colorPicker",
icon: "mdi-eyedropper",
tooltip:
@ -80,7 +80,7 @@ export const languagesBlueprint: I_Blueprint = {
<br>
This limitation also applies to any variation of lower or upper case iterations of the same tag.
<br>
Example: A tag called "Player Party" will be consider the same tag as "player party", "PlAyER PaRtY" or any similar.
Example: A tag called "Player Party" will be considered the same tag as "player party", "PlAyER PaRtY" or anything similar.
`,
sizing: 12
},

View file

@ -21,7 +21,7 @@ export const locationsBlueprint: I_Blueprint = {
},
{
id: "documentColor",
name: "Color",
name: "Text Color",
type: "colorPicker",
icon: "mdi-eyedropper",
tooltip:
@ -80,7 +80,7 @@ export const locationsBlueprint: I_Blueprint = {
<br>
This limitation also applies to any variation of lower or upper case iterations of the same tag.
<br>
Example: A tag called "Player Party" will be consider the same tag as "player party", "PlAyER PaRtY" or any similar.
Example: A tag called "Player Party" will be considered the same tag as "player party", "PlAyER PaRtY" or anything similar.
`,
sizing: 12
},
@ -315,7 +315,7 @@ export const locationsBlueprint: I_Blueprint = {
},
{
id: "governTech",
name: "Governing tech/scientifical groups",
name: "Governing tech/scientific groups",
type: "manyToManyRelationship",
icon: "fas fa-wrench",
sizing: 6,
@ -326,7 +326,7 @@ export const locationsBlueprint: I_Blueprint = {
},
{
id: "connectedTech",
name: "Connected tech/scientifical groups",
name: "Connected tech/scientific groups",
type: "manyToManyRelationship",
icon: "fas fa-wrench",
sizing: 6,
@ -354,7 +354,7 @@ export const locationsBlueprint: I_Blueprint = {
},
{
id: "pairedConnectedMyths",
name: "Connected to myths. legends and stories",
name: "Connected to myths, legends and stories",
type: "manyToManyRelationship",
icon: "fas fa-journal-whills",
sizing: 4,

View file

@ -21,7 +21,7 @@ export const loreNotesBlueprint: I_Blueprint = {
},
{
id: "documentColor",
name: "Color",
name: "Text Color",
type: "colorPicker",
icon: "mdi-eyedropper",
tooltip:
@ -80,7 +80,7 @@ export const loreNotesBlueprint: I_Blueprint = {
<br>
This limitation also applies to any variation of lower or upper case iterations of the same tag.
<br>
Example: A tag called "Player Party" will be consider the same tag as "player party", "PlAyER PaRtY" or any similar.
Example: A tag called "Player Party" will be considered the same tag as "player party", "PlAyER PaRtY" or anything similar.
`,
sizing: 12
},

View file

@ -21,7 +21,7 @@ export const magicBlueprint: I_Blueprint = {
},
{
id: "documentColor",
name: "Color",
name: "Text Color",
type: "colorPicker",
icon: "mdi-eyedropper",
tooltip:
@ -80,7 +80,7 @@ export const magicBlueprint: I_Blueprint = {
<br>
This limitation also applies to any variation of lower or upper case iterations of the same tag.
<br>
Example: A tag called "Player Party" will be consider the same tag as "player party", "PlAyER PaRtY" or any similar.
Example: A tag called "Player Party" will be considered the same tag as "player party", "PlAyER PaRtY" or anything similar.
`,
sizing: 12
},
@ -405,7 +405,7 @@ export const magicBlueprint: I_Blueprint = {
{
id: "pairedConnectedTechGroups",
name: "Connected scientifical/technological groups/teachings",
name: "Connected scientific/technological groups/teachings",
type: "manyToManyRelationship",
icon: "fas fa-wrench",
sizing: 4,
@ -416,7 +416,7 @@ export const magicBlueprint: I_Blueprint = {
},
{
id: "pairedAllyTechGroups",
name: "Allied scientifical/technological groups/teachings",
name: "Allied scientific/technological groups/teachings",
type: "manyToManyRelationship",
icon: "fas fa-wrench",
sizing: 4,
@ -427,7 +427,7 @@ export const magicBlueprint: I_Blueprint = {
},
{
id: "pairedEnemyTechGroups",
name: "Enemy scientifical/technological groups/teachings",
name: "Enemy scientific/technological groups/teachings",
type: "manyToManyRelationship",
icon: "fas fa-wrench",
sizing: 4,
@ -455,7 +455,7 @@ export const magicBlueprint: I_Blueprint = {
},
{
id: "pairedConnectedMyths",
name: "Connected to myths. legends and stories",
name: "Connected to myths, legends and stories",
type: "manyToManyRelationship",
icon: "fas fa-journal-whills",
sizing: 4,

View file

@ -21,7 +21,7 @@ export const mythsBlueprint: I_Blueprint = {
},
{
id: "documentColor",
name: "Color",
name: "Text Color",
type: "colorPicker",
icon: "mdi-eyedropper",
tooltip:
@ -80,7 +80,7 @@ export const mythsBlueprint: I_Blueprint = {
<br>
This limitation also applies to any variation of lower or upper case iterations of the same tag.
<br>
Example: A tag called "Player Party" will be consider the same tag as "player party", "PlAyER PaRtY" or any similar.
Example: A tag called "Player Party" will be considered the same tag as "player party", "PlAyER PaRtY" or anything similar.
`,
sizing: 12
},

View file

@ -21,7 +21,7 @@ export const politicalGroupsBlueprint: I_Blueprint = {
},
{
id: "documentColor",
name: "Color",
name: "Text Color",
type: "colorPicker",
icon: "mdi-eyedropper",
tooltip:
@ -80,7 +80,7 @@ export const politicalGroupsBlueprint: I_Blueprint = {
<br>
This limitation also applies to any variation of lower or upper case iterations of the same tag.
<br>
Example: A tag called "Player Party" will be consider the same tag as "player party", "PlAyER PaRtY" or any similar.
Example: A tag called "Player Party" will be considered the same tag as "player party", "PlAyER PaRtY" or anything similar.
`,
sizing: 12
},
@ -368,7 +368,7 @@ export const politicalGroupsBlueprint: I_Blueprint = {
{
id: "pairedConnectedTechGroups",
name: "Connected scientifical/technological groups/teachings",
name: "Connected scientific/technological groups/teachings",
type: "manyToManyRelationship",
icon: "fas fa-wrench",
sizing: 4,
@ -379,7 +379,7 @@ export const politicalGroupsBlueprint: I_Blueprint = {
},
{
id: "pairedAllyTechGroups",
name: "Allied scientifical/technological groups/teachings",
name: "Allied scientific/technological groups/teachings",
type: "manyToManyRelationship",
icon: "fas fa-wrench",
sizing: 4,
@ -390,7 +390,7 @@ export const politicalGroupsBlueprint: I_Blueprint = {
},
{
id: "pairedEnemyTechGroups",
name: "Enemy scientifical/technological groups/teachings",
name: "Enemy scientific/technological groups/teachings",
type: "manyToManyRelationship",
icon: "fas fa-wrench",
sizing: 4,
@ -418,7 +418,7 @@ export const politicalGroupsBlueprint: I_Blueprint = {
},
{
id: "pairedConnectedMyths",
name: "Connected to myths. legends and stories",
name: "Connected to myths, legends and stories",
type: "manyToManyRelationship",
icon: "fas fa-journal-whills",
sizing: 4,

View file

@ -21,7 +21,7 @@ export const racesBlueprint: I_Blueprint = {
},
{
id: "documentColor",
name: "Color",
name: "Text Color",
type: "colorPicker",
icon: "mdi-eyedropper",
tooltip:
@ -80,7 +80,7 @@ export const racesBlueprint: I_Blueprint = {
<br>
This limitation also applies to any variation of lower or upper case iterations of the same tag.
<br>
Example: A tag called "Player Party" will be consider the same tag as "player party", "PlAyER PaRtY" or any similar.
Example: A tag called "Player Party" will be considered the same tag as "player party", "PlAyER PaRtY" or anything similar.
`,
sizing: 12
},
@ -426,7 +426,7 @@ export const racesBlueprint: I_Blueprint = {
},
{
id: "pairedConnectedMyths",
name: "Connected to myths. legends and stories",
name: "Connected to myths, legends and stories",
type: "manyToManyRelationship",
icon: "fas fa-journal-whills",
sizing: 4,

View file

@ -21,7 +21,7 @@ export const religionsBlueprint: I_Blueprint = {
},
{
id: "documentColor",
name: "Color",
name: "Text Color",
type: "colorPicker",
icon: "mdi-eyedropper",
tooltip:
@ -80,7 +80,7 @@ export const religionsBlueprint: I_Blueprint = {
<br>
This limitation also applies to any variation of lower or upper case iterations of the same tag.
<br>
Example: A tag called "Player Party" will be consider the same tag as "player party", "PlAyER PaRtY" or any similar.
Example: A tag called "Player Party" will be considered the same tag as "player party", "PlAyER PaRtY" or anything similar.
`,
sizing: 12
},
@ -360,7 +360,7 @@ export const religionsBlueprint: I_Blueprint = {
},
{
id: "pairedConnectedTechGroups",
name: "Connected scientifical/technological groups/teachings",
name: "Connected scientific/technological groups/teachings",
type: "manyToManyRelationship",
icon: "fas fa-wrench",
sizing: 4,
@ -371,7 +371,7 @@ export const religionsBlueprint: I_Blueprint = {
},
{
id: "pairedAllyTechGroups",
name: "Allied scientifical/technological groups/teachings",
name: "Allied scientific/technological groups/teachings",
type: "manyToManyRelationship",
icon: "fas fa-wrench",
sizing: 4,
@ -382,7 +382,7 @@ export const religionsBlueprint: I_Blueprint = {
},
{
id: "pairedEnemyTechGroups",
name: "Enemy scientifical/technological groups/teachings",
name: "Enemy scientific/technological groups/teachings",
type: "manyToManyRelationship",
icon: "fas fa-wrench",
sizing: 4,
@ -410,7 +410,7 @@ export const religionsBlueprint: I_Blueprint = {
},
{
id: "pairedConnectedMyths",
name: "Connected to myths. legends and stories",
name: "Connected to myths, legends and stories",
type: "manyToManyRelationship",
icon: "fas fa-journal-whills",
sizing: 4,

View file

@ -21,7 +21,7 @@ export const techBlueprint: I_Blueprint = {
},
{
id: "documentColor",
name: "Color",
name: "Text Color",
type: "colorPicker",
icon: "mdi-eyedropper",
tooltip:
@ -80,7 +80,7 @@ export const techBlueprint: I_Blueprint = {
<br>
This limitation also applies to any variation of lower or upper case iterations of the same tag.
<br>
Example: A tag called "Player Party" will be consider the same tag as "player party", "PlAyER PaRtY" or any similar.
Example: A tag called "Player Party" will be considered the same tag as "player party", "PlAyER PaRtY" or anything similar.
`,
sizing: 12
},
@ -123,7 +123,7 @@ export const techBlueprint: I_Blueprint = {
"Machinery",
"Magi-tech creation",
"School of technology",
"Scientifical/Technological institution",
"scientific/Technological institution",
"Technique",
"Other"
]
@ -389,7 +389,7 @@ export const techBlueprint: I_Blueprint = {
},
{
id: "pairedConnectedTechGroups",
name: "Connected scientifical/technological groups/teachings",
name: "Connected scientific/technological groups/teachings",
type: "manyToManyRelationship",
icon: "fas fa-wrench",
sizing: 4,
@ -400,7 +400,7 @@ export const techBlueprint: I_Blueprint = {
},
{
id: "pairedAllyTechGroups",
name: "Allied scientifical/technological groups/teachings",
name: "Allied scientific/technological groups/teachings",
type: "manyToManyRelationship",
icon: "fas fa-wrench",
sizing: 4,
@ -411,7 +411,7 @@ export const techBlueprint: I_Blueprint = {
},
{
id: "pairedEnemyTechGroups",
name: "Enemy scientifical/technological groups/teachings",
name: "Enemy scientific/technological groups/teachings",
type: "manyToManyRelationship",
icon: "fas fa-wrench",
sizing: 4,
@ -439,7 +439,7 @@ export const techBlueprint: I_Blueprint = {
},
{
id: "pairedConnectedMyths",
name: "Connected to myths. legends and stories",
name: "Connected to myths, legends and stories",
type: "manyToManyRelationship",
icon: "fas fa-journal-whills",
sizing: 4,

View file

@ -74,7 +74,9 @@ export const exportProject = (projectName: string, Loading: any, loadingSetup: a
// @ts-ignore
const allDBS = await indexedDB.databases()
const DBnames: string[] = allDBS.map((db: {name: string}) => {
const DBnames: string[] = allDBS
.filter((d: {name: string}) => d.name !== '_pouch_fa-settings')
.map((db: {name: string}) => {
return db.name.replace("_pouch_", "")
})
@ -108,11 +110,15 @@ export const removeCurrentProject = async () => {
/*eslint-disable */
// @ts-ignore
const allDBS = await indexedDB.databases()
console.log(allDBS)
const DBnames: string[] = allDBS.map((db: {name: string}) => {
const DBnames: string[] = allDBS
.filter((d: {name: string}) => d.name !== '_pouch_fa-settings')
.map((db: {name: string}) => {
return db.name.replace("_pouch_", "")
})
for (const db of DBnames) {
const CurrentDB = new PouchDB(db)
await CurrentDB.destroy()

View file

@ -1,13 +1,14 @@
import { I_Blueprint } from "src/interfaces/I_Blueprint"
import { I_ShortenedDocument } from "./../../interfaces/I_OpenedDocument"
/**
* Handles advanced filtering for any kind of search-field involving documents
*/
export const advancedDocumentFilter = (inputString: string, documentList: I_ShortenedDocument[]) => {
export const advancedDocumentFilter = (inputString: string, currentDocumentList: I_ShortenedDocument[], blueprintList: I_Blueprint[], allDocuments: I_ShortenedDocument[]) => {
/****************************************************************/
// Data refresh from previous filters
/****************************************************************/
documentList = documentList.map(doc => {
currentDocumentList = currentDocumentList.map(doc => {
doc.label = doc.label.replace(/<[^>]+>/g, "")
// @ts-ignore
@ -24,7 +25,7 @@ export const advancedDocumentFilter = (inputString: string, documentList: I_Shor
/****************************************************************/
// Refresh sorting to avoid random reshufling from previous searches
/****************************************************************/
documentList = documentList.sort((a, b) => {
currentDocumentList = currentDocumentList.sort((a, b) => {
if (a.type !== b.type) {
return a.type.localeCompare(b.type)
}
@ -42,10 +43,12 @@ export const advancedDocumentFilter = (inputString: string, documentList: I_Shor
let categorySeach = false as unknown as string
let tagSearch = false as unknown as string
let typeSeach = false as unknown as string
let fieldSearch = false as unknown as string
const categorySeachIndex = searchWordList.findIndex(w => w.charAt(0) === ">")
const tagSearchIndex = searchWordList.findIndex(w => w.charAt(0) === "#")
const typeSeachIndex = searchWordList.findIndex(w => w.charAt(0) === "$")
const fieldSearchIndex = searchWordList.findIndex(w => w.charAt(0) === "%")
if (categorySeachIndex >= 0) {
categorySeach = searchWordList[categorySeachIndex].substring(1)
@ -60,14 +63,150 @@ export const advancedDocumentFilter = (inputString: string, documentList: I_Shor
searchWordList[typeSeachIndex] = ""
}
if (fieldSearchIndex >= 0) {
fieldSearch = searchWordList[fieldSearchIndex].substring(1)
searchWordList[fieldSearchIndex] = ""
}
const filteredSearchWordList = searchWordList.filter(w => w !== "")
/****************************************************************/
// Field filter
/****************************************************************/
if (fieldSearch) {
currentDocumentList = currentDocumentList.filter(doc => {
const documentType = blueprintList.find(bluePrint => bluePrint._id === doc.type)
if (!documentType) {
return
}
const mappedFields = documentType.extraFields
.filter(field => field.type !== "break")
.filter(field => field.type !== "tags")
.filter(field => field.type !== "switch")
.filter(field => field.id !== "name")
.filter(field => field.id !== "parentDoc")
.map(field => {
const matchedField = doc.extraFields.find(sub => sub.id === field.id)
let returnValue = matchedField?.value
// Convert numbers to strings
if (field.type === "number" && typeof returnValue === "number") {
returnValue = returnValue.toString()
}
// Build string out of multi-selects
if (field.type === "multiSelect" && Array.isArray(returnValue)) {
returnValue = returnValue.join(" ")
}
// Build string out of lists
if (field.type === "list" && Array.isArray(returnValue)) {
returnValue = returnValue
.map((e: {value: string, affix: string}) => `${e.value} ${e.affix}`)
.join("")
}
// Build string out of single-relationship
if ((
field.type === "singleToManyRelationship" ||
field.type === "singleToSingleRelationship" ||
field.type === "singleToNoneRelationship"
) && returnValue && returnValue.value
) {
const valueToMap = Array.isArray(returnValue.value) ? returnValue.value[0] : returnValue.value
// @ts-ignore
const matchingDocument = allDocuments.find(doc => doc.id === valueToMap._id)
if (matchingDocument) {
// @ts-ignore
returnValue = matchingDocument.extraFields.find(e => e.id === "name")?.value as string
}
else {
returnValue = ""
}
}
// Build string out of multi-relationship
if ((
field.type === "manyToManyRelationship" ||
field.type === "manyToSingleRelationship" ||
field.type === "manyToNoneRelationship"
) && returnValue && returnValue.value
) {
const valuesToMap = returnValue.value as {_id: string}[]
const mappedValues = valuesToMap
.map(value => {
// @ts-ignore
const matchingDocument = allDocuments.find(doc => doc.id === value._id)
if (matchingDocument) {
// @ts-ignore
return matchingDocument.extraFields.find(e => e.id === "name")?.value as string
}
return " "
})
.filter(e => e !== " ")
.join(" ")
returnValue = mappedValues
}
// Fix all missing values that slipped through
if (!returnValue || returnValue.value === null) {
returnValue = ""
}
let returnValueFormat = returnValue as string
returnValueFormat = returnValueFormat.toLowerCase()
returnValueFormat = returnValueFormat.replace(/>/gi, "")
returnValueFormat = returnValueFormat.replace(/ {2}/gi, " ")
returnValueFormat = returnValueFormat.replace(/ {2}/gi, " ")
returnValueFormat = returnValueFormat.replace(/ {2}/gi, " ")
returnValueFormat = returnValueFormat.replace(/ /gi, "-")
let name = field.name
name = name.toLowerCase()
name = name.replace(/>/gi, "")
name = name.replace(/ {2}/gi, "-")
name = name.replace(/ /gi, "-")
return {
name: name,
value: returnValueFormat
}
})
let searchString = fieldSearch.toLowerCase()
searchString = searchString.replace(/>/gi, "")
searchString = searchString.replace(/ {2}/gi, "-")
searchString = searchString.replace(/ /gi, "-")
const searchDuo = searchString.split(":")
if (searchDuo.length === 2) {
let foundMatch = false
mappedFields.forEach(field => {
if (field.name.includes(searchDuo[0]) && field.value.includes(searchDuo[1])) {
foundMatch = true
}
})
return foundMatch
}
else {
return false
}
})
}
/****************************************************************/
// Category filter
/****************************************************************/
if (categorySeach) {
documentList = documentList.filter(doc => {
currentDocumentList = currentDocumentList.filter(doc => {
let stringPath = doc.hierarchicalPath as unknown as string
stringPath = stringPath.toLowerCase()
stringPath = stringPath.replace(/>/gi, "")
@ -85,7 +224,7 @@ export const advancedDocumentFilter = (inputString: string, documentList: I_Shor
// Tag filter
/****************************************************************/
if (tagSearch) {
documentList = documentList.filter(doc => {
currentDocumentList = currentDocumentList.filter(doc => {
let matchFound = false
if (doc.tags) {
@ -110,10 +249,15 @@ export const advancedDocumentFilter = (inputString: string, documentList: I_Shor
/****************************************************************/
if (typeSeach) {
documentList = documentList.filter(doc => {
let stringPath = doc.type
currentDocumentList = currentDocumentList.filter(doc => {
let stringPath = blueprintList.find(bluePrint => bluePrint._id === doc.type)?.nameSingular
stringPath = stringPath.split(/(?=[A-Z])/).join("-")
if (!stringPath) {
return
}
stringPath = stringPath.replace(/>/gi, "")
stringPath = stringPath.replace(/ {2}/gi, "-")
stringPath = stringPath.replace(/ /gi, "-")
stringPath = stringPath.toLowerCase()
@ -127,13 +271,13 @@ export const advancedDocumentFilter = (inputString: string, documentList: I_Shor
// Return list without further lookup for actual text if none is present
/****************************************************************/
if (filteredSearchWordList.length === 0) {
return documentList
return currentDocumentList
}
/****************************************************************/
// Priority search & filtering out non-matching search results
/****************************************************************/
documentList.forEach(doc => {
currentDocumentList.forEach(doc => {
// If we have a precise matching letter to letter
if (doc.label.toLowerCase() === filteredSearchWordList.join(" ")) {
doc.exactMatch = true
@ -183,7 +327,7 @@ export const advancedDocumentFilter = (inputString: string, documentList: I_Shor
})
// Cover case of exact match
documentList.map(doc => {
currentDocumentList.map(doc => {
if (doc.exactMatch) {
doc.label = `<span class="text-primary text-bold">${doc.label}</span>`
}
@ -196,24 +340,24 @@ export const advancedDocumentFilter = (inputString: string, documentList: I_Shor
// Partial word matching
// @ts-ignore
documentList = documentList.sort((a, b) => (a?.partialWordMatch > b?.partialWordMatch) ? -1 : 1)
currentDocumentList = currentDocumentList.sort((a, b) => (a?.partialWordMatch > b?.partialWordMatch) ? -1 : 1)
// Full word matching
// @ts-ignore
documentList = documentList.sort((a, b) => (a?.fullWordMatch > b?.fullWordMatch) ? -1 : 1)
currentDocumentList = currentDocumentList.sort((a, b) => (a?.fullWordMatch > b?.fullWordMatch) ? -1 : 1)
// Exact matching
documentList = documentList.sort((a) => (a?.exactMatch) ? -1 : 1)
currentDocumentList = currentDocumentList.sort((a) => (a?.exactMatch) ? -1 : 1)
/****************************************************************/
// If there is anything in the search, filter properly
/****************************************************************/
if (filteredSearchWordList.length > 0) {
documentList = documentList.filter(doc => {
currentDocumentList = currentDocumentList.filter(doc => {
// @ts-ignore
return ((doc.exactMatch || doc.fullWordMatch > 0 || doc.partialWordMatch > 0) && !doc.filteredOut)
})
}
return documentList
return currentDocumentList
}

View file

@ -9,6 +9,7 @@ import blueprintsModule from "./module-blueprints"
import openedDocumentsModule from "./module-openedDocuments"
import keybindsModule from "./module-keybinds"
import dialogsModule from "./module-dialogs"
import optionsModule from "./module-options"
/*
* If not building with SSR mode, you can
@ -30,7 +31,8 @@ export default store(function ({ Vue }) {
blueprintsModule,
openedDocumentsModule,
keybindsModule,
dialogsModule
dialogsModule,
optionsModule
// example
},

View file

@ -10,7 +10,7 @@ const resetCurrentKey = () => {
ctrlKey: true,
shiftKey: true,
id: "",
keyCode: 99999
which: 99999
}
}
const mutation: MutationTree<KeybindsStateInterface> = {

View file

@ -22,7 +22,7 @@ function state (): KeybindsStateInterface {
ctrlKey: true,
shiftKey: true,
id: "",
keyCode: 99999
which: 99999
}
}
}

View file

@ -0,0 +1,24 @@
import { ActionTree } from "vuex"
import { StateInterface } from "../index"
import { OptionsStateInteface } from "./state"
import PouchDB from "pouchdb"
const actions: ActionTree<OptionsStateInteface, StateInterface> = {
async setOptions (state, input: OptionsStateInteface) {
const SettingsDB = new PouchDB("fa-settings")
const FASettings = input
let currentSettings = false as unknown as OptionsStateInteface
try {
currentSettings = await SettingsDB.get("settings")
FASettings._rev = currentSettings?._rev
}
catch (error) {}
await SettingsDB.put(FASettings)
state.commit("setOptions", input)
}
}
export default actions

View file

@ -0,0 +1,11 @@
import { GetterTree } from "vuex"
import { StateInterface } from "../index"
import { OptionsStateInteface } from "./state"
const getters: GetterTree<OptionsStateInteface, StateInterface> = {
getOptions (context) {
return context
}
}
export default getters

View file

@ -0,0 +1,16 @@
import { Module } from "vuex"
import { StateInterface } from "../index"
import state, { OptionsStateInteface } from "./state"
import actions from "./actions"
import getters from "./getters"
import mutations from "./mutations"
const keybindsModule: Module<OptionsStateInteface, StateInterface> = {
namespaced: true,
actions,
getters,
mutations,
state
}
export default keybindsModule

View file

@ -0,0 +1,13 @@
import { MutationTree } from "vuex"
import { OptionsStateInteface } from "./state"
const mutation: MutationTree<OptionsStateInteface> = {
setOptions (state: OptionsStateInteface, input: OptionsStateInteface) {
for (const [key, value] of Object.entries(input)) {
state[key] = value
}
}
}
export default mutation

View file

@ -0,0 +1,47 @@
export interface OptionsStateInteface {
_id: string,
_rev?: string,
darkMode: boolean
textShadow: boolean
noTags: boolean
tagsAtTop: boolean
compactTags: boolean
noProjectName: boolean
invertTreeSorting: boolean
disableDocumentToolTips: boolean
doNotcollaseTreeOptions: boolean
hideEmptyFields: boolean
disableDocumentControlBar: boolean
disableDocumentControlBarGuides: boolean
disableCloseAftertSelectQuickSearch: boolean
disableQuickSearchCategoryPrecheck: boolean
allowQuickPopupSameKeyClose: boolean
userKeybindList: any[]
treeWidth?: number
}
function state (): OptionsStateInteface {
return {
_id: "settings",
darkMode: false,
textShadow: false,
noTags: false,
tagsAtTop: false,
compactTags: false,
noProjectName: false,
invertTreeSorting: false,
disableDocumentToolTips: false,
doNotcollaseTreeOptions: false,
disableDocumentControlBar: false,
disableDocumentControlBarGuides: false,
disableCloseAftertSelectQuickSearch: false,
disableQuickSearchCategoryPrecheck: false,
allowQuickPopupSameKeyClose: false,
hideEmptyFields: false,
treeWidth: 374,
userKeybindList: []
}
}
export default state

View file

@ -1,28 +1,22 @@
### App options & Keybinds
- Add "Document relevance" switch and integrate it into filters
- Add "Find field" to quickly navigate the document
- Add "Show in search results" checkbox for relationship searches
- Add "Related notes"
- Add "Predecessors", "Successors", "Date of start", "Date of end" and "How long it lasted" fields to locations and all other groups
- Save scroll distance when switching tabs (consider some auto-scroll when opening edit mode)
- Fix lag on opening Quick-search popup
- Fix tag input hanging after adding new ones
- Mass tag rename
- Add click-through from chips in edit mode
- Add "Ctrl + F" finding in the document (fields/data)
- Fix filtering via the document type in advanced search
- Add advanced search capabilities to the hierarchical tree
- Add "is dead" or some similar switch to documents and show strike-through/etc on items in lists, tombstone icon or overlay crossed out icon
- Custom icons/images to documents
- Add colored backgrounds to documents (along with already existing colored texts)
- Add filtering by color and BG color (weird, but could work)
- "Save all" keybind and "Save all and exit" option on the exiting
- Add support for dates in "custom order"... reee
- Considering multiple "belongs under" category listing
- Add intelligent responsive design to the left tree and document body (maybe button to pull the left bar in and out?)
- Add top level tags
- Context menu: Expand all
- Context menu: Collapse all
- Context menu: Copy color (text and BG)
@ -31,33 +25,16 @@
- Context menu: Delete
- Context menu: Edit/Open
- Context menu: Create new doc with parent of THIS
- Context menu: Clone tab
- Context menu: Pin tab
- Context menu: Unpin tab
- Pinned tabs (save through program closing)
- Dark mode
- Add "Open all search matches" button in the Quick-search that opens a new page with a list of items
- Custom order document types
- Option: Show tags as a subcategory or as each individually
- Option: Show tags on or bottom of the list
- Option: Hide non-filled fields in document view mode
- Option: Retain opened tabs and their edited contents through sessions
- Option: Dark Mode
- Option: Quick-search popup determine if "Include categories" is prechecked or not
- Option: Show tags in the tree
- Option: Periodical backup (how many, how often, include files or not)
- Option: Disable tooltips in the document body
- Option: Single tab option coupled with pinned tabs
- Option: Sorting order via custom order number in the hiearachical tree
- Option: Wider sidebar
- Option: Show document as tabs or as one long documents
- Option: Dont close Quick-search after opening a search result
- Option: Also collapse subcategories
- Option: Disable project name in the tree
- Option: Close Quick-search and Quick-add dialogs with second keypress of the keybind instead of escape
- Option: Disable tags showing in the sidebar
- Option: Hide top navbar and move the document buttons back in the document body (top)
- Option: Periodical backup (how many, how often, include files or not)
### Project settings