@collabdt/core 0.0.44 → 0.0.46

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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/core/components/Toolbar.tsx"],"sourcesContent":["'use client'\n\n// Components\nimport * as React from 'react'\nimport dynamic from 'next/dynamic'\nimport { ViewerNames } from '../types'\nimport { mapToolbarTools } from '../components/viewers/map/src/tools/mapTools'\nimport { ToolbarBody } from './ToolbarBody'\n\n// The BIM and PointCloud toolbar tool registries transitively import\n// @thatopen and Potree-adjacent code. Statically importing them here (eagerly\n// mounted by Viewer.tsx) kept ~456 KB of @thatopen on the map route's\n// first-load JS. Dynamic-importing both per-viewer toolbars cuts that path.\n//\n// MapToolbar stays inline (no separate file, no dynamic) because the map\n// is the default landing surface and every user pays its cost anyway.\nconst BimToolbar = dynamic(\n () => import('./viewers/bim/BimToolbar').then(m => ({ default: m.BimToolbar })),\n { ssr: false },\n)\nconst PointCloudToolbar = dynamic(\n () => import('./viewers/pointcloud/PointCloudToolbar').then(m => ({ default: m.PointCloudToolbar })),\n { ssr: false },\n)\n\ninterface Props {\n viewer: ViewerNames\n}\n\nexport function Toolbar({ viewer }: Props) {\n if (viewer === ViewerNames.map) {\n return <ToolbarBody viewer=\"map\" tools={mapToolbarTools()} />\n }\n if (viewer === ViewerNames.bim) {\n return <BimToolbar />\n }\n if (viewer === ViewerNames.pointcloud) {\n return <PointCloudToolbar />\n }\n return null\n}\n"],"mappings":";AA+BW;AA3BX,OAAO,aAAa;AACpB,SAAS,mBAAmB;AAC5B,SAAS,uBAAuB;AAChC,SAAS,mBAAmB;AAS5B,MAAM,aAAa;AAAA,EACjB,MAAM,OAAO,0BAA0B,EAAE,KAAK,QAAM,EAAE,SAAS,EAAE,WAAW,EAAE;AAAA,EAC9E,EAAE,KAAK,MAAM;AACf;AACA,MAAM,oBAAoB;AAAA,EACxB,MAAM,OAAO,wCAAwC,EAAE,KAAK,QAAM,EAAE,SAAS,EAAE,kBAAkB,EAAE;AAAA,EACnG,EAAE,KAAK,MAAM;AACf;AAMO,SAAS,QAAQ,EAAE,OAAO,GAAU;AACzC,MAAI,WAAW,YAAY,KAAK;AAC9B,WAAO,oBAAC,eAAY,QAAO,OAAM,OAAO,gBAAgB,GAAG;AAAA,EAC7D;AACA,MAAI,WAAW,YAAY,KAAK;AAC9B,WAAO,oBAAC,cAAW;AAAA,EACrB;AACA,MAAI,WAAW,YAAY,YAAY;AACrC,WAAO,oBAAC,qBAAkB;AAAA,EAC5B;AACA,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":["../../../src/core/components/Toolbar.tsx"],"sourcesContent":["'use client'\r\n\r\n// Components\r\nimport * as React from 'react'\r\nimport dynamic from 'next/dynamic'\r\nimport { ViewerNames } from '../types'\r\nimport { mapToolbarTools } from '../components/viewers/map/src/tools/mapTools'\r\nimport { ToolbarBody } from './ToolbarBody'\r\n\r\n// The BIM and PointCloud toolbar tool registries transitively import\r\n// @thatopen and Potree-adjacent code. Statically importing them here (eagerly\r\n// mounted by Viewer.tsx) kept ~456 KB of @thatopen on the map route's\r\n// first-load JS. Dynamic-importing both per-viewer toolbars cuts that path.\r\n//\r\n// MapToolbar stays inline (no separate file, no dynamic) because the map\r\n// is the default landing surface and every user pays its cost anyway.\r\nconst BimToolbar = dynamic(\r\n () => import('./viewers/bim/BimToolbar').then(m => ({ default: m.BimToolbar })),\r\n { ssr: false },\r\n)\r\nconst PointCloudToolbar = dynamic(\r\n () => import('./viewers/pointcloud/PointCloudToolbar').then(m => ({ default: m.PointCloudToolbar })),\r\n { ssr: false },\r\n)\r\n\r\ninterface Props {\r\n viewer: ViewerNames\r\n}\r\n\r\nexport function Toolbar({ viewer }: Props) {\r\n if (viewer === ViewerNames.map) {\r\n return <ToolbarBody viewer=\"map\" tools={mapToolbarTools()} />\r\n }\r\n if (viewer === ViewerNames.bim) {\r\n return <BimToolbar />\r\n }\r\n if (viewer === ViewerNames.pointcloud) {\r\n return <PointCloudToolbar />\r\n }\r\n return null\r\n}\r\n"],"mappings":";AA+BW;AA3BX,OAAO,aAAa;AACpB,SAAS,mBAAmB;AAC5B,SAAS,uBAAuB;AAChC,SAAS,mBAAmB;AAS5B,MAAM,aAAa;AAAA,EACjB,MAAM,OAAO,0BAA0B,EAAE,KAAK,QAAM,EAAE,SAAS,EAAE,WAAW,EAAE;AAAA,EAC9E,EAAE,KAAK,MAAM;AACf;AACA,MAAM,oBAAoB;AAAA,EACxB,MAAM,OAAO,wCAAwC,EAAE,KAAK,QAAM,EAAE,SAAS,EAAE,kBAAkB,EAAE;AAAA,EACnG,EAAE,KAAK,MAAM;AACf;AAMO,SAAS,QAAQ,EAAE,OAAO,GAAU;AACzC,MAAI,WAAW,YAAY,KAAK;AAC9B,WAAO,oBAAC,eAAY,QAAO,OAAM,OAAO,gBAAgB,GAAG;AAAA,EAC7D;AACA,MAAI,WAAW,YAAY,KAAK;AAC9B,WAAO,oBAAC,cAAW;AAAA,EACrB;AACA,MAAI,WAAW,YAAY,YAAY;AACrC,WAAO,oBAAC,qBAAkB;AAAA,EAC5B;AACA,SAAO;AACT;","names":[]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/core/components/ToolbarBody.tsx"],"sourcesContent":["'use client'\n\nimport * as React from 'react'\nimport { Menubar } from './ui/Menubar'\nimport { Tool } from '../types/tools'\nimport ToolbarButton from './ui/ToolbarButton'\nimport { SubmenuProvider } from './ToolbarSubmenu'\n\n// Shared menubar wrapper used by the per-viewer toolbars (MapToolbar inline\n// in Toolbar.tsx, BimToolbar + PointCloudToolbar in their respective viewer\n// folders so they ride the viewer's lazy chunk). Extracted from Toolbar.tsx\n// to avoid duplicating the JSX across three sites.\n\ninterface Props {\n viewer: string\n tools: Tool[]\n}\n\nexport function ToolbarBody({ viewer, tools }: Props) {\n return (\n <div className=\"fixed left-1/2 transform -translate-x-1/2 bottom-[10px] flex items-center pointer-events-none z-10\">\n <SubmenuProvider>\n <Menubar\n id={`${viewer}-toolbar`}\n className=\"flex flex-row px-1 gap-1 justify-center w-fit bg-primary-light rounded space-y-0\"\n >\n {tools.map((tool, index) => (\n <div key={index} className=\"flex items-center\">\n <ToolbarButton tool={tool} />\n </div>\n ))}\n </Menubar>\n </SubmenuProvider>\n </div>\n )\n}\n"],"mappings":";AA4Bc;AAzBd,SAAS,eAAe;AAExB,OAAO,mBAAmB;AAC1B,SAAS,uBAAuB;AAYzB,SAAS,YAAY,EAAE,QAAQ,MAAM,GAAU;AACpD,SACE,oBAAC,SAAI,WAAU,sGACb,8BAAC,mBACC;AAAA,IAAC;AAAA;AAAA,MACC,IAAI,GAAG,MAAM;AAAA,MACb,WAAU;AAAA,MAET,gBAAM,IAAI,CAAC,MAAM,UAChB,oBAAC,SAAgB,WAAU,qBACzB,8BAAC,iBAAc,MAAY,KADnB,KAEV,CACD;AAAA;AAAA,EACH,GACF,GACF;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../src/core/components/ToolbarBody.tsx"],"sourcesContent":["'use client'\r\n\r\nimport * as React from 'react'\r\nimport { Menubar } from './ui/Menubar'\r\nimport { Tool } from '../types/tools'\r\nimport ToolbarButton from './ui/ToolbarButton'\r\nimport { SubmenuProvider } from './ToolbarSubmenu'\r\n\r\n// Shared menubar wrapper used by the per-viewer toolbars (MapToolbar inline\r\n// in Toolbar.tsx, BimToolbar + PointCloudToolbar in their respective viewer\r\n// folders so they ride the viewer's lazy chunk). Extracted from Toolbar.tsx\r\n// to avoid duplicating the JSX across three sites.\r\n\r\ninterface Props {\r\n viewer: string\r\n tools: Tool[]\r\n}\r\n\r\nexport function ToolbarBody({ viewer, tools }: Props) {\r\n return (\r\n <div className=\"fixed left-1/2 transform -translate-x-1/2 bottom-[10px] flex items-center pointer-events-none z-10\">\r\n <SubmenuProvider>\r\n <Menubar\r\n id={`${viewer}-toolbar`}\r\n className=\"flex flex-row px-1 gap-1 justify-center w-fit bg-primary-light rounded space-y-0\"\r\n >\r\n {tools.map((tool, index) => (\r\n <div key={index} className=\"flex items-center\">\r\n <ToolbarButton tool={tool} />\r\n </div>\r\n ))}\r\n </Menubar>\r\n </SubmenuProvider>\r\n </div>\r\n )\r\n}\r\n"],"mappings":";AA4Bc;AAzBd,SAAS,eAAe;AAExB,OAAO,mBAAmB;AAC1B,SAAS,uBAAuB;AAYzB,SAAS,YAAY,EAAE,QAAQ,MAAM,GAAU;AACpD,SACE,oBAAC,SAAI,WAAU,sGACb,8BAAC,mBACC;AAAA,IAAC;AAAA;AAAA,MACC,IAAI,GAAG,MAAM;AAAA,MACb,WAAU;AAAA,MAET,gBAAM,IAAI,CAAC,MAAM,UAChB,oBAAC,SAAgB,WAAU,qBACzB,8BAAC,iBAAc,MAAY,KADnB,KAEV,CACD;AAAA;AAAA,EACH,GACF,GACF;AAEJ;","names":[]}
@@ -175,16 +175,7 @@ function SignInContent() {
175
175
  children: /* @__PURE__ */ jsx(GoogleIcon, { size: 20 })
176
176
  }
177
177
  )
178
- ] }),
179
- /* @__PURE__ */ jsx("div", { className: "flex justify-start", children: /* @__PURE__ */ jsx(
180
- "a",
181
- {
182
- href: `/${orgName}/auth/reset-password`,
183
- className: "text-sm underline underline-offset-4 hover:opacity-80",
184
- style: { color: "var(--hp-primary)" },
185
- children: "Reset your password"
186
- }
187
- ) })
178
+ ] })
188
179
  ] }),
189
180
  step === "mfa" && /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
190
181
  /* @__PURE__ */ jsx(
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/core/components/authentication/Signin.tsx"],"sourcesContent":["'use client'\r\n\r\nimport * as React from 'react'\r\nimport { signIn } from 'next-auth/react'\r\nimport { useTranslations } from 'next-intl'\r\nimport * as LR from 'lucide-react'\r\nimport { toast, Toaster } from 'sonner'\r\n// Direct file imports (not the ui barrel). The ui barrel\r\n// `src/core/components/ui/index.ts` re-exports dozens of components plus\r\n// `useFileUploadHandler` and `InfoSidebar`, the latter of which transitively\r\n// pulls in @thatopen and the BIM viewer tree. Importing through the barrel\r\n// would drag all of that into the auth pages; direct file imports avoid it.\r\nimport { Button } from '../ui/Button'\r\nimport { GoogleIcon } from '../ui/Icons/GoogleIcon'\r\nimport { Input } from '../ui/Input'\r\nimport { LoadingSpinner } from '../ui/LoadingSpinner'\r\nimport { AuthPage, useAuthTheme } from './AuthPage'\r\nimport { useParams,useSearchParams  } from 'next/navigation'\r\nimport ReCAPTCHA from 'react-google-recaptcha'\r\n\r\nfunction SignInContent() {\r\n const [step, setStep] = React.useState<'login' | 'mfa'>('login')\r\n\r\n const [email, setEmail] = React.useState('')\r\n const [password, setPassword] = React.useState('')\r\n const [code, setCode] = React.useState('')\r\n\r\n const [captchaStatus, setCaptchaStatus] = React.useState(false)\r\n const [captchaToken, setCaptchaToken] = React.useState('')\r\n const [isLoading, setIsLoading] = React.useState(false)\r\n const [error, setError] = React.useState('')\r\n const [showPassword, setShowPassword] = React.useState(false)\r\n\r\n const searchParams = useSearchParams()\r\n const params = useParams()\r\n const orgName = params.instance ?? 'canada'\r\n const [googleError, setGoogleError] = React.useState(\r\n searchParams.get('error')\r\n )\r\n //Clean up the Google query error in URL, and have it only return /orgName/signin to clear frontend error responses \r\n React.useEffect(() => {\r\n if (googleError) {\r\n window.history.replaceState({}, '', `/${orgName}/signin`)\r\n }\r\n}, [googleError, orgName])\r\n\r\n const t = useTranslations('Signin')\r\n const tMfa = useTranslations('MFA')\r\n const authTheme = useAuthTheme()\r\n\r\n const reCaptchaSiteKey = `${process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY}`\r\n\r\n const onReCaptchaSuccess = (token) => {\r\n setCaptchaToken(token)\r\n setCaptchaStatus(true)\r\n }\r\n\r\n // Initial LOGIN\r\n const handleSubmit = async (e) => {\r\n e.preventDefault()\r\n setGoogleError(null)\r\n setIsLoading(true)\r\n setError('')\r\n\r\n try {\r\n const result = await signIn('credentials', {\r\n redirect: false,\r\n email,\r\n password,\r\n captchaToken,\r\n })\r\n\r\n if (result?.error) {\r\n\r\n if (result.code === 'rate_limit_error') {\r\n\r\n setIsLoading(true)\r\n setError('Too many failed login attempts. Please try again later.') \r\n return\r\n }\r\n else if(result.code === 'invalid_credentials'){\r\n setError('Invalid email or password')\r\n setIsLoading(false)\r\n return \r\n }\r\n //Commenting out Captcha code for Development \r\n else if(!captchaStatus){\r\n setTimeout(() => {\r\n setError('Captcha Verification Failed.')\r\n }, 500) \r\n setIsLoading(false) \r\n return\r\n }\r\n //MFA TRIGGER\r\n else if (result.code === 'mfa_required') {\r\n setStep('mfa')\r\n setIsLoading(false)\r\n return\r\n }\r\n\r\n else{\r\n setError('Captcha Verification Expired. Please try again later.')\r\n setIsLoading(false)\r\n return\r\n }\r\n }\r\n\r\n // Completed Login + MFA - Redirect to the Platform's Organization Dashboard \r\n window.location.href = `/${orgName}`\r\n\r\n } catch (err) {\r\n setError('Unexpected error. Please try again.')\r\n setIsLoading(false)\r\n }\r\n }\r\n\r\n // VERIFY OTP for MFA\r\n const handleVerifyOTP = async () => {\r\n setIsLoading(true)\r\n setError('')\r\n\r\n try {\r\n const res = await fetch('/api/mfa', {\r\n method: 'POST',\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify({ email, code }),\r\n })\r\n\r\n if (!res.ok) {\r\n toast.error(tMfa('invalidCode'))\r\n setIsLoading(false)\r\n return\r\n }\r\n\r\n toast.success(tMfa('success'))\r\n\r\n // FINAL LOGIN after successful MFA process\r\n await signIn('credentials', {\r\n email,\r\n password,\r\n mfaVerified: true,\r\n redirect: true,\r\n redirectTo: `/${orgName}`,\r\n })\r\n\r\n } catch (err) {\r\n toast.error(tMfa('verificationFailed'))\r\n setIsLoading(false)\r\n }\r\n }\r\n\r\n return (\r\n <>\r\n <Toaster richColors position=\"top-right\" />\r\n {/* Header */}\r\n <div className=\"space-y-2 text-left\">\r\n <h1 className=\"font-display font-bold\" style={{\r\n fontSize: '1.75rem',\r\n lineHeight: '1.1',\r\n letterSpacing: '-0.02em',\r\n color: 'var(--hp-on-surface)',\r\n }}>\r\n {step === 'login' ? t('title') : tMfa('title')}\r\n </h1>\r\n\r\n <p style={{ color: 'var(--hp-on-surface-variant)', fontSize: '0.9rem' }}>\r\n {step === 'login'\r\n ? t('message')\r\n : tMfa('subtitle', { email })}\r\n </p>\r\n </div>\r\n\r\n {/* LOGIN FORM */}\r\n {step === 'login' && (\r\n <form onSubmit={handleSubmit} className=\"space-y-4\">\r\n\r\n <Input\r\n type=\"email\"\r\n placeholder={t('placeholder1')}\r\n value={email}\r\n onChange={(e) => setEmail(e.target.value)}\r\n disabled={isLoading}\r\n required\r\n />\r\n\r\n <div className=\"relative\">\r\n <Input\r\n type={showPassword ? 'text' : 'password'}\r\n placeholder={t('placeholder2')}\r\n value={password}\r\n onChange={(e) => setPassword(e.target.value)}\r\n disabled={isLoading}\r\n required\r\n className=\"pr-10\"\r\n />\r\n <button\r\n type=\"button\"\r\n onClick={() => setShowPassword(v => !v)}\r\n tabIndex={-1}\r\n className=\"absolute right-3 top-1/2 -translate-y-1/2 transition-colors\"\r\n style={{ color: 'var(--hp-on-surface-variant)' }}\r\n >\r\n {showPassword ? <LR.EyeOff size={16}/> : <LR.Eye size={16}/>}\r\n </button>\r\n </div>\r\n {/* Google Not Linked Error */}\r\n {googleError === 'google_not_linked' && (\r\n   <div className=\"auth-pw-error\">\r\n     Your Google account is not linked. Please sign in with your credentials first, then link your Google account.\r\n   </div>\r\n )}\r\n {error && <div className=\"auth-pw-error\">{error}</div>}\r\n\r\n <div className=\"flex items-center gap-2\">\r\n <Button type=\"submit\" disabled={isLoading} className=\"flex-1 auth-btn-primary\">\r\n {isLoading ? <LoadingSpinner /> : 'Login'}\r\n </Button>\r\n\r\n {/* Google button */} \r\n { <button \r\n type=\"button\"\r\n onClick={() => signIn('google', { redirectTo: `/${orgName}` })}\r\n disabled={isLoading}\r\n aria-label=\"Sign in with Google\"\r\n className=\"auth-google-btn\"\r\n >\r\n <GoogleIcon size={20} />\r\n </button> } \r\n \r\n </div>\r\n\r\n {/* Reset Password link */}\r\n <div className=\"flex justify-start\">\r\n <a\r\n href={`/${orgName}/auth/reset-password`}\r\n className=\"text-sm underline underline-offset-4 hover:opacity-80\"\r\n style={{ color: 'var(--hp-primary)' }}\r\n >\r\n Reset your password\r\n </a>\r\n </div>\r\n </form>\r\n )}\r\n\r\n {/* MFA FORM */}\r\n {step === 'mfa' && (\r\n <div className=\"space-y-4\">\r\n\r\n <Input\r\n placeholder={tMfa('placeholder')}\r\n value={code}\r\n onChange={(e) => setCode(e.target.value)}\r\n disabled={isLoading}\r\n />\r\n\r\n <Button onClick={handleVerifyOTP} disabled={isLoading} className=\"w-full\">\r\n {isLoading ? <LoadingSpinner /> : tMfa('verify')}\r\n </Button>\r\n\r\n {/* Back */}\r\n <Button\r\n variant=\"outline\"\r\n onClick={() => setStep('login')}\r\n className=\"w-full mt-3 inline-flex items-center justify-center gap-2\"\r\n >\r\n <LR.ArrowLeft size={16} />\r\n {tMfa('backToLogin')}\r\n </Button>\r\n </div>\r\n )}\r\n\r\n {/* CAPTCHA only on Credentials(Username and Password) Provider login */}\r\n {step === 'login' && (\r\n <div className=\"auth-captcha-wrapper\">\r\n <ReCAPTCHA\r\n key={`recaptcha-${authTheme}`}\r\n sitekey={reCaptchaSiteKey}\r\n onChange={onReCaptchaSuccess}\r\n theme={authTheme}\r\n />\r\n </div> \r\n )}\r\n </>\r\n )\r\n}\r\n\r\nexport function SignIn() {\r\n return (\r\n <AuthPage>\r\n <SignInContent />\r\n </AuthPage>\r\n )\r\n}\r\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../../src/core/components/authentication/Signin.tsx"],"sourcesContent":["'use client'\r\n\r\nimport * as React from 'react'\r\nimport { signIn } from 'next-auth/react'\r\nimport { useTranslations } from 'next-intl'\r\nimport * as LR from 'lucide-react'\r\nimport { toast, Toaster } from 'sonner'\r\n// Direct file imports (not the ui barrel). The ui barrel\r\n// `src/core/components/ui/index.ts` re-exports dozens of components plus\r\n// `useFileUploadHandler` and `InfoSidebar`, the latter of which transitively\r\n// pulls in @thatopen and the BIM viewer tree. Importing through the barrel\r\n// would drag all of that into the auth pages; direct file imports avoid it.\r\nimport { Button } from '../ui/Button'\r\nimport { GoogleIcon } from '../ui/Icons/GoogleIcon'\r\nimport { Input } from '../ui/Input'\r\nimport { LoadingSpinner } from '../ui/LoadingSpinner'\r\nimport { AuthPage, useAuthTheme } from './AuthPage'\r\nimport { useParams, useSearchParams } from 'next/navigation'\r\nimport ReCAPTCHA from 'react-google-recaptcha'\r\n\r\nfunction SignInContent() {\r\n const [step, setStep] = React.useState<'login' | 'mfa'>('login')\r\n\r\n const [email, setEmail] = React.useState('')\r\n const [password, setPassword] = React.useState('')\r\n const [code, setCode] = React.useState('')\r\n\r\n const [captchaStatus, setCaptchaStatus] = React.useState(false)\r\n const [captchaToken, setCaptchaToken] = React.useState('')\r\n const [isLoading, setIsLoading] = React.useState(false)\r\n const [error, setError] = React.useState('')\r\n const [showPassword, setShowPassword] = React.useState(false)\r\n\r\n const searchParams = useSearchParams()\r\n const params = useParams()\r\n const orgName = params.instance ?? 'canada'\r\n const [googleError, setGoogleError] = React.useState(\r\n searchParams.get('error')\r\n )\r\n //Clean up the Google query error in URL, and have it only return /orgName/signin to clear frontend error responses \r\n React.useEffect(() => {\r\n if (googleError) {\r\n window.history.replaceState({}, '', `/${orgName}/signin`)\r\n }\r\n }, [googleError, orgName])\r\n\r\n const t = useTranslations('Signin')\r\n const tMfa = useTranslations('MFA')\r\n const authTheme = useAuthTheme()\r\n\r\n const reCaptchaSiteKey = `${process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY}`\r\n\r\n const onReCaptchaSuccess = (token) => {\r\n setCaptchaToken(token)\r\n setCaptchaStatus(true)\r\n }\r\n\r\n // Initial LOGIN\r\n const handleSubmit = async (e) => {\r\n e.preventDefault()\r\n setGoogleError(null)\r\n setIsLoading(true)\r\n setError('')\r\n\r\n try {\r\n const result = await signIn('credentials', {\r\n redirect: false,\r\n email,\r\n password,\r\n captchaToken,\r\n })\r\n\r\n if (result?.error) {\r\n\r\n if (result.code === 'rate_limit_error') {\r\n\r\n setIsLoading(true)\r\n setError('Too many failed login attempts. Please try again later.')\r\n return\r\n }\r\n else if (result.code === 'invalid_credentials') {\r\n setError('Invalid email or password')\r\n setIsLoading(false)\r\n return\r\n }\r\n //Commenting out Captcha code for Development \r\n else if (!captchaStatus) {\r\n setTimeout(() => {\r\n setError('Captcha Verification Failed.')\r\n }, 500)\r\n setIsLoading(false)\r\n return\r\n }\r\n //MFA TRIGGER\r\n else if (result.code === 'mfa_required') {\r\n setStep('mfa')\r\n setIsLoading(false)\r\n return\r\n }\r\n\r\n else {\r\n setError('Captcha Verification Expired. Please try again later.')\r\n setIsLoading(false)\r\n return\r\n }\r\n }\r\n\r\n // Completed Login + MFA - Redirect to the Platform's Organization Dashboard \r\n window.location.href = `/${orgName}`\r\n\r\n } catch (err) {\r\n setError('Unexpected error. Please try again.')\r\n setIsLoading(false)\r\n }\r\n }\r\n\r\n // VERIFY OTP for MFA\r\n const handleVerifyOTP = async () => {\r\n setIsLoading(true)\r\n setError('')\r\n\r\n try {\r\n const res = await fetch('/api/mfa', {\r\n method: 'POST',\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify({ email, code }),\r\n })\r\n\r\n if (!res.ok) {\r\n toast.error(tMfa('invalidCode'))\r\n setIsLoading(false)\r\n return\r\n }\r\n\r\n toast.success(tMfa('success'))\r\n\r\n // FINAL LOGIN after successful MFA process\r\n await signIn('credentials', {\r\n email,\r\n password,\r\n mfaVerified: true,\r\n redirect: true,\r\n redirectTo: `/${orgName}`,\r\n })\r\n\r\n } catch (err) {\r\n toast.error(tMfa('verificationFailed'))\r\n setIsLoading(false)\r\n }\r\n }\r\n\r\n return (\r\n <>\r\n <Toaster richColors position=\"top-right\" />\r\n {/* Header */}\r\n <div className=\"space-y-2 text-left\">\r\n <h1 className=\"font-display font-bold\" style={{\r\n fontSize: '1.75rem',\r\n lineHeight: '1.1',\r\n letterSpacing: '-0.02em',\r\n color: 'var(--hp-on-surface)',\r\n }}>\r\n {step === 'login' ? t('title') : tMfa('title')}\r\n </h1>\r\n\r\n <p style={{ color: 'var(--hp-on-surface-variant)', fontSize: '0.9rem' }}>\r\n {step === 'login'\r\n ? t('message')\r\n : tMfa('subtitle', { email })}\r\n </p>\r\n </div>\r\n\r\n {/* LOGIN FORM */}\r\n {step === 'login' && (\r\n <form onSubmit={handleSubmit} className=\"space-y-4\">\r\n\r\n <Input\r\n type=\"email\"\r\n placeholder={t('placeholder1')}\r\n value={email}\r\n onChange={(e) => setEmail(e.target.value)}\r\n disabled={isLoading}\r\n required\r\n />\r\n\r\n <div className=\"relative\">\r\n <Input\r\n type={showPassword ? 'text' : 'password'}\r\n placeholder={t('placeholder2')}\r\n value={password}\r\n onChange={(e) => setPassword(e.target.value)}\r\n disabled={isLoading}\r\n required\r\n className=\"pr-10\"\r\n />\r\n <button\r\n type=\"button\"\r\n onClick={() => setShowPassword(v => !v)}\r\n tabIndex={-1}\r\n className=\"absolute right-3 top-1/2 -translate-y-1/2 transition-colors\"\r\n style={{ color: 'var(--hp-on-surface-variant)' }}\r\n >\r\n {showPassword ? <LR.EyeOff size={16} /> : <LR.Eye size={16} />}\r\n </button>\r\n </div>\r\n {/* Google Not Linked Error */}\r\n {googleError === 'google_not_linked' && (\r\n <div className=\"auth-pw-error\">\r\n Your Google account is not linked. Please sign in with your credentials first, then link your Google account.\r\n </div>\r\n )}\r\n {error && <div className=\"auth-pw-error\">{error}</div>}\r\n\r\n <div className=\"flex items-center gap-2\">\r\n <Button type=\"submit\" disabled={isLoading} className=\"flex-1 auth-btn-primary\">\r\n {isLoading ? <LoadingSpinner /> : 'Login'}\r\n </Button>\r\n\r\n {/* Google button */}\r\n {<button\r\n type=\"button\"\r\n onClick={() => signIn('google', { redirectTo: `/${orgName}` })}\r\n disabled={isLoading}\r\n aria-label=\"Sign in with Google\"\r\n className=\"auth-google-btn\"\r\n >\r\n <GoogleIcon size={20} />\r\n </button>}\r\n\r\n </div>\r\n\r\n {/* Reset Password link */}\r\n {/* <div className=\"flex justify-start\">\r\n <a\r\n href={`/${orgName}/auth/reset-password`}\r\n className=\"text-sm underline underline-offset-4 hover:opacity-80\"\r\n style={{ color: 'var(--hp-primary)' }}\r\n >\r\n Reset your password\r\n </a>\r\n </div> */}\r\n </form>\r\n )}\r\n\r\n {/* MFA FORM */}\r\n {step === 'mfa' && (\r\n <div className=\"space-y-4\">\r\n\r\n <Input\r\n placeholder={tMfa('placeholder')}\r\n value={code}\r\n onChange={(e) => setCode(e.target.value)}\r\n disabled={isLoading}\r\n />\r\n\r\n <Button onClick={handleVerifyOTP} disabled={isLoading} className=\"w-full\">\r\n {isLoading ? <LoadingSpinner /> : tMfa('verify')}\r\n </Button>\r\n\r\n {/* Back */}\r\n <Button\r\n variant=\"outline\"\r\n onClick={() => setStep('login')}\r\n className=\"w-full mt-3 inline-flex items-center justify-center gap-2\"\r\n >\r\n <LR.ArrowLeft size={16} />\r\n {tMfa('backToLogin')}\r\n </Button>\r\n </div>\r\n )}\r\n\r\n {/* CAPTCHA only on Credentials(Username and Password) Provider login */}\r\n {step === 'login' && (\r\n <div className=\"auth-captcha-wrapper\">\r\n <ReCAPTCHA\r\n key={`recaptcha-${authTheme}`}\r\n sitekey={reCaptchaSiteKey}\r\n onChange={onReCaptchaSuccess}\r\n theme={authTheme}\r\n />\r\n </div>\r\n )}\r\n </>\r\n )\r\n}\r\n\r\nexport function SignIn() {\r\n return (\r\n <AuthPage>\r\n <SignInContent />\r\n </AuthPage>\r\n )\r\n}\r\n"],"mappings":"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","names":[]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../src/core/components/ui/FilesManager/src/convertIfcToFragmentsFile.ts"],"sourcesContent":["import * as OBC from '@thatopen/components'\nimport { IfcToFragments } from '../../../viewers/bim/src/IfcToFragments'\n\n/**\n * Convert an IFC file to a fragments .frag file. The returned File is a\n * `(baseName)(ifc).frag` blob ready to upload via `performUploadFile`.\n *\n * Lazy: this module brings in `@thatopen/components`, `@thatopen/fragments`,\n * and the `web-ifc` WASM bootstrap, so it is intentionally not imported\n * statically by any UI code.\n */\nexport async function convertIfcToFragmentsFile(file: File): Promise<File> {\n const bimComponents = new OBC.Components()\n const converter = bimComponents.get(IfcToFragments)\n const fragmentBytes = await converter.loadFromFile(file)\n\n // Convert Uint8Array to ArrayBuffer slice to satisfy Blob typing.\n const arrayBuffer = (fragmentBytes.buffer as ArrayBuffer).slice(\n fragmentBytes.byteOffset,\n fragmentBytes.byteOffset + fragmentBytes.byteLength,\n )\n const fragBlob = new Blob([arrayBuffer], { type: 'application/octet-stream' })\n const baseName = file.name.replace(/\\.[^.]+$/, '')\n const fragName = `${baseName}(ifc).frag`\n return new File([fragBlob], fragName, { type: 'application/octet-stream' })\n}\n"],"mappings":"AAAA,YAAY,SAAS;AACrB,SAAS,sBAAsB;AAU/B,eAAsB,0BAA0B,MAA2B;AACzE,QAAM,gBAAgB,IAAI,IAAI,WAAW;AACzC,QAAM,YAAY,cAAc,IAAI,cAAc;AAClD,QAAM,gBAAgB,MAAM,UAAU,aAAa,IAAI;AAGvD,QAAM,cAAe,cAAc,OAAuB;AAAA,IACxD,cAAc;AAAA,IACd,cAAc,aAAa,cAAc;AAAA,EAC3C;AACA,QAAM,WAAW,IAAI,KAAK,CAAC,WAAW,GAAG,EAAE,MAAM,2BAA2B,CAAC;AAC7E,QAAM,WAAW,KAAK,KAAK,QAAQ,YAAY,EAAE;AACjD,QAAM,WAAW,GAAG,QAAQ;AAC5B,SAAO,IAAI,KAAK,CAAC,QAAQ,GAAG,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAC5E;","names":[]}
1
+ {"version":3,"sources":["../../../../../../src/core/components/ui/FilesManager/src/convertIfcToFragmentsFile.ts"],"sourcesContent":["import * as OBC from '@thatopen/components'\r\nimport { IfcToFragments } from '../../../viewers/bim/src/IfcToFragments'\r\n\r\n/**\r\n * Convert an IFC file to a fragments .frag file. The returned File is a\r\n * `(baseName)(ifc).frag` blob ready to upload via `performUploadFile`.\r\n *\r\n * Lazy: this module brings in `@thatopen/components`, `@thatopen/fragments`,\r\n * and the `web-ifc` WASM bootstrap, so it is intentionally not imported\r\n * statically by any UI code.\r\n */\r\nexport async function convertIfcToFragmentsFile(file: File): Promise<File> {\r\n const bimComponents = new OBC.Components()\r\n const converter = bimComponents.get(IfcToFragments)\r\n const fragmentBytes = await converter.loadFromFile(file)\r\n\r\n // Convert Uint8Array to ArrayBuffer slice to satisfy Blob typing.\r\n const arrayBuffer = (fragmentBytes.buffer as ArrayBuffer).slice(\r\n fragmentBytes.byteOffset,\r\n fragmentBytes.byteOffset + fragmentBytes.byteLength,\r\n )\r\n const fragBlob = new Blob([arrayBuffer], { type: 'application/octet-stream' })\r\n const baseName = file.name.replace(/\\.[^.]+$/, '')\r\n const fragName = `${baseName}(ifc).frag`\r\n return new File([fragBlob], fragName, { type: 'application/octet-stream' })\r\n}\r\n"],"mappings":"AAAA,YAAY,SAAS;AACrB,SAAS,sBAAsB;AAU/B,eAAsB,0BAA0B,MAA2B;AACzE,QAAM,gBAAgB,IAAI,IAAI,WAAW;AACzC,QAAM,YAAY,cAAc,IAAI,cAAc;AAClD,QAAM,gBAAgB,MAAM,UAAU,aAAa,IAAI;AAGvD,QAAM,cAAe,cAAc,OAAuB;AAAA,IACxD,cAAc;AAAA,IACd,cAAc,aAAa,cAAc;AAAA,EAC3C;AACA,QAAM,WAAW,IAAI,KAAK,CAAC,WAAW,GAAG,EAAE,MAAM,2BAA2B,CAAC;AAC7E,QAAM,WAAW,KAAK,KAAK,QAAQ,YAAY,EAAE;AACjD,QAAM,WAAW,GAAG,QAAQ;AAC5B,SAAO,IAAI,KAAK,CAAC,QAAQ,GAAG,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAC5E;","names":[]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../src/core/components/viewers/bim/BimToolbar.tsx"],"sourcesContent":["'use client'\n\nimport * as React from 'react'\nimport { bimToolbarTools } from './src/tools/bimToolbar'\nimport { ToolbarBody } from '../../ToolbarBody'\n\nexport function BimToolbar() {\n return <ToolbarBody viewer=\"bim\" tools={bimToolbarTools()} />\n}\n"],"mappings":";AAOS;AAJT,SAAS,uBAAuB;AAChC,SAAS,mBAAmB;AAErB,SAAS,aAAa;AAC3B,SAAO,oBAAC,eAAY,QAAO,OAAM,OAAO,gBAAgB,GAAG;AAC7D;","names":[]}
1
+ {"version":3,"sources":["../../../../../src/core/components/viewers/bim/BimToolbar.tsx"],"sourcesContent":["'use client'\r\n\r\nimport * as React from 'react'\r\nimport { bimToolbarTools } from './src/tools/bimToolbar'\r\nimport { ToolbarBody } from '../../ToolbarBody'\r\n\r\nexport function BimToolbar() {\r\n return <ToolbarBody viewer=\"bim\" tools={bimToolbarTools()} />\r\n}\r\n"],"mappings":";AAOS;AAJT,SAAS,uBAAuB;AAChC,SAAS,mBAAmB;AAErB,SAAS,aAAa;AAC3B,SAAO,oBAAC,eAAY,QAAO,OAAM,OAAO,gBAAgB,GAAG;AAC7D;","names":[]}
@@ -1,5 +1,4 @@
1
1
  import { Feature } from 'geojson';
2
- import { MarkerManager } from './MarkerManager';
3
2
  export declare const getMunicipalityAndProvince: (latitude: string, longitude: string, countryCode?: string) => Promise<{
4
3
  municipality: any;
5
4
  countrySubdivision: any;
@@ -65,8 +64,4 @@ export declare const buildingToFeature: (building: any) => {
65
64
  };
66
65
  is_database_building: boolean;
67
66
  };
68
- export declare const createGeocoderMarker: (coordinates: [number, number], map: any, options?: {
69
- editing?: boolean;
70
- }) => MarkerManager;
71
- export declare const removeGeocoderMarker: () => void;
72
67
  //# sourceMappingURL=geocoder.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"geocoder.d.ts","sourceRoot":"","sources":["../../../../../../src/core/components/viewers/map/utils/geocoder.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAS,MAAM,SAAS,CAAA;AACxC,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAe/C,eAAO,MAAM,0BAA0B,GAAU,UAAU,MAAM,EAAE,WAAW,MAAM,EAAE,cAAc,MAAM;;;EAmBzG,CAAA;AAGD,eAAO,MAAM,gBAAgB,GAAU,OAAO,MAAM,EAAE,cAAc,MAAM;;KAYzE,CAAA;AAGD,eAAO,MAAM,aAAa,GAAI,SAAS,OAAO;;;;CAoC7C,CAAA;AAGD,eAAO,MAAM,oBAAoB,GAAI,SAAS,OAAO,EAAE,KAAK,GAAG,EAAE,MAAM,MAAM,EAAE,UAAU;IAAE,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqF7G,CAAA;AAGD,eAAO,MAAM,kBAAkB,GAAU,aAAa,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,cAAc,MAAM;;GAc3F,CAAA;AAGD,eAAO,MAAM,iBAAiB,GAAI,aAAQ;;;;;;;;;;;;;;;CAgCzC,CAAA;AAGD,eAAO,MAAM,oBAAoB,GAAI,aAAa,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,KAAK,GAAG,EAAE,UAAU;IAAE,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,kBAK5G,CAAA;AAGD,eAAO,MAAM,oBAAoB,QAAO,IAGvC,CAAA"}
1
+ {"version":3,"file":"geocoder.d.ts","sourceRoot":"","sources":["../../../../../../src/core/components/viewers/map/utils/geocoder.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAS,MAAM,SAAS,CAAA;AAgBxC,eAAO,MAAM,0BAA0B,GAAU,UAAU,MAAM,EAAE,WAAW,MAAM,EAAE,cAAc,MAAM;;;EAmBzG,CAAA;AAGD,eAAO,MAAM,gBAAgB,GAAU,OAAO,MAAM,EAAE,cAAc,MAAM;;KAYzE,CAAA;AAGD,eAAO,MAAM,aAAa,GAAI,SAAS,OAAO;;;;CAoC7C,CAAA;AAGD,eAAO,MAAM,oBAAoB,GAAI,SAAS,OAAO,EAAE,KAAK,GAAG,EAAE,MAAM,MAAM,EAAE,UAAU;IAAE,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqF7G,CAAA;AAGD,eAAO,MAAM,kBAAkB,GAAU,aAAa,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,cAAc,MAAM;;GAc3F,CAAA;AAGD,eAAO,MAAM,iBAAiB,GAAI,aAAQ;;;;;;;;;;;;;;;CAgCzC,CAAA"}
@@ -77,7 +77,6 @@ const handleLocationSelect = (feature, map, type, options) => {
77
77
  is_database_building: true
78
78
  };
79
79
  if (map && center2) {
80
- markerManager.create(center2, map);
81
80
  map.flyTo({
82
81
  center: addressData2.coordinates,
83
82
  zoom: 18,
@@ -122,7 +121,6 @@ const handleLocationSelect = (feature, map, type, options) => {
122
121
  // How well the result matches the query
123
122
  };
124
123
  if (map && center) {
125
- markerManager.create(center, map, { editing });
126
124
  if (feature.bbox) {
127
125
  map.fitBounds(feature.bbox, { speed: 2 });
128
126
  } else {
@@ -175,24 +173,12 @@ const buildingToFeature = (building) => {
175
173
  is_database_building: true
176
174
  };
177
175
  };
178
- const createGeocoderMarker = (coordinates, map, options) => {
179
- const editing = (options == null ? void 0 : options.editing) || false;
180
- const markerManager = getGeocoderMarkerManager();
181
- markerManager.create(coordinates, map, { editing });
182
- return markerManager;
183
- };
184
- const removeGeocoderMarker = () => {
185
- const markerManager = getGeocoderMarkerManager();
186
- markerManager.remove();
187
- };
188
176
  export {
189
177
  buildingToFeature,
190
- createGeocoderMarker,
191
178
  fetchSuggestions,
192
179
  getDetailedAddress,
193
180
  getMunicipalityAndProvince,
194
181
  handleLocationSelect,
195
- parseLocation,
196
- removeGeocoderMarker
182
+ parseLocation
197
183
  };
198
184
  //# sourceMappingURL=geocoder.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../src/core/components/viewers/map/utils/geocoder.ts"],"sourcesContent":["import { Feature, Point } from 'geojson'\r\nimport { MarkerManager } from './MarkerManager'\r\nimport { autocompleteGeocode, reverseGeocode } from './geocoding'\r\n\r\n// Global marker manager instance for geocoder\r\nlet geocoderMarkerManager: MarkerManager | null = null\r\n\r\n// Helper function to get or create the marker manager\r\nconst getGeocoderMarkerManager = (): MarkerManager => {\r\n if (!geocoderMarkerManager) {\r\n geocoderMarkerManager = new MarkerManager()\r\n }\r\n return geocoderMarkerManager\r\n}\r\n\r\n// Convert longitude and latitude into Municipality and Country Subdivision\r\nexport const getMunicipalityAndProvince = async (latitude: string, longitude: string, countryCode?: string) => {\r\n try {\r\n const features = await reverseGeocode(latitude, longitude, countryCode, { size: 1, coarse: true })\r\n\r\n if (features.length > 0) {\r\n const properties = features[0].properties as any\r\n\r\n const municipality = properties.locality || properties.neighbourhood || properties.county || ''\r\n const countrySubdivision = properties.region_a || properties.region || '' // region_a gives abbreviation like \"ON\", \"BC\"\r\n\r\n return { municipality, countrySubdivision }\r\n }\r\n\r\n return { municipality: '', countrySubdivision: '' }\r\n }\r\n catch (error) {\r\n console.error('Error fetching municipality and country subdivision:', error)\r\n return { municipality: '', countrySubdivision: '' }\r\n }\r\n}\r\n\r\n// Fetch address suggestions from the active autocomplete provider\r\nexport const fetchSuggestions = async (input: string, countryCode?: string) => {\r\n if (!input || input.length < 3) {\r\n return []\r\n }\r\n\r\n try {\r\n return await autocompleteGeocode(input, countryCode, 5)\r\n }\r\n catch (error) {\r\n console.error('Error fetching address suggestions:', error)\r\n return []\r\n }\r\n}\r\n\r\n// Parse location details for better display\r\nexport const parseLocation = (feature: Feature) => {\r\n const locationName = feature.properties?.name || ''\r\n const fullLabel = feature.properties?.label || ''\r\n\r\n // Try to extract location details from properties\r\n const properties = feature.properties\r\n let regionDetails = ''\r\n\r\n if (properties) {\r\n const locality = properties.locality\r\n const region = properties.region // Full countrySubdivision name\r\n const region_a = properties.region_a // CountrySubdivision abbreviation (ON, BC, etc.)\r\n const country = properties.country\r\n const country_a = properties.country_a // Country abbreviation (CA)\r\n\r\n // Build region details similar to your original format\r\n if (locality && region_a && country_a) {\r\n regionDetails = `${locality}, ${region_a}, ${country_a}`\r\n }\r\n else if (locality && region && country) {\r\n regionDetails = `${locality}, ${region}, ${country}`\r\n }\r\n else {\r\n // Fallback: try to extract from the full label\r\n const parts = fullLabel.split(', ')\r\n if (parts.length > 1) {\r\n regionDetails = parts.slice(1, 3).join(', ')\r\n }\r\n }\r\n }\r\n\r\n return {\r\n name: locationName,\r\n region: regionDetails,\r\n fullLabel: fullLabel,\r\n }\r\n}\r\n\r\n// Handle suggestion selection and map movement\r\nexport const handleLocationSelect = (feature: Feature, map: any, type: string, options?: { editing?: boolean }) => {\r\n const editing = options?.editing || false\r\n const markerManager = getGeocoderMarkerManager()\r\n\r\n // Handle feature with is_database_building flag\r\n if (type === 'dbBuilding') {\r\n const geometry = feature.geometry as Point\r\n const center = geometry.coordinates\r\n const addressData = {\r\n formatted_address: feature.properties?.label || '',\r\n coordinates: center,\r\n properties: feature.properties,\r\n building_id: feature.properties.gid?.replace('building-', ''),\r\n is_database_building: true,\r\n }\r\n\r\n if (map && center) {\r\n // Add marker to the center with editing capability\r\n markerManager.create(center as [number, number], map)\r\n\r\n map.flyTo({\r\n center: addressData.coordinates,\r\n zoom: 18,\r\n speed: 1.5,\r\n })\r\n }\r\n\r\n return addressData\r\n }\r\n\r\n // Calculate center coordinates\r\n let center\r\n\r\n // Use bbox if available (more precise for areas)\r\n if (feature.bbox) {\r\n center = [\r\n feature.bbox[0] + (feature.bbox[2] - feature.bbox[0]) / 2,\r\n feature.bbox[1] + (feature.bbox[3] - feature.bbox[1]) / 2,\r\n ]\r\n }\r\n // Otherwise extract coordinates based on geometry type\r\n else if (feature.geometry?.type === 'Point') {\r\n center = (feature.geometry as any).coordinates\r\n }\r\n\r\n const addressData = {\r\n formatted_address: feature.properties?.label || feature.properties?.name || '',\r\n coordinates: center,\r\n properties: feature.properties,\r\n address_details: {\r\n name: feature.properties?.name,\r\n label: feature.properties?.label,\r\n locality: feature.properties?.locality,\r\n neighbourhood: feature.properties?.neighbourhood,\r\n region: feature.properties?.region,\r\n region_a: feature.properties?.region_a,\r\n country: feature.properties?.country,\r\n country_a: feature.properties?.country_a,\r\n postalcode: feature.properties?.postalcode,\r\n housenumber: feature.properties?.housenumber,\r\n street: feature.properties?.street,\r\n },\r\n geocode_earth_id: feature.properties?.gid, // Geocode Earth's unique id\r\n layer: feature.properties?.layer, // Type of place (address, venue, locality, etc.)\r\n confidence: feature.properties?.confidence, // Geocode Earth's confidence score\r\n match_type: feature.properties?.match_type, // How well the result matches the query\r\n }\r\n\r\n if (map && center) {\r\n // Add marker to the center with editing capability\r\n markerManager.create(center as [number, number], map, { editing })\r\n\r\n if (feature.bbox) {\r\n map.fitBounds(feature.bbox, { speed: 2 })\r\n }\r\n else {\r\n map.flyTo({\r\n center: addressData.coordinates,\r\n zoom: 14, // Might be good idea to set zoom level depending on layer / type of place\r\n speed: 2,\r\n })\r\n }\r\n }\r\n\r\n return addressData\r\n}\r\n\r\n// Util function to get detailed address information\r\nexport const getDetailedAddress = async (coordinates: [number, number], countryCode?: string) => {\r\n const [longitude, latitude] = coordinates\r\n\r\n try {\r\n // Restrict to address-layer results (the original popover used layers=address) so\r\n // callers get a street address rather than the nearest venue/POI. Nominatim's zoom-18\r\n // reverse is already building-level, so this only refines the Pelias path.\r\n const features = await reverseGeocode(latitude.toString(), longitude.toString(), countryCode, { size: 1, layers: 'address' })\r\n return features.length > 0 ? features[0] : null\r\n }\r\n catch (error) {\r\n console.error('Error fetching detailed address:', error)\r\n return null\r\n }\r\n}\r\n\r\n// Convert a building object to a GeoJSON feature format compatible with handleLocationSelect\r\nexport const buildingToFeature = (building) => {\r\n const name = building.buildingName || ''\r\n\r\n // Validate coordinates - use the correct property names\r\n const longitude = Number.parseFloat(building.buildingLongitude)\r\n const latitude = Number.parseFloat(building.buildingLatitude)\r\n\r\n if (isNaN(longitude) || isNaN(latitude)) {\r\n console.error('Invalid building coordinates:', {\r\n longitude: building.buildingLongitude,\r\n latitude: building.buildingLatitude,\r\n buildingId: building.id,\r\n })\r\n return null\r\n }\r\n\r\n return {\r\n properties: {\r\n name: name,\r\n label: `${name}, ${[building.buildingAddress, building.buildingMunicipality, building.buildingCountrySubdivision].filter(Boolean).join(', ')}`,\r\n address: building.buildingAddress,\r\n municipality: building.buildingMunicipality,\r\n countrySubdivision: building.buildingCountrySubdivision,\r\n postalCode: building.buildingPostalCode,\r\n gid: `building-${building.id}`,\r\n },\r\n geometry: {\r\n type: 'Point',\r\n coordinates: [longitude, latitude],\r\n },\r\n is_database_building: true,\r\n }\r\n}\r\n\r\n// Enhanced marker creation with editing capabilities\r\nexport const createGeocoderMarker = (coordinates: [number, number], map: any, options?: { editing?: boolean }) => {\r\n const editing = options?.editing || false\r\n const markerManager = getGeocoderMarkerManager()\r\n markerManager.create(coordinates, map, { editing })\r\n return markerManager\r\n}\r\n\r\n// Helper function to remove the current marker\r\nexport const removeGeocoderMarker = (): void => {\r\n const markerManager = getGeocoderMarkerManager()\r\n markerManager.remove()\r\n}\r\n"],"mappings":"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","names":["center","addressData"]}
1
+ {"version":3,"sources":["../../../../../../src/core/components/viewers/map/utils/geocoder.ts"],"sourcesContent":["import { Feature, Point } from 'geojson'\r\nimport { MarkerManager } from './MarkerManager'\r\nimport { autocompleteGeocode, reverseGeocode } from './geocoding'\r\n\r\n// Global marker manager instance for geocoder\r\nlet geocoderMarkerManager: MarkerManager | null = null\r\n\r\n// Helper function to get or create the marker manager\r\nconst getGeocoderMarkerManager = (): MarkerManager => {\r\n if (!geocoderMarkerManager) {\r\n geocoderMarkerManager = new MarkerManager()\r\n }\r\n return geocoderMarkerManager\r\n}\r\n\r\n// Convert longitude and latitude into Municipality and Country Subdivision\r\nexport const getMunicipalityAndProvince = async (latitude: string, longitude: string, countryCode?: string) => {\r\n try {\r\n const features = await reverseGeocode(latitude, longitude, countryCode, { size: 1, coarse: true })\r\n\r\n if (features.length > 0) {\r\n const properties = features[0].properties as any\r\n\r\n const municipality = properties.locality || properties.neighbourhood || properties.county || ''\r\n const countrySubdivision = properties.region_a || properties.region || '' // region_a gives abbreviation like \"ON\", \"BC\"\r\n\r\n return { municipality, countrySubdivision }\r\n }\r\n\r\n return { municipality: '', countrySubdivision: '' }\r\n }\r\n catch (error) {\r\n console.error('Error fetching municipality and country subdivision:', error)\r\n return { municipality: '', countrySubdivision: '' }\r\n }\r\n}\r\n\r\n// Fetch address suggestions from the active autocomplete provider\r\nexport const fetchSuggestions = async (input: string, countryCode?: string) => {\r\n if (!input || input.length < 3) {\r\n return []\r\n }\r\n\r\n try {\r\n return await autocompleteGeocode(input, countryCode, 5)\r\n }\r\n catch (error) {\r\n console.error('Error fetching address suggestions:', error)\r\n return []\r\n }\r\n}\r\n\r\n// Parse location details for better display\r\nexport const parseLocation = (feature: Feature) => {\r\n const locationName = feature.properties?.name || ''\r\n const fullLabel = feature.properties?.label || ''\r\n\r\n // Try to extract location details from properties\r\n const properties = feature.properties\r\n let regionDetails = ''\r\n\r\n if (properties) {\r\n const locality = properties.locality\r\n const region = properties.region // Full countrySubdivision name\r\n const region_a = properties.region_a // CountrySubdivision abbreviation (ON, BC, etc.)\r\n const country = properties.country\r\n const country_a = properties.country_a // Country abbreviation (CA)\r\n\r\n // Build region details similar to your original format\r\n if (locality && region_a && country_a) {\r\n regionDetails = `${locality}, ${region_a}, ${country_a}`\r\n }\r\n else if (locality && region && country) {\r\n regionDetails = `${locality}, ${region}, ${country}`\r\n }\r\n else {\r\n // Fallback: try to extract from the full label\r\n const parts = fullLabel.split(', ')\r\n if (parts.length > 1) {\r\n regionDetails = parts.slice(1, 3).join(', ')\r\n }\r\n }\r\n }\r\n\r\n return {\r\n name: locationName,\r\n region: regionDetails,\r\n fullLabel: fullLabel,\r\n }\r\n}\r\n\r\n// Handle suggestion selection and map movement\r\nexport const handleLocationSelect = (feature: Feature, map: any, type: string, options?: { editing?: boolean }) => {\r\n const editing = options?.editing || false\r\n const markerManager = getGeocoderMarkerManager()\r\n\r\n // Handle feature with is_database_building flag\r\n if (type === 'dbBuilding') {\r\n const geometry = feature.geometry as Point\r\n const center = geometry.coordinates\r\n const addressData = {\r\n formatted_address: feature.properties?.label || '',\r\n coordinates: center,\r\n properties: feature.properties,\r\n building_id: feature.properties.gid?.replace('building-', ''),\r\n is_database_building: true,\r\n }\r\n\r\n if (map && center) {\r\n // Add marker to the center with editing capability\r\n // markerManager.create(center as [number, number], map)\r\n\r\n map.flyTo({\r\n center: addressData.coordinates,\r\n zoom: 18,\r\n speed: 1.5,\r\n })\r\n }\r\n\r\n return addressData\r\n }\r\n\r\n // Calculate center coordinates\r\n let center\r\n\r\n // Use bbox if available (more precise for areas)\r\n if (feature.bbox) {\r\n center = [\r\n feature.bbox[0] + (feature.bbox[2] - feature.bbox[0]) / 2,\r\n feature.bbox[1] + (feature.bbox[3] - feature.bbox[1]) / 2,\r\n ]\r\n }\r\n // Otherwise extract coordinates based on geometry type\r\n else if (feature.geometry?.type === 'Point') {\r\n center = (feature.geometry as any).coordinates\r\n }\r\n\r\n const addressData = {\r\n formatted_address: feature.properties?.label || feature.properties?.name || '',\r\n coordinates: center,\r\n properties: feature.properties,\r\n address_details: {\r\n name: feature.properties?.name,\r\n label: feature.properties?.label,\r\n locality: feature.properties?.locality,\r\n neighbourhood: feature.properties?.neighbourhood,\r\n region: feature.properties?.region,\r\n region_a: feature.properties?.region_a,\r\n country: feature.properties?.country,\r\n country_a: feature.properties?.country_a,\r\n postalcode: feature.properties?.postalcode,\r\n housenumber: feature.properties?.housenumber,\r\n street: feature.properties?.street,\r\n },\r\n geocode_earth_id: feature.properties?.gid, // Geocode Earth's unique id\r\n layer: feature.properties?.layer, // Type of place (address, venue, locality, etc.)\r\n confidence: feature.properties?.confidence, // Geocode Earth's confidence score\r\n match_type: feature.properties?.match_type, // How well the result matches the query\r\n }\r\n\r\n if (map && center) {\r\n // Add marker to the center with editing capability\r\n // markerManager.create(center as [number, number], map, { editing })\r\n\r\n if (feature.bbox) {\r\n map.fitBounds(feature.bbox, { speed: 2 })\r\n }\r\n else {\r\n map.flyTo({\r\n center: addressData.coordinates,\r\n zoom: 14, // Might be good idea to set zoom level depending on layer / type of place\r\n speed: 2,\r\n })\r\n }\r\n }\r\n\r\n return addressData\r\n}\r\n\r\n// Util function to get detailed address information\r\nexport const getDetailedAddress = async (coordinates: [number, number], countryCode?: string) => {\r\n const [longitude, latitude] = coordinates\r\n\r\n try {\r\n // Restrict to address-layer results (the original popover used layers=address) so\r\n // callers get a street address rather than the nearest venue/POI. Nominatim's zoom-18\r\n // reverse is already building-level, so this only refines the Pelias path.\r\n const features = await reverseGeocode(latitude.toString(), longitude.toString(), countryCode, { size: 1, layers: 'address' })\r\n return features.length > 0 ? features[0] : null\r\n }\r\n catch (error) {\r\n console.error('Error fetching detailed address:', error)\r\n return null\r\n }\r\n}\r\n\r\n// Convert a building object to a GeoJSON feature format compatible with handleLocationSelect\r\nexport const buildingToFeature = (building) => {\r\n const name = building.buildingName || ''\r\n\r\n // Validate coordinates - use the correct property names\r\n const longitude = Number.parseFloat(building.buildingLongitude)\r\n const latitude = Number.parseFloat(building.buildingLatitude)\r\n\r\n if (isNaN(longitude) || isNaN(latitude)) {\r\n console.error('Invalid building coordinates:', {\r\n longitude: building.buildingLongitude,\r\n latitude: building.buildingLatitude,\r\n buildingId: building.id,\r\n })\r\n return null\r\n }\r\n\r\n return {\r\n properties: {\r\n name: name,\r\n label: `${name}, ${[building.buildingAddress, building.buildingMunicipality, building.buildingCountrySubdivision].filter(Boolean).join(', ')}`,\r\n address: building.buildingAddress,\r\n municipality: building.buildingMunicipality,\r\n countrySubdivision: building.buildingCountrySubdivision,\r\n postalCode: building.buildingPostalCode,\r\n gid: `building-${building.id}`,\r\n },\r\n geometry: {\r\n type: 'Point',\r\n coordinates: [longitude, latitude],\r\n },\r\n is_database_building: true,\r\n }\r\n}\r\n\r\n// // Enhanced marker creation with editing capabilities\r\n// export const createGeocoderMarker = (coordinates: [number, number], map: any, options?: { editing?: boolean }) => {\r\n// const editing = options?.editing || false\r\n// const markerManager = getGeocoderMarkerManager()\r\n// markerManager.create(coordinates, map, { editing })\r\n// return markerManager\r\n// }\r\n\r\n// Helper function to remove the current marker\r\n// export const removeGeocoderMarker = (): void => {\r\n// const markerManager = getGeocoderMarkerManager()\r\n// markerManager.remove()\r\n// }\r\n"],"mappings":"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","names":["center","addressData"]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../src/core/components/viewers/pointcloud/PointCloudToolbar.tsx"],"sourcesContent":["'use client'\n\nimport * as React from 'react'\nimport { pointcloudToolbarTools } from './src/tools'\nimport { ToolbarBody } from '../../ToolbarBody'\n\nexport function PointCloudToolbar() {\n return <ToolbarBody viewer=\"pointcloud\" tools={pointcloudToolbarTools()} />\n}\n"],"mappings":";AAOS;AAJT,SAAS,8BAA8B;AACvC,SAAS,mBAAmB;AAErB,SAAS,oBAAoB;AAClC,SAAO,oBAAC,eAAY,QAAO,cAAa,OAAO,uBAAuB,GAAG;AAC3E;","names":[]}
1
+ {"version":3,"sources":["../../../../../src/core/components/viewers/pointcloud/PointCloudToolbar.tsx"],"sourcesContent":["'use client'\r\n\r\nimport * as React from 'react'\r\nimport { pointcloudToolbarTools } from './src/tools'\r\nimport { ToolbarBody } from '../../ToolbarBody'\r\n\r\nexport function PointCloudToolbar() {\r\n return <ToolbarBody viewer=\"pointcloud\" tools={pointcloudToolbarTools()} />\r\n}\r\n"],"mappings":";AAOS;AAJT,SAAS,8BAA8B;AACvC,SAAS,mBAAmB;AAErB,SAAS,oBAAoB;AAClC,SAAO,oBAAC,eAAY,QAAO,cAAa,OAAO,uBAAuB,GAAG;AAC3E;","names":[]}
@@ -1 +1 @@
1
- {"version":3,"file":"PointCloudViewer.d.ts","sourceRoot":"","sources":["../../../../../src/core/components/viewers/pointcloud/PointCloudViewer.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAe/B,wBAAgB,gBAAgB,sBA+N/B"}
1
+ {"version":3,"file":"PointCloudViewer.d.ts","sourceRoot":"","sources":["../../../../../src/core/components/viewers/pointcloud/PointCloudViewer.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAe/B,wBAAgB,gBAAgB,sBAsO/B"}
@@ -78,6 +78,12 @@ function PointCloudViewer() {
78
78
  gizmoRef.current = null;
79
79
  }
80
80
  const v = viewerRef.current;
81
+ if (v) {
82
+ v.loop = () => {
83
+ };
84
+ v.update = () => {
85
+ };
86
+ }
81
87
  if ((_a2 = v == null ? void 0 : v.scene) == null ? void 0 : _a2.pointclouds) {
82
88
  v.scene.pointclouds.forEach((pc) => {
83
89
  var _a3, _b2, _c2, _d, _e;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../src/core/components/viewers/pointcloud/PointCloudViewer.tsx"],"sourcesContent":["\"use client\"\r\n\r\nimport * as React from \"react\";\r\nimport * as THREE from \"three\";\r\nimport { useSearchParams } from \"next/navigation\";\r\nimport { loadAllAssets } from \"./utils/potreeLoader\";\r\nimport { BuildingsContext, PointCloudContext } from '../../../store'\r\nimport { restoreCameraFromUrl } from \"./utils/restoreCameraFromUrl\";\r\nimport { PointCloudLoadingState } from \"./src/PointCloudLoadingState\";\r\nimport { useFilesByBuildingId } from '../../../hooks/files/files'\r\nimport { ViewportGizmo } from './src/ViewportGizmo';\r\nimport { usePointCloudCoordinateSystem } from '../useCoordinateSystem';\r\n\r\n\r\nconst API_BASE =\r\n process.env.NEXT_PUBLIC_POINTCLOUD_API_URL ?? \"http://localhost:5101\";\r\n\r\nexport function PointCloudViewer() {\r\n // Enforce Z-up coordinate system for Potree. Resets to Y-up on unmount\r\n // so the BIM viewer doesn't inherit Z-up when switching viewers.\r\n usePointCloudCoordinateSystem();\r\n\r\n const containerRef = React.useRef<HTMLDivElement>(null);\r\n const viewerRef = React.useRef<any | null>(null);\r\n const gizmoRef = React.useRef<ViewportGizmo | null>(null);\r\n // Tracks Potree point cloud objects keyed by point cloud ID\r\n const loadedCloudsRef = React.useRef<Map<string, any>>(new Map());\r\n // IDs currently being fetched/loaded — prevents duplicate loads while async is in-flight\r\n const pendingIdsRef = React.useRef<Set<string>>(new Set());\r\n const cameraRestoredRef = React.useRef(false);\r\n\r\n const { state: pointCloudState, dispatch: pointCloudDispatch } = React.useContext(PointCloudContext)\r\n const PointCloudGlobalState = pointCloudState.pointcloud\r\n\r\n const { state: buildingState } = React.useContext(BuildingsContext)\r\n const { building } = buildingState.buildings\r\n\r\n const searchParams = useSearchParams()\r\n\r\n // Get files for the building to check if there are LAZ/LAS files\r\n const { files, isError: filesError } = useFilesByBuildingId(building?.id)\r\n const hasPointCloudFiles = React.useMemo(() =>\r\n !filesError && files.some(file => {\r\n const ext = file.extension?.toLowerCase()\r\n if (!ext) return false\r\n return ext === \"laz\" || ext === \"las\"\r\n }),\r\n [files, filesError])\r\n\r\n // --- Load CSS/JS assets once\r\n React.useEffect(() => {\r\n (async () => {\r\n try {\r\n await loadAllAssets()\r\n pointCloudDispatch({ type: \"SET_READY\", payload: { ready: true } })\r\n }\r\n catch (err) {\r\n console.error(\"PointCloudViewer - Failed to load assets\", err)\r\n }\r\n })();\r\n }, []);\r\n\r\n // --- Init viewer once when ready. Separate from cloud loading so the viewer\r\n // persists across toggle on/off of individual point clouds.\r\n React.useEffect(() => {\r\n if (!PointCloudGlobalState.ready || !containerRef.current || !(window as any)?.Potree) return;\r\n\r\n const Potree = (window as any).Potree;\r\n const el = containerRef.current;\r\n\r\n // Clear tracking state since we're creating a fresh viewer\r\n loadedCloudsRef.current.clear();\r\n pendingIdsRef.current.clear();\r\n cameraRestoredRef.current = false;\r\n\r\n const viewer = new Potree.Viewer(el);\r\n viewerRef.current = viewer;\r\n\r\n viewer.setEDLEnabled(true);\r\n viewer.setFOV(60);\r\n viewer.setPointBudget(10_000_000);\r\n viewer.loadSettingsFromURL();\r\n viewer.setBackground(\"white\");\r\n\r\n const gizmo = new ViewportGizmo(viewer);\r\n gizmoRef.current = gizmo;\r\n gizmo.enabled = true;\r\n\r\n pointCloudDispatch({\r\n type: \"INIT\",\r\n payload: {\r\n initialState: {\r\n ready: true,\r\n viewer: viewerRef.current,\r\n }\r\n }\r\n })\r\n\r\n return () => {\r\n try {\r\n if (gizmoRef.current) {\r\n gizmoRef.current.dispose();\r\n gizmoRef.current = null;\r\n }\r\n const v = viewerRef.current;\r\n if (v?.scene?.pointclouds) {\r\n v.scene.pointclouds.forEach((pc: any) => {\r\n v.scene.removePointCloud?.(pc) ?? v.scene.scene?.remove?.(pc);\r\n });\r\n }\r\n v?.renderer?.dispose?.();\r\n } catch { }\r\n loadedCloudsRef.current.clear();\r\n pendingIdsRef.current.clear();\r\n viewerRef.current = null;\r\n el.innerHTML = \"\";\r\n };\r\n }, [PointCloudGlobalState.ready]);\r\n\r\n // --- Add/remove point clouds whenever loadedPointCloudIds changes or the\r\n // viewer is (re)initialized (PointCloudGlobalState.viewer updates on INIT).\r\n React.useEffect(() => {\r\n const viewer = viewerRef.current;\r\n if (!PointCloudGlobalState.ready || !viewer) return;\r\n\r\n const Potree = (window as any).Potree;\r\n if (!Potree) return;\r\n\r\n const currentIds = new Set(PointCloudGlobalState.loadedPointCloudIds);\r\n const alreadyLoadedIds = new Set(loadedCloudsRef.current.keys());\r\n\r\n // Remove point clouds no longer in the list.\r\n // Potree classic has no removePointCloud() — splice from the internal\r\n // pointclouds array and remove from the Three.js scene separately.\r\n for (const id of alreadyLoadedIds) {\r\n if (!currentIds.has(id)) {\r\n const pc = loadedCloudsRef.current.get(id);\r\n if (pc) {\r\n try {\r\n const pcs: any[] = viewer.scene.pointclouds ?? [];\r\n const idx = pcs.indexOf(pc);\r\n if (idx !== -1) pcs.splice(idx, 1);\r\n viewer.scene.scene?.remove(pc);\r\n } catch { }\r\n loadedCloudsRef.current.delete(id);\r\n pendingIdsRef.current.delete(id);\r\n try { viewer.fitToScreen(); } catch { }\r\n }\r\n }\r\n }\r\n\r\n // Add newly requested point clouds.\r\n // Skip if already tracked in loadedCloudsRef OR still in-flight in pendingIdsRef\r\n // — prevents duplicate loads when Effect 2 fires again before the callback runs.\r\n for (const id of currentIds) {\r\n if (alreadyLoadedIds.has(id) || pendingIdsRef.current.has(id)) continue;\r\n\r\n pendingIdsRef.current.add(id);\r\n ;(async () => {\r\n try {\r\n const res = await fetch(`${API_BASE}/point-cloud/${encodeURIComponent(id)}`);\r\n const data = await res.json();\r\n const url = `${API_BASE}/private/${data.bucket}/${data.potreeMetadataFileKey}`;\r\n\r\n Potree.loadPointCloud(url, id, (e: any) => {\r\n pendingIdsRef.current.delete(id);\r\n // Bail if the viewer was torn down (or re-init'd) while this load was in flight —\r\n // the init-effect cleanup nulls viewerRef, so this guard avoids attaching to a dead viewer.\r\n if (viewerRef.current !== viewer) return;\r\n try {\r\n viewer.scene.addPointCloud(e.pointcloud);\r\n loadedCloudsRef.current.set(id, e.pointcloud);\r\n\r\n const mat = e.pointcloud.material;\r\n mat.size = 1;\r\n mat.pointSizeType = Potree.PointSizeType.ADAPTIVE;\r\n\r\n const restored = !cameraRestoredRef.current && restoreCameraFromUrl(viewer, searchParams)\r\n cameraRestoredRef.current = true;\r\n if (!restored) viewer.fitToScreen();\r\n\r\n if (gizmoRef.current) {\r\n gizmoRef.current.add();\r\n }\r\n } catch (err) {\r\n console.error(\"[PointCloudViewer] Error adding point cloud\", err);\r\n }\r\n });\r\n } catch (err) {\r\n pendingIdsRef.current.delete(id);\r\n console.error(\"[PointCloudViewer] Error fetching point cloud metadata\", err);\r\n }\r\n })();\r\n }\r\n }, [PointCloudGlobalState.loadedPointCloudIds, PointCloudGlobalState.viewer, PointCloudGlobalState.ready]);\r\n\r\n const { loadedPointCloudIds } = PointCloudGlobalState;\r\n const hasLoadedClouds = loadedPointCloudIds.length > 0;\r\n\r\n return (\r\n <div\r\n style={{\r\n position: \"absolute\",\r\n left: 0,\r\n top: 0,\r\n width: \"100%\",\r\n height: \"100%\",\r\n }}\r\n >\r\n {/* Potree container — always rendered so the viewer can measure its dimensions on init.\r\n PointCloudLoadingState sits above it via z-index when no clouds are loaded. */}\r\n <div>\r\n <div\r\n className={`pointer-events-none absolute inset-0 `}\r\n style={{\r\n zIndex: 9,\r\n background: 'radial-gradient(circle at center, rgba(220,220,220,0) 0%, rgba(220,220,220,0) 50%, rgba(220,220,220,0.9) 100%)',\r\n mixBlendMode: 'multiply'\r\n }}\r\n />\r\n <div\r\n ref={containerRef}\r\n className=\"potree_container\"\r\n style={{\r\n position: \"absolute\",\r\n inset: 0,\r\n backgroundSize: \"cover\",\r\n }}\r\n />\r\n </div>\r\n\r\n {/* Show PointCloudLoadingState only when there are no point cloud files uploaded at all.\r\n Rendered after the container so it layers on top (higher DOM order = higher paint order). */}\r\n {!hasPointCloudFiles && !hasLoadedClouds && (\r\n <div className=\"absolute inset-0\" style={{ zIndex: 10 }}>\r\n <PointCloudLoadingState />\r\n </div>\r\n )}\r\n </div>\r\n );\r\n}\r\n"],"mappings":"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","names":["_a","_b","_c"]}
1
+ {"version":3,"sources":["../../../../../src/core/components/viewers/pointcloud/PointCloudViewer.tsx"],"sourcesContent":["\"use client\"\r\n\r\nimport * as React from \"react\";\r\nimport * as THREE from \"three\";\r\nimport { useSearchParams } from \"next/navigation\";\r\nimport { loadAllAssets } from \"./utils/potreeLoader\";\r\nimport { BuildingsContext, PointCloudContext } from '../../../store'\r\nimport { restoreCameraFromUrl } from \"./utils/restoreCameraFromUrl\";\r\nimport { PointCloudLoadingState } from \"./src/PointCloudLoadingState\";\r\nimport { useFilesByBuildingId } from '../../../hooks/files/files'\r\nimport { ViewportGizmo } from './src/ViewportGizmo';\r\nimport { usePointCloudCoordinateSystem } from '../useCoordinateSystem';\r\n\r\n\r\nconst API_BASE =\r\n process.env.NEXT_PUBLIC_POINTCLOUD_API_URL ?? \"http://localhost:5101\";\r\n\r\nexport function PointCloudViewer() {\r\n // Enforce Z-up coordinate system for Potree. Resets to Y-up on unmount\r\n // so the BIM viewer doesn't inherit Z-up when switching viewers.\r\n usePointCloudCoordinateSystem();\r\n\r\n const containerRef = React.useRef<HTMLDivElement>(null);\r\n const viewerRef = React.useRef<any | null>(null);\r\n const gizmoRef = React.useRef<ViewportGizmo | null>(null);\r\n // Tracks Potree point cloud objects keyed by point cloud ID\r\n const loadedCloudsRef = React.useRef<Map<string, any>>(new Map());\r\n // IDs currently being fetched/loaded — prevents duplicate loads while async is in-flight\r\n const pendingIdsRef = React.useRef<Set<string>>(new Set());\r\n const cameraRestoredRef = React.useRef(false);\r\n\r\n const { state: pointCloudState, dispatch: pointCloudDispatch } = React.useContext(PointCloudContext)\r\n const PointCloudGlobalState = pointCloudState.pointcloud\r\n\r\n const { state: buildingState } = React.useContext(BuildingsContext)\r\n const { building } = buildingState.buildings\r\n\r\n const searchParams = useSearchParams()\r\n\r\n // Get files for the building to check if there are LAZ/LAS files\r\n const { files, isError: filesError } = useFilesByBuildingId(building?.id)\r\n const hasPointCloudFiles = React.useMemo(() =>\r\n !filesError && files.some(file => {\r\n const ext = file.extension?.toLowerCase()\r\n if (!ext) return false\r\n return ext === \"laz\" || ext === \"las\"\r\n }),\r\n [files, filesError])\r\n\r\n // --- Load CSS/JS assets once\r\n React.useEffect(() => {\r\n (async () => {\r\n try {\r\n await loadAllAssets()\r\n pointCloudDispatch({ type: \"SET_READY\", payload: { ready: true } })\r\n }\r\n catch (err) {\r\n console.error(\"PointCloudViewer - Failed to load assets\", err)\r\n }\r\n })();\r\n }, []);\r\n\r\n // --- Init viewer once when ready. Separate from cloud loading so the viewer\r\n // persists across toggle on/off of individual point clouds.\r\n React.useEffect(() => {\r\n if (!PointCloudGlobalState.ready || !containerRef.current || !(window as any)?.Potree) return;\r\n\r\n const Potree = (window as any).Potree;\r\n const el = containerRef.current;\r\n\r\n // Clear tracking state since we're creating a fresh viewer\r\n loadedCloudsRef.current.clear();\r\n pendingIdsRef.current.clear();\r\n cameraRestoredRef.current = false;\r\n\r\n const viewer = new Potree.Viewer(el);\r\n viewerRef.current = viewer;\r\n\r\n viewer.setEDLEnabled(true);\r\n viewer.setFOV(60);\r\n viewer.setPointBudget(10_000_000);\r\n viewer.loadSettingsFromURL();\r\n viewer.setBackground(\"white\");\r\n\r\n const gizmo = new ViewportGizmo(viewer);\r\n gizmoRef.current = gizmo;\r\n gizmo.enabled = true;\r\n\r\n pointCloudDispatch({\r\n type: \"INIT\",\r\n payload: {\r\n initialState: {\r\n ready: true,\r\n viewer: viewerRef.current,\r\n }\r\n }\r\n })\r\n\r\n return () => {\r\n try {\r\n if (gizmoRef.current) {\r\n gizmoRef.current.dispose();\r\n gizmoRef.current = null;\r\n }\r\n const v = viewerRef.current;\r\n // Potree's render loop re-schedules itself via requestAnimationFrame with no stop API;\r\n // shadow it with a no-op so it halts on unmount instead of running forever against a\r\n // disposed renderer (otherwise a new loop accumulates on every visit).\r\n if (v) {\r\n v.loop = () => { };\r\n v.update = () => { };\r\n }\r\n if (v?.scene?.pointclouds) {\r\n v.scene.pointclouds.forEach((pc: any) => {\r\n v.scene.removePointCloud?.(pc) ?? v.scene.scene?.remove?.(pc);\r\n });\r\n }\r\n v?.renderer?.dispose?.();\r\n } catch { }\r\n loadedCloudsRef.current.clear();\r\n pendingIdsRef.current.clear();\r\n viewerRef.current = null;\r\n el.innerHTML = \"\";\r\n };\r\n }, [PointCloudGlobalState.ready]);\r\n\r\n // --- Add/remove point clouds whenever loadedPointCloudIds changes or the\r\n // viewer is (re)initialized (PointCloudGlobalState.viewer updates on INIT).\r\n React.useEffect(() => {\r\n const viewer = viewerRef.current;\r\n if (!PointCloudGlobalState.ready || !viewer) return;\r\n\r\n const Potree = (window as any).Potree;\r\n if (!Potree) return;\r\n\r\n const currentIds = new Set(PointCloudGlobalState.loadedPointCloudIds);\r\n const alreadyLoadedIds = new Set(loadedCloudsRef.current.keys());\r\n\r\n // Remove point clouds no longer in the list.\r\n // Potree classic has no removePointCloud() — splice from the internal\r\n // pointclouds array and remove from the Three.js scene separately.\r\n for (const id of alreadyLoadedIds) {\r\n if (!currentIds.has(id)) {\r\n const pc = loadedCloudsRef.current.get(id);\r\n if (pc) {\r\n try {\r\n const pcs: any[] = viewer.scene.pointclouds ?? [];\r\n const idx = pcs.indexOf(pc);\r\n if (idx !== -1) pcs.splice(idx, 1);\r\n viewer.scene.scene?.remove(pc);\r\n } catch { }\r\n loadedCloudsRef.current.delete(id);\r\n pendingIdsRef.current.delete(id);\r\n try { viewer.fitToScreen(); } catch { }\r\n }\r\n }\r\n }\r\n\r\n // Add newly requested point clouds.\r\n // Skip if already tracked in loadedCloudsRef OR still in-flight in pendingIdsRef\r\n // — prevents duplicate loads when Effect 2 fires again before the callback runs.\r\n for (const id of currentIds) {\r\n if (alreadyLoadedIds.has(id) || pendingIdsRef.current.has(id)) continue;\r\n\r\n pendingIdsRef.current.add(id);\r\n ; (async () => {\r\n try {\r\n const res = await fetch(`${API_BASE}/point-cloud/${encodeURIComponent(id)}`);\r\n const data = await res.json();\r\n const url = `${API_BASE}/private/${data.bucket}/${data.potreeMetadataFileKey}`;\r\n\r\n Potree.loadPointCloud(url, id, (e: any) => {\r\n pendingIdsRef.current.delete(id);\r\n // Bail if the viewer was torn down (or re-init'd) while this load was in flight —\r\n // the init-effect cleanup nulls viewerRef, so this guard avoids attaching to a dead viewer.\r\n if (viewerRef.current !== viewer) return;\r\n try {\r\n viewer.scene.addPointCloud(e.pointcloud);\r\n loadedCloudsRef.current.set(id, e.pointcloud);\r\n\r\n const mat = e.pointcloud.material;\r\n mat.size = 1;\r\n mat.pointSizeType = Potree.PointSizeType.ADAPTIVE;\r\n\r\n const restored = !cameraRestoredRef.current && restoreCameraFromUrl(viewer, searchParams)\r\n cameraRestoredRef.current = true;\r\n if (!restored) viewer.fitToScreen();\r\n\r\n if (gizmoRef.current) {\r\n gizmoRef.current.add();\r\n }\r\n } catch (err) {\r\n console.error(\"[PointCloudViewer] Error adding point cloud\", err);\r\n }\r\n });\r\n } catch (err) {\r\n pendingIdsRef.current.delete(id);\r\n console.error(\"[PointCloudViewer] Error fetching point cloud metadata\", err);\r\n }\r\n })();\r\n }\r\n }, [PointCloudGlobalState.loadedPointCloudIds, PointCloudGlobalState.viewer, PointCloudGlobalState.ready]);\r\n\r\n const { loadedPointCloudIds } = PointCloudGlobalState;\r\n const hasLoadedClouds = loadedPointCloudIds.length > 0;\r\n\r\n return (\r\n <div\r\n style={{\r\n position: \"absolute\",\r\n left: 0,\r\n top: 0,\r\n width: \"100%\",\r\n height: \"100%\",\r\n }}\r\n >\r\n {/* Potree container — always rendered so the viewer can measure its dimensions on init.\r\n PointCloudLoadingState sits above it via z-index when no clouds are loaded. */}\r\n <div>\r\n <div\r\n className={`pointer-events-none absolute inset-0 `}\r\n style={{\r\n zIndex: 9,\r\n background: 'radial-gradient(circle at center, rgba(220,220,220,0) 0%, rgba(220,220,220,0) 50%, rgba(220,220,220,0.9) 100%)',\r\n mixBlendMode: 'multiply'\r\n }}\r\n />\r\n <div\r\n ref={containerRef}\r\n className=\"potree_container\"\r\n style={{\r\n position: \"absolute\",\r\n inset: 0,\r\n backgroundSize: \"cover\",\r\n }}\r\n />\r\n </div>\r\n\r\n {/* Show PointCloudLoadingState only when there are no point cloud files uploaded at all.\r\n Rendered after the container so it layers on top (higher DOM order = higher paint order). */}\r\n {!hasPointCloudFiles && !hasLoadedClouds && (\r\n <div className=\"absolute inset-0\" style={{ zIndex: 10 }}>\r\n <PointCloudLoadingState />\r\n </div>\r\n )}\r\n </div>\r\n );\r\n}\r\n"],"mappings":"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","names":["_a","_b","_c"]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/core/hooks/provider.tsx"],"sourcesContent":["\"use client\";\nimport React, { createContext, useContext, useMemo } from \"react\";\nimport type { ApiAdapter } from \"./ports/apiAdapter\";\nimport { createBuildingHooks } from \"./buildings/createBuildingHooks\";\nimport { createFileHooks } from \"./files/createFileHooks\";\nimport { createOpenDataPortalHooks } from \"./openDataPortals/createOpenDataPortalHooks\";\nimport { createSiteHooks } from \"./sites/createSiteHooks\";\nimport { createUserHooks } from \"./users/createUserHooks\";\nimport { createOrganizationHooks } from \"./organizations/createOrganizationHooks\";\nimport { createCommentHooks } from \"./comments/createCommentHooks\";\nimport { createSensorHooks } from \"./sensors/createSensorHooks\";\nimport { createInfrastructureHooks } from './infrastructures/createInfrastructureHooks';\nimport { createSensorTypeHooks } from \"./sensorTypes/createSensorTypeHooks\";\n\n// CoreHooksProvider takes an ApiAdapter (the object that knows how to fetch data) and\n// creates all the app's data-fetching hooks from it. Mount it once at the root so every\n// page has access. Inside any component, call useCoreHooks() to get the hooks, or import\n// from the per-domain convenience wrappers (e.g. useGetBuildings from hooks/buildings/buildings.ts).\n\nexport type HooksBag = {\n building: ReturnType<typeof createBuildingHooks>;\n file: ReturnType<typeof createFileHooks>;\n openData: ReturnType<typeof createOpenDataPortalHooks>;\n site: ReturnType<typeof createSiteHooks>;\n infrastructure: ReturnType<typeof createInfrastructureHooks>;\n user: ReturnType<typeof createUserHooks>;\n organization: ReturnType<typeof createOrganizationHooks>;\n comment: ReturnType<typeof createCommentHooks>;\n sensor: ReturnType<typeof createSensorHooks>;\n sensorType: ReturnType<typeof createSensorTypeHooks>;\n};\nexport const HooksCtx = createContext<HooksBag | null>(null);\n\nexport function CoreHooksProvider({ adapter, children }:{ adapter: ApiAdapter; children: React.ReactNode }) {\n // Build the bound hook set exactly once per adapter instance\n const hooks = useMemo<HooksBag>(() => ({\n building: createBuildingHooks(adapter),\n file: createFileHooks(adapter),\n openData: createOpenDataPortalHooks(adapter),\n site: createSiteHooks(adapter),\n infrastructure: createInfrastructureHooks(adapter),\n user: createUserHooks(adapter),\n organization: createOrganizationHooks(adapter),\n comment: createCommentHooks(adapter),\n sensor: createSensorHooks(adapter),\n sensorType: createSensorTypeHooks(adapter),\n }), [adapter]);\n return <HooksCtx.Provider value={hooks}>{children}</HooksCtx.Provider>;\n}\n\nexport function useCoreHooks() {\n const ctx = useContext(HooksCtx);\n if (!ctx) throw new Error(\"useCoreHooks must be used inside <CoreHooksProvider>\");\n return ctx;\n}"],"mappings":";AA+CS;AA9CT,SAAgB,eAAe,YAAY,eAAe;AAE1D,SAAS,2BAA2B;AACpC,SAAS,uBAAuB;AAChC,SAAS,iCAAiC;AAC1C,SAAS,uBAAuB;AAChC,SAAS,uBAAuB;AAChC,SAAS,+BAA+B;AACxC,SAAS,0BAA0B;AACnC,SAAS,yBAAyB;AAClC,SAAS,iCAAiC;AAC1C,SAAS,6BAA6B;AAmB/B,MAAM,WAAW,cAA+B,IAAI;AAEpD,SAAS,kBAAkB,EAAE,SAAS,SAAS,GAAsD;AAE1G,QAAM,QAAQ,QAAkB,OAAO;AAAA,IACrC,UAAU,oBAAoB,OAAO;AAAA,IACrC,MAAM,gBAAgB,OAAO;AAAA,IAC7B,UAAU,0BAA0B,OAAO;AAAA,IAC3C,MAAM,gBAAgB,OAAO;AAAA,IAC7B,gBAAgB,0BAA0B,OAAO;AAAA,IACjD,MAAM,gBAAgB,OAAO;AAAA,IAC7B,cAAc,wBAAwB,OAAO;AAAA,IAC7C,SAAS,mBAAmB,OAAO;AAAA,IACnC,QAAQ,kBAAkB,OAAO;AAAA,IACjC,YAAY,sBAAsB,OAAO;AAAA,EAC3C,IAAI,CAAC,OAAO,CAAC;AACb,SAAO,oBAAC,SAAS,UAAT,EAAkB,OAAO,OAAQ,UAAS;AACpD;AAEO,SAAS,eAAe;AAC7B,QAAM,MAAM,WAAW,QAAQ;AAC/B,MAAI,CAAC,IAAK,OAAM,IAAI,MAAM,sDAAsD;AAChF,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":["../../../src/core/hooks/provider.tsx"],"sourcesContent":["\"use client\";\r\nimport React, { createContext, useContext, useMemo } from \"react\";\r\nimport type { ApiAdapter } from \"./ports/apiAdapter\";\r\nimport { createBuildingHooks } from \"./buildings/createBuildingHooks\";\r\nimport { createFileHooks } from \"./files/createFileHooks\";\r\nimport { createOpenDataPortalHooks } from \"./openDataPortals/createOpenDataPortalHooks\";\r\nimport { createSiteHooks } from \"./sites/createSiteHooks\";\r\nimport { createUserHooks } from \"./users/createUserHooks\";\r\nimport { createOrganizationHooks } from \"./organizations/createOrganizationHooks\";\r\nimport { createCommentHooks } from \"./comments/createCommentHooks\";\r\nimport { createSensorHooks } from \"./sensors/createSensorHooks\";\r\nimport { createInfrastructureHooks } from './infrastructures/createInfrastructureHooks';\r\nimport { createSensorTypeHooks } from \"./sensorTypes/createSensorTypeHooks\";\r\n\r\n// CoreHooksProvider takes an ApiAdapter (the object that knows how to fetch data) and\r\n// creates all the app's data-fetching hooks from it. Mount it once at the root so every\r\n// page has access. Inside any component, call useCoreHooks() to get the hooks, or import\r\n// from the per-domain convenience wrappers (e.g. useGetBuildings from hooks/buildings/buildings.ts).\r\n\r\nexport type HooksBag = {\r\n building: ReturnType<typeof createBuildingHooks>;\r\n file: ReturnType<typeof createFileHooks>;\r\n openData: ReturnType<typeof createOpenDataPortalHooks>;\r\n site: ReturnType<typeof createSiteHooks>;\r\n infrastructure: ReturnType<typeof createInfrastructureHooks>;\r\n user: ReturnType<typeof createUserHooks>;\r\n organization: ReturnType<typeof createOrganizationHooks>;\r\n comment: ReturnType<typeof createCommentHooks>;\r\n sensor: ReturnType<typeof createSensorHooks>;\r\n sensorType: ReturnType<typeof createSensorTypeHooks>;\r\n};\r\nexport const HooksCtx = createContext<HooksBag | null>(null);\r\n\r\nexport function CoreHooksProvider({ adapter, children }:{ adapter: ApiAdapter; children: React.ReactNode }) {\r\n // Build the bound hook set exactly once per adapter instance\r\n const hooks = useMemo<HooksBag>(() => ({\r\n building: createBuildingHooks(adapter),\r\n file: createFileHooks(adapter),\r\n openData: createOpenDataPortalHooks(adapter),\r\n site: createSiteHooks(adapter),\r\n infrastructure: createInfrastructureHooks(adapter),\r\n user: createUserHooks(adapter),\r\n organization: createOrganizationHooks(adapter),\r\n comment: createCommentHooks(adapter),\r\n sensor: createSensorHooks(adapter),\r\n sensorType: createSensorTypeHooks(adapter),\r\n }), [adapter]);\r\n return <HooksCtx.Provider value={hooks}>{children}</HooksCtx.Provider>;\r\n}\r\n\r\nexport function useCoreHooks() {\r\n const ctx = useContext(HooksCtx);\r\n if (!ctx) throw new Error(\"useCoreHooks must be used inside <CoreHooksProvider>\");\r\n return ctx;\r\n}"],"mappings":";AA+CS;AA9CT,SAAgB,eAAe,YAAY,eAAe;AAE1D,SAAS,2BAA2B;AACpC,SAAS,uBAAuB;AAChC,SAAS,iCAAiC;AAC1C,SAAS,uBAAuB;AAChC,SAAS,uBAAuB;AAChC,SAAS,+BAA+B;AACxC,SAAS,0BAA0B;AACnC,SAAS,yBAAyB;AAClC,SAAS,iCAAiC;AAC1C,SAAS,6BAA6B;AAmB/B,MAAM,WAAW,cAA+B,IAAI;AAEpD,SAAS,kBAAkB,EAAE,SAAS,SAAS,GAAsD;AAE1G,QAAM,QAAQ,QAAkB,OAAO;AAAA,IACrC,UAAU,oBAAoB,OAAO;AAAA,IACrC,MAAM,gBAAgB,OAAO;AAAA,IAC7B,UAAU,0BAA0B,OAAO;AAAA,IAC3C,MAAM,gBAAgB,OAAO;AAAA,IAC7B,gBAAgB,0BAA0B,OAAO;AAAA,IACjD,MAAM,gBAAgB,OAAO;AAAA,IAC7B,cAAc,wBAAwB,OAAO;AAAA,IAC7C,SAAS,mBAAmB,OAAO;AAAA,IACnC,QAAQ,kBAAkB,OAAO;AAAA,IACjC,YAAY,sBAAsB,OAAO;AAAA,EAC3C,IAAI,CAAC,OAAO,CAAC;AACb,SAAO,oBAAC,SAAS,UAAT,EAAkB,OAAO,OAAQ,UAAS;AACpD;AAEO,SAAS,eAAe;AAC7B,QAAM,MAAM,WAAW,QAAQ;AAC/B,MAAI,CAAC,IAAK,OAAM,IAAI,MAAM,sDAAsD;AAChF,SAAO;AACT;","names":[]}
@@ -7,16 +7,16 @@ async function checkImageUrl(url) {
7
7
  }
8
8
  }
9
9
  async function uploadOrganizationLogoToPublicBucket(file) {
10
- const uploadUrl = `${process.env.NEXT_PUBLIC_MINIO_BUCKET_URL}/org-logos/${file.name}`;
11
- const uploadResponse = await fetch(uploadUrl, {
10
+ const presignedUrlResult = await fetch(`/api/presigned-url-upload?asset=${encodeURIComponent(file.name)}&bucket=org-logos`);
11
+ if (!presignedUrlResult.ok) throw new Error("Failed to get presigned URL for org logo");
12
+ const { presignedUrl, key } = await presignedUrlResult.json();
13
+ const uploadResult = await fetch(presignedUrl, {
12
14
  method: "PUT",
13
15
  body: file,
14
16
  headers: { "Content-Type": file.type || "application/octet-stream" }
15
17
  });
16
- if (!uploadResponse.ok) {
17
- throw new Error("Failed to upload organization logo");
18
- }
19
- return file.name;
18
+ if (!uploadResult.ok) throw new Error("Failed to upload organization logo");
19
+ return key;
20
20
  }
21
21
  export {
22
22
  checkImageUrl,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/core/utils/imageUtils.ts"],"sourcesContent":["\r\nexport async function checkImageUrl(url: string): Promise<boolean> {\r\n try {\r\n const response = await fetch(url, { method: 'HEAD' })\r\n return response.ok\r\n } catch {\r\n return false\r\n }\r\n}\r\n\r\nexport async function uploadOrganizationLogoToPublicBucket(file: File): Promise<string> {\r\n const uploadUrl = `${process.env.NEXT_PUBLIC_MINIO_BUCKET_URL}/org-logos/${file.name}`\r\n const uploadResponse = await fetch(uploadUrl, {\r\n method: 'PUT',\r\n body: file,\r\n headers: { 'Content-Type': file.type || 'application/octet-stream' },\r\n })\r\n\r\n if (!uploadResponse.ok) {\r\n throw new Error('Failed to upload organization logo')\r\n }\r\n\r\n return file.name\r\n}\r\n"],"mappings":"AACA,eAAsB,cAAc,KAA+B;AACjE,MAAI;AACF,UAAM,WAAW,MAAM,MAAM,KAAK,EAAE,QAAQ,OAAO,CAAC;AACpD,WAAO,SAAS;AAAA,EAClB,SAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAsB,qCAAqC,MAA6B;AACtF,QAAM,YAAY,GAAG,QAAQ,IAAI,4BAA4B,cAAc,KAAK,IAAI;AACpF,QAAM,iBAAiB,MAAM,MAAM,WAAW;AAAA,IAC5C,QAAQ;AAAA,IACR,MAAM;AAAA,IACN,SAAS,EAAE,gBAAgB,KAAK,QAAQ,2BAA2B;AAAA,EACrE,CAAC;AAED,MAAI,CAAC,eAAe,IAAI;AACtB,UAAM,IAAI,MAAM,oCAAoC;AAAA,EACtD;AAEA,SAAO,KAAK;AACd;","names":[]}
1
+ {"version":3,"sources":["../../../src/core/utils/imageUtils.ts"],"sourcesContent":["\r\nexport async function checkImageUrl(url: string): Promise<boolean> {\r\n try {\r\n const response = await fetch(url, { method: 'HEAD' })\r\n return response.ok\r\n } catch {\r\n return false\r\n }\r\n}\r\n\r\nexport async function uploadOrganizationLogoToPublicBucket(file: File): Promise<string> {\r\n const presignedUrlResult = await fetch(`/api/presigned-url-upload?asset=${encodeURIComponent(file.name)}&bucket=org-logos`)\r\n if (!presignedUrlResult.ok) throw new Error('Failed to get presigned URL for org logo')\r\n const { presignedUrl, key } = await presignedUrlResult.json()\r\n\r\n const uploadResult = await fetch(presignedUrl, {\r\n method: 'PUT',\r\n body: file,\r\n headers: { 'Content-Type': file.type || 'application/octet-stream' },\r\n })\r\n if (!uploadResult.ok) throw new Error('Failed to upload organization logo')\r\n\r\n return key\r\n}\r\n"],"mappings":"AACA,eAAsB,cAAc,KAA+B;AACjE,MAAI;AACF,UAAM,WAAW,MAAM,MAAM,KAAK,EAAE,QAAQ,OAAO,CAAC;AACpD,WAAO,SAAS;AAAA,EAClB,SAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAsB,qCAAqC,MAA6B;AACtF,QAAM,qBAAqB,MAAM,MAAM,mCAAmC,mBAAmB,KAAK,IAAI,CAAC,mBAAmB;AAC1H,MAAI,CAAC,mBAAmB,GAAI,OAAM,IAAI,MAAM,0CAA0C;AACtF,QAAM,EAAE,cAAc,IAAI,IAAI,MAAM,mBAAmB,KAAK;AAE5D,QAAM,eAAe,MAAM,MAAM,cAAc;AAAA,IAC7C,QAAQ;AAAA,IACR,MAAM;AAAA,IACN,SAAS,EAAE,gBAAgB,KAAK,QAAQ,2BAA2B;AAAA,EACrE,CAAC;AACD,MAAI,CAAC,aAAa,GAAI,OAAM,IAAI,MAAM,oCAAoC;AAE1E,SAAO;AACT;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@collabdt/core",
3
- "version": "0.0.44",
3
+ "version": "0.0.46",
4
4
  "description": "CDT Core contains all the core technology for your CDT platform",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",