picocrank 1.18.0 → 1.18.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +9 -9
- package/vue/components/Navigation.vue +8 -2
- package/vue/components/NavigationGrid.vue +69 -2
- package/vue/components/ReadOnlyTextArea.vue +44 -6
- package/vue/components/Tabs.vue +6 -3
- package/vue/views/ReadOnlyTextAreaExample.vue +29 -2
- package/vue/views/UserControlPanel.vue +5 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "picocrank",
|
|
3
|
-
"version": "1.18.
|
|
3
|
+
"version": "1.18.2",
|
|
4
4
|
"description": "A set of zero dependency Vue components built on the Femtocrank global CSS theme.",
|
|
5
5
|
"main": "vite.config.js",
|
|
6
6
|
"scripts": {
|
|
@@ -18,17 +18,17 @@
|
|
|
18
18
|
"url": "https://github.com/jamesread/picocrank/issues"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@hugeicons/core-free-icons": "^4.
|
|
22
|
-
"@hugeicons/vue": "^1.0.
|
|
23
|
-
"@vitejs/plugin-vue": "^6.0.
|
|
21
|
+
"@hugeicons/core-free-icons": "^4.2.2",
|
|
22
|
+
"@hugeicons/vue": "^1.0.7",
|
|
23
|
+
"@vitejs/plugin-vue": "^6.0.7",
|
|
24
24
|
"femtocrank": "^2.5.0",
|
|
25
|
-
"unplugin-vue-components": "^32.
|
|
26
|
-
"vite": "^8.
|
|
27
|
-
"vue": "^3.5.
|
|
28
|
-
"vue-router": "^5.0
|
|
25
|
+
"unplugin-vue-components": "^32.1.0",
|
|
26
|
+
"vite": "^8.1.2",
|
|
27
|
+
"vue": "^3.5.39",
|
|
28
|
+
"vue-router": "^5.1.0"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
|
-
"semantic-release": "^25.0.
|
|
31
|
+
"semantic-release": "^25.0.5"
|
|
32
32
|
},
|
|
33
33
|
"homepage": "https://github.com/jamesread/picocrank#readme"
|
|
34
34
|
}
|
|
@@ -47,7 +47,10 @@ function addRouterLink(link, altTitle = null, options = {}) {
|
|
|
47
47
|
icon: foundRoute.meta?.icon || PinIcon,
|
|
48
48
|
type: 'route',
|
|
49
49
|
props: options.props || {},
|
|
50
|
-
description: options.description || foundRoute.meta?.description || null
|
|
50
|
+
description: options.description || foundRoute.meta?.description || null,
|
|
51
|
+
disabled: options.disabled || false,
|
|
52
|
+
iconColor: options.iconColor || null,
|
|
53
|
+
indicator: options.indicator || false
|
|
51
54
|
}
|
|
52
55
|
|
|
53
56
|
addNavigationLink(routeLink)
|
|
@@ -69,7 +72,10 @@ function addCallback(title, callback, options = {}) {
|
|
|
69
72
|
icon: options.icon || PinIcon,
|
|
70
73
|
callback: callback || (() => {}),
|
|
71
74
|
title: title,
|
|
72
|
-
description: options.description || null
|
|
75
|
+
description: options.description || null,
|
|
76
|
+
disabled: options.disabled || false,
|
|
77
|
+
iconColor: options.iconColor || null,
|
|
78
|
+
indicator: options.indicator || false
|
|
73
79
|
}
|
|
74
80
|
|
|
75
81
|
addNavigationLink(callbackLink)
|
|
@@ -4,11 +4,17 @@
|
|
|
4
4
|
<button
|
|
5
5
|
v-for="link in filteredLinks"
|
|
6
6
|
:key="link.name"
|
|
7
|
-
:class="['nav-button', { active: isLinkActive(link) }]"
|
|
7
|
+
:class="['nav-button', { active: isLinkActive(link), disabled: link.disabled }]"
|
|
8
|
+
:disabled="link.disabled"
|
|
8
9
|
:title="link.title"
|
|
9
10
|
@click="handleLinkClick(link)"
|
|
10
11
|
>
|
|
11
|
-
<
|
|
12
|
+
<span
|
|
13
|
+
v-if="showIndicator(link)"
|
|
14
|
+
class="nav-button-indicator"
|
|
15
|
+
aria-label="Requires attention"
|
|
16
|
+
/>
|
|
17
|
+
<div class="nav-button-icon" :style="getIconStyle(link)">
|
|
12
18
|
<HugeiconsIcon
|
|
13
19
|
:icon="link.icon"
|
|
14
20
|
:width="iconSize"
|
|
@@ -102,10 +108,30 @@ const filteredLinks = computed(() => {
|
|
|
102
108
|
});
|
|
103
109
|
|
|
104
110
|
function isLinkActive(link) {
|
|
111
|
+
if (link.disabled) {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
105
114
|
return isActive(link);
|
|
106
115
|
}
|
|
107
116
|
|
|
117
|
+
function getIconStyle(link) {
|
|
118
|
+
if (link.disabled || isLinkActive(link)) {
|
|
119
|
+
return undefined;
|
|
120
|
+
}
|
|
121
|
+
if (link.iconColor) {
|
|
122
|
+
return { color: link.iconColor };
|
|
123
|
+
}
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function showIndicator(link) {
|
|
128
|
+
return link.indicator && !link.disabled;
|
|
129
|
+
}
|
|
130
|
+
|
|
108
131
|
function handleLinkClick(link) {
|
|
132
|
+
if (link.disabled) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
109
135
|
if (link.type === 'route') {
|
|
110
136
|
const to = link.to || link.path;
|
|
111
137
|
if (to) {
|
|
@@ -147,6 +173,7 @@ function handleLinkClick(link) {
|
|
|
147
173
|
cursor: pointer;
|
|
148
174
|
transition: all 0.2s ease;
|
|
149
175
|
text-align: left;
|
|
176
|
+
position: relative;
|
|
150
177
|
}
|
|
151
178
|
|
|
152
179
|
.navigation-grid.compact .nav-button {
|
|
@@ -188,6 +215,46 @@ function handleLinkClick(link) {
|
|
|
188
215
|
color: #fff;
|
|
189
216
|
}
|
|
190
217
|
|
|
218
|
+
.nav-button:disabled,
|
|
219
|
+
.nav-button.disabled {
|
|
220
|
+
cursor: not-allowed;
|
|
221
|
+
opacity: 0.6;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
.nav-button:disabled:hover,
|
|
225
|
+
.nav-button.disabled:hover {
|
|
226
|
+
border-color: var(--border-color, #e1e5e9);
|
|
227
|
+
transform: none;
|
|
228
|
+
box-shadow: none;
|
|
229
|
+
background: transparent;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.nav-button:disabled .nav-button-icon,
|
|
233
|
+
.nav-button.disabled .nav-button-icon {
|
|
234
|
+
color: var(--text-muted, #999);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
.nav-button-indicator {
|
|
238
|
+
position: absolute;
|
|
239
|
+
top: 0.75rem;
|
|
240
|
+
right: 0.75rem;
|
|
241
|
+
width: 0.625rem;
|
|
242
|
+
height: 0.625rem;
|
|
243
|
+
border-radius: 50%;
|
|
244
|
+
background: var(--indicator-color, #dc3545);
|
|
245
|
+
box-shadow: 0 0 0 2px var(--background-color, #fff);
|
|
246
|
+
flex-shrink: 0;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.navigation-grid.compact .nav-button-indicator {
|
|
250
|
+
top: 0.5rem;
|
|
251
|
+
right: 0.5rem;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
.nav-button.active .nav-button-indicator {
|
|
255
|
+
box-shadow: 0 0 0 2px var(--primary-color, #007bff);
|
|
256
|
+
}
|
|
257
|
+
|
|
191
258
|
.nav-button-label {
|
|
192
259
|
font-weight: bold;
|
|
193
260
|
font-size: 1.125rem;
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
</template>
|
|
29
29
|
|
|
30
30
|
<script setup>
|
|
31
|
-
import { ref, computed, useId } from 'vue'
|
|
31
|
+
import { ref, computed, useId, watch } from 'vue'
|
|
32
32
|
import { HugeiconsIcon } from '@hugeicons/vue'
|
|
33
33
|
import { Copy01Icon, CopyCheckIcon } from '@hugeicons/core-free-icons'
|
|
34
34
|
|
|
@@ -83,25 +83,58 @@ const props = defineProps({
|
|
|
83
83
|
},
|
|
84
84
|
})
|
|
85
85
|
|
|
86
|
-
const emit = defineEmits(['copy', 'copy-error'])
|
|
86
|
+
const emit = defineEmits(['copy', 'copy-error', 'update:modelValue'])
|
|
87
87
|
|
|
88
88
|
const generatedId = useId()
|
|
89
89
|
const textareaId = props.id || `readonly-textarea-${generatedId}`
|
|
90
90
|
const textareaRef = ref(null)
|
|
91
91
|
const copied = ref(false)
|
|
92
|
+
const content = ref(props.modelValue)
|
|
92
93
|
let copiedTimer = null
|
|
93
94
|
|
|
95
|
+
watch(() => props.modelValue, (value) => {
|
|
96
|
+
content.value = value
|
|
97
|
+
})
|
|
98
|
+
|
|
94
99
|
const displayValue = computed(() => {
|
|
95
|
-
if (!props.markdownTicks || !
|
|
96
|
-
return
|
|
100
|
+
if (!props.markdownTicks || !content.value) {
|
|
101
|
+
return content.value
|
|
97
102
|
}
|
|
98
103
|
|
|
99
104
|
const openingFence = props.markdownLang ? `\`\`\`${props.markdownLang}` : '```'
|
|
100
|
-
return `${openingFence}\n${
|
|
105
|
+
return `${openingFence}\n${content.value}\n\`\`\``
|
|
101
106
|
})
|
|
102
107
|
|
|
108
|
+
function append(text) {
|
|
109
|
+
content.value += text
|
|
110
|
+
emit('update:modelValue', content.value)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function appendYamlProperty(k, v) {
|
|
114
|
+
const line = `${k}: ${v}`
|
|
115
|
+
if (content.value && !content.value.endsWith('\n')) {
|
|
116
|
+
content.value += '\n'
|
|
117
|
+
}
|
|
118
|
+
content.value += line
|
|
119
|
+
emit('update:modelValue', content.value)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function appendSection(sectionName) {
|
|
123
|
+
content.value += `\n# ${sectionName} ####\n`
|
|
124
|
+
emit('update:modelValue', content.value)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function clear() {
|
|
128
|
+
content.value = ''
|
|
129
|
+
emit('update:modelValue', content.value)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function getContentAsString() {
|
|
133
|
+
return content.value
|
|
134
|
+
}
|
|
135
|
+
|
|
103
136
|
async function copy() {
|
|
104
|
-
if (!
|
|
137
|
+
if (!content.value) {
|
|
105
138
|
return
|
|
106
139
|
}
|
|
107
140
|
|
|
@@ -133,6 +166,11 @@ function markCopied() {
|
|
|
133
166
|
|
|
134
167
|
defineExpose({
|
|
135
168
|
copy,
|
|
169
|
+
append,
|
|
170
|
+
appendYamlProperty,
|
|
171
|
+
appendSection,
|
|
172
|
+
clear,
|
|
173
|
+
getContentAsString,
|
|
136
174
|
})
|
|
137
175
|
</script>
|
|
138
176
|
|
package/vue/components/Tabs.vue
CHANGED
|
@@ -143,6 +143,7 @@ defineExpose({
|
|
|
143
143
|
background: transparent;
|
|
144
144
|
border: none;
|
|
145
145
|
border-bottom: 2px solid transparent;
|
|
146
|
+
border-radius: 0;
|
|
146
147
|
cursor: pointer;
|
|
147
148
|
font-size: 1em;
|
|
148
149
|
color: var(--text-muted, #666);
|
|
@@ -165,7 +166,7 @@ defineExpose({
|
|
|
165
166
|
.tab-button:focus {
|
|
166
167
|
outline: 2px solid var(--primary-color, #007bff);
|
|
167
168
|
outline-offset: -2px;
|
|
168
|
-
border-radius:
|
|
169
|
+
border-radius: 0;
|
|
169
170
|
}
|
|
170
171
|
|
|
171
172
|
.tabs-content {
|
|
@@ -174,7 +175,8 @@ defineExpose({
|
|
|
174
175
|
}
|
|
175
176
|
|
|
176
177
|
.tab-panel {
|
|
177
|
-
padding: 1.5rem;
|
|
178
|
+
padding-block: 1.5rem;
|
|
179
|
+
padding-inline: 0.75rem;
|
|
178
180
|
display: none;
|
|
179
181
|
}
|
|
180
182
|
|
|
@@ -227,7 +229,8 @@ defineExpose({
|
|
|
227
229
|
}
|
|
228
230
|
|
|
229
231
|
.tab-panel {
|
|
230
|
-
padding: 1rem;
|
|
232
|
+
padding-block: 1rem;
|
|
233
|
+
padding-inline: 0.5rem;
|
|
231
234
|
}
|
|
232
235
|
}
|
|
233
236
|
</style>
|
|
@@ -29,7 +29,15 @@
|
|
|
29
29
|
/>
|
|
30
30
|
|
|
31
31
|
<ReadOnlyTextArea
|
|
32
|
-
|
|
32
|
+
ref="yamlConfigRef"
|
|
33
|
+
v-model="yamlConfig"
|
|
34
|
+
label="Configuration (YAML)"
|
|
35
|
+
:rows="12"
|
|
36
|
+
markdown-ticks
|
|
37
|
+
markdown-lang="yaml"
|
|
38
|
+
/>
|
|
39
|
+
|
|
40
|
+
<ReadOnlyTextArea
|
|
33
41
|
:rows="4"
|
|
34
42
|
placeholder="No diagnostics available."
|
|
35
43
|
/>
|
|
@@ -42,13 +50,15 @@
|
|
|
42
50
|
</template>
|
|
43
51
|
|
|
44
52
|
<script setup>
|
|
45
|
-
import { ref } from 'vue'
|
|
53
|
+
import { ref, nextTick } from 'vue'
|
|
46
54
|
import Section from '../components/Section.vue'
|
|
47
55
|
import ReadOnlyTextArea from '../components/ReadOnlyTextArea.vue'
|
|
48
56
|
|
|
49
57
|
const diagnosticLog = ref('')
|
|
50
58
|
const environmentInfo = ref('')
|
|
59
|
+
const yamlConfig = ref('')
|
|
51
60
|
const lastCopied = ref('')
|
|
61
|
+
const yamlConfigRef = ref(null)
|
|
52
62
|
|
|
53
63
|
function buildDiagnosticLog() {
|
|
54
64
|
const now = new Date().toISOString()
|
|
@@ -81,10 +91,27 @@ function buildEnvironmentInfo() {
|
|
|
81
91
|
}, null, 2)
|
|
82
92
|
}
|
|
83
93
|
|
|
94
|
+
function buildYamlConfig() {
|
|
95
|
+
yamlConfig.value = ''
|
|
96
|
+
const area = yamlConfigRef.value
|
|
97
|
+
if (!area) {
|
|
98
|
+
return
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
area.appendSection('Application')
|
|
102
|
+
area.appendYamlProperty('name', 'picocrank')
|
|
103
|
+
area.appendYamlProperty('version', '1.0.0')
|
|
104
|
+
area.appendYamlProperty('environment', 'development')
|
|
105
|
+
area.appendSection('Runtime')
|
|
106
|
+
area.appendYamlProperty('language', navigator.language)
|
|
107
|
+
area.appendYamlProperty('online', navigator.onLine)
|
|
108
|
+
}
|
|
109
|
+
|
|
84
110
|
function refreshDiagnostics() {
|
|
85
111
|
diagnosticLog.value = buildDiagnosticLog()
|
|
86
112
|
environmentInfo.value = buildEnvironmentInfo()
|
|
87
113
|
lastCopied.value = ''
|
|
114
|
+
nextTick(() => buildYamlConfig())
|
|
88
115
|
}
|
|
89
116
|
|
|
90
117
|
function onCopy(value) {
|
|
@@ -60,7 +60,9 @@ onMounted(() => {
|
|
|
60
60
|
}, {
|
|
61
61
|
icon: ViewIcon,
|
|
62
62
|
name: 'notifications',
|
|
63
|
-
description: 'Manage notification settings'
|
|
63
|
+
description: 'Manage notification settings',
|
|
64
|
+
iconColor: '#e67e22',
|
|
65
|
+
indicator: true
|
|
64
66
|
});
|
|
65
67
|
|
|
66
68
|
localNavigation.value.addCallback('Billing', () => {
|
|
@@ -68,7 +70,8 @@ onMounted(() => {
|
|
|
68
70
|
}, {
|
|
69
71
|
icon: EditIcon,
|
|
70
72
|
name: 'billing',
|
|
71
|
-
description: 'View billing and subscription details'
|
|
73
|
+
description: 'View billing and subscription details',
|
|
74
|
+
disabled: true
|
|
72
75
|
});
|
|
73
76
|
}
|
|
74
77
|
});
|