Refactor usage of tooltips for flexibility
This commit introduces a new Vue component to handle tooltips. It acts as a wrapper for the `v-tooltip`. It enhances the maintainability, readability and portability of tooltips by enabling the direct inclusion of inline HTML in the tooltip components. It solves issues such as absence of linting or editor support and cumbersome string concatenation. It also provides an abstraction layer that simplifies the switching between different tooltip implementations, which would allow a smooth migration to Vue 3 (see #230).
This commit is contained in:
@@ -61,9 +61,16 @@ Stateful components can mutate and/or react to state changes (e.g., user selecti
|
||||
|
||||
📖 Refer to [architecture.md | Application State](./architecture.md#application-state) for an overview of event handling and [application.md | Application State](./presentation.md#application-state) for an in-depth understanding of state management in the application layer.
|
||||
|
||||
## Modals
|
||||
## Shared UI components
|
||||
|
||||
- [ModalDialog.vue](./../src/presentation/components/Shared/Modal/ModalDialog.vue) is a shared component utilized for rendering modal windows.
|
||||
Shared UI components promote consistency and simplifies the creation of the front-end.
|
||||
|
||||
In order to maintain portability and easy maintainability, the preference is towards using homegrown components over third-party ones or comprehensive UI frameworks like Quasar.
|
||||
|
||||
Shared components include:
|
||||
|
||||
- [ModalDialog.vue](./../src/presentation/components/Shared/Modal/ModalDialog.vue) is utilized for rendering modal windows.
|
||||
- [TooltipWrapper.vue](./../src/presentation/components/Shared/TooltipWrapper.vue) acts as a wrapper for rendering tooltips.
|
||||
|
||||
## Sass naming convention
|
||||
|
||||
|
||||
@@ -2,20 +2,28 @@
|
||||
<span class="code-wrapper">
|
||||
<span class="dollar">$</span>
|
||||
<code><slot /></code>
|
||||
<font-awesome-icon
|
||||
class="copy-button"
|
||||
:icon="['fas', 'copy']"
|
||||
@click="copyCode"
|
||||
v-tooltip.top-center="'Copy'"
|
||||
/>
|
||||
<TooltipWrapper>
|
||||
<font-awesome-icon
|
||||
class="copy-button"
|
||||
:icon="['fas', 'copy']"
|
||||
@click="copyCode"
|
||||
/>
|
||||
<template v-slot:tooltip>
|
||||
Copy
|
||||
</template>
|
||||
</TooltipWrapper>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, useSlots } from 'vue';
|
||||
import { Clipboard } from '@/infrastructure/Clipboard';
|
||||
import TooltipWrapper from '@/presentation/components/Shared/TooltipWrapper.vue';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
TooltipWrapper,
|
||||
},
|
||||
setup() {
|
||||
const slots = useSlots();
|
||||
|
||||
|
||||
@@ -26,21 +26,27 @@
|
||||
>
|
||||
<div class="step__action">
|
||||
<span>{{ step.action.instruction }}</span>
|
||||
<font-awesome-icon
|
||||
v-if="step.action.details"
|
||||
class="explanation"
|
||||
:icon="['fas', 'info-circle']"
|
||||
v-tooltip.top-center="step.action.details"
|
||||
/>
|
||||
<TooltipWrapper v-if="step.action.details">
|
||||
<font-awesome-icon
|
||||
class="explanation"
|
||||
:icon="['fas', 'info-circle']"
|
||||
/>
|
||||
<template v-slot:tooltip>
|
||||
<div v-html="step.action.details" />
|
||||
</template>
|
||||
</TooltipWrapper>
|
||||
</div>
|
||||
<div v-if="step.code" class="step__code">
|
||||
<CodeInstruction>{{ step.code.instruction }}</CodeInstruction>
|
||||
<font-awesome-icon
|
||||
v-if="step.code.details"
|
||||
class="explanation"
|
||||
:icon="['fas', 'info-circle']"
|
||||
v-tooltip.top-center="step.code.details"
|
||||
/>
|
||||
<TooltipWrapper v-if="step.code.details">
|
||||
<font-awesome-icon
|
||||
class="explanation"
|
||||
:icon="['fas', 'info-circle']"
|
||||
/>
|
||||
<template v-slot:tooltip>
|
||||
<div v-html="step.code.details" />
|
||||
</template>
|
||||
</TooltipWrapper>
|
||||
</div>
|
||||
</li>
|
||||
</ol>
|
||||
@@ -53,6 +59,7 @@ import {
|
||||
defineComponent, PropType, computed,
|
||||
} from 'vue';
|
||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||
import TooltipWrapper from '@/presentation/components/Shared/TooltipWrapper.vue';
|
||||
import { useApplication } from '@/presentation/components/Shared/Hooks/UseApplication';
|
||||
import CodeInstruction from './CodeInstruction.vue';
|
||||
import { IInstructionListData } from './InstructionListData';
|
||||
@@ -60,6 +67,7 @@ import { IInstructionListData } from './InstructionListData';
|
||||
export default defineComponent({
|
||||
components: {
|
||||
CodeInstruction,
|
||||
TooltipWrapper,
|
||||
},
|
||||
props: {
|
||||
data: {
|
||||
|
||||
@@ -1,49 +1,73 @@
|
||||
<template>
|
||||
<MenuOptionList label="Select">
|
||||
<MenuOptionListItem
|
||||
label="None"
|
||||
:enabled="currentSelection !== SelectionType.None"
|
||||
@click="selectType(SelectionType.None)"
|
||||
v-tooltip="
|
||||
'Deselect all selected scripts.<br/>'
|
||||
+ '💡 Good start to dive deeper into tweaks and select only what you want.'
|
||||
"
|
||||
/>
|
||||
<MenuOptionListItem
|
||||
label="Standard"
|
||||
:enabled="currentSelection !== SelectionType.Standard"
|
||||
@click="selectType(SelectionType.Standard)"
|
||||
v-tooltip="
|
||||
'🛡️ Balanced for privacy and functionality.<br/>'
|
||||
+ 'OS and applications will function normally.<br/>'
|
||||
+ '💡 Recommended for everyone'"
|
||||
/>
|
||||
<MenuOptionListItem
|
||||
label="Strict"
|
||||
:enabled="currentSelection !== SelectionType.Strict"
|
||||
@click="selectType(SelectionType.Strict)"
|
||||
v-tooltip="
|
||||
'🚫 Stronger privacy, disables risky functions that may leak your data.<br/>'
|
||||
+ '⚠️ Double check to remove scripts where you would trade functionality for privacy<br/>'
|
||||
+ '💡 Recommended for daily users that prefers more privacy over non-essential functions'
|
||||
"
|
||||
/>
|
||||
<MenuOptionListItem
|
||||
label="All"
|
||||
:enabled="currentSelection !== SelectionType.All"
|
||||
@click="selectType(SelectionType.All)"
|
||||
v-tooltip="
|
||||
'🔒 Strongest privacy, disabling any functionality that may leak your data.<br/>'
|
||||
+ '🛑 Not designed for daily users, it will break important functionalities.<br/>'
|
||||
+ '💡 Only recommended for extreme use-cases like crime labs where no leak is acceptable'
|
||||
"
|
||||
/>
|
||||
<TooltipWrapper>
|
||||
<!-- None -->
|
||||
<MenuOptionListItem
|
||||
label="None"
|
||||
:enabled="currentSelection !== SelectionType.None"
|
||||
@click="selectType(SelectionType.None)"
|
||||
/>
|
||||
<template v-slot:tooltip>
|
||||
Deselect all selected scripts.
|
||||
<br />
|
||||
💡 Good start to dive deeper into tweaks and select only what you want.
|
||||
</template>
|
||||
</TooltipWrapper>
|
||||
|
||||
<!-- Standard -->
|
||||
<TooltipWrapper>
|
||||
<MenuOptionListItem
|
||||
label="Standard"
|
||||
:enabled="currentSelection !== SelectionType.Standard"
|
||||
@click="selectType(SelectionType.Standard)"
|
||||
/>
|
||||
<template v-slot:tooltip>
|
||||
🛡️ Balanced for privacy and functionality.
|
||||
<br />
|
||||
OS and applications will function normally.
|
||||
<br />
|
||||
💡 Recommended for everyone
|
||||
</template>
|
||||
</TooltipWrapper>
|
||||
|
||||
<!-- Strict -->
|
||||
<TooltipWrapper>
|
||||
<MenuOptionListItem
|
||||
label="Strict"
|
||||
:enabled="currentSelection !== SelectionType.Strict"
|
||||
@click="selectType(SelectionType.Strict)"
|
||||
/>
|
||||
<template v-slot:tooltip>
|
||||
🚫 Stronger privacy, disables risky functions that may leak your data.
|
||||
<br />
|
||||
⚠️ Double check to remove scripts where you would trade functionality for privacy
|
||||
<br />
|
||||
💡 Recommended for daily users that prefers more privacy over non-essential functions
|
||||
</template>
|
||||
</TooltipWrapper>
|
||||
|
||||
<!-- All -->
|
||||
<TooltipWrapper>
|
||||
<MenuOptionListItem
|
||||
label="All"
|
||||
:enabled="currentSelection !== SelectionType.All"
|
||||
@click="selectType(SelectionType.All)"
|
||||
/>
|
||||
<template v-slot:tooltip>
|
||||
🔒 Strongest privacy, disabling any functionality that may leak your data.
|
||||
<br />
|
||||
🛑 Not designed for daily users, it will break important functionalities.
|
||||
<br />
|
||||
💡 Only recommended for extreme use-cases like crime labs where no leak is acceptable
|
||||
</template>
|
||||
</TooltipWrapper>
|
||||
</MenuOptionList>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref } from 'vue';
|
||||
import { useCollectionState } from '@/presentation/components/Shared/Hooks/UseCollectionState';
|
||||
import TooltipWrapper from '@/presentation/components/Shared/TooltipWrapper.vue';
|
||||
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
|
||||
import MenuOptionList from '../MenuOptionList.vue';
|
||||
import MenuOptionListItem from '../MenuOptionListItem.vue';
|
||||
@@ -53,6 +77,7 @@ export default defineComponent({
|
||||
components: {
|
||||
MenuOptionList,
|
||||
MenuOptionListItem,
|
||||
TooltipWrapper,
|
||||
},
|
||||
setup() {
|
||||
const { modifyCurrentState, onStateChange, events } = useCollectionState();
|
||||
|
||||
65
src/presentation/components/Shared/TooltipWrapper.vue
Normal file
65
src/presentation/components/Shared/TooltipWrapper.vue
Normal file
@@ -0,0 +1,65 @@
|
||||
<!--
|
||||
This component acts as a wrapper for the v-tooltip to solve the following:
|
||||
- Direct inclusion of inline HTML in tooltip components has challenges such as
|
||||
- absence of linting or editor support,
|
||||
- involves cumbersome string concatenation.
|
||||
This component caters to these issues by permitting HTML usage in a slot.
|
||||
- It provides an abstraction for a third-party component which simplifies
|
||||
switching and acts as an anti-corruption layer.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="tooltip-container" v-tooltip.top-center="tooltipHtml">
|
||||
<slot />
|
||||
<div class="tooltip-content" ref="tooltipWrapper">
|
||||
<slot name="tooltip" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {
|
||||
defineComponent, ref, onMounted, onUpdated, nextTick,
|
||||
} from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
setup() {
|
||||
const tooltipWrapper = ref<HTMLElement | undefined>();
|
||||
const tooltipHtml = ref<string | undefined>();
|
||||
|
||||
onMounted(() => updateTooltipHTML());
|
||||
|
||||
onUpdated(() => {
|
||||
nextTick(() => {
|
||||
updateTooltipHTML();
|
||||
});
|
||||
});
|
||||
|
||||
function updateTooltipHTML() {
|
||||
const newValue = tooltipWrapper.value?.innerHTML;
|
||||
const oldValue = tooltipHtml.value;
|
||||
if (newValue === oldValue) {
|
||||
return;
|
||||
}
|
||||
tooltipHtml.value = newValue;
|
||||
}
|
||||
|
||||
return {
|
||||
tooltipWrapper,
|
||||
tooltipHtml,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@use "@/presentation/assets/styles/main" as *;
|
||||
|
||||
.tooltip-container {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.tooltip-content {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user