@cavulsqa/create 2.9.3 → 2.10.1
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/README.md +12 -1
- package/bin/create.mjs +18 -25
- package/lib/templates.mjs +56 -0
- package/package.json +13 -5
- package/templates/f7-app/auto-imports.d.ts +9 -0
- package/templates/f7-app/package.json +6 -3
- package/templates/f7-app/src/env.d.ts +1 -0
- package/templates/f7-app/src/main.ts +3 -0
- package/templates/f7-app/src/plugins/bootstrapError.ts +2 -0
- package/templates/f7-app/src/plugins/recorder.plugin.ts +155 -0
- package/templates/f7-app/src/shared/recorder/capacitorSink.ts +124 -0
- package/templates/f7-app/vite.config.ts +1 -0
- package/templates/m3e-app/.claude/rules/data-fetching.md +68 -0
- package/templates/m3e-app/.claude/rules/database.md +106 -0
- package/templates/m3e-app/.claude/rules/m3e-ui.md +48 -0
- package/templates/m3e-app/.claude/rules/modules.md +43 -0
- package/templates/m3e-app/.claude/rules/native.md +60 -0
- package/templates/m3e-app/.claude/skills/m3-expressive/SKILL.md +68 -0
- package/templates/m3e-app/.claude/skills/m3-expressive/color.md +44 -0
- package/templates/m3e-app/.claude/skills/m3-expressive/components.md +325 -0
- package/templates/m3e-app/.claude/skills/m3-expressive/layout.md +42 -0
- package/templates/m3e-app/.claude/skills/m3-expressive/motion.md +51 -0
- package/templates/m3e-app/.claude/skills/m3-expressive/shapes-type.md +48 -0
- package/templates/m3e-app/.claude/skills/m3-expressive/sources.md +53 -0
- package/templates/m3e-app/.claude/skills/module-architecture/SKILL.md +65 -0
- package/templates/m3e-app/.claude/skills/module-architecture/file-templates.md +178 -0
- package/templates/m3e-app/.claude/skills/reactive-data/SKILL.md +90 -0
- package/templates/m3e-app/.claude/skills/reactive-data/testing.md +55 -0
- package/templates/m3e-app/.env.example +32 -0
- package/templates/m3e-app/CLAUDE.md +93 -0
- package/templates/m3e-app/auto-imports.d.ts +851 -0
- package/templates/m3e-app/capacitor.config.ts +44 -0
- package/templates/m3e-app/components.d.ts +252 -0
- package/templates/m3e-app/index.html +16 -0
- package/templates/m3e-app/package.json +108 -0
- package/templates/m3e-app/src/App.vue +129 -0
- package/templates/m3e-app/src/app/pragmas.config.ts +35 -0
- package/templates/m3e-app/src/app/scroll.config.ts +20 -0
- package/templates/m3e-app/src/app/storage.config.ts +65 -0
- package/templates/m3e-app/src/app/tabs.ts +33 -0
- package/templates/m3e-app/src/app/theme.config.ts +51 -0
- package/templates/m3e-app/src/assets/css/app.css +18 -0
- package/templates/m3e-app/src/assets/css/base.css +53 -0
- package/templates/m3e-app/src/assets/css/layout/container-transform.css +113 -0
- package/templates/m3e-app/src/assets/css/layout/shell.css +69 -0
- package/templates/m3e-app/src/assets/css/layout/transitions.css +121 -0
- package/templates/m3e-app/src/assets/css/m3e.css +6 -0
- package/templates/m3e-app/src/assets/css/theme/framework7.css +18 -0
- package/templates/m3e-app/src/assets/css/theme/tailwind.css +365 -0
- package/templates/m3e-app/src/domains/benchmark/benchmark.dataset.ts +209 -0
- package/templates/m3e-app/src/domains/benchmark/benchmark.suite.ts +662 -0
- package/templates/m3e-app/src/domains/sales/sales.repository.ts +458 -0
- package/templates/m3e-app/src/env.d.ts +40 -0
- package/templates/m3e-app/src/locales/ar.json +1260 -0
- package/templates/m3e-app/src/locales/en.json +1260 -0
- package/templates/m3e-app/src/locales/fr.json +1260 -0
- package/templates/m3e-app/src/main.ts +41 -0
- package/templates/m3e-app/src/modules/demo/components/DemoBenchmark.vue +119 -0
- package/templates/m3e-app/src/modules/demo/components/DemoBusLog.vue +63 -0
- package/templates/m3e-app/src/modules/demo/components/DemoCreateOrderSheet.vue +203 -0
- package/templates/m3e-app/src/modules/demo/components/DemoMetricsSheet.vue +75 -0
- package/templates/m3e-app/src/modules/demo/components/DemoOrderList.vue +75 -0
- package/templates/m3e-app/src/modules/demo/components/DemoPipelineBenchmark.vue +65 -0
- package/templates/m3e-app/src/modules/demo/components/DemoStatCards.vue +71 -0
- package/templates/m3e-app/src/modules/demo/composables/useBenchmark.ts +139 -0
- package/templates/m3e-app/src/modules/demo/composables/useOrderStatus.ts +64 -0
- package/templates/m3e-app/src/modules/demo/composables/useReactiveDemo.ts +171 -0
- package/templates/m3e-app/src/modules/demo/router/routes/demo.routes.ts +22 -0
- package/templates/m3e-app/src/modules/demo/views/DemoView.vue +210 -0
- package/templates/m3e-app/src/modules/demo/views/OrderDetailView.vue +136 -0
- package/templates/m3e-app/src/modules/demo/views/OrderSearchView.vue +74 -0
- package/templates/m3e-app/src/modules/gallery/components/GalleryBlock.vue +16 -0
- package/templates/m3e-app/src/modules/gallery/components/GalleryCarouselTile.vue +71 -0
- package/templates/m3e-app/src/modules/gallery/components/GalleryCustomerForm.vue +76 -0
- package/templates/m3e-app/src/modules/gallery/components/GalleryProofOfDelivery.vue +69 -0
- package/templates/m3e-app/src/modules/gallery/components/agenda/AgendaDay.vue +106 -0
- package/templates/m3e-app/src/modules/gallery/components/agenda/AgendaMonth.vue +40 -0
- package/templates/m3e-app/src/modules/gallery/components/agenda/AgendaVisitList.vue +45 -0
- package/templates/m3e-app/src/modules/gallery/components/chat/ChatAttachSheet.vue +106 -0
- package/templates/m3e-app/src/modules/gallery/components/chat/ChatContactPicker.vue +85 -0
- package/templates/m3e-app/src/modules/gallery/components/chat/ChatInviteComposer.vue +131 -0
- package/templates/m3e-app/src/modules/gallery/components/chat/ChatPollComposer.vue +148 -0
- package/templates/m3e-app/src/modules/gallery/components/chat/ChatRecentPhotos.vue +62 -0
- package/templates/m3e-app/src/modules/gallery/components/inputs/InputsAccount.vue +79 -0
- package/templates/m3e-app/src/modules/gallery/components/inputs/InputsTags.vue +53 -0
- package/templates/m3e-app/src/modules/gallery/components/inputs/InputsVerification.vue +73 -0
- package/templates/m3e-app/src/modules/gallery/components/inputs/PasswordStrength.vue +37 -0
- package/templates/m3e-app/src/modules/gallery/components/sections/GalleryButtons.vue +128 -0
- package/templates/m3e-app/src/modules/gallery/components/sections/GalleryCarousels.vue +236 -0
- package/templates/m3e-app/src/modules/gallery/components/sections/GalleryCharts.vue +84 -0
- package/templates/m3e-app/src/modules/gallery/components/sections/GalleryFabs.vue +70 -0
- package/templates/m3e-app/src/modules/gallery/components/sections/GalleryInputs.vue +219 -0
- package/templates/m3e-app/src/modules/gallery/components/sections/GalleryNavigation.vue +141 -0
- package/templates/m3e-app/src/modules/gallery/components/sections/GalleryOverlays.vue +368 -0
- package/templates/m3e-app/src/modules/gallery/components/sections/GalleryPickers.vue +175 -0
- package/templates/m3e-app/src/modules/gallery/components/sections/GalleryProgress.vue +118 -0
- package/templates/m3e-app/src/modules/gallery/components/sections/GalleryScale.vue +103 -0
- package/templates/m3e-app/src/modules/gallery/components/sections/GallerySelection.vue +219 -0
- package/templates/m3e-app/src/modules/gallery/components/sections/GalleryShapes.vue +48 -0
- package/templates/m3e-app/src/modules/gallery/components/sections/GallerySurfaces.vue +213 -0
- package/templates/m3e-app/src/modules/gallery/components/sections/GalleryTables.vue +185 -0
- package/templates/m3e-app/src/modules/gallery/components/surfaces/SurfacesContainerTransform.vue +46 -0
- package/templates/m3e-app/src/modules/gallery/composables/chatTeam.ts +18 -0
- package/templates/m3e-app/src/modules/gallery/composables/composeKind.ts +2 -0
- package/templates/m3e-app/src/modules/gallery/composables/currentPosition.ts +35 -0
- package/templates/m3e-app/src/modules/gallery/composables/demoOrders.ts +29 -0
- package/templates/m3e-app/src/modules/gallery/composables/featuredStories.ts +73 -0
- package/templates/m3e-app/src/modules/gallery/composables/fieldFormats.ts +36 -0
- package/templates/m3e-app/src/modules/gallery/composables/openExternal.ts +11 -0
- package/templates/m3e-app/src/modules/gallery/composables/routePlan.ts +90 -0
- package/templates/m3e-app/src/modules/gallery/composables/useAgenda.ts +61 -0
- package/templates/m3e-app/src/modules/gallery/composables/useChatCustomers.ts +31 -0
- package/templates/m3e-app/src/modules/gallery/composables/useChatDemo.ts +299 -0
- package/templates/m3e-app/src/modules/gallery/composables/useGallerySections.ts +230 -0
- package/templates/m3e-app/src/modules/gallery/composables/useLocalAttachments.ts +71 -0
- package/templates/m3e-app/src/modules/gallery/composables/useLocationShare.ts +38 -0
- package/templates/m3e-app/src/modules/gallery/composables/usePhotoScenes.ts +82 -0
- package/templates/m3e-app/src/modules/gallery/composables/useTreeDemo.ts +67 -0
- package/templates/m3e-app/src/modules/gallery/composables/wilayas.ts +67 -0
- package/templates/m3e-app/src/modules/gallery/router/routes/gallery.routes.ts +52 -0
- package/templates/m3e-app/src/modules/gallery/views/GalleryAgendaView.vue +117 -0
- package/templates/m3e-app/src/modules/gallery/views/GalleryChatView.vue +292 -0
- package/templates/m3e-app/src/modules/gallery/views/GalleryContactsView.vue +77 -0
- package/templates/m3e-app/src/modules/gallery/views/GalleryFeaturedView.vue +60 -0
- package/templates/m3e-app/src/modules/gallery/views/GalleryLoginView.vue +135 -0
- package/templates/m3e-app/src/modules/gallery/views/GalleryOnboardingView.vue +116 -0
- package/templates/m3e-app/src/modules/gallery/views/GallerySectionView.vue +27 -0
- package/templates/m3e-app/src/modules/gallery/views/GalleryTabsView.vue +135 -0
- package/templates/m3e-app/src/modules/gallery/views/GalleryView.vue +50 -0
- package/templates/m3e-app/src/modules/home/components/HomeHero.vue +57 -0
- package/templates/m3e-app/src/modules/home/composables/useHomeFeatures.ts +128 -0
- package/templates/m3e-app/src/modules/home/composables/useOpenFeature.ts +22 -0
- package/templates/m3e-app/src/modules/home/router/routes/home.routes.ts +21 -0
- package/templates/m3e-app/src/modules/home/views/FeatureDetailView.vue +66 -0
- package/templates/m3e-app/src/modules/home/views/HomeView.vue +47 -0
- package/templates/m3e-app/src/modules/settings/components/RolePalette.vue +38 -0
- package/templates/m3e-app/src/modules/settings/components/SeedSwatches.vue +41 -0
- package/templates/m3e-app/src/modules/settings/components/SettingsChoice.vue +43 -0
- package/templates/m3e-app/src/modules/settings/components/StudioPreview.vue +52 -0
- package/templates/m3e-app/src/modules/settings/router/routes/settings.routes.ts +17 -0
- package/templates/m3e-app/src/modules/settings/types.ts +7 -0
- package/templates/m3e-app/src/modules/settings/views/ColorStudioView.vue +166 -0
- package/templates/m3e-app/src/modules/settings/views/SettingsView.vue +157 -0
- package/templates/m3e-app/src/plugins/bootstrapError.ts +62 -0
- package/templates/m3e-app/src/plugins/capacitor/index.ts +14 -0
- package/templates/m3e-app/src/plugins/capacitor/useAndroidBackButton.ts +36 -0
- package/templates/m3e-app/src/plugins/capacitor/useKeyboard.ts +48 -0
- package/templates/m3e-app/src/plugins/capacitor/useSplashScreen.ts +43 -0
- package/templates/m3e-app/src/plugins/capacitor/useStatusBar.ts +16 -0
- package/templates/m3e-app/src/plugins/framework7.plugin.ts +36 -0
- package/templates/m3e-app/src/plugins/i18n.plugin.ts +87 -0
- package/templates/m3e-app/src/plugins/m3e.plugin.ts +22 -0
- package/templates/m3e-app/src/plugins/seed.plugin.ts +7 -0
- package/templates/m3e-app/src/plugins/sqlite.plugin.ts +32 -0
- package/templates/m3e-app/src/router/global/global.routes.ts +12 -0
- package/templates/m3e-app/src/router/index.ts +20 -0
- package/templates/m3e-app/src/shared/components/error/404.vue +13 -0
- package/templates/m3e-app/src/shared/components/layout/EmptyState.vue +30 -0
- package/templates/m3e-app/src/shared/components/layout/SectionHeader.vue +10 -0
- package/templates/m3e-app/src/shared/components/page/AppPage.vue +104 -0
- package/templates/m3e-app/src/shared/composables/navigation/useActiveTab.ts +39 -0
- package/templates/m3e-app/src/shared/composables/navigation/useContainerTransform.ts +117 -0
- package/templates/m3e-app/src/shared/composables/navigation/useNavigationGuard.ts +48 -0
- package/templates/m3e-app/src/shared/composables/navigation/useNavigationVisibility.ts +64 -0
- package/templates/m3e-app/src/shared/composables/navigation/useViewRouter.ts +25 -0
- package/templates/m3e-app/src/shared/composables/navigation/useWindowClass.ts +22 -0
- package/templates/m3e-app/src/shared/composables/theme/useThemeSettings.ts +155 -0
- package/templates/m3e-app/src/shared/database/candidates/capacitorSqlite.ts +36 -0
- package/templates/m3e-app/src/shared/database/candidates/index.ts +4 -0
- package/templates/m3e-app/src/shared/database/candidates/opfsSahPool.ts +25 -0
- package/templates/m3e-app/src/shared/database/candidates/types.ts +56 -0
- package/templates/m3e-app/src/shared/database/candidates/waSqlite.ts +56 -0
- package/templates/m3e-app/src/shared/database/database.ts +219 -0
- package/templates/m3e-app/src/shared/database/index.ts +3 -0
- package/templates/m3e-app/src/shared/database/migrations.ts +223 -0
- package/templates/m3e-app/src/shared/database/opfs.worker.ts +5 -0
- package/templates/m3e-app/src/shared/database/queries.ts +26 -0
- package/templates/m3e-app/src/shared/database/schema.ts +153 -0
- package/templates/m3e-app/src/shared/database/storage.ts +64 -0
- package/templates/m3e-app/src/shared/database/wa.worker.ts +5 -0
- package/templates/m3e-app/src/shared/utils/lazyRoute.ts +41 -0
- package/templates/m3e-app/src/shared/utils/resolvers/resolvers.ts +42 -0
- package/templates/m3e-app/src/shared/utils/textDirection.ts +22 -0
- package/templates/m3e-app/src/shared/utils/theme/themeSettings.ts +59 -0
- package/templates/m3e-app/src/shared/utils/tone.ts +8 -0
- package/templates/m3e-app/tests/benchmark.suite.test.ts +104 -0
- package/templates/m3e-app/tests/containerTransform.test.ts +58 -0
- package/templates/m3e-app/tests/fieldFormats.test.ts +25 -0
- package/templates/m3e-app/tests/lazyRoute.test.ts +30 -0
- package/templates/m3e-app/tests/locales.test.ts +73 -0
- package/templates/m3e-app/tests/migrations.test.ts +74 -0
- package/templates/m3e-app/tests/navigationGuard.test.ts +82 -0
- package/templates/m3e-app/tests/openDatabase.test.ts +54 -0
- package/templates/m3e-app/tests/sales.repository.test.ts +329 -0
- package/templates/m3e-app/tests/storage.test.ts +117 -0
- package/templates/m3e-app/tests/textDirection.test.ts +11 -0
- package/templates/m3e-app/tooling/linkedPackages.ts +112 -0
- package/templates/m3e-app/tsconfig.json +21 -0
- package/templates/m3e-app/vite.config.ts +144 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import Framework7 from "framework7/lite";
|
|
2
|
+
import Tabs from "framework7/components/tabs";
|
|
3
|
+
import Framework7Vue from "framework7-vue";
|
|
4
|
+
|
|
5
|
+
import "@fontsource-variable/google-sans-flex/rond.css";
|
|
6
|
+
import "./assets/css/app.css";
|
|
7
|
+
import "./assets/css/m3e.css";
|
|
8
|
+
|
|
9
|
+
import App from "./App.vue";
|
|
10
|
+
import { i18n } from "./plugins/i18n.plugin";
|
|
11
|
+
import { m3e } from "./plugins/m3e.plugin";
|
|
12
|
+
import { renderBootstrapError } from "./plugins/bootstrapError";
|
|
13
|
+
import { seedPlugin } from "./plugins/seed.plugin";
|
|
14
|
+
import { sqlitePlugin } from "./plugins/sqlite.plugin";
|
|
15
|
+
import { startTheme } from "./shared/composables/theme/useThemeSettings";
|
|
16
|
+
|
|
17
|
+
Framework7.use([Tabs]);
|
|
18
|
+
Framework7.use(Framework7Vue);
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The theme starts first, so even the bootstrap error screen is drawn in the user's colours and
|
|
22
|
+
* mode. The database opens before mount so the first screen never renders against a missing
|
|
23
|
+
* schema; if it fails, `renderBootstrapError` takes the screen instead of leaving an empty `#app`.
|
|
24
|
+
*/
|
|
25
|
+
async function bootstrap(): Promise<void> {
|
|
26
|
+
startTheme();
|
|
27
|
+
try {
|
|
28
|
+
await sqlitePlugin();
|
|
29
|
+
await seedPlugin();
|
|
30
|
+
} catch (error) {
|
|
31
|
+
renderBootstrapError(error);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const app = createApp(App);
|
|
36
|
+
app.use(i18n);
|
|
37
|
+
app.use(m3e);
|
|
38
|
+
app.mount("#app");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
void bootstrap();
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<section class="mx-4 flex flex-col gap-4 rounded-lg bg-surface-container-low p-4">
|
|
3
|
+
<header class="flex flex-col gap-1">
|
|
4
|
+
<h3 class="type-title-medium-emphasized m-0">{{ t("bench.title") }}</h3>
|
|
5
|
+
<p class="type-body-small m-0 text-on-surface-variant">{{ t("bench.intro") }}</p>
|
|
6
|
+
</header>
|
|
7
|
+
|
|
8
|
+
<div class="flex flex-wrap gap-2">
|
|
9
|
+
<M3Chip :label="engineName">
|
|
10
|
+
<template #icon><i-ms-database-outline-rounded /></template>
|
|
11
|
+
</M3Chip>
|
|
12
|
+
<M3Chip v-if="baseline" :label="t('bench.against', { engine: baseline.engine })" />
|
|
13
|
+
<M3Chip :label="t('bench.pragmas', { profile })" />
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
<M3Button :disabled="running" size="m" @click="run">
|
|
17
|
+
<template #icon><i-ms-speed-rounded /></template>
|
|
18
|
+
{{ running ? t("bench.running") : t("bench.run") }}
|
|
19
|
+
</M3Button>
|
|
20
|
+
|
|
21
|
+
<div v-if="running" class="flex flex-col gap-2">
|
|
22
|
+
<M3LinearProgress :label="t('bench.running')" />
|
|
23
|
+
<span v-if="progress" class="type-body-small text-on-surface-variant">{{ progress }}</span>
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<p
|
|
27
|
+
v-if="failure"
|
|
28
|
+
class="type-body-medium m-0 rounded-md bg-error-container p-3 text-on-error-container"
|
|
29
|
+
role="alert"
|
|
30
|
+
>
|
|
31
|
+
{{ failure }}
|
|
32
|
+
</p>
|
|
33
|
+
|
|
34
|
+
<div v-if="result" class="grid grid-cols-3 gap-0.5 overflow-hidden rounded-md text-center">
|
|
35
|
+
<div class="flex flex-col gap-1 bg-surface-container p-3">
|
|
36
|
+
<span class="type-label-medium text-on-surface-variant">{{ t("bench.cases") }}</span>
|
|
37
|
+
<span class="type-title-medium tabular-nums">{{ result.cases.length }}</span>
|
|
38
|
+
</div>
|
|
39
|
+
<div class="flex flex-col gap-1 bg-surface-container p-3">
|
|
40
|
+
<span class="type-label-medium text-on-surface-variant">{{ t("bench.rows") }}</span>
|
|
41
|
+
<span class="type-title-medium tabular-nums">{{ result.rowsSeeded }}</span>
|
|
42
|
+
</div>
|
|
43
|
+
<div class="flex flex-col gap-1 bg-surface-container p-3">
|
|
44
|
+
<span class="type-label-medium text-on-surface-variant">{{ t("bench.wall") }}</span>
|
|
45
|
+
<span class="type-title-medium tabular-nums">{{ Math.round(result.totalMs) }} ms</span>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
</section>
|
|
49
|
+
|
|
50
|
+
<template v-for="group in groups" :key="group">
|
|
51
|
+
<template v-if="byGroup(group).length">
|
|
52
|
+
<SectionHeader :title="t(`bench.group.${group}`)" />
|
|
53
|
+
<M3List variant="segmented" inset>
|
|
54
|
+
<M3ListItem
|
|
55
|
+
v-for="entry in byGroup(group)"
|
|
56
|
+
:key="entry.name"
|
|
57
|
+
:headline="entry.name"
|
|
58
|
+
:supporting="`${entry.current.operations} ops · p50 ${entry.current.medianMs} ms · ${t('bench.worst')} ${entry.current.worstMs} ms`"
|
|
59
|
+
:trailing-text="`${entry.current.msPerOperation} ms`"
|
|
60
|
+
:overline="entry.speedup ? speedupText(entry.speedup) : entry.note"
|
|
61
|
+
>
|
|
62
|
+
<template v-if="entry.speedup" #leading>
|
|
63
|
+
<M3Shape
|
|
64
|
+
:shape="entry.speedup >= 1 ? 'sunny' : 'square'"
|
|
65
|
+
class="size-8"
|
|
66
|
+
:class="verdictClass(entry.speedup)"
|
|
67
|
+
/>
|
|
68
|
+
</template>
|
|
69
|
+
</M3ListItem>
|
|
70
|
+
</M3List>
|
|
71
|
+
</template>
|
|
72
|
+
</template>
|
|
73
|
+
|
|
74
|
+
<div v-if="result" class="flex gap-2 px-4 pt-4">
|
|
75
|
+
<M3Button variant="outlined" class="flex-1" @click="copy">{{ t("bench.copy") }}</M3Button>
|
|
76
|
+
<M3Button variant="text" class="flex-1 text-error" @click="clear">{{
|
|
77
|
+
t("bench.clear")
|
|
78
|
+
}}</M3Button>
|
|
79
|
+
</div>
|
|
80
|
+
</template>
|
|
81
|
+
|
|
82
|
+
<script setup lang="ts">
|
|
83
|
+
import { pragmaProfile } from "@/app/pragmas.config";
|
|
84
|
+
import { useBenchmark, type CaseComparison } from "@/modules/demo/composables/useBenchmark";
|
|
85
|
+
|
|
86
|
+
const { t } = useI18n();
|
|
87
|
+
const snackbar = useSnackbar();
|
|
88
|
+
const { engineName, running, progress, failure, result, baseline, comparison, run, clear, asJson } =
|
|
89
|
+
useBenchmark();
|
|
90
|
+
|
|
91
|
+
const profile = pragmaProfile;
|
|
92
|
+
const groups = ["read", "join", "write", "transaction", "schema", "concurrency", "sync"] as const;
|
|
93
|
+
|
|
94
|
+
function byGroup(group: CaseComparison["group"]): CaseComparison[] {
|
|
95
|
+
return comparison.value.filter((entry) => entry.group === group);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Both directions read plainly; "0.4x faster" would not. */
|
|
99
|
+
function speedupText(speedup: number): string {
|
|
100
|
+
return speedup >= 1
|
|
101
|
+
? t("bench.faster", { times: speedup.toFixed(2) })
|
|
102
|
+
: t("bench.slower", { times: (1 / speedup).toFixed(2) });
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function verdictClass(speedup: number): string {
|
|
106
|
+
if (speedup >= 1.2) return "bg-success-container";
|
|
107
|
+
if (speedup <= 0.83) return "bg-error-container";
|
|
108
|
+
return "bg-surface-container-highest";
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function copy() {
|
|
112
|
+
try {
|
|
113
|
+
await navigator.clipboard.writeText(asJson());
|
|
114
|
+
void snackbar.show(t("bench.copied"));
|
|
115
|
+
} catch {
|
|
116
|
+
void snackbar.show({ message: t("bench.copyFailed"), duration: "long" });
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
</script>
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<section class="mx-4 flex flex-col gap-3 rounded-lg bg-surface-container-low p-4">
|
|
3
|
+
<header class="flex flex-col gap-0.5">
|
|
4
|
+
<h3 class="type-title-medium-emphasized m-0">{{ t("demo.bus") }}</h3>
|
|
5
|
+
<p class="type-body-small m-0 text-on-surface-variant">{{ t("demo.busHint") }}</p>
|
|
6
|
+
</header>
|
|
7
|
+
<TransitionGroup
|
|
8
|
+
v-if="entries.length"
|
|
9
|
+
tag="ul"
|
|
10
|
+
name="bus"
|
|
11
|
+
class="m-0 flex list-none flex-col gap-0.5 p-0"
|
|
12
|
+
>
|
|
13
|
+
<li
|
|
14
|
+
v-for="entry in entries"
|
|
15
|
+
:key="entry.id"
|
|
16
|
+
class="flex items-center justify-between rounded-xs bg-surface-container px-3 py-2 first:rounded-t-md last:rounded-b-md"
|
|
17
|
+
>
|
|
18
|
+
<span class="type-label-large"
|
|
19
|
+
>{{ entry.table }}<span class="text-on-surface-variant">.{{ entry.type }}</span></span
|
|
20
|
+
>
|
|
21
|
+
<span class="type-label-small tabular-nums text-on-surface-variant">{{
|
|
22
|
+
formatTime(entry.at)
|
|
23
|
+
}}</span>
|
|
24
|
+
</li>
|
|
25
|
+
</TransitionGroup>
|
|
26
|
+
<p v-else class="type-body-medium m-0 text-on-surface-variant">{{ t("demo.busEmpty") }}</p>
|
|
27
|
+
</section>
|
|
28
|
+
</template>
|
|
29
|
+
|
|
30
|
+
<script setup lang="ts">
|
|
31
|
+
import type { BusEntry } from "@/modules/demo/composables/useReactiveDemo";
|
|
32
|
+
|
|
33
|
+
defineProps<{ entries: BusEntry[] }>();
|
|
34
|
+
const { t } = useI18n();
|
|
35
|
+
|
|
36
|
+
function formatTime(at: number): string {
|
|
37
|
+
return new Date(at).toLocaleTimeString(undefined, { hour12: false });
|
|
38
|
+
}
|
|
39
|
+
</script>
|
|
40
|
+
|
|
41
|
+
<style scoped>
|
|
42
|
+
.bus-enter-active {
|
|
43
|
+
transition:
|
|
44
|
+
transform var(--md-sys-motion-spring-fast-spatial-duration)
|
|
45
|
+
var(--md-sys-motion-spring-fast-spatial),
|
|
46
|
+
opacity var(--md-sys-motion-spring-fast-effects-duration)
|
|
47
|
+
var(--md-sys-motion-spring-fast-effects);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.bus-enter-from {
|
|
51
|
+
opacity: 0;
|
|
52
|
+
transform: translateY(-8px) scale(0.98);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.bus-move {
|
|
56
|
+
transition: transform var(--md-sys-motion-spring-default-spatial-duration)
|
|
57
|
+
var(--md-sys-motion-spring-default-spatial);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.bus-leave-active {
|
|
61
|
+
display: none;
|
|
62
|
+
}
|
|
63
|
+
</style>
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<M3BottomSheet v-model:open="open" :title="t('demo.newOrder')" @opened="focusReference">
|
|
3
|
+
<div class="flex flex-col gap-2 px-4">
|
|
4
|
+
<M3TextField
|
|
5
|
+
ref="referenceField"
|
|
6
|
+
v-model="reference"
|
|
7
|
+
:label="t('demo.reference')"
|
|
8
|
+
autocomplete="off"
|
|
9
|
+
/>
|
|
10
|
+
<M3List variant="segmented">
|
|
11
|
+
<M3ListItem
|
|
12
|
+
clickable
|
|
13
|
+
:overline="t('demo.customer')"
|
|
14
|
+
:headline="customerLabel"
|
|
15
|
+
@click="chooseCustomer"
|
|
16
|
+
>
|
|
17
|
+
<template #leading><i-ms-person-outline-rounded /></template>
|
|
18
|
+
<template #trailing><i-ms-chevron-right-rounded class="rtl:-scale-x-100" /></template>
|
|
19
|
+
</M3ListItem>
|
|
20
|
+
</M3List>
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
<SectionHeader :title="t('demo.products')" />
|
|
24
|
+
<M3List variant="segmented" inset :label="t('demo.products')">
|
|
25
|
+
<M3ListItem
|
|
26
|
+
v-for="product in products"
|
|
27
|
+
:key="product.id"
|
|
28
|
+
:headline="product.name"
|
|
29
|
+
:supporting="formatMoney(product.price_cents)"
|
|
30
|
+
:selected="quantityOf(product.id) > 0"
|
|
31
|
+
>
|
|
32
|
+
<template #action>
|
|
33
|
+
<div class="flex items-center gap-1">
|
|
34
|
+
<M3IconButton
|
|
35
|
+
:label="t('demo.less', { product: product.name })"
|
|
36
|
+
variant="tonal"
|
|
37
|
+
size="xs"
|
|
38
|
+
:disabled="quantityOf(product.id) === 0"
|
|
39
|
+
@click="step(product.id, -1)"
|
|
40
|
+
>
|
|
41
|
+
<i-ms-remove-rounded />
|
|
42
|
+
</M3IconButton>
|
|
43
|
+
<span
|
|
44
|
+
class="type-title-small-emphasized w-7 text-center tabular-nums"
|
|
45
|
+
aria-live="polite"
|
|
46
|
+
>
|
|
47
|
+
{{ quantityOf(product.id) }}
|
|
48
|
+
</span>
|
|
49
|
+
<M3IconButton
|
|
50
|
+
:label="t('demo.more', { product: product.name })"
|
|
51
|
+
variant="tonal"
|
|
52
|
+
size="xs"
|
|
53
|
+
@click="step(product.id, 1)"
|
|
54
|
+
>
|
|
55
|
+
<i-ms-add-rounded />
|
|
56
|
+
</M3IconButton>
|
|
57
|
+
</div>
|
|
58
|
+
</template>
|
|
59
|
+
</M3ListItem>
|
|
60
|
+
</M3List>
|
|
61
|
+
|
|
62
|
+
<template #footer>
|
|
63
|
+
<div class="flex items-center gap-4">
|
|
64
|
+
<div class="flex flex-1 flex-col">
|
|
65
|
+
<span class="type-label-medium text-on-surface-variant">{{ t("demo.total") }}</span>
|
|
66
|
+
<span class="type-title-large-emphasized tabular-nums">{{
|
|
67
|
+
formatMoney(totalCents)
|
|
68
|
+
}}</span>
|
|
69
|
+
</div>
|
|
70
|
+
<M3Button size="m" :disabled="!canSave || saving" @click="save">{{
|
|
71
|
+
t("demo.save")
|
|
72
|
+
}}</M3Button>
|
|
73
|
+
</div>
|
|
74
|
+
</template>
|
|
75
|
+
</M3BottomSheet>
|
|
76
|
+
</template>
|
|
77
|
+
|
|
78
|
+
<script setup lang="ts">
|
|
79
|
+
import type { ComponentPublicInstance } from "vue";
|
|
80
|
+
import {
|
|
81
|
+
listCustomers,
|
|
82
|
+
listProducts,
|
|
83
|
+
nextOrderReference,
|
|
84
|
+
type DraftLine,
|
|
85
|
+
type ProductRow,
|
|
86
|
+
} from "@/domains/sales/sales.repository";
|
|
87
|
+
import { formatMoney } from "@/modules/demo/composables/useOrderStatus";
|
|
88
|
+
import { getDatabase } from "@/shared/database/database";
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* The new-order form in a bottom sheet. Saving is handed in as an async prop rather than emitted, so
|
|
92
|
+
* the sheet stays open and says so when the write fails instead of closing on a lost order.
|
|
93
|
+
*/
|
|
94
|
+
const open = defineModel<boolean>("open", { default: false });
|
|
95
|
+
const props = defineProps<{
|
|
96
|
+
save: (draft: { customerId: number; reference: string; lines: DraftLine[] }) => Promise<void>;
|
|
97
|
+
}>();
|
|
98
|
+
const { t } = useI18n();
|
|
99
|
+
const actionSheet = useActionSheet();
|
|
100
|
+
const snackbar = useSnackbar();
|
|
101
|
+
|
|
102
|
+
const customers = ref<Array<{ id: number; name: string; city: string }>>([]);
|
|
103
|
+
const products = ref<ProductRow[]>([]);
|
|
104
|
+
const quantities = ref<Record<number, number>>({});
|
|
105
|
+
const reference = ref("");
|
|
106
|
+
const customerId = ref(0);
|
|
107
|
+
const saving = ref(false);
|
|
108
|
+
const referenceField = useTemplateRef<ComponentPublicInstance>("referenceField");
|
|
109
|
+
|
|
110
|
+
const customerLabel = computed(() => {
|
|
111
|
+
const customer = customers.value.find((entry) => entry.id === customerId.value);
|
|
112
|
+
return customer ? `${customer.name} — ${customer.city}` : t("demo.chooseCustomer");
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
/** Loaded each time the sheet opens rather than on mount, so the reference follows the latest order. */
|
|
116
|
+
async function load() {
|
|
117
|
+
try {
|
|
118
|
+
const db = getDatabase().db;
|
|
119
|
+
const [loadedCustomers, loadedProducts, nextReference] = await Promise.all([
|
|
120
|
+
listCustomers(db),
|
|
121
|
+
listProducts(db),
|
|
122
|
+
nextOrderReference(db),
|
|
123
|
+
]);
|
|
124
|
+
customers.value = loadedCustomers;
|
|
125
|
+
products.value = loadedProducts;
|
|
126
|
+
reference.value = nextReference;
|
|
127
|
+
customerId.value = loadedCustomers[0]?.id ?? 0;
|
|
128
|
+
quantities.value = {};
|
|
129
|
+
} catch (error) {
|
|
130
|
+
console.error("[demo] could not load the order form", error);
|
|
131
|
+
void snackbar.show({ message: t("demo.loadFailed"), duration: "long" });
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
watch(open, (isOpen) => {
|
|
136
|
+
if (isOpen) void load();
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
function focusReference() {
|
|
140
|
+
(referenceField.value?.$el as HTMLElement | undefined)
|
|
141
|
+
?.querySelector("input")
|
|
142
|
+
?.focus({ preventScroll: true });
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const quantityOf = (productId: number) => quantities.value[productId] ?? 0;
|
|
146
|
+
|
|
147
|
+
function step(productId: number, delta: number) {
|
|
148
|
+
const next = Math.min(99, Math.max(0, quantityOf(productId) + delta));
|
|
149
|
+
quantities.value = { ...quantities.value, [productId]: next };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async function chooseCustomer() {
|
|
153
|
+
const choice = await actionSheet.open({
|
|
154
|
+
title: t("demo.customer"),
|
|
155
|
+
groups: [
|
|
156
|
+
{
|
|
157
|
+
items: customers.value.map((customer) => ({
|
|
158
|
+
id: String(customer.id),
|
|
159
|
+
label: customer.name,
|
|
160
|
+
supporting: customer.city,
|
|
161
|
+
selected: customer.id === customerId.value,
|
|
162
|
+
})),
|
|
163
|
+
},
|
|
164
|
+
],
|
|
165
|
+
});
|
|
166
|
+
if (choice) customerId.value = Number(choice);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const lines = computed(() =>
|
|
170
|
+
products.value
|
|
171
|
+
.filter((product) => quantityOf(product.id) > 0)
|
|
172
|
+
.map((product) => ({
|
|
173
|
+
productId: product.id,
|
|
174
|
+
quantity: quantityOf(product.id),
|
|
175
|
+
unitPriceCents: product.price_cents,
|
|
176
|
+
})),
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
const totalCents = computed(() =>
|
|
180
|
+
lines.value.reduce((sum, line) => sum + line.quantity * line.unitPriceCents, 0),
|
|
181
|
+
);
|
|
182
|
+
const canSave = computed(
|
|
183
|
+
() => customerId.value > 0 && lines.value.length > 0 && reference.value.trim() !== "",
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
async function save() {
|
|
187
|
+
if (!canSave.value || saving.value) return;
|
|
188
|
+
saving.value = true;
|
|
189
|
+
try {
|
|
190
|
+
await props.save({
|
|
191
|
+
customerId: customerId.value,
|
|
192
|
+
reference: reference.value.trim(),
|
|
193
|
+
lines: lines.value,
|
|
194
|
+
});
|
|
195
|
+
open.value = false;
|
|
196
|
+
} catch (error) {
|
|
197
|
+
console.error("[demo] saving the order failed", error);
|
|
198
|
+
void snackbar.show({ message: t("demo.saveFailed"), duration: "long" });
|
|
199
|
+
} finally {
|
|
200
|
+
saving.value = false;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
</script>
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<M3BottomSheet v-model:open="open" :title="t('metrics.title')">
|
|
3
|
+
<div class="grid grid-cols-2 gap-2 px-4">
|
|
4
|
+
<div
|
|
5
|
+
v-for="tile in tiles"
|
|
6
|
+
:key="tile.labelKey"
|
|
7
|
+
class="flex flex-col gap-1 rounded-lg bg-surface-container p-4"
|
|
8
|
+
>
|
|
9
|
+
<span class="type-label-medium text-on-surface-variant">{{ t(tile.labelKey) }}</span>
|
|
10
|
+
<span class="type-title-large-emphasized tabular-nums">{{ tile.value }}</span>
|
|
11
|
+
</div>
|
|
12
|
+
</div>
|
|
13
|
+
|
|
14
|
+
<SectionHeader :title="t('metrics.refetchesTitle')" />
|
|
15
|
+
<M3List v-if="refetches.length" variant="segmented" inset>
|
|
16
|
+
<M3ListItem
|
|
17
|
+
v-for="[table, count] in refetches"
|
|
18
|
+
:key="table"
|
|
19
|
+
:headline="table"
|
|
20
|
+
:trailing-text="String(count)"
|
|
21
|
+
>
|
|
22
|
+
<template #leading><i-ms-sync-rounded /></template>
|
|
23
|
+
</M3ListItem>
|
|
24
|
+
</M3List>
|
|
25
|
+
<p v-else class="type-body-medium m-0 px-6 text-on-surface-variant">
|
|
26
|
+
{{ t("metrics.noRefetches") }}
|
|
27
|
+
</p>
|
|
28
|
+
|
|
29
|
+
<SectionHeader :title="t('metrics.slowest')" />
|
|
30
|
+
<M3List v-if="slowestQueries.length" variant="segmented" inset>
|
|
31
|
+
<M3ListItem
|
|
32
|
+
v-for="entry in slowestQueries"
|
|
33
|
+
:key="entry.key"
|
|
34
|
+
:headline="`${entry.avgTime.toFixed(1)} ms`"
|
|
35
|
+
:supporting="entry.key"
|
|
36
|
+
:trailing-text="t('metrics.calls', { count: entry.count }, entry.count)"
|
|
37
|
+
multiline
|
|
38
|
+
>
|
|
39
|
+
<template #leading><i-ms-speed-rounded /></template>
|
|
40
|
+
</M3ListItem>
|
|
41
|
+
</M3List>
|
|
42
|
+
<p v-else class="type-body-medium m-0 px-6 text-on-surface-variant">
|
|
43
|
+
{{ t("metrics.noQueries") }}
|
|
44
|
+
</p>
|
|
45
|
+
|
|
46
|
+
<template #footer>
|
|
47
|
+
<M3Button variant="outlined" class="w-full" @click="reset">{{ t("metrics.reset") }}</M3Button>
|
|
48
|
+
</template>
|
|
49
|
+
</M3BottomSheet>
|
|
50
|
+
</template>
|
|
51
|
+
|
|
52
|
+
<script setup lang="ts">
|
|
53
|
+
import { useQueryMetrics } from "@/shared/database/queries";
|
|
54
|
+
|
|
55
|
+
/** The query metrics, in a sheet over whichever screen asked - the numbers belong to no one page. */
|
|
56
|
+
const open = defineModel<boolean>("open", { default: false });
|
|
57
|
+
const { t } = useI18n();
|
|
58
|
+
const {
|
|
59
|
+
totalQueries,
|
|
60
|
+
avgQueryTime,
|
|
61
|
+
cacheHitRate,
|
|
62
|
+
activeListeners,
|
|
63
|
+
refetchesByTable,
|
|
64
|
+
slowestQueries,
|
|
65
|
+
reset,
|
|
66
|
+
} = useQueryMetrics();
|
|
67
|
+
|
|
68
|
+
const refetches = computed(() => Object.entries(refetchesByTable.value));
|
|
69
|
+
const tiles = computed(() => [
|
|
70
|
+
{ labelKey: "metrics.totalQueries", value: String(totalQueries.value) },
|
|
71
|
+
{ labelKey: "metrics.avgQueryTime", value: `${avgQueryTime.value.toFixed(1)} ms` },
|
|
72
|
+
{ labelKey: "metrics.cacheHitRate", value: `${cacheHitRate.value.toFixed(0)} %` },
|
|
73
|
+
{ labelKey: "metrics.activeListeners", value: String(activeListeners.value) },
|
|
74
|
+
]);
|
|
75
|
+
</script>
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<M3List variant="segmented" inset :label="t('demo.orders')">
|
|
3
|
+
<M3ListItem
|
|
4
|
+
v-for="order in orders"
|
|
5
|
+
:key="order.id"
|
|
6
|
+
v-model:swiped="swiped[order.id]"
|
|
7
|
+
clickable
|
|
8
|
+
swipe-full="end"
|
|
9
|
+
:headline="order.reference"
|
|
10
|
+
:supporting="`${order.customerName} · ${t('demo.lines', { count: order.lines }, order.lines)}`"
|
|
11
|
+
@click="emit('open', order, $event)"
|
|
12
|
+
>
|
|
13
|
+
<template #leading>
|
|
14
|
+
<M3Shape
|
|
15
|
+
:shape="statusLook(order.status).shape"
|
|
16
|
+
class="size-10"
|
|
17
|
+
:class="statusLook(order.status).classes"
|
|
18
|
+
>
|
|
19
|
+
<component :is="statusLook(order.status).icon" class="size-5" />
|
|
20
|
+
</M3Shape>
|
|
21
|
+
</template>
|
|
22
|
+
<template #trailing>
|
|
23
|
+
<span class="type-title-small-emphasized tabular-nums text-on-surface">{{
|
|
24
|
+
formatMoney(order.totalCents)
|
|
25
|
+
}}</span>
|
|
26
|
+
</template>
|
|
27
|
+
<template #swipe-end>
|
|
28
|
+
<M3SwipeAction
|
|
29
|
+
v-if="nextStep(order)"
|
|
30
|
+
tone="primary"
|
|
31
|
+
:label="nextStep(order) === 'confirm' ? t('demo.confirm') : t('demo.deliver')"
|
|
32
|
+
@click="emit('advance', order)"
|
|
33
|
+
>
|
|
34
|
+
<i-ms-local-shipping-outline-rounded v-if="nextStep(order) === 'confirm'" />
|
|
35
|
+
<i-ms-verified-outline-rounded v-else />
|
|
36
|
+
</M3SwipeAction>
|
|
37
|
+
<M3SwipeAction tone="error" :label="t('demo.delete')" @click="emit('delete', order)">
|
|
38
|
+
<i-ms-delete-outline-rounded />
|
|
39
|
+
</M3SwipeAction>
|
|
40
|
+
</template>
|
|
41
|
+
<template #action>
|
|
42
|
+
<M3IconButton
|
|
43
|
+
:label="t('demo.actionsFor', { reference: order.reference })"
|
|
44
|
+
size="s"
|
|
45
|
+
@click="emit('more', order)"
|
|
46
|
+
>
|
|
47
|
+
<i-ms-more-vert-rounded />
|
|
48
|
+
</M3IconButton>
|
|
49
|
+
</template>
|
|
50
|
+
</M3ListItem>
|
|
51
|
+
</M3List>
|
|
52
|
+
</template>
|
|
53
|
+
|
|
54
|
+
<script setup lang="ts">
|
|
55
|
+
import type { OrderRow } from "@/domains/sales/sales.repository";
|
|
56
|
+
import { formatMoney, statusLook } from "@/modules/demo/composables/useOrderStatus";
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Tapping a row opens the order. Swiping it toward the start reveals its next step and Delete - a
|
|
60
|
+
* long swipe deletes - and the trailing button offers the same actions in a sheet, for anyone who
|
|
61
|
+
* never discovers the gesture.
|
|
62
|
+
*/
|
|
63
|
+
defineProps<{ orders: OrderRow[] }>();
|
|
64
|
+
const emit = defineEmits<{
|
|
65
|
+
open: [order: OrderRow, event: MouseEvent];
|
|
66
|
+
more: [order: OrderRow];
|
|
67
|
+
advance: [order: OrderRow];
|
|
68
|
+
delete: [order: OrderRow];
|
|
69
|
+
}>();
|
|
70
|
+
const { t } = useI18n();
|
|
71
|
+
const swiped = reactive<Record<number, "start" | "end" | null>>({});
|
|
72
|
+
|
|
73
|
+
const nextStep = (order: OrderRow) =>
|
|
74
|
+
order.status === "draft" ? "confirm" : order.status === "confirmed" ? "deliver" : null;
|
|
75
|
+
</script>
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<section class="mx-4 flex flex-col gap-4 rounded-lg bg-surface-container-low p-4">
|
|
3
|
+
<header class="flex flex-col gap-1">
|
|
4
|
+
<h3 class="type-title-medium-emphasized m-0">{{ t("demo.pipelining") }}</h3>
|
|
5
|
+
<p class="type-body-small m-0 text-on-surface-variant">
|
|
6
|
+
{{
|
|
7
|
+
isNative
|
|
8
|
+
? t("demo.pipeliningNative", { count: readsPerRun })
|
|
9
|
+
: t("demo.pipeliningWeb", { count: readsPerRun })
|
|
10
|
+
}}
|
|
11
|
+
</p>
|
|
12
|
+
</header>
|
|
13
|
+
|
|
14
|
+
<div class="flex flex-wrap items-center gap-2">
|
|
15
|
+
<M3Chip :label="engineName">
|
|
16
|
+
<template #icon><i-ms-database-outline-rounded /></template>
|
|
17
|
+
</M3Chip>
|
|
18
|
+
<span class="type-body-small text-on-surface-variant">{{ t("demo.engineSwitch") }}</span>
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
<div class="flex items-center gap-3">
|
|
22
|
+
<M3Button :disabled="measuring" @click="emit('measure')">
|
|
23
|
+
<template #icon><i-ms-speed-rounded /></template>
|
|
24
|
+
{{ measuring ? t("demo.measuring") : t("demo.measure") }}
|
|
25
|
+
</M3Button>
|
|
26
|
+
<M3LoadingIndicator v-if="measuring" :size="40" :label="t('demo.measuring')" />
|
|
27
|
+
</div>
|
|
28
|
+
|
|
29
|
+
<template v-if="result">
|
|
30
|
+
<div class="grid grid-cols-3 gap-0.5 overflow-hidden rounded-md text-center">
|
|
31
|
+
<div class="flex flex-col gap-1 bg-surface-container p-3">
|
|
32
|
+
<span class="type-label-medium text-on-surface-variant">{{ t("demo.parallel") }}</span>
|
|
33
|
+
<span class="type-title-medium tabular-nums">{{ result.parallelMs }} ms</span>
|
|
34
|
+
</div>
|
|
35
|
+
<div class="flex flex-col gap-1 bg-surface-container p-3">
|
|
36
|
+
<span class="type-label-medium text-on-surface-variant">{{ t("demo.sequential") }}</span>
|
|
37
|
+
<span class="type-title-medium tabular-nums">{{ result.sequentialMs }} ms</span>
|
|
38
|
+
</div>
|
|
39
|
+
<div class="flex flex-col gap-1 bg-primary-container p-3 text-on-primary-container">
|
|
40
|
+
<span class="type-label-medium">{{ t("demo.ratio") }}</span>
|
|
41
|
+
<span class="type-title-medium-emphasized tabular-nums">{{ result.ratio }}×</span>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
<p class="type-body-small m-0 text-on-surface-variant">
|
|
45
|
+
{{ t("demo.measuredOn", { engine: result.engine }) }} ·
|
|
46
|
+
{{ isNative ? t("demo.ratioNative") : t("demo.ratioWeb", { platform }) }}
|
|
47
|
+
</p>
|
|
48
|
+
</template>
|
|
49
|
+
</section>
|
|
50
|
+
</template>
|
|
51
|
+
|
|
52
|
+
<script setup lang="ts">
|
|
53
|
+
import type { PipelineResult } from "@/modules/demo/composables/useReactiveDemo";
|
|
54
|
+
|
|
55
|
+
defineProps<{
|
|
56
|
+
result: PipelineResult | null;
|
|
57
|
+
measuring: boolean;
|
|
58
|
+
readsPerRun: number;
|
|
59
|
+
isNative: boolean;
|
|
60
|
+
platform: string;
|
|
61
|
+
engineName: string;
|
|
62
|
+
}>();
|
|
63
|
+
const emit = defineEmits<{ measure: [] }>();
|
|
64
|
+
const { t } = useI18n();
|
|
65
|
+
</script>
|