@cavulsqa/create 2.9.2 → 2.10.0
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/CLAUDE.md +3 -0
- package/templates/f7-app/auto-imports.d.ts +15 -0
- package/templates/f7-app/package.json +6 -3
- package/templates/f7-app/src/App.vue +1 -0
- package/templates/f7-app/src/env.d.ts +1 -0
- package/templates/f7-app/src/main.ts +3 -0
- package/templates/f7-app/src/modules/demo/router/routes/demo.routes.ts +4 -16
- package/templates/f7-app/src/modules/home/router/routes/home.routes.ts +3 -11
- package/templates/f7-app/src/modules/settings/router/routes/settings.routes.ts +2 -6
- 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/router/global/global.routes.ts +2 -6
- package/templates/f7-app/src/shared/composables/useNavigationGuard.ts +48 -0
- package/templates/f7-app/src/shared/recorder/capacitorSink.ts +124 -0
- package/templates/f7-app/src/shared/utils/lazyRoute.ts +25 -0
- package/templates/f7-app/tests/lazyRoute.test.ts +30 -0
- package/templates/f7-app/tests/navigationGuard.test.ts +82 -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 +107 -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,82 @@
|
|
|
1
|
+
import type { PhotoItem } from "@cavulsqa/m3e-vue";
|
|
2
|
+
|
|
3
|
+
type SceneKind = "mountains" | "coast" | "dunes" | "city" | "forest" | "harbour";
|
|
4
|
+
|
|
5
|
+
interface Scene {
|
|
6
|
+
kind: SceneKind;
|
|
7
|
+
width: number;
|
|
8
|
+
height: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const SCENES: readonly Scene[] = [
|
|
12
|
+
{ kind: "mountains", width: 1600, height: 1200 },
|
|
13
|
+
{ kind: "coast", width: 1200, height: 1600 },
|
|
14
|
+
{ kind: "dunes", width: 2400, height: 1000 },
|
|
15
|
+
{ kind: "city", width: 1600, height: 1200 },
|
|
16
|
+
{ kind: "forest", width: 1200, height: 1600 },
|
|
17
|
+
{ kind: "harbour", width: 1600, height: 1200 },
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
function token(name: string): string {
|
|
21
|
+
return getComputedStyle(document.documentElement)
|
|
22
|
+
.getPropertyValue(`--md-sys-color-${name}`)
|
|
23
|
+
.trim();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function ridge(width: number, height: number, base: number, peaks: number, seed: number): string {
|
|
27
|
+
const points = [`0,${height}`];
|
|
28
|
+
for (let i = 0; i <= peaks; i++) {
|
|
29
|
+
const x = (i / peaks) * width;
|
|
30
|
+
const wave = Math.sin(i * 1.7 + seed) * 0.5 + Math.sin(i * 0.6 + seed * 2) * 0.5;
|
|
31
|
+
points.push(`${x.toFixed(0)},${(height * base - wave * height * 0.12).toFixed(0)}`);
|
|
32
|
+
}
|
|
33
|
+
points.push(`${width},${height}`);
|
|
34
|
+
return points.join(" ");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function scene({ kind, width, height }: Scene): string {
|
|
38
|
+
const sky = token("primary-container");
|
|
39
|
+
const glow = token("tertiary-container");
|
|
40
|
+
const sun = token("tertiary");
|
|
41
|
+
const far = token("secondary");
|
|
42
|
+
const near = token("primary");
|
|
43
|
+
const deep = token("on-primary-container");
|
|
44
|
+
const seed = kind.length;
|
|
45
|
+
const shapes: Record<SceneKind, string> = {
|
|
46
|
+
mountains: `<polygon points="${ridge(width, height, 0.55, 7, seed)}" fill="${far}" opacity=".7"/><polygon points="${ridge(width, height, 0.72, 5, seed + 2)}" fill="${near}"/>`,
|
|
47
|
+
coast: `<rect y="${height * 0.6}" width="${width}" height="${height * 0.4}" fill="${far}"/><polygon points="${ridge(width, height, 0.78, 3, seed)}" fill="${near}"/>`,
|
|
48
|
+
dunes: `<polygon points="${ridge(width, height, 0.62, 4, seed)}" fill="${glow}"/><polygon points="${ridge(width, height, 0.78, 3, seed + 1)}" fill="${sun}" opacity=".8"/>`,
|
|
49
|
+
city: Array.from({ length: 14 }, (_, i) => {
|
|
50
|
+
const w = width / 14;
|
|
51
|
+
const h = height * (0.25 + ((i * 37) % 9) / 22);
|
|
52
|
+
return `<rect x="${i * w + 4}" y="${height - h}" width="${w - 8}" height="${h}" fill="${i % 2 ? near : far}"/>`;
|
|
53
|
+
}).join(""),
|
|
54
|
+
forest: Array.from({ length: 9 }, (_, i) => {
|
|
55
|
+
const x = (i + 0.5) * (width / 9);
|
|
56
|
+
const top = height * (0.45 + ((i * 13) % 5) / 20);
|
|
57
|
+
return `<polygon points="${x},${top} ${x - 90},${height} ${x + 90},${height}" fill="${i % 2 ? near : far}"/>`;
|
|
58
|
+
}).join(""),
|
|
59
|
+
harbour: `<rect y="${height * 0.65}" width="${width}" height="${height * 0.35}" fill="${far}"/><polygon points="${width * 0.42},${height * 0.62} ${width * 0.58},${height * 0.62} ${width * 0.55},${height * 0.68} ${width * 0.45},${height * 0.68}" fill="${deep}"/><polygon points="${width * 0.5},${height * 0.3} ${width * 0.5},${height * 0.6} ${width * 0.6},${height * 0.6}" fill="${glow}"/>`,
|
|
60
|
+
};
|
|
61
|
+
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}"><defs><linearGradient id="s" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="${sky}"/><stop offset="1" stop-color="${glow}"/></linearGradient></defs><rect width="${width}" height="${height}" fill="url(#s)"/><circle cx="${width * 0.72}" cy="${height * 0.28}" r="${Math.min(width, height) * 0.09}" fill="${sun}"/>${shapes[kind]}</svg>`;
|
|
62
|
+
return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Demo photos drawn from the live theme, so the gallery needs no network and no bundled images:
|
|
67
|
+
* vector scenes at photo sizes - landscape, portrait and panorama - that stay sharp when zoomed.
|
|
68
|
+
*/
|
|
69
|
+
export function usePhotoScenes(label: (kind: SceneKind) => string) {
|
|
70
|
+
const photos = shallowRef<PhotoItem[]>([]);
|
|
71
|
+
const build = () => {
|
|
72
|
+
photos.value = SCENES.map((item) => ({
|
|
73
|
+
src: scene(item),
|
|
74
|
+
alt: label(item.kind),
|
|
75
|
+
caption: label(item.kind),
|
|
76
|
+
width: item.width,
|
|
77
|
+
height: item.height,
|
|
78
|
+
}));
|
|
79
|
+
};
|
|
80
|
+
onMounted(build);
|
|
81
|
+
return { photos, rebuild: build };
|
|
82
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { TreeNode } from "@cavulsqa/m3e-vue";
|
|
2
|
+
|
|
3
|
+
/** A catalogue and a permission set to show the treeview with, labelled in the current language. */
|
|
4
|
+
export function useTreeDemo() {
|
|
5
|
+
const { t } = useI18n();
|
|
6
|
+
const label = (key: string) => t(`gallery.tree.nodes.${key}`);
|
|
7
|
+
|
|
8
|
+
const categories = computed<TreeNode[]>(() => [
|
|
9
|
+
{
|
|
10
|
+
id: "drinks",
|
|
11
|
+
label: label("drinks"),
|
|
12
|
+
supporting: t("gallery.tree.products", { count: 42 }),
|
|
13
|
+
children: [
|
|
14
|
+
{ id: "water", label: label("water") },
|
|
15
|
+
{
|
|
16
|
+
id: "juice",
|
|
17
|
+
label: label("juice"),
|
|
18
|
+
children: [
|
|
19
|
+
{ id: "orange", label: label("orange") },
|
|
20
|
+
{ id: "apple", label: label("apple") },
|
|
21
|
+
],
|
|
22
|
+
},
|
|
23
|
+
{ id: "soda", label: label("soda") },
|
|
24
|
+
],
|
|
25
|
+
},
|
|
26
|
+
{ id: "dairy", label: label("dairy"), supporting: t("gallery.tree.loadsLater"), lazy: true },
|
|
27
|
+
{
|
|
28
|
+
id: "grocery",
|
|
29
|
+
label: label("grocery"),
|
|
30
|
+
children: [
|
|
31
|
+
{ id: "pasta", label: label("pasta") },
|
|
32
|
+
{ id: "oil", label: label("oil") },
|
|
33
|
+
],
|
|
34
|
+
},
|
|
35
|
+
]);
|
|
36
|
+
|
|
37
|
+
const permissions = computed<TreeNode[]>(() => [
|
|
38
|
+
{
|
|
39
|
+
id: "sales",
|
|
40
|
+
label: label("sales"),
|
|
41
|
+
children: [
|
|
42
|
+
{ id: "sales.read", label: label("read") },
|
|
43
|
+
{ id: "sales.create", label: label("create") },
|
|
44
|
+
{ id: "sales.cancel", label: label("cancel") },
|
|
45
|
+
],
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
id: "stock",
|
|
49
|
+
label: label("stock"),
|
|
50
|
+
children: [
|
|
51
|
+
{ id: "stock.read", label: label("read") },
|
|
52
|
+
{ id: "stock.adjust", label: label("adjust") },
|
|
53
|
+
],
|
|
54
|
+
},
|
|
55
|
+
]);
|
|
56
|
+
|
|
57
|
+
async function loadChildren(): Promise<TreeNode[]> {
|
|
58
|
+
await new Promise((resolve) => setTimeout(resolve, 900));
|
|
59
|
+
return [
|
|
60
|
+
{ id: "milk", label: label("milk") },
|
|
61
|
+
{ id: "yoghurt", label: label("yoghurt") },
|
|
62
|
+
{ id: "cheese", label: label("cheese") },
|
|
63
|
+
];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return { categories, permissions, loadChildren };
|
|
67
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/** Algeria's 58 wilayas in code order - a realistic list for the autocomplete demo. */
|
|
2
|
+
export const WILAYAS = [
|
|
3
|
+
"Adrar",
|
|
4
|
+
"Chlef",
|
|
5
|
+
"Laghouat",
|
|
6
|
+
"Oum El Bouaghi",
|
|
7
|
+
"Batna",
|
|
8
|
+
"Béjaïa",
|
|
9
|
+
"Biskra",
|
|
10
|
+
"Béchar",
|
|
11
|
+
"Blida",
|
|
12
|
+
"Bouira",
|
|
13
|
+
"Tamanrasset",
|
|
14
|
+
"Tébessa",
|
|
15
|
+
"Tlemcen",
|
|
16
|
+
"Tiaret",
|
|
17
|
+
"Tizi Ouzou",
|
|
18
|
+
"Alger",
|
|
19
|
+
"Djelfa",
|
|
20
|
+
"Jijel",
|
|
21
|
+
"Sétif",
|
|
22
|
+
"Saïda",
|
|
23
|
+
"Skikda",
|
|
24
|
+
"Sidi Bel Abbès",
|
|
25
|
+
"Annaba",
|
|
26
|
+
"Guelma",
|
|
27
|
+
"Constantine",
|
|
28
|
+
"Médéa",
|
|
29
|
+
"Mostaganem",
|
|
30
|
+
"M'Sila",
|
|
31
|
+
"Mascara",
|
|
32
|
+
"Ouargla",
|
|
33
|
+
"Oran",
|
|
34
|
+
"El Bayadh",
|
|
35
|
+
"Illizi",
|
|
36
|
+
"Bordj Bou Arréridj",
|
|
37
|
+
"Boumerdès",
|
|
38
|
+
"El Tarf",
|
|
39
|
+
"Tindouf",
|
|
40
|
+
"Tissemsilt",
|
|
41
|
+
"El Oued",
|
|
42
|
+
"Khenchela",
|
|
43
|
+
"Souk Ahras",
|
|
44
|
+
"Tipaza",
|
|
45
|
+
"Mila",
|
|
46
|
+
"Aïn Defla",
|
|
47
|
+
"Naâma",
|
|
48
|
+
"Aïn Témouchent",
|
|
49
|
+
"Ghardaïa",
|
|
50
|
+
"Relizane",
|
|
51
|
+
"Timimoun",
|
|
52
|
+
"Bordj Badji Mokhtar",
|
|
53
|
+
"Ouled Djellal",
|
|
54
|
+
"Béni Abbès",
|
|
55
|
+
"In Salah",
|
|
56
|
+
"In Guezzam",
|
|
57
|
+
"Touggourt",
|
|
58
|
+
"Djanet",
|
|
59
|
+
"El M'Ghair",
|
|
60
|
+
"El Meniaa",
|
|
61
|
+
] as const;
|
|
62
|
+
|
|
63
|
+
export const WILAYA_OPTIONS = WILAYAS.map((label, index) => ({
|
|
64
|
+
value: index + 1,
|
|
65
|
+
label,
|
|
66
|
+
supporting: String(index + 1).padStart(2, "0"),
|
|
67
|
+
}));
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { Router } from "framework7/types";
|
|
2
|
+
import { lazyRoute } from "@/shared/utils/lazyRoute";
|
|
3
|
+
|
|
4
|
+
const galleryRoutes: Router.RouteParameters[] = [
|
|
5
|
+
{
|
|
6
|
+
name: "gallery",
|
|
7
|
+
path: "/gallery/",
|
|
8
|
+
async: lazyRoute(() => import("@/modules/gallery/views/GalleryView.vue")),
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
name: "gallery-tabs",
|
|
12
|
+
path: "/gallery/tabs/",
|
|
13
|
+
async: lazyRoute(() => import("@/modules/gallery/views/GalleryTabsView.vue")),
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
name: "gallery-contacts",
|
|
17
|
+
path: "/gallery/contacts/",
|
|
18
|
+
async: lazyRoute(() => import("@/modules/gallery/views/GalleryContactsView.vue")),
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
name: "gallery-chat",
|
|
22
|
+
path: "/gallery/chat/",
|
|
23
|
+
async: lazyRoute(() => import("@/modules/gallery/views/GalleryChatView.vue")),
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
name: "gallery-onboarding",
|
|
27
|
+
path: "/gallery/onboarding/",
|
|
28
|
+
async: lazyRoute(() => import("@/modules/gallery/views/GalleryOnboardingView.vue")),
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
name: "gallery-login",
|
|
32
|
+
path: "/gallery/login/",
|
|
33
|
+
async: lazyRoute(() => import("@/modules/gallery/views/GalleryLoginView.vue")),
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: "gallery-agenda",
|
|
37
|
+
path: "/gallery/agenda/",
|
|
38
|
+
async: lazyRoute(() => import("@/modules/gallery/views/GalleryAgendaView.vue")),
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: "gallery-featured",
|
|
42
|
+
path: "/gallery/featured/:id/",
|
|
43
|
+
async: lazyRoute(() => import("@/modules/gallery/views/GalleryFeaturedView.vue")),
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
name: "gallery-section",
|
|
47
|
+
path: "/gallery/:section/",
|
|
48
|
+
async: lazyRoute(() => import("@/modules/gallery/views/GallerySectionView.vue")),
|
|
49
|
+
},
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
export default galleryRoutes;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<AppPage :title="t('gallery.agenda.title')" :subtitle="month" back variant="small">
|
|
3
|
+
<template #actions>
|
|
4
|
+
<M3IconButton
|
|
5
|
+
:label="t('gallery.agenda.today')"
|
|
6
|
+
:disabled="day === today"
|
|
7
|
+
@click="day = today"
|
|
8
|
+
>
|
|
9
|
+
<i-ms-today-outline-rounded />
|
|
10
|
+
</M3IconButton>
|
|
11
|
+
</template>
|
|
12
|
+
<template #bottom>
|
|
13
|
+
<div>
|
|
14
|
+
<M3ButtonGroup
|
|
15
|
+
variant="connected"
|
|
16
|
+
size="s"
|
|
17
|
+
class="agenda-switcher px-4 pb-2"
|
|
18
|
+
:label="t('gallery.agenda.view')"
|
|
19
|
+
>
|
|
20
|
+
<M3Button
|
|
21
|
+
v-for="option in AGENDA_VIEWS"
|
|
22
|
+
:key="option"
|
|
23
|
+
variant="tonal"
|
|
24
|
+
toggle
|
|
25
|
+
:selected="view === option"
|
|
26
|
+
@update:selected="view = option"
|
|
27
|
+
>
|
|
28
|
+
{{ t(`gallery.agenda.views.${option}`) }}
|
|
29
|
+
</M3Button>
|
|
30
|
+
</M3ButtonGroup>
|
|
31
|
+
<M3WeekStrip
|
|
32
|
+
v-if="view !== 'month'"
|
|
33
|
+
v-model="day"
|
|
34
|
+
class="pb-2"
|
|
35
|
+
:label="t('gallery.agenda.days')"
|
|
36
|
+
:locale="locale"
|
|
37
|
+
:marks="weekMarks"
|
|
38
|
+
:mark-label="visitsLabel"
|
|
39
|
+
/>
|
|
40
|
+
</div>
|
|
41
|
+
</template>
|
|
42
|
+
|
|
43
|
+
<Transition name="agenda-through" mode="out-in" @enter="rewind">
|
|
44
|
+
<AgendaDay
|
|
45
|
+
v-if="view === 'day'"
|
|
46
|
+
key="day"
|
|
47
|
+
:day="day"
|
|
48
|
+
:today="today"
|
|
49
|
+
:heading="heading"
|
|
50
|
+
:visits="visits"
|
|
51
|
+
@step="day = addDays(day, $event)"
|
|
52
|
+
/>
|
|
53
|
+
<AgendaMonth
|
|
54
|
+
v-else-if="view === 'month'"
|
|
55
|
+
key="month"
|
|
56
|
+
v-model="day"
|
|
57
|
+
:heading="heading"
|
|
58
|
+
:visits="visits"
|
|
59
|
+
:marks="monthMarks"
|
|
60
|
+
:mark-label="visitsLabel"
|
|
61
|
+
@month="shownMonth = $event"
|
|
62
|
+
/>
|
|
63
|
+
<AgendaVisitList v-else key="week" :heading="heading" :visits="visits" />
|
|
64
|
+
</Transition>
|
|
65
|
+
</AppPage>
|
|
66
|
+
</template>
|
|
67
|
+
|
|
68
|
+
<script setup lang="ts">
|
|
69
|
+
import { addDays } from "@cavulsqa/m3e-vue";
|
|
70
|
+
import AgendaDay from "@/modules/gallery/components/agenda/AgendaDay.vue";
|
|
71
|
+
import AgendaMonth from "@/modules/gallery/components/agenda/AgendaMonth.vue";
|
|
72
|
+
import AgendaVisitList from "@/modules/gallery/components/agenda/AgendaVisitList.vue";
|
|
73
|
+
import { AGENDA_VIEWS, useAgenda } from "@/modules/gallery/composables/useAgenda";
|
|
74
|
+
|
|
75
|
+
const { t, locale } = useI18n();
|
|
76
|
+
const { today, day, view, visits, weekMarks, monthMarks, shownMonth, month, heading, visitsLabel } =
|
|
77
|
+
useAgenda();
|
|
78
|
+
|
|
79
|
+
function rewind(element: Element) {
|
|
80
|
+
element.closest(".page-content")?.scrollTo({ top: 0 });
|
|
81
|
+
}
|
|
82
|
+
</script>
|
|
83
|
+
|
|
84
|
+
<style scoped>
|
|
85
|
+
.agenda-switcher {
|
|
86
|
+
display: flex;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.agenda-switcher > :deep(*) {
|
|
90
|
+
flex: 1 1 0;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.agenda-through-leave-active {
|
|
94
|
+
transition: opacity 90ms var(--md-sys-motion-easing-emphasized-accelerate);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.agenda-through-enter-active {
|
|
98
|
+
transition:
|
|
99
|
+
opacity 210ms var(--md-sys-motion-easing-emphasized-decelerate),
|
|
100
|
+
transform 210ms var(--md-sys-motion-easing-emphasized-decelerate);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.agenda-through-leave-to,
|
|
104
|
+
.agenda-through-enter-from {
|
|
105
|
+
opacity: 0;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.agenda-through-enter-from {
|
|
109
|
+
transform: scale(0.96);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
@media (prefers-reduced-motion: reduce) {
|
|
113
|
+
.agenda-through-enter-from {
|
|
114
|
+
transform: none;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
</style>
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<AppPage
|
|
3
|
+
:title="t('gallery.chat.title')"
|
|
4
|
+
:subtitle="t('gallery.chat.subtitle')"
|
|
5
|
+
back
|
|
6
|
+
variant="small"
|
|
7
|
+
class="gallery-chat"
|
|
8
|
+
>
|
|
9
|
+
<M3Messages
|
|
10
|
+
:messages="messages"
|
|
11
|
+
:label="t('gallery.chat.title')"
|
|
12
|
+
:locale="locale"
|
|
13
|
+
:typing="typing"
|
|
14
|
+
:you-label="t('gallery.chat.you')"
|
|
15
|
+
:retry-label="t('gallery.chat.retry')"
|
|
16
|
+
:latest-label="t('gallery.chat.latest')"
|
|
17
|
+
:status-labels="statusLabels"
|
|
18
|
+
:typing-label="typingLabel"
|
|
19
|
+
:unread-label="unreadLabel"
|
|
20
|
+
authors
|
|
21
|
+
:reactions="REACTIONS"
|
|
22
|
+
:actions="actions"
|
|
23
|
+
:react-label="t('gallery.chat.react')"
|
|
24
|
+
:menu-label="t('gallery.chat.messageActions')"
|
|
25
|
+
:reactions-label="reactionsLabel"
|
|
26
|
+
:reactions-title="t('gallery.chat.reactionsTitle')"
|
|
27
|
+
:all-reactions-label="t('gallery.chat.allReactions')"
|
|
28
|
+
:remove-reaction-label="t('gallery.chat.removeReaction')"
|
|
29
|
+
:others-label="othersLabel"
|
|
30
|
+
:card-labels="cardLabels"
|
|
31
|
+
@react="react"
|
|
32
|
+
@action="onAction"
|
|
33
|
+
@press="openMessage"
|
|
34
|
+
@retry="retry"
|
|
35
|
+
@vote="vote"
|
|
36
|
+
@rsvp="rsvp"
|
|
37
|
+
>
|
|
38
|
+
<template #before>
|
|
39
|
+
<M3InfiniteScroll
|
|
40
|
+
edge="start"
|
|
41
|
+
:load="loadOlder"
|
|
42
|
+
:loading-label="t('gallery.chat.loadingOlder')"
|
|
43
|
+
:error-text="t('gallery.chat.loadFailed')"
|
|
44
|
+
:retry-label="t('gallery.chat.retry')"
|
|
45
|
+
:end-text="t('gallery.chat.start')"
|
|
46
|
+
/>
|
|
47
|
+
</template>
|
|
48
|
+
</M3Messages>
|
|
49
|
+
|
|
50
|
+
<template #fixed>
|
|
51
|
+
<M3MessageBar
|
|
52
|
+
v-model="draft"
|
|
53
|
+
:label="t('gallery.chat.compose')"
|
|
54
|
+
:send-label="t('gallery.chat.send')"
|
|
55
|
+
@send="send"
|
|
56
|
+
>
|
|
57
|
+
<template #leading>
|
|
58
|
+
<M3IconButton
|
|
59
|
+
:label="t('gallery.chat.attach')"
|
|
60
|
+
:aria-expanded="attachOpen"
|
|
61
|
+
@click="attachOpen = !attachOpen"
|
|
62
|
+
>
|
|
63
|
+
<i-ms-add-rounded class="chat-plus" :class="{ 'chat-plus--open': attachOpen }" />
|
|
64
|
+
</M3IconButton>
|
|
65
|
+
</template>
|
|
66
|
+
<template #trailing>
|
|
67
|
+
<M3IconButton :label="t('gallery.chat.thumbsUp')" @click="draft += '👍'">
|
|
68
|
+
<i-ms-add-reaction-outline-rounded />
|
|
69
|
+
</M3IconButton>
|
|
70
|
+
</template>
|
|
71
|
+
</M3MessageBar>
|
|
72
|
+
|
|
73
|
+
<ChatAttachSheet
|
|
74
|
+
v-model:open="attachOpen"
|
|
75
|
+
:photos="photos"
|
|
76
|
+
@image="sendImage"
|
|
77
|
+
@text="send"
|
|
78
|
+
@recent="sendPhoto"
|
|
79
|
+
@failed="onFailed"
|
|
80
|
+
@compose="compose"
|
|
81
|
+
/>
|
|
82
|
+
<ChatPollComposer v-model:open="pollOpen" @send="sendPoll" />
|
|
83
|
+
<ChatInviteComposer v-model:open="inviteOpen" @send="sendInvite" />
|
|
84
|
+
<ChatContactPicker
|
|
85
|
+
v-model:open="contactOpen"
|
|
86
|
+
:customers="customers"
|
|
87
|
+
:loading="customersLoading"
|
|
88
|
+
:failed="customersFailed"
|
|
89
|
+
@pick="sendContact"
|
|
90
|
+
/>
|
|
91
|
+
|
|
92
|
+
<M3PhotoBrowser
|
|
93
|
+
v-model:open="browserOpen"
|
|
94
|
+
v-model:index="photoIndex"
|
|
95
|
+
:photos="shared"
|
|
96
|
+
:label="t('gallery.chat.photos')"
|
|
97
|
+
:close-label="t('shell.dismiss')"
|
|
98
|
+
/>
|
|
99
|
+
</template>
|
|
100
|
+
</AppPage>
|
|
101
|
+
</template>
|
|
102
|
+
|
|
103
|
+
<script setup lang="ts">
|
|
104
|
+
import type {
|
|
105
|
+
ChatMessage,
|
|
106
|
+
MessageAction,
|
|
107
|
+
MessageCardLabels,
|
|
108
|
+
MessageReaction,
|
|
109
|
+
MessageStatus,
|
|
110
|
+
PhotoItem,
|
|
111
|
+
} from "@cavulsqa/m3e-vue";
|
|
112
|
+
import type { Router } from "framework7/types";
|
|
113
|
+
import { markRaw } from "vue";
|
|
114
|
+
import CopyIcon from "~icons/material-symbols/content-copy-outline-rounded";
|
|
115
|
+
import DeleteIcon from "~icons/material-symbols/delete-outline-rounded";
|
|
116
|
+
import ChatAttachSheet from "@/modules/gallery/components/chat/ChatAttachSheet.vue";
|
|
117
|
+
import ChatContactPicker from "@/modules/gallery/components/chat/ChatContactPicker.vue";
|
|
118
|
+
import ChatInviteComposer from "@/modules/gallery/components/chat/ChatInviteComposer.vue";
|
|
119
|
+
import ChatPollComposer from "@/modules/gallery/components/chat/ChatPollComposer.vue";
|
|
120
|
+
import type { ComposeKind } from "@/modules/gallery/composables/composeKind";
|
|
121
|
+
import { mapsLink } from "@/modules/gallery/composables/currentPosition";
|
|
122
|
+
import { openExternal } from "@/modules/gallery/composables/openExternal";
|
|
123
|
+
import { useChatCustomers } from "@/modules/gallery/composables/useChatCustomers";
|
|
124
|
+
import { useChatDemo } from "@/modules/gallery/composables/useChatDemo";
|
|
125
|
+
import { useLocationShare } from "@/modules/gallery/composables/useLocationShare";
|
|
126
|
+
import { usePhotoScenes } from "@/modules/gallery/composables/usePhotoScenes";
|
|
127
|
+
|
|
128
|
+
const props = defineProps<{ f7router: Router.Router }>();
|
|
129
|
+
const { t, locale } = useI18n();
|
|
130
|
+
const snackbar = useSnackbar();
|
|
131
|
+
const { photos } = usePhotoScenes((kind) => t(`gallery.carousels.scenes.${kind}`));
|
|
132
|
+
const REACTIONS = ["👍", "❤️", "😂", "😮", "😢", "🙏", "🔥"] as const;
|
|
133
|
+
|
|
134
|
+
const {
|
|
135
|
+
messages,
|
|
136
|
+
typing,
|
|
137
|
+
draft,
|
|
138
|
+
send,
|
|
139
|
+
sendPhoto,
|
|
140
|
+
sendImage,
|
|
141
|
+
sendLocation,
|
|
142
|
+
sendContact,
|
|
143
|
+
sendPoll,
|
|
144
|
+
sendInvite,
|
|
145
|
+
vote,
|
|
146
|
+
rsvp,
|
|
147
|
+
react,
|
|
148
|
+
retry,
|
|
149
|
+
remove,
|
|
150
|
+
loadOlder,
|
|
151
|
+
} = useChatDemo((index) => {
|
|
152
|
+
const photo = photos.value[index % Math.max(1, photos.value.length)];
|
|
153
|
+
if (!photo?.width || !photo.height) return null;
|
|
154
|
+
return { src: photo.src, width: photo.width, height: photo.height, alt: photo.alt };
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
const browserOpen = ref(false);
|
|
158
|
+
const attachOpen = ref(false);
|
|
159
|
+
const pollOpen = ref(false);
|
|
160
|
+
const inviteOpen = ref(false);
|
|
161
|
+
const contactOpen = ref(false);
|
|
162
|
+
|
|
163
|
+
const {
|
|
164
|
+
customers,
|
|
165
|
+
loading: customersLoading,
|
|
166
|
+
failed: customersFailed,
|
|
167
|
+
} = useChatCustomers(contactOpen);
|
|
168
|
+
const { share: shareLocation } = useLocationShare(sendLocation);
|
|
169
|
+
|
|
170
|
+
const cardLabels = computed<MessageCardLabels>(() => ({
|
|
171
|
+
openLocation: t("gallery.chat.cards.openLocation"),
|
|
172
|
+
openContact: t("gallery.chat.cards.openContact"),
|
|
173
|
+
pollSingle: t("gallery.chat.cards.pollSingle"),
|
|
174
|
+
pollMultiple: t("gallery.chat.cards.pollMultiple"),
|
|
175
|
+
votes: (count: number) => t("gallery.chat.cards.votes", { count }, count),
|
|
176
|
+
going: t("gallery.chat.cards.going"),
|
|
177
|
+
maybe: t("gallery.chat.cards.maybe"),
|
|
178
|
+
no: t("gallery.chat.cards.no"),
|
|
179
|
+
}));
|
|
180
|
+
|
|
181
|
+
const actions = computed<MessageAction[]>(() => [
|
|
182
|
+
{
|
|
183
|
+
id: "copy",
|
|
184
|
+
label: t("gallery.chat.copy"),
|
|
185
|
+
icon: markRaw(CopyIcon),
|
|
186
|
+
when: (message) => Boolean(message.text),
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
id: "delete",
|
|
190
|
+
label: t("gallery.chat.delete"),
|
|
191
|
+
icon: markRaw(DeleteIcon),
|
|
192
|
+
tone: "destructive",
|
|
193
|
+
},
|
|
194
|
+
]);
|
|
195
|
+
const photoIndex = ref(0);
|
|
196
|
+
|
|
197
|
+
const statusLabels = computed<Record<MessageStatus, string>>(() => ({
|
|
198
|
+
sending: t("gallery.chat.status.sending"),
|
|
199
|
+
sent: t("gallery.chat.status.sent"),
|
|
200
|
+
delivered: t("gallery.chat.status.delivered"),
|
|
201
|
+
read: t("gallery.chat.status.read"),
|
|
202
|
+
failed: t("gallery.chat.status.failed"),
|
|
203
|
+
}));
|
|
204
|
+
|
|
205
|
+
const shared = computed<PhotoItem[]>(() =>
|
|
206
|
+
messages.value.flatMap((message) =>
|
|
207
|
+
message.image
|
|
208
|
+
? [{ ...message.image, alt: message.image.alt ?? "", caption: message.text }]
|
|
209
|
+
: [],
|
|
210
|
+
),
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
function typingLabel(author: string | undefined) {
|
|
214
|
+
return t("gallery.chat.typing", { name: author ?? "" });
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function unreadLabel(count: number) {
|
|
218
|
+
return t("gallery.chat.unread", { count }, count);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function compose(kind: ComposeKind) {
|
|
222
|
+
if (kind === "location") void shareLocation();
|
|
223
|
+
else if (kind === "contact") contactOpen.value = true;
|
|
224
|
+
else if (kind === "poll") pollOpen.value = true;
|
|
225
|
+
else inviteOpen.value = true;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function openMessage(message: ChatMessage) {
|
|
229
|
+
if (message.location) openExternal(mapsLink(message.location.lat, message.location.lng));
|
|
230
|
+
else if (message.contact)
|
|
231
|
+
props.f7router.navigate(`/demo/search/?q=${encodeURIComponent(message.contact.name)}`);
|
|
232
|
+
else if (message.image) openPhoto(message);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function openPhoto(message: ChatMessage) {
|
|
236
|
+
const index = shared.value.findIndex((photo) => photo.src === message.image?.src);
|
|
237
|
+
photoIndex.value = Math.max(0, index);
|
|
238
|
+
browserOpen.value = true;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function reactionsLabel(reactions: readonly MessageReaction[]) {
|
|
242
|
+
return reactions
|
|
243
|
+
.map((reaction) =>
|
|
244
|
+
t(
|
|
245
|
+
"gallery.chat.reactionCount",
|
|
246
|
+
{ emoji: reaction.emoji, count: reaction.count ?? 1 },
|
|
247
|
+
reaction.count ?? 1,
|
|
248
|
+
),
|
|
249
|
+
)
|
|
250
|
+
.join(", ");
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function othersLabel(count: number) {
|
|
254
|
+
return t("gallery.chat.othersReacted", { count }, count);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function onFailed(name: string) {
|
|
258
|
+
void snackbar.show({ message: t("gallery.chat.attachFailed", { name }) });
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
async function onAction(message: ChatMessage, id: string) {
|
|
262
|
+
if (id === "delete") {
|
|
263
|
+
remove(message);
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
if (id !== "copy" || !message.text) return;
|
|
267
|
+
try {
|
|
268
|
+
await navigator.clipboard.writeText(message.text);
|
|
269
|
+
void snackbar.show({ message: t("gallery.chat.copied") });
|
|
270
|
+
} catch {
|
|
271
|
+
void snackbar.show({ message: t("gallery.chat.copyFailed") });
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
</script>
|
|
275
|
+
|
|
276
|
+
<style scoped>
|
|
277
|
+
.chat-plus {
|
|
278
|
+
transition: rotate var(--md-sys-motion-spring-fast-spatial-duration)
|
|
279
|
+
var(--md-sys-motion-spring-fast-spatial);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
.chat-plus--open {
|
|
283
|
+
rotate: 45deg;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
:global(.gallery-chat .page-content) {
|
|
287
|
+
--m3-messages-min-height: calc(100% - 64px - env(safe-area-inset-top));
|
|
288
|
+
|
|
289
|
+
padding-bottom: 0;
|
|
290
|
+
scroll-padding-bottom: var(--m3-message-bar-height, 0px);
|
|
291
|
+
}
|
|
292
|
+
</style>
|