feat default icon cli hugeicon (#1036)
This commit is contained in:
commit
e0eea9bc90
|
|
@ -39,6 +39,22 @@ And you should get something that looks like this;
|
||||||
|
|
||||||
image::../action-button-iconify.png[]
|
image::../action-button-iconify.png[]
|
||||||
|
|
||||||
|
== Default Icon (bundled HugeIcon)
|
||||||
|
|
||||||
|
OliveTin used to use a default emoji smiley face as the default icon for actions, but that was a bit too "emoji" and not everyone liked it. Now, OliveTin uses a simple "command line" icon from the HugeIcons set as the default icon for actions. This is a simple and neutral icon that should work well for most actions.
|
||||||
|
|
||||||
|
If you need to reset a default icon for some reason, this is how you can do it;
|
||||||
|
|
||||||
|
.`config.yaml`
|
||||||
|
----
|
||||||
|
actions:
|
||||||
|
- title: Action with the bundled CLI HugeIcon
|
||||||
|
icon: hugeicons:CommandLineIcon
|
||||||
|
shell: echo hello
|
||||||
|
----
|
||||||
|
|
||||||
|
If you want to use other icons from the HugeIcons set, you need to use the Iconify method described above, not with the "hugeicons:" prefix - that only works for the default icon.
|
||||||
|
|
||||||
== Unicode icons ("emoji")
|
== Unicode icons ("emoji")
|
||||||
|
|
||||||
Using simple emoji (unicode) icons from your browser's font is extremely fast, and can look good on some platforms. However, the icons are platform specific, which mean's they'll look different between browsers and between operating systems.
|
Using simple emoji (unicode) icons from your browser's font is extremely fast, and can look good on some platforms. However, the icons are platform specific, which mean's they'll look different between browsers and between operating systems.
|
||||||
|
|
@ -56,7 +72,7 @@ actions:
|
||||||
shell: echo "You are awesome"
|
shell: echo "You are awesome"
|
||||||
----
|
----
|
||||||
|
|
||||||
=== Unicode alises
|
=== Unicode aliases
|
||||||
|
|
||||||
OliveTin has hard-coded aliases for a few commonly used icons, so you don't have to type out the full unicode codes. A list of those hard coded icons is;
|
OliveTin has hard-coded aliases for a few commonly used icons, so you don't have to type out the full unicode codes. A list of those hard coded icons is;
|
||||||
|
|
||||||
|
|
@ -140,5 +156,3 @@ examples;
|
||||||
shell: echo "I like purple"
|
shell: echo "I like purple"
|
||||||
----
|
----
|
||||||
////
|
////
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ actions:
|
||||||
shell: echo 'Hello World!'
|
shell: echo 'Hello World!'
|
||||||
----
|
----
|
||||||
|
|
||||||
The configuration does not really get more complicated than that. You can of course add more actions, and customize more, but the syntax otherwise extremely simple.
|
The configuration does not really get more complicated than that. You can of course add more actions, and customize more, but the syntax is otherwise extremely simple.
|
||||||
|
|
||||||
For building up from here, look at the following resources;
|
For building up from here, look at the following resources;
|
||||||
|
|
||||||
|
|
@ -54,7 +54,7 @@ All configuration options are covered in the solution sections
|
||||||
| `showNavigateOnStartIcons` | Show (or hide) the small icons on action buttons that indicate popup/argument/background behavior on start. | `true` | Live reloadable | xref:advanced_configuration/webui.adoc[Customize the web UI].
|
| `showNavigateOnStartIcons` | Show (or hide) the small icons on action buttons that indicate popup/argument/background behavior on start. | `true` | Live reloadable | xref:advanced_configuration/webui.adoc[Customize the web UI].
|
||||||
| `sectionNavigationStyle` | The style of the section navigation. `sidebar`, `topbar` | `sidebar` | Live reloadable | xref:advanced_configuration/webui.adoc[Customize the web UI].
|
| `sectionNavigationStyle` | The style of the section navigation. `sidebar`, `topbar` | `sidebar` | Live reloadable | xref:advanced_configuration/webui.adoc[Customize the web UI].
|
||||||
| `defaultPopupOnStart` | The default popup to show on start. | `none` | Live reloadable | xref:action_customization/popuponstart.adoc[Popup On Start].
|
| `defaultPopupOnStart` | The default popup to show on start. | `none` | Live reloadable | xref:action_customization/popuponstart.adoc[Popup On Start].
|
||||||
| `defaultIconForActions` | The default icon to use for actions. | `smile` | Requires Restart | -
|
| `defaultIconForActions` | The default icon string for actions (Unicode aliases such as `smile`, `hugeicons:NeutralIcon`, HTML, Iconify snippets, images, etc.). See xref:action_customization/icons.adoc[Icons]. | `hugeicons:CommandLineIcon` | Requires Restart | -
|
||||||
| `defaultIconForDirectories` | The default icon to use for directories. | `directory` | Requires Restart | -
|
| `defaultIconForDirectories` | The default icon to use for directories. | `directory` | Requires Restart | -
|
||||||
| `defaultIconForBack` | The default icon to use for back (from directories). | `«` | Requires Restart | -
|
| `defaultIconForBack` | The default icon to use for back (from directories). | `«` | Requires Restart | -
|
||||||
| `enableCustomJs` | Enable custom JavaScript. | `false` | Live Reloadable, but refreshing the web browser is required. | xref:advanced_configuration/webui.adoc[Custom JS].
|
| `enableCustomJs` | Enable custom JavaScript. | `false` | Live Reloadable, but refreshing the web browser is required. | xref:advanced_configuration/webui.adoc[Custom JS].
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span class="icon" v-html="unicodeIcon"></span>
|
<ActionIconGlyph class="icon" :glyph="actionGlyph" />
|
||||||
<span class="title" aria-live="polite">{{ displayTitle }}
|
<span class="title" aria-live="polite">{{ displayTitle }}
|
||||||
</span>
|
</span>
|
||||||
<span v-if="rateLimitMessage" class="rate-limit-message">{{ rateLimitMessage }}</span>
|
<span v-if="rateLimitMessage" class="rate-limit-message">{{ rateLimitMessage }}</span>
|
||||||
|
|
@ -33,7 +33,9 @@ import { useRouter } from 'vue-router'
|
||||||
import { HugeiconsIcon } from '@hugeicons/vue'
|
import { HugeiconsIcon } from '@hugeicons/vue'
|
||||||
import { WorkoutRunIcon, TypeCursorIcon, ComputerTerminal01Icon, WorkHistoryIcon } from '@hugeicons/core-free-icons'
|
import { WorkoutRunIcon, TypeCursorIcon, ComputerTerminal01Icon, WorkHistoryIcon } from '@hugeicons/core-free-icons'
|
||||||
|
|
||||||
import { ref, watch, onMounted, onUnmounted, inject, computed } from 'vue'
|
import ActionIconGlyph from './components/ActionIconGlyph.vue'
|
||||||
|
|
||||||
|
import { ref, watch, onMounted, onUnmounted, computed } from 'vue'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const navigateOnStart = ref('')
|
const navigateOnStart = ref('')
|
||||||
|
|
@ -56,7 +58,6 @@ const canExec = ref(true)
|
||||||
const popupOnStart = ref('')
|
const popupOnStart = ref('')
|
||||||
|
|
||||||
// Display properties
|
// Display properties
|
||||||
const unicodeIcon = ref('💩')
|
|
||||||
const displayTitle = ref('')
|
const displayTitle = ref('')
|
||||||
|
|
||||||
// State
|
// State
|
||||||
|
|
@ -77,6 +78,9 @@ const showNavigateOnStartIcons = computed(() => {
|
||||||
return window.initResponse?.showNavigateOnStartIcons ?? true
|
return window.initResponse?.showNavigateOnStartIcons ?? true
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const actionGlyph = computed(() => props.actionData?.icon ?? '')
|
||||||
|
const glyph = ref('')
|
||||||
|
|
||||||
// Combined classes including custom cssClass
|
// Combined classes including custom cssClass
|
||||||
const combinedClasses = computed(() => {
|
const combinedClasses = computed(() => {
|
||||||
const classes = [...buttonClasses.value]
|
const classes = [...buttonClasses.value]
|
||||||
|
|
@ -89,16 +93,6 @@ const combinedClasses = computed(() => {
|
||||||
// Timestamps
|
// Timestamps
|
||||||
const updateIterationTimestamp = ref(0)
|
const updateIterationTimestamp = ref(0)
|
||||||
|
|
||||||
function getUnicodeIcon(icon) {
|
|
||||||
if (icon === '') {
|
|
||||||
console.log('icon not found ', icon)
|
|
||||||
|
|
||||||
return '💩'
|
|
||||||
} else {
|
|
||||||
return unescape(icon)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function constructFromJson(json) {
|
function constructFromJson(json) {
|
||||||
updateIterationTimestamp.value = 0
|
updateIterationTimestamp.value = 0
|
||||||
|
|
||||||
|
|
@ -119,8 +113,7 @@ function constructFromJson(json) {
|
||||||
|
|
||||||
isDisabled.value = !json.canExec
|
isDisabled.value = !json.canExec
|
||||||
displayTitle.value = title.value
|
displayTitle.value = title.value
|
||||||
unicodeIcon.value = getUnicodeIcon(json.icon)
|
glyph.value = json.icon ?? ''
|
||||||
|
|
||||||
// Initialize rate limit from action data (parse datetime string)
|
// Initialize rate limit from action data (parse datetime string)
|
||||||
if (json.datetimeRateLimitExpires) {
|
if (json.datetimeRateLimitExpires) {
|
||||||
const date = new Date(json.datetimeRateLimitExpires.replace(' ', 'T'))
|
const date = new Date(json.datetimeRateLimitExpires.replace(' ', 'T'))
|
||||||
|
|
@ -139,8 +132,6 @@ function updateFromJson(json) {
|
||||||
// Fields that should not be updated
|
// Fields that should not be updated
|
||||||
// title - as the callback URL relies on it
|
// title - as the callback URL relies on it
|
||||||
|
|
||||||
unicodeIcon.value = getUnicodeIcon(json.icon)
|
|
||||||
|
|
||||||
// Update rate limiting if changed (parse datetime string)
|
// Update rate limiting if changed (parse datetime string)
|
||||||
if (json.datetimeRateLimitExpires) {
|
if (json.datetimeRateLimitExpires) {
|
||||||
const date = new Date(json.datetimeRateLimitExpires.replace(' ', 'T'))
|
const date = new Date(json.datetimeRateLimitExpires.replace(' ', 'T'))
|
||||||
|
|
@ -334,10 +325,17 @@ watch(
|
||||||
() => props.actionData,
|
() => props.actionData,
|
||||||
(newData) => {
|
(newData) => {
|
||||||
updateFromJson(newData)
|
updateFromJson(newData)
|
||||||
|
if (newData?.icon !== undefined) {
|
||||||
|
glyph.value = newData.icon ?? ''
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{ deep: true }
|
{ deep: true }
|
||||||
)
|
)
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
glyph
|
||||||
|
})
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
<template>
|
||||||
|
<span class="action-icon-glyph">
|
||||||
|
<HugeiconsIcon
|
||||||
|
v-if="hugeiconsModel"
|
||||||
|
:icon="hugeiconsModel"
|
||||||
|
width="1em"
|
||||||
|
height="1em"
|
||||||
|
class="action-icon-glyph-svg"
|
||||||
|
/>
|
||||||
|
<span v-else v-text="decodedTextGlyph"></span>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import { HugeiconsIcon } from '@hugeicons/vue'
|
||||||
|
import { CommandLineIcon } from '@hugeicons/core-free-icons'
|
||||||
|
|
||||||
|
const hugeiconsPrefix = 'hugeicons:'
|
||||||
|
|
||||||
|
/** Maps config values like hugeicons:CommandLineIcon to Hugeicons icon definitions. */
|
||||||
|
const hugeiconsRegistry = {
|
||||||
|
CommandLineIcon,
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
glyph: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const hugeiconsModel = computed(() => {
|
||||||
|
if (!props.glyph) {
|
||||||
|
return CommandLineIcon
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!props.glyph.startsWith(hugeiconsPrefix)) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const name = props.glyph.slice(hugeiconsPrefix.length)
|
||||||
|
const iconModel = hugeiconsRegistry[name]
|
||||||
|
|
||||||
|
return iconModel ?? CommandLineIcon
|
||||||
|
})
|
||||||
|
|
||||||
|
function decodeHtmlEntities(text) {
|
||||||
|
return text.replace(/&#x([0-9a-fA-F]+);?/g, (_, hex) => {
|
||||||
|
const codePoint = Number.parseInt(hex, 16)
|
||||||
|
return Number.isFinite(codePoint) ? String.fromCodePoint(codePoint) : ''
|
||||||
|
}).replace(/&#(\d+);?/g, (_, decimal) => {
|
||||||
|
const codePoint = Number.parseInt(decimal, 10)
|
||||||
|
return Number.isFinite(codePoint) ? String.fromCodePoint(codePoint) : ''
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const decodedTextGlyph = computed(() => {
|
||||||
|
if (hugeiconsModel.value) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
return decodeHtmlEntities(props.glyph)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.action-icon-glyph {
|
||||||
|
display: inline-flex;
|
||||||
|
vertical-align: middle;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-icon-glyph-svg {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -32,7 +32,7 @@
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div style = "align-self: start; text-align: right;">
|
<div style = "align-self: start; text-align: right;">
|
||||||
<span class="icon" v-html="action.icon"></span>
|
<ActionIconGlyph class="icon" :glyph="action.icon" />
|
||||||
|
|
||||||
<div class="filter-container">
|
<div class="filter-container">
|
||||||
<label class="input-with-icons">
|
<label class="input-with-icons">
|
||||||
|
|
@ -104,6 +104,7 @@ import { ref, computed, onMounted, onUnmounted, watch } from 'vue'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import Pagination from 'picocrank/vue/components/Pagination.vue'
|
import Pagination from 'picocrank/vue/components/Pagination.vue'
|
||||||
import Section from 'picocrank/vue/components/Section.vue'
|
import Section from 'picocrank/vue/components/Section.vue'
|
||||||
|
import ActionIconGlyph from '../components/ActionIconGlyph.vue'
|
||||||
import ActionStatusDisplay from '../components/ActionStatusDisplay.vue'
|
import ActionStatusDisplay from '../components/ActionStatusDisplay.vue'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|
@ -446,4 +447,3 @@ watch(
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@
|
||||||
<ActionStatusDisplay :log-entry="logEntry" id = "execution-dialog-status" />
|
<ActionStatusDisplay :log-entry="logEntry" id = "execution-dialog-status" />
|
||||||
</dd>
|
</dd>
|
||||||
</dl>
|
</dl>
|
||||||
<span class="icon" role="img" v-html="icon" style = "align-self: start"></span>
|
<ActionIconGlyph class="icon" role="img" :glyph="icon" style="align-self: start" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="notFound" class="error-message padded-content">
|
<div v-if="notFound" class="error-message padded-content">
|
||||||
|
|
@ -62,6 +62,7 @@
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted, onBeforeUnmount, watch } from 'vue'
|
import { ref, onMounted, onBeforeUnmount, watch } from 'vue'
|
||||||
|
import ActionIconGlyph from '../components/ActionIconGlyph.vue'
|
||||||
import ActionStatusDisplay from '../components/ActionStatusDisplay.vue'
|
import ActionStatusDisplay from '../components/ActionStatusDisplay.vue'
|
||||||
import Section from 'picocrank/vue/components/Section.vue'
|
import Section from 'picocrank/vue/components/Section.vue'
|
||||||
import { OutputTerminal } from '../../../js/OutputTerminal.js'
|
import { OutputTerminal } from '../../../js/OutputTerminal.js'
|
||||||
|
|
@ -151,7 +152,7 @@ async function reset() {
|
||||||
|
|
||||||
function show(actionButton) {
|
function show(actionButton) {
|
||||||
if (actionButton) {
|
if (actionButton) {
|
||||||
icon.value = actionButton.domIcon.innerText
|
icon.value = actionButton.glyph ?? ''
|
||||||
}
|
}
|
||||||
|
|
||||||
canKill.value = true
|
canKill.value = true
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@
|
||||||
<tr v-for="log in filteredLogs" :key="log.executionTrackingId" class="log-row" :title="log.actionTitle">
|
<tr v-for="log in filteredLogs" :key="log.executionTrackingId" class="log-row" :title="log.actionTitle">
|
||||||
<td class="timestamp">{{ formatTimestamp(log.datetimeStarted) }}</td>
|
<td class="timestamp">{{ formatTimestamp(log.datetimeStarted) }}</td>
|
||||||
<td>
|
<td>
|
||||||
<span class="icon" v-html="log.actionIcon"></span>
|
<ActionIconGlyph class="icon" :glyph="log.actionIcon" />
|
||||||
<router-link :to="`/logs/${log.executionTrackingId}`">
|
<router-link :to="`/logs/${log.executionTrackingId}`">
|
||||||
{{ log.actionTitle }}
|
{{ log.actionTitle }}
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|
@ -93,6 +93,7 @@ import Pagination from 'picocrank/vue/components/Pagination.vue'
|
||||||
import Section from 'picocrank/vue/components/Section.vue'
|
import Section from 'picocrank/vue/components/Section.vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import ActionStatusDisplay from '../components/ActionStatusDisplay.vue'
|
import ActionStatusDisplay from '../components/ActionStatusDisplay.vue'
|
||||||
|
import ActionIconGlyph from '../components/ActionIconGlyph.vue'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
|
||||||
|
|
@ -286,7 +286,7 @@ func DefaultConfigWithBasePort(basePort int) *Config {
|
||||||
config.Security.HeaderXContentTypeOptions = true
|
config.Security.HeaderXContentTypeOptions = true
|
||||||
config.Security.HeaderXFrameOptions = true
|
config.Security.HeaderXFrameOptions = true
|
||||||
config.Security.XFrameOptions = "DENY"
|
config.Security.XFrameOptions = "DENY"
|
||||||
config.DefaultIconForActions = "😀"
|
config.DefaultIconForActions = "hugeicons:CommandLineIcon"
|
||||||
config.DefaultIconForDirectories = "📁"
|
config.DefaultIconForDirectories = "📁"
|
||||||
config.DefaultIconForBack = "«"
|
config.DefaultIconForBack = "«"
|
||||||
config.ThemeCacheDisabled = false
|
config.ThemeCacheDisabled = false
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ func TestSanitizeConfig(t *testing.T) {
|
||||||
|
|
||||||
assert.NotNil(t, a2, "Found action after adding it")
|
assert.NotNil(t, a2, "Found action after adding it")
|
||||||
assert.Equal(t, 3, a2.Timeout, "Default timeout is set")
|
assert.Equal(t, 3, a2.Timeout, "Default timeout is set")
|
||||||
assert.Equal(t, "😀", a2.Icon, "Default icon is a smiley")
|
assert.Equal(t, "hugeicons:CommandLineIcon", a2.Icon, "Default icon is the neutral CLI glyph")
|
||||||
assert.Equal(t, "Carrots", a2.Arguments[0].Title, "Arg title is set to name")
|
assert.Equal(t, "Carrots", a2.Arguments[0].Title, "Arg title is set to name")
|
||||||
assert.Equal(t, "Waffle", a2.Arguments[0].Choices[0].Title, "Choice title is set to name")
|
assert.Equal(t, "Waffle", a2.Arguments[0].Choices[0].Title, "Choice title is set to name")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue