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 Mar 31, 2024
1 parent 2f7ea65 commit 12492d1
Show file tree
Hide file tree
Showing 6 changed files with 166 additions and 8 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",
"ace-code": "^1.32.8",
"md5": "^2.3.0",
"naive-ui": "^2.38.1",
Expand Down
72 changes: 65 additions & 7 deletions src/components/BSMap/BSCell.vue
Original file line number Diff line number Diff line change
@@ -1,25 +1,40 @@
<template>
<BSSelectable v-slot="{ selectable }" :row="props.row" :offset="props.offset" :length="props.src.length">
<div :class="[selectable, $style.cell]" :title="props.src" @click="handleClick" :style="style">
<BSIcon v-for="(icon, index) in (parts?.icons || [])" :key="index" :src="icon"
@ratio="(ratio: number) => updateRatio(index, ratio)" />
</div>
<BSSelectable v-slot="{ selectable }" :row="props.row" :offset="props.offset" :length="props.src.length"
:hovered="popoverShow">
<n-dropdown show-arrow trigger="hover" placement="right" v-model:show="popoverShow" :disabled="!popoverEnabled"
:options="popoverOptions" :render-icon="renderPopoverIcon" :render-label="renderPopoverLabel"
@select="onPopoverSelect">
<div :class="[selectable, $style.cell]" :style="style" @click="handleClick">
<BSIcon v-for="({ part }, index) in (parts?.icons || [])" :key="index" :src="part"
@ratio="(ratio: number) => updateRatio(index, ratio)" />
</div>
</n-dropdown>
</BSSelectable>
</template>
<script lang="ts" setup>
import {
type CSSProperties,
type VNode,
computed,
defineProps,
h,
ref,
} from 'vue';
import {
type DropdownOption,
NDropdown,
} from 'naive-ui';
import { useEditorStore } from '@/stores/editor';
import splitWithOffset from '@/utils/splitWithOffset';
import styleFromParams from '@/utils/styleFromParams';
import BSSelectable from './BSSelectable.vue';
import BSIcon from './BSIcon.vue';
import BSCellPopoverIcon from './BSCellPopoverIcon.vue';
import BSCellPopoverLabel from './BSCellPopoverLabel.vue';
const props = defineProps<{
src: string;
Expand All @@ -34,9 +49,13 @@ const ratio = ref(1);
const parts = computed(() => {
if (!props.src) return;
const [nonParam, ...params] = props.src.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 @@ -61,6 +80,45 @@ function updateRatio(layer: number, newRatio: number): void {
ratio.value = newRatio;
}
}
type BSCellPopoverOption = DropdownOption & {
offset: number;
};
const popoverShow = ref(false);
const popoverEnabled = computed(() => (parts.value?.icons || []).length > 0);
const popoverOptions = computed(() => (parts.value?.icons || []).map(({ part, offset }) => ({
label: part,
key: `${offset}_${part}`,
offset: offset,
} as BSCellPopoverOption)));
function renderPopoverLabel(option: DropdownOption): VNode {
return h(BSCellPopoverLabel, {
src: option.label as string,
});
}
function renderPopoverIcon(option: DropdownOption): VNode {
return h(BSCellPopoverIcon, {
src: option.label as string,
ratio: ratio.value,
params: parts.value?.params?.[0],
});
}
function onPopoverSelect(_key: string, option: DropdownOption): void {
const { label, offset } = option as BSCellPopoverOption;
const src = (label as string).split('__')[0];
editorStore.selection = {
row: props.row,
offset: props.offset + offset,
length: src.length,
from: 'preview',
};
}
</script>
<style lang="scss" module>
Expand Down
35 changes: 35 additions & 0 deletions src/components/BSMap/BSCellPopoverIcon.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/BSCellPopoverLabel.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>
5 changes: 4 additions & 1 deletion src/components/BSMap/BSSelectable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
<slot :selectable="{
[$style.selectable]: true,
[$style.focused]: focused,
[$style.hovered]: props.hovered,
}" />
</template>

Expand All @@ -14,6 +15,7 @@ const props = defineProps<{
row: number;
offset: number;
length: number;
hovered?: boolean;
}>();
const editorStore = useEditorStore();
Expand Down Expand Up @@ -50,7 +52,8 @@ const focused = computed(() => {
transition: opacity 200ms;
}
&:hover::after {
&:hover::after,
&.hovered::after {
opacity: 0.2;
}
Expand Down

0 comments on commit 12492d1

Please sign in to comment.