Skip to content

Commit

Permalink
Add dropdown menu to icon cells
Browse files Browse the repository at this point in the history
  • Loading branch information
xingrz committed Apr 14, 2024
1 parent 42ce987 commit a936338
Show file tree
Hide file tree
Showing 8 changed files with 202 additions and 11 deletions.
6 changes: 6 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"lint": "eslint --ext .ts,.vue src"
},
"dependencies": {
"@vicons/ionicons5": "^0.12.0",
"@vueuse/core": "^10.9.0",
"ace-code": "^1.32.8",
"md5": "^2.3.0",
Expand Down
31 changes: 24 additions & 7 deletions src/components/BSMap/BSCell.vue
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
<template>
<BSSelectable v-slot="{ selectable }" :focused="props.focused">
<div :class="[selectable, $style.cell]" :title="props.src" :style="style" @click="() => emit('select')">
<BSIcon v-for="(icon, index) in (parts?.icons || [])" :key="index" :class="$style.icon" :src="icon"
@ratio="(ratio: number) => updateRatio(index, ratio)" />
</div>
<BSPopover v-model:show="popoverShown" :ratio="ratio" :icons="parts?.icons || []" :params="parts?.params"
@select="(offset, length) => emit('select', offset, length)">
<div :class="[selectable, $style.cell]" :style="style" @click="() => emit('select', 0, props.src.length)"
@click.right.prevent="handleRightClick">
<BSIcon v-for="({ part }, index) in (parts?.icons || [])" :key="index" :class="$style.icon" :src="part"
@ratio="(ratio: number) => updateRatio(index, ratio)" />
</div>
</BSPopover>
</BSSelectable>
</template>

Expand All @@ -16,9 +20,11 @@ import {
ref,
} from 'vue';
import splitWithOffset from '@/utils/splitWithOffset';
import styleFromParams from '@/utils/styleFromParams';
import BSSelectable from './BSSelectable.vue';
import BSPopover from './BSPopover.vue';
import BSIcon from './BSIcon.vue';
const props = defineProps<{
Expand All @@ -27,17 +33,21 @@ const props = defineProps<{
}>();
const emit = defineEmits<{
(e: 'select'): void;
(e: 'select', offset: number, length: number): void;
}>();
const ratio = ref(1);
const parts = computed(() => {
if (!props.src) return;
const [nonParam, ...params] = props.src.trim().split('!_');
const match = props.src.match(/^( *)([^ ]+)( *)$/);
const leading = (match?.[1] || '').length;
const nonSpace = match?.[2] || props.src;
const [nonParam, ...params] = nonSpace.split('!_');
const [nonLink, ...links] = nonParam.split('!@');
const icons = nonLink.split('!~').filter((icon) => !!icon);
const icons = splitWithOffset(nonLink, '!~', leading).filter(({ part }) => !!part);
return { icons, links, params };
});
Expand All @@ -47,6 +57,13 @@ const style = computed(() => ({
'--bs-map-cell-ratio': (ratio.value == 1 ? undefined : ratio.value),
}) as CSSProperties);
const popoverShown = ref(false);
function handleRightClick(): void {
popoverShown.value = true;
emit('select', 0, props.src.length);
}
function updateRatio(layer: number, newRatio: number): void {
if (layer == 0) {
// Only ratio of the first icon affects the cell
Expand Down
76 changes: 76 additions & 0 deletions src/components/BSMap/BSPopover.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<template>
<n-dropdown show-arrow trigger="manual" placement="right" :disabled="!enabled" :show="show" :options="options"
:render-icon="renderIcon" :render-label="renderLabel" @select="onSelect" @clickoutside="onClickOutside">
<slot />
</n-dropdown>
</template>

<script lang="ts" setup>
import {
type VNode,
computed,
defineEmits,
defineProps,
h,
} from 'vue';
import {
type DropdownOption,
NDropdown,
} from 'naive-ui';
import BSPopoverIcon from './BSPopoverIcon.vue';
import BSPopoverLabel from './BSPopoverLabel.vue';
const props = defineProps<{
ratio: number;
icons: {
part: string;
offset: number;
}[];
params: string[] | undefined;
}>();
const emit = defineEmits<{
(e: 'select', offset: number, length: number): void;
}>();
const show = defineModel<boolean>('show');
type BSCellPopoverOption = DropdownOption & {
offset: number;
};
const enabled = computed(() => props.icons.length > 0);
const options = computed(() => props.icons.map(({ part, offset }) => ({
label: part,
key: `${offset}_${part}`,
offset: offset,
} as BSCellPopoverOption)));
function renderLabel(option: DropdownOption): VNode {
return h(BSPopoverLabel, {
src: option.label as string,
});
}
function renderIcon(option: DropdownOption): VNode {
return h(BSPopoverIcon, {
src: option.label as string,
ratio: props.ratio,
params: props.params?.[0],
});
}
function onSelect(_key: string, option: DropdownOption): void {
const { label, offset } = option as BSCellPopoverOption;
const src = (label as string).split('__')[0];
emit('select', offset, src.length);
show.value = false;
}
function onClickOutside(): void {
show.value = false;
}
</script>
35 changes: 35 additions & 0 deletions src/components/BSMap/BSPopoverIcon.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<template>
<div :class="$style.cell" :style="style">
<BSIcon :src="props.src" />
</div>
</template>

<script lang="ts" setup>
import { type CSSProperties, computed, defineProps } from 'vue';
import styleFromParams from '@/utils/styleFromParams';
import BSIcon from './BSIcon.vue';
const props = defineProps<{
src: string;
ratio: number | undefined;
params: string | undefined;
}>();
const style = computed(() => ({
...styleFromParams(props.params, true),
'--bs-map-cell-ratio': (props.ratio == 1 ? undefined : props.ratio),
}) as CSSProperties);
</script>


<style lang="scss" module>
.cell {
--bs-map-size: 20;
width: calc(var(--bs-map-size) * var(--bs-map-cell-ratio, 1) * 1px);
height: calc(var(--bs-map-size) * 1px);
line-height: calc(var(--bs-map-size) * 1px);
border: 1px solid #ddd;
}
</style>
55 changes: 55 additions & 0 deletions src/components/BSMap/BSPopoverLabel.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<template>
<div :class="$style.item">
<div :class="$style.name">{{ src }}</div>
<n-button v-if="isIcon" :class="$style.open" quaternary block @click.stop="onClick">
<n-icon>
<OpenOutline />
</n-icon>
</n-button>
</div>
</template>

<script lang="ts" setup>
import { computed, defineProps } from 'vue';
import { NButton, NIcon } from 'naive-ui';
import { OpenOutline } from '@vicons/ionicons5';
const props = defineProps<{
src: string;
}>();
const src = computed(() => props.src.split('__')[0]);
const isIcon = computed(() => !props.src.includes('*'));
function onClick(): void {
window.open(`https://commons.wikimedia.org/wiki/File:BSicon_${src.value}.svg`);
}
</script>

<style lang="scss" module>
.item {
display: flex;
width: auto;
margin-right: calc(var(--n-option-suffix-width) * -1 + 2px);
.name {
flex: 1 1 auto;
user-select: none;
font-family: monospace;
margin-right: 16px;
}
.open {
flex: 0 0 20px;
opacity: 0;
transition: opacity 200ms;
}
&:hover {
.open {
opacity: 1;
}
}
}
</style>
4 changes: 2 additions & 2 deletions src/components/BSMap/BSRow.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
<div :class="$style.row">
<div :class="$style.cells" :style="rowStyle">
<BSCell v-for="({ part, offset }, index) in cells" :key="index" :src="part"
:focused="isFocused(offset, part.length)" @select="() => emit('select', offset, part.length)" />
:focused="isFocused(offset, part.length)" @select="(o, length) => emit('select', offset + o, length)" />
</div>
<div :class="$style.texts">
<BSText v-for="({ part, offset, align }, index) in texts" :key="index" :src="part" :align="align"
:focused="isFocused(offset, part.length)" @select="() => emit('select', offset, part.length)" />
:focused="isFocused(offset, part.length)" @select="(o, length) => emit('select', offset + o, length)" />
</div>
</div>
</template>
Expand Down
5 changes: 3 additions & 2 deletions src/components/BSMap/BSText.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<template>
<BSSelectable v-slot="{ selectable }" :focused="props.focused">
<div :class="[selectable, $style.text]" :data-align="align" :title="props.src" @click="() => emit('select')">
<div :class="[selectable, $style.text]" :data-align="align" :title="props.src"
@click="() => emit('select', 0, props.src.length)">
{{ props.src }}
</div>
</BSSelectable>
Expand All @@ -18,7 +19,7 @@ const props = defineProps<{
}>();
const emit = defineEmits<{
(e: 'select'): void;
(e: 'select', offset: number, length: number): void;
}>();
</script>

Expand Down

0 comments on commit a936338

Please sign in to comment.