mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-23 05:18:11 +08:00
Move layouts definitions to backend and fix Firefox overlay scrollbars (#647)
* Fix two-axis scrollbars in scrollable regions on Firefox * Move Document Mode dropdown to the backend; and related code cleanup * Port the Layer Tree options bar layout to the backend * Port the tool shelf to the backend * Clean up initialization and wasm wrapper * Fix crash * Fix missing document bar * Remove unused functions in api.rs * Code review * Tool initalisation * Remove some frontend functions * Initalise -> Init so en-US/GB doesn't have to matter :) * Remove blend_mode and opacity from LayerPanelEntry Co-authored-by: 0hypercube <0hypercube@gmail.com>
This commit is contained in:
co-authored by
0hypercube
parent
95435d8bf1
commit
a8f09da5a2
@@ -1,8 +1,6 @@
|
||||
<template>
|
||||
<div class="widget-layout">
|
||||
<template v-for="(layoutRow, index) in layout.layout" :key="index">
|
||||
<component :is="layoutRowType(layoutRow)" :widgetData="layoutRow" :layoutTarget="layout.layout_target"></component>
|
||||
</template>
|
||||
<component :is="layoutRowType(layoutRow)" :widgetData="layoutRow" :layoutTarget="layout.layout_target" v-for="(layoutRow, index) in layout.layout" :key="index" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -18,7 +16,7 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType } from "vue";
|
||||
|
||||
import { isWidgetRow, isWidgetSection, LayoutRow, WidgetLayout } from "@/dispatcher/js-messages";
|
||||
import { isWidgetColumn, isWidgetRow, isWidgetSection, LayoutRow, WidgetLayout } from "@/dispatcher/js-messages";
|
||||
|
||||
import WidgetRow from "@/components/widgets/WidgetRow.vue";
|
||||
import WidgetSection from "@/components/widgets/WidgetSection.vue";
|
||||
@@ -29,6 +27,7 @@ export default defineComponent({
|
||||
},
|
||||
methods: {
|
||||
layoutRowType(layoutRow: LayoutRow): unknown {
|
||||
if (isWidgetColumn(layoutRow)) return WidgetRow;
|
||||
if (isWidgetRow(layoutRow)) return WidgetRow;
|
||||
if (isWidgetSection(layoutRow)) return WidgetSection;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="widget-row">
|
||||
<template v-for="(component, index) in widgetData.widgets" :key="index">
|
||||
<div :class="`widget-${direction}`">
|
||||
<template v-for="(component, index) in widgets" :key="index">
|
||||
<!-- TODO: Use `<component :is="" v-bind="attributesObject"></component>` to avoid all the separate components with `v-if` -->
|
||||
<CheckboxInput v-if="component.kind === 'CheckboxInput'" v-bind="component.props" @update:checked="(value: boolean) => updateLayout(component.widget_id, value)" />
|
||||
<ColorInput v-if="component.kind === 'ColorInput'" v-bind="component.props" @update:value="(value: string) => updateLayout(component.widget_id, value)" />
|
||||
@@ -35,6 +35,12 @@
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.widget-column {
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.widget-row {
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
@@ -63,7 +69,7 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType } from "vue";
|
||||
|
||||
import { WidgetRow } from "@/dispatcher/js-messages";
|
||||
import { WidgetColumn, WidgetRow, isWidgetColumn, isWidgetRow } from "@/dispatcher/js-messages";
|
||||
|
||||
import IconButton from "@/components/widgets/buttons/IconButton.vue";
|
||||
import PopoverButton from "@/components/widgets/buttons/PopoverButton.vue";
|
||||
@@ -84,9 +90,21 @@ import Separator from "@/components/widgets/separators/Separator.vue";
|
||||
export default defineComponent({
|
||||
inject: ["editor"],
|
||||
props: {
|
||||
widgetData: { type: Object as PropType<WidgetRow>, required: true },
|
||||
widgetData: { type: Object as PropType<WidgetColumn | WidgetRow>, required: true },
|
||||
layoutTarget: { required: true },
|
||||
},
|
||||
computed: {
|
||||
direction() {
|
||||
if (isWidgetColumn(this.widgetData)) return "column";
|
||||
if (isWidgetRow(this.widgetData)) return "row";
|
||||
return "ERROR";
|
||||
},
|
||||
widgets() {
|
||||
if (isWidgetColumn(this.widgetData)) return this.widgetData.columnWidgets;
|
||||
if (isWidgetRow(this.widgetData)) return this.widgetData.rowWidgets;
|
||||
return [];
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
updateLayout(widgetId: BigInt, value: unknown) {
|
||||
this.editor.instance.update_layout(this.layoutTarget, widgetId, value);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<button class="icon-button" :class="`size-${size}`" @click="(e: MouseEvent) => action(e)">
|
||||
<button :class="['icon-button', `size-${size}`, active && 'active']" @click="(e: MouseEvent) => action(e)">
|
||||
<IconLabel :icon="icon" />
|
||||
</button>
|
||||
</template>
|
||||
@@ -25,7 +25,11 @@
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
&.active {
|
||||
background: var(--color-accent);
|
||||
}
|
||||
|
||||
&:hover:not(.active) {
|
||||
background: var(--color-6-lowergray);
|
||||
color: var(--color-f-white);
|
||||
|
||||
@@ -68,6 +72,7 @@ export default defineComponent({
|
||||
action: { type: Function as PropType<(e?: MouseEvent) => void>, required: true },
|
||||
icon: { type: String as PropType<IconName>, required: true },
|
||||
size: { type: Number as PropType<IconSize>, required: true },
|
||||
active: { type: Boolean as PropType<boolean>, default: false },
|
||||
gapAfter: { type: Boolean as PropType<boolean>, default: false },
|
||||
},
|
||||
components: { IconLabel },
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
</LayoutCol>
|
||||
<LayoutCol class="main-column">
|
||||
<WidgetLayout v-if="dialog.state.widgets.layout.length > 0" :layout="dialog.state.widgets" class="details" />
|
||||
<LayoutRow v-if="dialog.state.jsCallbackBasedButtons?.length > 0" class="panic-buttons-row">
|
||||
<LayoutRow v-if="(dialog.state.jsCallbackBasedButtons?.length || NaN) > 0" class="panic-buttons-row">
|
||||
<TextButton v-for="(button, index) in dialog.state.jsCallbackBasedButtons" :key="index" :action="() => button.callback?.()" v-bind="button.props" />
|
||||
</LayoutRow>
|
||||
</LayoutCol>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<FloatingMenu class="menu-list" :direction="direction" :type="'Dropdown'" ref="floatingMenu" :windowEdgeMargin="0" :scrollableY="scrollableY" data-hover-menu-keep-open>
|
||||
<template v-for="(section, sectionIndex) in menuEntries" :key="sectionIndex">
|
||||
<template v-for="(section, sectionIndex) in entries" :key="sectionIndex">
|
||||
<Separator :type="'List'" :direction="'Vertical'" v-if="sectionIndex > 0" />
|
||||
<LayoutRow
|
||||
v-for="(entry, entryIndex) in section"
|
||||
@@ -27,7 +27,7 @@
|
||||
<MenuList
|
||||
v-if="entry.children"
|
||||
:direction="'TopRight'"
|
||||
:menuEntries="entry.children"
|
||||
:entries="entry.children"
|
||||
v-bind="{ defaultAction, minWidth, drawIcon, scrollableY }"
|
||||
:ref="(ref: any) => setEntryRefs(entry, ref)"
|
||||
/>
|
||||
@@ -167,7 +167,7 @@ const MenuList = defineComponent({
|
||||
inject: ["fullscreen"],
|
||||
props: {
|
||||
direction: { type: String as PropType<MenuDirection>, default: "Bottom" },
|
||||
menuEntries: { type: Array as PropType<SectionsOfMenuListEntries>, required: true },
|
||||
entries: { type: Array as PropType<SectionsOfMenuListEntries>, required: true },
|
||||
activeEntry: { type: Object as PropType<MenuListEntry>, required: false },
|
||||
defaultAction: { type: Function as PropType<() => void>, required: false },
|
||||
minWidth: { type: Number as PropType<number>, default: 0 },
|
||||
@@ -243,9 +243,9 @@ const MenuList = defineComponent({
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
menuEntriesWithoutRefs(): MenuListEntryData[][] {
|
||||
return this.menuEntries.map((entries) =>
|
||||
entries.map((entry) => {
|
||||
entriesWithoutRefs(): MenuListEntryData[][] {
|
||||
return this.entries.map((menuListEntries) =>
|
||||
menuListEntries.map((entry) => {
|
||||
const { ref, ...entryWithoutRef } = entry;
|
||||
return entryWithoutRef;
|
||||
})
|
||||
@@ -259,7 +259,7 @@ const MenuList = defineComponent({
|
||||
this.measureAndReportWidth();
|
||||
},
|
||||
watch: {
|
||||
menuEntriesWithoutRefs: {
|
||||
entriesWithoutRefs: {
|
||||
handler() {
|
||||
this.measureAndReportWidth();
|
||||
},
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
v-model:activeEntry="activeEntry"
|
||||
@update:activeEntry="(newActiveEntry: typeof MENU_LIST_ENTRY) => activeEntryChanged(newActiveEntry)"
|
||||
@widthChanged="(newWidth: number) => onWidthChanged(newWidth)"
|
||||
:menuEntries="menuEntries"
|
||||
:entries="entries"
|
||||
:direction="'Bottom'"
|
||||
:drawIcon="drawIcon"
|
||||
:scrollableY="true"
|
||||
@@ -100,23 +100,23 @@ declare global {
|
||||
export default defineComponent({
|
||||
emits: ["update:selectedIndex"],
|
||||
props: {
|
||||
menuEntries: { type: Array as PropType<SectionsOfMenuListEntries>, required: true },
|
||||
selectedIndex: { type: Number as PropType<number>, required: true },
|
||||
entries: { type: Array as PropType<SectionsOfMenuListEntries>, required: true },
|
||||
selectedIndex: { type: Number as PropType<number>, required: false }, // When not provided, a dash is displayed
|
||||
drawIcon: { type: Boolean as PropType<boolean>, default: false },
|
||||
disabled: { type: Boolean as PropType<boolean>, default: false },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
activeEntry: this.menuEntries.flat()[this.selectedIndex],
|
||||
activeEntry: this.selectedIndex !== undefined ? this.entries.flat()[this.selectedIndex] : { label: "-" },
|
||||
minWidth: 0,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
// Called only when `selectedIndex` is changed from outside this component (with v-model)
|
||||
selectedIndex(newSelectedIndex: number) {
|
||||
const entries = this.menuEntries.flat();
|
||||
selectedIndex(newSelectedIndex: number | undefined) {
|
||||
const entries = this.entries.flat();
|
||||
|
||||
if (!Number.isNaN(newSelectedIndex) && newSelectedIndex >= 0 && newSelectedIndex < entries.length) {
|
||||
if (newSelectedIndex !== undefined && newSelectedIndex >= 0 && newSelectedIndex < entries.length) {
|
||||
this.activeEntry = entries[newSelectedIndex];
|
||||
} else {
|
||||
this.activeEntry = { label: "-" };
|
||||
@@ -126,7 +126,7 @@ export default defineComponent({
|
||||
methods: {
|
||||
// Called only when `activeEntry` is changed from the child MenuList component via user input
|
||||
activeEntryChanged(newActiveEntry: MenuListEntry) {
|
||||
this.$emit("update:selectedIndex", this.menuEntries.flat().indexOf(newActiveEntry));
|
||||
this.$emit("update:selectedIndex", this.entries.flat().indexOf(newActiveEntry));
|
||||
},
|
||||
clickDropdownBox() {
|
||||
if (!this.disabled) (this.$refs.menuList as typeof MenuList).setOpen();
|
||||
|
||||
@@ -4,14 +4,7 @@
|
||||
<span>{{ activeEntry.label }}</span>
|
||||
<IconLabel class="dropdown-arrow" :icon="'DropdownArrow'" />
|
||||
</LayoutRow>
|
||||
<MenuList
|
||||
v-model:activeEntry="activeEntry"
|
||||
@widthChanged="(newWidth: number) => onWidthChanged(newWidth)"
|
||||
:menuEntries="menuEntries"
|
||||
:direction="'Bottom'"
|
||||
:scrollableY="true"
|
||||
ref="menuList"
|
||||
/>
|
||||
<MenuList v-model:activeEntry="activeEntry" @widthChanged="(newWidth: number) => onWidthChanged(newWidth)" :entries="entries" :direction="'Bottom'" :scrollableY="true" ref="menuList" />
|
||||
</LayoutRow>
|
||||
</template>
|
||||
|
||||
@@ -100,9 +93,9 @@ export default defineComponent({
|
||||
isStyle: { type: Boolean as PropType<boolean>, default: false },
|
||||
},
|
||||
data() {
|
||||
const { menuEntries, activeEntry } = this.updateEntries();
|
||||
const { entries, activeEntry } = this.updateEntries();
|
||||
return {
|
||||
menuEntries,
|
||||
entries,
|
||||
activeEntry,
|
||||
minWidth: 0,
|
||||
};
|
||||
@@ -133,12 +126,12 @@ export default defineComponent({
|
||||
onWidthChanged(newWidth: number) {
|
||||
this.minWidth = newWidth;
|
||||
},
|
||||
updateEntries(): { menuEntries: SectionsOfMenuListEntries; activeEntry: MenuListEntry } {
|
||||
updateEntries(): { entries: SectionsOfMenuListEntries; activeEntry: MenuListEntry } {
|
||||
const choices = this.isStyle ? getFontStyles(this.fontFamily) : fontNames();
|
||||
const selectedChoice = this.isStyle ? this.fontStyle : this.fontFamily;
|
||||
|
||||
let selectedEntry: MenuListEntry | undefined;
|
||||
const entries = choices.map((name) => {
|
||||
const menuListEntries = choices.map((name) => {
|
||||
const result: MenuListEntry = {
|
||||
label: name,
|
||||
action: (): void => this.selectFont(name),
|
||||
@@ -149,21 +142,21 @@ export default defineComponent({
|
||||
return result;
|
||||
});
|
||||
|
||||
const menuEntries: SectionsOfMenuListEntries = [entries];
|
||||
const entries: SectionsOfMenuListEntries = [menuListEntries];
|
||||
const activeEntry = selectedEntry || { label: "-" };
|
||||
|
||||
return { menuEntries, activeEntry };
|
||||
return { entries, activeEntry };
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
fontFamily() {
|
||||
const { menuEntries, activeEntry } = this.updateEntries();
|
||||
this.menuEntries = menuEntries;
|
||||
const { entries, activeEntry } = this.updateEntries();
|
||||
this.entries = entries;
|
||||
this.activeEntry = activeEntry;
|
||||
},
|
||||
fontStyle() {
|
||||
const { menuEntries, activeEntry } = this.updateEntries();
|
||||
this.menuEntries = menuEntries;
|
||||
const { entries, activeEntry } = this.updateEntries();
|
||||
this.entries = entries;
|
||||
this.activeEntry = activeEntry;
|
||||
},
|
||||
},
|
||||
|
||||
@@ -5,12 +5,12 @@
|
||||
<IconLabel :icon="'GraphiteLogo'" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="entry-container" v-for="(entry, index) in menuEntries" :key="index">
|
||||
<div class="entry-container" v-for="(entry, index) in entries" :key="index">
|
||||
<div @click="() => handleEntryClick(entry)" class="entry" :class="{ open: entry.ref?.isOpen() }" data-hover-menu-spawner>
|
||||
<IconLabel :icon="entry.icon" v-if="entry.icon" />
|
||||
<span v-if="entry.label">{{ entry.label }}</span>
|
||||
</div>
|
||||
<MenuList :menuEntries="entry.children || []" :direction="'Bottom'" :minWidth="240" :drawIcon="true" :defaultAction="comingSoon" :ref="(ref: any) => setEntryRefs(entry, ref)" />
|
||||
<MenuList :entries="entry.children || []" :direction="'Bottom'" :minWidth="240" :drawIcon="true" :defaultAction="comingSoon" :ref="(ref: any) => setEntryRefs(entry, ref)" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -58,7 +58,7 @@ import { EditorState } from "@/state/wasm-loader";
|
||||
import MenuList, { MenuListEntry, MenuListEntries } from "@/components/widgets/floating-menus/MenuList.vue";
|
||||
import IconLabel from "@/components/widgets/labels/IconLabel.vue";
|
||||
|
||||
function makeMenuEntries(editor: EditorState): MenuListEntries {
|
||||
function makeEntries(editor: EditorState): MenuListEntries {
|
||||
return [
|
||||
{
|
||||
label: "File",
|
||||
@@ -213,7 +213,7 @@ export default defineComponent({
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
menuEntries: makeMenuEntries(this.editor),
|
||||
entries: makeEntries(this.editor),
|
||||
comingSoon: (): void => this.dialog.comingSoon(),
|
||||
};
|
||||
},
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
@cancelTextChange="() => onCancelTextChange()"
|
||||
ref="fieldInput"
|
||||
>
|
||||
<button v-if="!Number.isNaN(value)" class="arrow left" @click="() => onIncrement('Decrease')"></button>
|
||||
<button v-if="!Number.isNaN(value)" class="arrow right" @click="() => onIncrement('Increase')"></button>
|
||||
<button v-if="value !== undefined" class="arrow left" @click="() => onIncrement('Decrease')"></button>
|
||||
<button v-if="value !== undefined" class="arrow right" @click="() => onIncrement('Increase')"></button>
|
||||
</FieldInput>
|
||||
</template>
|
||||
|
||||
@@ -94,7 +94,7 @@ import FieldInput from "@/components/widgets/inputs/FieldInput.vue";
|
||||
export default defineComponent({
|
||||
emits: ["update:value"],
|
||||
props: {
|
||||
value: { type: Number as PropType<number>, required: true },
|
||||
value: { type: Number as PropType<number>, required: false }, // When not provided, a dash is displayed
|
||||
min: { type: Number as PropType<number>, required: false },
|
||||
max: { type: Number as PropType<number>, required: false },
|
||||
incrementBehavior: { type: String as PropType<IncrementBehavior>, default: "Add" },
|
||||
@@ -116,7 +116,7 @@ export default defineComponent({
|
||||
},
|
||||
methods: {
|
||||
onTextFocused() {
|
||||
if (Number.isNaN(this.value)) this.text = "";
|
||||
if (this.value === undefined) this.text = "";
|
||||
else if (this.unitIsHiddenWhenEditing) this.text = `${this.value}`;
|
||||
else this.text = `${this.value}${this.unit}`;
|
||||
|
||||
@@ -131,18 +131,20 @@ export default defineComponent({
|
||||
// enter key (via the `change` event) or when the <input> element is defocused (with the `blur` event binding)
|
||||
onTextChanged() {
|
||||
// The `inputElement.blur()` call at the bottom of this function causes itself to be run again, so this check skips a second run
|
||||
if (this.editing) {
|
||||
const newValue = parseFloat(this.text);
|
||||
this.updateValue(newValue);
|
||||
if (!this.editing) return;
|
||||
|
||||
this.editing = false;
|
||||
const parsed = parseFloat(this.text);
|
||||
const newValue = Number.isNaN(parsed) ? undefined : parsed;
|
||||
|
||||
const inputElement = (this.$refs.fieldInput as typeof FieldInput).$refs.input as HTMLInputElement;
|
||||
inputElement.blur();
|
||||
}
|
||||
this.updateValue(newValue);
|
||||
|
||||
this.editing = false;
|
||||
|
||||
const inputElement = (this.$refs.fieldInput as typeof FieldInput).$refs.input as HTMLInputElement;
|
||||
inputElement.blur();
|
||||
},
|
||||
onCancelTextChange() {
|
||||
this.updateValue(NaN);
|
||||
this.updateValue(undefined);
|
||||
|
||||
this.editing = false;
|
||||
|
||||
@@ -150,16 +152,16 @@ export default defineComponent({
|
||||
inputElement.blur();
|
||||
},
|
||||
onIncrement(direction: IncrementDirection) {
|
||||
if (Number.isNaN(this.value)) return;
|
||||
if (this.value === undefined) return;
|
||||
|
||||
const actions = {
|
||||
Add: (): void => {
|
||||
const directionAddend = direction === "Increase" ? this.incrementFactor : -this.incrementFactor;
|
||||
this.updateValue(this.value + directionAddend);
|
||||
this.updateValue(this.value !== undefined ? this.value + directionAddend : undefined);
|
||||
},
|
||||
Multiply: (): void => {
|
||||
const directionMultiplier = direction === "Increase" ? this.incrementFactor : 1 / this.incrementFactor;
|
||||
this.updateValue(this.value * directionMultiplier);
|
||||
this.updateValue(this.value !== undefined ? this.value * directionMultiplier : undefined);
|
||||
},
|
||||
Callback: (): void => {
|
||||
if (direction === "Increase") this.incrementCallbackIncrease?.();
|
||||
@@ -170,20 +172,20 @@ export default defineComponent({
|
||||
const action = actions[this.incrementBehavior];
|
||||
action();
|
||||
},
|
||||
updateValue(newValue: number) {
|
||||
const invalid = Number.isNaN(newValue);
|
||||
updateValue(newValue: number | undefined) {
|
||||
const nowValid = this.value !== undefined && this.isInteger ? Math.round(this.value) : this.value;
|
||||
let cleaned = newValue !== undefined ? newValue : nowValid;
|
||||
|
||||
let sanitized = newValue;
|
||||
if (invalid) sanitized = this.value;
|
||||
if (this.isInteger) sanitized = Math.round(sanitized);
|
||||
if (typeof this.min === "number" && !Number.isNaN(this.min)) sanitized = Math.max(sanitized, this.min);
|
||||
if (typeof this.max === "number" && !Number.isNaN(this.max)) sanitized = Math.min(sanitized, this.max);
|
||||
if (typeof this.min === "number" && !Number.isNaN(this.min) && cleaned !== undefined) cleaned = Math.max(cleaned, this.min);
|
||||
if (typeof this.max === "number" && !Number.isNaN(this.max) && cleaned !== undefined) cleaned = Math.min(cleaned, this.max);
|
||||
|
||||
if (!invalid) this.$emit("update:value", sanitized);
|
||||
if (newValue !== undefined) this.$emit("update:value", cleaned);
|
||||
|
||||
this.text = this.displayText(sanitized);
|
||||
this.text = this.displayText(cleaned);
|
||||
},
|
||||
displayText(value: number): string {
|
||||
displayText(value: number | undefined): string {
|
||||
if (value === undefined) return "-";
|
||||
|
||||
// Find the amount of digits on the left side of the decimal
|
||||
// 10.25 == 2
|
||||
// 1.23 == 1
|
||||
@@ -199,8 +201,8 @@ export default defineComponent({
|
||||
},
|
||||
watch: {
|
||||
// Called only when `value` is changed from outside this component (with v-model)
|
||||
value(newValue: number) {
|
||||
if (Number.isNaN(newValue)) {
|
||||
value(newValue: number | undefined) {
|
||||
if (newValue === undefined) {
|
||||
this.text = "-";
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
<template>
|
||||
<LayoutRow class="shelf-item-input" :class="{ active: active }">
|
||||
<IconButton :action="action" :icon="icon" :size="32" />
|
||||
</LayoutRow>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.shelf-item-input {
|
||||
flex: 0 0 auto;
|
||||
border-radius: 2px;
|
||||
|
||||
&:hover {
|
||||
background: var(--color-6-lowergray);
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: var(--color-accent);
|
||||
}
|
||||
|
||||
.icon-button {
|
||||
background: unset;
|
||||
}
|
||||
|
||||
svg {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType } from "vue";
|
||||
|
||||
import { IconName } from "@/utilities/icons";
|
||||
|
||||
import LayoutRow from "@/components/layout/LayoutRow.vue";
|
||||
import IconButton from "@/components/widgets/buttons/IconButton.vue";
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
IconButton,
|
||||
LayoutRow,
|
||||
},
|
||||
props: {
|
||||
icon: { type: String as PropType<IconName>, required: true },
|
||||
action: { type: Function as PropType<(e?: MouseEvent) => void>, required: true },
|
||||
active: { type: Boolean as PropType<boolean>, default: false },
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@@ -7,12 +7,14 @@
|
||||
<style lang="scss">
|
||||
.separator {
|
||||
&.vertical {
|
||||
flex: 0 0 auto;
|
||||
|
||||
&.related {
|
||||
margin-top: 4px;
|
||||
height: 4px;
|
||||
}
|
||||
|
||||
&.unrelated {
|
||||
margin-top: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
&.section,
|
||||
@@ -37,12 +39,14 @@
|
||||
}
|
||||
|
||||
&.horizontal {
|
||||
flex: 0 0 auto;
|
||||
|
||||
&.related {
|
||||
margin-left: 4px;
|
||||
width: 4px;
|
||||
}
|
||||
|
||||
&.unrelated {
|
||||
margin-left: 8px;
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
&.section,
|
||||
|
||||
Reference in New Issue
Block a user