openstack-uicore-foundation 5.0.31-beta.0 → 5.0.32
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/.claude/rules/openstack-uicore-foundation-components.md +62 -0
- package/.claude/rules/openstack-uicore-foundation-project.md +64 -0
- package/.claude/rules/openstack-uicore-foundation-security.md +39 -0
- package/.claude/settings.local.json +1 -7
- package/.codegraph/config.json +140 -0
- package/docs/plans/2026-04-09-showconfirmdialog-react16-compat.md +83 -0
- package/docs/plans/2026-04-22-dropzone-pooling-ux.md +129 -0
- package/docs/plans/2026-04-23-uploadv3-duplicate-file-max-reached.md +109 -0
- package/docs/plans/2026-04-23-uploadv3-premature-complete-on-202.md +99 -0
- package/lib/components/clock-context.js +2 -0
- package/lib/components/clock-context.js.map +1 -0
- package/lib/components/index.js +1 -1
- package/lib/components/index.js.map +1 -1
- package/lib/components/inputs/access-levels-input.js.map +1 -1
- package/lib/components/inputs/attendee-input.js.map +1 -1
- package/lib/components/inputs/company-input-v2.js.map +1 -1
- package/lib/components/inputs/company-input.js.map +1 -1
- package/lib/components/inputs/country-dropdown.js.map +1 -1
- package/lib/components/inputs/country-input.js.map +1 -1
- package/lib/components/inputs/event-input.js.map +1 -1
- package/lib/components/inputs/group-input.js.map +1 -1
- package/lib/components/inputs/language-input.js.map +1 -1
- package/lib/components/inputs/member-input.js.map +1 -1
- package/lib/components/inputs/organization-input.js.map +1 -1
- package/lib/components/inputs/promocode-input.js.map +1 -1
- package/lib/components/inputs/registration-company-input.js.map +1 -1
- package/lib/components/inputs/speaker-input.js.map +1 -1
- package/lib/components/inputs/sponsor-input.js.map +1 -1
- package/lib/components/inputs/summit-input.js.map +1 -1
- package/lib/components/inputs/tag-input.js.map +1 -1
- package/lib/components/inputs/ticket-types-input.js.map +1 -1
- package/lib/components/inputs/upload-input-v3.js +1 -1
- package/lib/components/inputs/upload-input-v3.js.map +1 -1
- package/lib/components/mui/download-btn.js +1 -1
- package/lib/components/mui/download-btn.js.map +1 -1
- package/lib/components/mui/dropdown.js +2 -0
- package/lib/components/mui/dropdown.js.map +1 -0
- package/lib/components/mui/editable-table.js.map +1 -1
- package/lib/components/mui/form-item-table.js.map +1 -1
- package/lib/components/mui/formik-inputs/additional-input-list.js.map +1 -1
- package/lib/components/mui/formik-inputs/additional-input.js.map +1 -1
- package/lib/components/mui/formik-inputs/async-select.js.map +1 -1
- package/lib/components/mui/formik-inputs/checkbox-group.js.map +1 -1
- package/lib/components/mui/formik-inputs/company-input.js.map +1 -1
- package/lib/components/mui/formik-inputs/discount-field.js.map +1 -1
- package/lib/components/mui/formik-inputs/file-size-field.js.map +1 -1
- package/lib/components/mui/formik-inputs/item-price-tiers.js.map +1 -1
- package/lib/components/mui/formik-inputs/price-field.js.map +1 -1
- package/lib/components/mui/formik-inputs/sponsor-input.js.map +1 -1
- package/lib/components/mui/formik-inputs/sponsorship-input.js.map +1 -1
- package/lib/components/mui/formik-inputs/sponsorship-summit-select.js.map +1 -1
- package/lib/components/mui/formik-inputs/summit-addon-select.js.map +1 -1
- package/lib/components/mui/formik-inputs/upload.js +1 -1
- package/lib/components/mui/formik-inputs/upload.js.map +1 -1
- package/lib/components/mui/grid-filter.js +2 -0
- package/lib/components/mui/grid-filter.js.map +1 -0
- package/lib/components/mui/infinite-table.js.map +1 -1
- package/lib/components/mui/info-note.js +1 -1
- package/lib/components/mui/info-note.js.map +1 -1
- package/lib/components/mui/order-summary.js.map +1 -1
- package/lib/components/mui/round-button.js +2 -0
- package/lib/components/mui/round-button.js.map +1 -0
- package/lib/components/mui/search-input.js.map +1 -1
- package/lib/components/mui/snackbar-notification.js +1 -1
- package/lib/components/mui/snackbar-notification.js.map +1 -1
- package/lib/components/mui/sortable-table.js.map +1 -1
- package/lib/components/mui/sponsor-addon-select.js.map +1 -1
- package/lib/components/mui/sponsor-order-grid.js +1 -1
- package/lib/components/mui/sponsor-order-grid.js.map +1 -1
- package/lib/components/mui/status-chip.js.map +1 -1
- package/lib/components/mui/stripe-payment.js.map +1 -1
- package/lib/components/mui/summit-addon-select.js.map +1 -1
- package/lib/components/mui/summits-dropdown.js.map +1 -1
- package/lib/components/mui/table/extra-rows.js +1 -1
- package/lib/components/mui/table/extra-rows.js.map +1 -1
- package/lib/components/mui/table.js.map +1 -1
- package/lib/components/mui/toggle-buttons.js +2 -0
- package/lib/components/mui/toggle-buttons.js.map +1 -0
- package/lib/components/mui/upload-btn.js +1 -1
- package/lib/components/mui/upload-btn.js.map +1 -1
- package/lib/components/mui/upload-dialog.js +1 -1
- package/lib/components/mui/upload-dialog.js.map +1 -1
- package/lib/components/sponsored-project-input.js.map +1 -1
- package/lib/css/components/inputs/upload-input-v3.css +1 -1
- package/lib/css/components/inputs/upload-input-v3.css.map +1 -1
- package/lib/css/components/mui/formik-inputs/upload.css +1 -1
- package/lib/css/components/mui/formik-inputs/upload.css.map +1 -1
- package/lib/css/components/mui/upload-dialog.css +1 -1
- package/lib/css/components/mui/upload-dialog.css.map +1 -1
- package/lib/i18n.js +1 -1
- package/lib/i18n.js.map +1 -1
- package/lib/utils/actions.js.map +1 -1
- package/lib/utils/external-store.js +2 -0
- package/lib/utils/external-store.js.map +1 -0
- package/lib/utils/money.js.map +1 -1
- package/lib/utils/query-actions.js.map +1 -1
- package/package.json +9 -6
- package/readme.md +4 -0
- package/package-lock.json +0 -22291
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# UploadInputV3 Premature Completion on HTTP 202 Fix Plan
|
|
2
|
+
|
|
3
|
+
Created: 2026-04-23
|
|
4
|
+
Author: smarcet@gmail.com
|
|
5
|
+
Status: VERIFIED
|
|
6
|
+
Approved: Yes
|
|
7
|
+
Iterations: 0
|
|
8
|
+
Worktree: No
|
|
9
|
+
Type: Bugfix
|
|
10
|
+
|
|
11
|
+
## Summary
|
|
12
|
+
|
|
13
|
+
**Symptom:** After uploading a file via UploadInputV3 that triggers HTTP 202 (async server processing), the file immediately shows as "Complete" instead of waiting for polling to confirm the server has finished processing.
|
|
14
|
+
**Trigger:** Upload a file via UploadInputV3. Server returns HTTP 202 with `file_id`. Dropzone's `success` event fires immediately (before polling starts or completes), and V3's `handleFileCompleted` marks the file as `complete: true`.
|
|
15
|
+
**Root Cause:** `src/components/inputs/upload-input-v3/index.js:153` — `handleFileCompleted()` unconditionally marks the file as `complete: true` when Dropzone's `success` event fires, without checking `file._asyncProcessing`. For non-chunked 202 uploads, `_finished()` is called directly by Dropzone (`dropzone.js:2707`), bypassing the `chunksUploaded` override that defers completion for chunked uploads.
|
|
16
|
+
|
|
17
|
+
**Secondary issue:** `wrappedOnUploadComplete` at line 190 is a passthrough — it doesn't mark uploading files as complete. When polling finishes, `onUploadComplete` fires but the `uploadingFiles` entry isn't cleaned up because `complete` was never set (if the guard blocks `handleFileCompleted`).
|
|
18
|
+
|
|
19
|
+
## Investigation
|
|
20
|
+
|
|
21
|
+
- **V3-specific bug.** V2 has zero handling for `success`/`onFileCompleted`/`complete` — it renders only from the `value` prop, which is updated after `onUploadComplete` (correctly waits for polling in the 202 case).
|
|
22
|
+
- **Flow for non-chunked 202:** `xhr.onload` → `file._asyncProcessing = true` → `dropzoneOnLoad(e)` → `_finishedUploading` → `!chunked` → `_finished(files, response, e)` → emits `success` → DropzoneV3 `success` handler → `onFileCompleted` → `handleFileCompleted` marks `complete: true` → file shows "Complete" immediately. Polling starts AFTER `dropzoneOnLoad` returns, but the file already appears done.
|
|
23
|
+
- **Chunking decision:** `file.upload.chunked = options.chunking && (options.forceChunking || file.size > options.chunkSize)`. Config sets `chunking: true` but not `forceChunking` (default `false`), and `chunkSize` defaults to 2MB. Files ≤ 2MB are uploaded as non-chunked, bypassing the `chunksUploaded` override entirely.
|
|
24
|
+
- **`chunksUploaded` override** at `dropzone/index.js:164` correctly defers `done()` for chunked 202 uploads, but is never called for non-chunked uploads.
|
|
25
|
+
- **`wrappedOnUploadComplete`** at `upload-input-v3/index.js:190` just passes through to the parent — it doesn't update `uploadingFiles` state at all.
|
|
26
|
+
|
|
27
|
+
## Behavior Contract
|
|
28
|
+
|
|
29
|
+
**Given:** UploadInputV3 with a file upload that returns HTTP 202 (async processing), where `file._asyncProcessing` is set to `true` on the Dropzone file object
|
|
30
|
+
**When:** Dropzone's `success` event fires (immediately after `_finishedUploading` calls `_finished` for non-chunked uploads, or via `chunksUploaded` for chunked uploads)
|
|
31
|
+
**Currently (bug):** `handleFileCompleted` marks the file as `complete: true` immediately, showing "Complete" in the UI before polling confirms server processing is done
|
|
32
|
+
**Expected (fix):** When `file._asyncProcessing` is true, `handleFileCompleted` does NOT mark the file as complete. The file stays in "Loading" state. When polling finishes and `onUploadComplete` fires (via `wrappedOnUploadComplete`), uploading files are marked complete, enabling the existing useEffect cleanup to remove them when `value` updates.
|
|
33
|
+
**Anti-regression:** Synchronous uploads (HTTP 200) must still mark files as "Complete" immediately via `handleFileCompleted`. File deletion, error display, progress tracking, and the existing server-renamed-filename cleanup must remain intact.
|
|
34
|
+
|
|
35
|
+
## Fix Approach
|
|
36
|
+
|
|
37
|
+
**Chosen:** Guard in V3 handlers
|
|
38
|
+
**Why:** The `_asyncProcessing` flag is already set on the Dropzone file object before `success` fires. V3 just needs to check it in `handleFileCompleted` and mark files complete in `wrappedOnUploadComplete`. No changes to DropzoneJS or DropzoneV3 — purely V3 state management.
|
|
39
|
+
**Alternatives considered:**
|
|
40
|
+
- *Guard in DropzoneV3 success handler* — would work but touches the shared wrapper file used by all upload versions, increasing regression surface.
|
|
41
|
+
- *Override _finishedUploading in DropzoneJS* — fixes at the Dropzone level but patches library internals, fragile across Dropzone upgrades.
|
|
42
|
+
|
|
43
|
+
**Files:**
|
|
44
|
+
- `src/components/inputs/upload-input-v3/index.js` (primary fix — handleFileCompleted guard + wrappedOnUploadComplete)
|
|
45
|
+
- `src/components/inputs/upload-input-v3/__tests__/upload-input-v3.test.js` (reproducing test)
|
|
46
|
+
|
|
47
|
+
**Strategy:**
|
|
48
|
+
1. In `handleFileCompleted`: check `file._asyncProcessing` — if true, return early (don't mark complete)
|
|
49
|
+
2. In `wrappedOnUploadComplete`: before calling the parent callback, mark all uploading files as `complete: true` via `setUploadingFiles`. This ensures cleanup works for both sync (where `handleFileCompleted` already set it) and async (where it was skipped).
|
|
50
|
+
|
|
51
|
+
**Tests:** Add test to `upload-input-v3.test.js` that simulates: file added → file completed with `_asyncProcessing: true` → verify file still shows "Loading" (not "Complete").
|
|
52
|
+
|
|
53
|
+
## Verification Scenario
|
|
54
|
+
|
|
55
|
+
### TS-001: Async Upload Processing State
|
|
56
|
+
**Preconditions:** UploadInputV3 with `maxFiles=1`, server configured to return HTTP 202 for uploads
|
|
57
|
+
|
|
58
|
+
| Step | Action | Expected Result (after fix) |
|
|
59
|
+
|------|--------|-----------------------------|
|
|
60
|
+
| 1 | Upload a file that triggers HTTP 202 async processing | File shows "Loading" with progress bar, NOT "Complete" |
|
|
61
|
+
| 2 | Wait for polling to return `status: 'complete'` and parent to update value | File transitions to "Complete" (from value section), no duplicate entries |
|
|
62
|
+
|
|
63
|
+
## Progress
|
|
64
|
+
|
|
65
|
+
- [x] Task 1: Write Reproducing Test (RED)
|
|
66
|
+
- [x] Task 2: Implement Fix at Root Cause
|
|
67
|
+
- [x] Task 3: Quality Gate
|
|
68
|
+
**Tasks:** 3 | **Done:** 3
|
|
69
|
+
|
|
70
|
+
## Tasks
|
|
71
|
+
|
|
72
|
+
### Task 1: Write Reproducing Test (RED)
|
|
73
|
+
|
|
74
|
+
**Objective:** Encode the Behavior Contract as a failing test BEFORE writing any fix code.
|
|
75
|
+
**Files:** `src/components/inputs/upload-input-v3/__tests__/upload-input-v3.test.js`
|
|
76
|
+
**Entry point:** `UploadInputV3` component (rendered with mock DropzoneV3)
|
|
77
|
+
**Test scenario:**
|
|
78
|
+
1. Render UploadInputV3 with `maxFiles=1` and `value=[]`
|
|
79
|
+
2. Simulate `onAddedFile({ name: 'video.mp4', size: 5000000 })`
|
|
80
|
+
3. Simulate `onFileCompleted({ name: 'video.mp4', size: 5000000, _asyncProcessing: true })`
|
|
81
|
+
4. Assert: file still shows "Loading" (not "Complete") — the `_asyncProcessing` flag should prevent marking as complete
|
|
82
|
+
**DoD:** Test exists, named `test('does not mark file as complete when _asyncProcessing is true')`, runs, fails because `handleFileCompleted` currently ignores `_asyncProcessing` and marks complete unconditionally.
|
|
83
|
+
**Verify:** `npx jest src/components/inputs/upload-input-v3/__tests__/upload-input-v3.test.js --verbose`
|
|
84
|
+
|
|
85
|
+
### Task 2: Implement Fix at Root Cause
|
|
86
|
+
|
|
87
|
+
**Objective:** Minimal change to `handleFileCompleted` and `wrappedOnUploadComplete` to prevent premature completion on HTTP 202.
|
|
88
|
+
**Files:** `src/components/inputs/upload-input-v3/index.js`
|
|
89
|
+
**Strategy:**
|
|
90
|
+
1. In `handleFileCompleted` (line 153): add early return when `file._asyncProcessing` is true
|
|
91
|
+
2. In `wrappedOnUploadComplete` (line 190): add `setUploadingFiles(prev => prev.map(f => ({ ...f, complete: true })))` before calling the parent callback, so all uploading files are marked complete when the server confirms processing is done
|
|
92
|
+
**DoD:** Reproducing test PASSES. Full test suite PASSES. Diff touches root-cause file only.
|
|
93
|
+
**Verify:** `npx jest --verbose`
|
|
94
|
+
|
|
95
|
+
### Task 3: Quality Gate
|
|
96
|
+
|
|
97
|
+
**Objective:** Full suite re-run, build clean.
|
|
98
|
+
**DoD:** Full suite green, build succeeds, no performance regressions.
|
|
99
|
+
**Verify:** `npx jest --verbose && npm run build-dev`
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define("openstack-uicore-foundation",[],t):"object"==typeof exports?exports["openstack-uicore-foundation"]=t():e["openstack-uicore-foundation"]=t()}(this,(()=>(()=>{"use strict";var e={2593:(e,t,s)=>{s.d(t,{default:()=>p});var i=s(1116),r=s.n(i),n=s(2015),o=s.n(n),a=s(5812),l=s.n(a),h=s(8311),c=s(9558);class u extends o().Component{constructor(e){super(e),r()(this,"processServerTimeResponse",((e,t)=>{const s=l()().unix();let i=s;e&&(i=e.timestamp+(s-t)),this._isMounted&&(console.log(`Clock::processServerTimeResponse setting timestamp ${i}`),this.setState({timestamp:i})),this.props.onTick&&this.props.onTick(i)})),r()(this,"processServerTimeResponseError",(()=>{let e=l()().unix();this._isMounted&&(console.log(`Clock::processServerTimeResponseError setting timestamp ${e}`),this.setState({timestamp:e})),this.props.onTick&&this.props.onTick(e)})),r()(this,"getServerTime",(()=>{const e=(0,c.getTimeServiceUrl)();return fetch(`${e}`).then((async e=>200===e.status?e.json():Promise.reject(null))).catch((e=>(console.log(e),Promise.reject(e))))})),r()(this,"tick",(()=>{const{timestamp:e}=this.state;null!==e&&(this.props.onTick&&this.props.onTick(e+1),this._isMounted&&this.setState({timestamp:e+1}))})),r()(this,"now",(()=>this.state.timestamp)),this.fragmentParser=new h.default,this.interval=null,this.state={timestamp:null,manualSet:!1},this._isMounted=!1,this.onVisibilityChange=this.onVisibilityChange.bind(this)}componentDidMount(){this._isMounted=!0;const{timezone:e="UTC",now:t}=this.props,s=this.fragmentParser.getParam("now"),i=l().tz(s,"YYYY-MM-DD,hh:mm:ss",e);let r=null,n=!1;if(i.isValid())r=i.valueOf()/1e3,console.log(`Clock::componentDidMount nowQS ${s} is valid setting timestamp ${r}`),n=!0;else if(t)r=t;else{const e=l()().unix();this.getServerTime().then((t=>this.processServerTimeResponse(t,e))).catch((()=>this.processServerTimeResponseError()))}r&&(this.setState({timestamp:r,manualSet:n}),this.props.onTick&&this.props.onTick(r)),this.interval=setInterval(this.tick,1e3),document.addEventListener("visibilitychange",this.onVisibilityChange,!1)}onVisibilityChange(){const e=document.visibilityState,{manualSet:t}=this.state;if("visible"===e){if(console.log(`Clock::onVisibilityChange manualSet ${t}`),t)return;const e=l()().unix();this.getServerTime().then((t=>this.processServerTimeResponse(t,e))).catch((()=>this.processServerTimeResponseError()))}}componentWillUnmount(){this._isMounted=!1,clearInterval(this.interval),document.removeEventListener("visibilitychange",this.onVisibilityChange),this.interval=null}render(){const{display:e,timezone:t="UTC"}=this.props,{timestamp:s}=this.state;return e&&s?o().createElement("div",{style:{marginTop:"50px",textAlign:"center",fontSize:"20px"}},l().tz(1e3*s,t).format("YYYY-MM-DD hh:mm:ss")):null}}const p=u},8311:(e,t,s)=>{s.d(t,{default:()=>o});var i=s(1116),r=s.n(i);function n(e,t){var s=Object.keys(e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);t&&(i=i.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),s.push.apply(s,i)}return s}class o{constructor(){this.originalHash="",this.hash={}}convertToHash(e){let t=(e=e.substr(1).toLowerCase()).split("&"),s={};for(let e of t){if(e=e.split("="),2!==e.length)continue;let t=e[1].trim();""!==t&&("true"!==t&&"false"!==t||(e[1]="true"==t),s[e[0]]=e[1])}return s}clearParams(){this.originalHash="",this.hash={}}getParam(e){return"undefined"!=typeof window&&this.originalHash!==window.location.hash&&(this.originalHash=window.location.hash,this.hash=this.convertToHash(this.originalHash)),this.hash.hasOwnProperty(e)?this.hash[e]:null}getParams(){return"undefined"!=typeof window&&this.originalHash!==window.location.hash&&(this.originalHash=window.location.hash,this.hash=this.convertToHash(this.originalHash)),function(e){for(var t=1;t<arguments.length;t++){var s=null!=arguments[t]?arguments[t]:{};t%2?n(Object(s),!0).forEach((function(t){r()(e,t,s[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(s)):n(Object(s)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(s,t))}))}return e}({},this.hash)}deleteParam(e){var t=this.getParams();this.clearParams();for(let s in t)s!=e&&(this.hash[s]=t[s])}deleteParams(e){var t=this.getParams();this.clearParams();for(let s in t)e.includes(s)||(this.hash[s]=t[s])}setParam(e,t){return"undefined"!=typeof window&&this.originalHash!==window.location.hash&&(this.originalHash=window.location.hash,this.hash=this.convertToHash(this.originalHash)),null!==t&&""!==t?this.hash[e]=t:delete this.hash[e],this}serialize(){let e="";for(let t in this.hash){""!==e&&(e+="&"),e+=t+"="+this.hash[t]}return e}}},5890:(e,t,s)=>{s.d(t,{createExternalStore:()=>l});var i=s(2015),r=s.n(i);const n=require("use-sync-external-store/shim"),o=require("use-sync-external-store/shim/with-selector"),a=(e,t)=>e===t;function l(e="ExternalStore"){const t=(0,i.createContext)(null),s=()=>{const s=(0,i.useContext)(t);if(null===s)throw new Error(`${e} hooks must be used within their Provider`);return s};return{Provider:({initialValue:e=null,children:s})=>{const n=(0,i.useRef)(e),o=(0,i.useRef)(new Set),a=(0,i.useCallback)((e=>(o.current.add(e),()=>o.current.delete(e))),[]),l=(0,i.useCallback)((()=>n.current),[]),h=(0,i.useCallback)((e=>{n.current=e,o.current.forEach((e=>e()))}),[]),c=(0,i.useMemo)((()=>({subscribe:a,getSnapshot:l})),[a,l]);return r().createElement(t.Provider,{value:c},"function"==typeof s?s(h):s)},useValue:()=>{const{subscribe:e,getSnapshot:t}=s();return(0,n.useSyncExternalStore)(e,t)},useSelector:(e,t=a)=>{const{subscribe:i,getSnapshot:r}=s();return(0,o.useSyncExternalStoreWithSelector)(i,r,r,e,t)}}}},9558:(e,t,s)=>{s.d(t,{getTimeServiceUrl:()=>i});s(5812),s(8041);const i=()=>"undefined"!=typeof window?window.TIMEINTERVALSINCE1970_API_URL||process.env.TIMEINTERVALSINCE1970_API_URL:null},1116:e=>{e.exports=require("@babel/runtime/helpers/defineProperty")},5812:e=>{e.exports=require("moment-timezone")},2015:e=>{e.exports=require("react")},8041:e=>{e.exports=require("urijs")}},t={};function s(i){var r=t[i];if(void 0!==r)return r.exports;var n=t[i]={exports:{}};return e[i](n,n.exports,s),n.exports}(()=>{s.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return s.d(t,{a:t}),t}})(),(()=>{s.d=(e,t)=>{for(var i in t)s.o(t,i)&&!s.o(e,i)&&Object.defineProperty(e,i,{enumerable:!0,get:t[i]})}})(),(()=>{s.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t)})(),(()=>{s.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})}})();var i={};s.r(i),s.d(i,{ClockProvider:()=>u,useClock:()=>h,useClockSelector:()=>c});var r=s(2015),n=s.n(r),o=s(5890),a=s(2593);const{Provider:l,useValue:h,useSelector:c}=(0,o.createExternalStore)("Clock"),u=({timezone:e,now:t,children:s})=>n().createElement(l,{initialValue:t},(i=>n().createElement(n().Fragment,null,n().createElement(a.default,{onTick:i,timezone:e,now:t}),s)));return i})()));
|
|
2
|
+
//# sourceMappingURL=clock-context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"components/clock-context.js","mappings":"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","sources":["webpack://openstack-uicore-foundation/webpack/universalModuleDefinition","webpack://openstack-uicore-foundation/./src/components/clock.js","webpack://openstack-uicore-foundation/./src/components/fragment-parser.js","webpack://openstack-uicore-foundation/external commonjs \"use-sync-external-store/shim\"","webpack://openstack-uicore-foundation/external commonjs \"use-sync-external-store/shim/with-selector\"","webpack://openstack-uicore-foundation/./src/utils/external-store.js","webpack://openstack-uicore-foundation/./src/utils/methods.js","webpack://openstack-uicore-foundation/external commonjs \"@babel/runtime/helpers/defineProperty\"","webpack://openstack-uicore-foundation/external commonjs \"moment-timezone\"","webpack://openstack-uicore-foundation/external commonjs \"react\"","webpack://openstack-uicore-foundation/external commonjs \"urijs\"","webpack://openstack-uicore-foundation/webpack/bootstrap","webpack://openstack-uicore-foundation/webpack/runtime/compat get default export","webpack://openstack-uicore-foundation/webpack/runtime/define property getters","webpack://openstack-uicore-foundation/webpack/runtime/hasOwnProperty shorthand","webpack://openstack-uicore-foundation/webpack/runtime/make namespace object","webpack://openstack-uicore-foundation/./src/components/clock-context.js"],"sourcesContent":["(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory();\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine(\"openstack-uicore-foundation\", [], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"openstack-uicore-foundation\"] = factory();\n\telse\n\t\troot[\"openstack-uicore-foundation\"] = factory();\n})(this, () => {\nreturn ","/**\n * Copyright 2020 OpenStack Foundation\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n **/\nimport React from 'react';\nimport moment from \"moment-timezone\";\nimport FragmentParser from \"./fragment-parser\";\nimport {getTimeServiceUrl} from '../utils/methods';\n\n/**\n * class Clock\n */\nclass Clock extends React.Component {\n\n constructor(props) {\n super(props);\n this.fragmentParser = new FragmentParser();\n this.interval = null;\n this.state = {\n timestamp: null,\n manualSet : false,\n }\n this._isMounted = false;\n this.onVisibilityChange = this.onVisibilityChange.bind(this);\n }\n\n /**\n * @param response\n * @param localBefore\n */\n processServerTimeResponse = (response, localBefore) => {\n const localAfter = moment().unix();\n let timestamp = localAfter;\n if (response) {\n timestamp = response.timestamp + (localAfter - localBefore);\n }\n if(this._isMounted) {\n console.log(`Clock::processServerTimeResponse setting timestamp ${timestamp}`)\n this.setState({timestamp});\n }\n if(this.props.onTick)\n this.props.onTick(timestamp);\n }\n\n processServerTimeResponseError = () => {\n const localAfter = moment().unix();\n let timestamp = localAfter;\n if(this._isMounted) {\n console.log(`Clock::processServerTimeResponseError setting timestamp ${timestamp}`)\n this.setState({timestamp});\n }\n if(this.props.onTick)\n this.props.onTick(timestamp);\n }\n\n componentDidMount() {\n this._isMounted = true;\n const {timezone = 'UTC', now} = this.props;\n const nowQS = this.fragmentParser.getParam('now');\n const momentQS = moment.tz(nowQS, 'YYYY-MM-DD,hh:mm:ss', timezone);\n let timestamp = null;\n let manualSet = false;\n if (momentQS.isValid()) {\n timestamp = momentQS.valueOf() / 1000;\n console.log(`Clock::componentDidMount nowQS ${nowQS} is valid setting timestamp ${timestamp}`);\n manualSet = true;\n } else if (now) {\n timestamp = now\n } else {\n const localBefore = moment().unix();\n this.getServerTime()\n .then((response) => this.processServerTimeResponse(response, localBefore))\n .catch(() => this.processServerTimeResponseError())\n }\n\n if(timestamp) {\n this.setState({timestamp, manualSet});\n if(this.props.onTick)\n this.props.onTick(timestamp);\n }\n\n this.interval = setInterval(this.tick, 1000);\n document.addEventListener(\"visibilitychange\", this.onVisibilityChange, false)\n }\n\n onVisibilityChange() {\n const visibilityState = document.visibilityState;\n const {manualSet} = this.state;\n if (visibilityState === \"visible\") {\n console.log(`Clock::onVisibilityChange manualSet ${manualSet}`)\n if(manualSet) return;\n const localBefore = moment().unix();\n this.getServerTime()\n .then((response) => this.processServerTimeResponse(response, localBefore))\n .catch(() => this.processServerTimeResponseError());\n }\n }\n\n componentWillUnmount() {\n this._isMounted = false;\n clearInterval(this.interval);\n document.removeEventListener(\"visibilitychange\", this.onVisibilityChange)\n this.interval = null;\n }\n\n getServerTime = () => {\n const timeServiceUrl = getTimeServiceUrl();\n return fetch(`${timeServiceUrl}`).then(async (response) => {\n if (response.status === 200) {\n return response.json();\n }\n return Promise.reject(null);\n })\n .catch(err => {\n console.log(err);\n return Promise.reject(err);\n });\n };\n\n tick = () => {\n const {timestamp} = this.state;\n if (timestamp !== null) {\n if(this.props.onTick)\n this.props.onTick(timestamp + 1);\n if(this._isMounted)\n this.setState({timestamp: timestamp + 1})\n }\n };\n\n // epoch utc time in seconds\n now = () => {\n return this.state.timestamp;\n };\n\n render() {\n const {display, timezone = 'UTC'} = this.props;\n const {timestamp} = this.state;\n\n if (!display || !timestamp) return null;\n\n return (\n <div style={{marginTop: '50px', textAlign: 'center', fontSize: '20px'}}>\n {moment.tz(timestamp * 1000, timezone).format('YYYY-MM-DD hh:mm:ss')}\n </div>\n );\n }\n\n}\n\nexport default Clock;\n","\nexport default class FragmentParser {\n\n constructor(){\n this.originalHash = '';\n this.hash = {};\n }\n\n convertToHash(strHash)\n {\n strHash = strHash.substr(1).toLowerCase();\n let params = strHash.split('&');\n let res = {};\n for(let param of params)\n {\n param = param.split('=');\n if(param.length !== 2) continue;\n let val = param[1].trim();\n if(val === '') continue;\n\n if (val === 'true' || val === 'false')\n param[1] = val == 'true';\n\n res[param[0]] = param[1];\n }\n return res;\n }\n\n clearParams(){\n this.originalHash = '';\n this.hash = {};\n }\n\n getParam(key){\n if(typeof window !== 'undefined' && this.originalHash !== window.location.hash){\n this.originalHash = window.location.hash;\n this.hash = this.convertToHash(this.originalHash);\n }\n\n if(!this.hash.hasOwnProperty(key) ) return null;\n return this.hash[key];\n }\n\n getParams(){\n if(typeof window !== 'undefined' && this.originalHash !== window.location.hash){\n this.originalHash = window.location.hash;\n this.hash = this.convertToHash(this.originalHash);\n }\n\n return { ... this.hash };\n }\n\n deleteParam(param){\n var hash = this.getParams();\n this.clearParams();\n for(let key in hash) {\n if(key == param) continue;\n this.hash[key] = hash[key];\n }\n }\n\n deleteParams(params){\n var hash = this.getParams();\n this.clearParams();\n for(let key in hash) {\n if(params.includes(key)) continue;\n this.hash[key] = hash[key];\n }\n }\n\n setParam(key, value){\n if(typeof window !== 'undefined' && this.originalHash !== window.location.hash){\n this.originalHash = window.location.hash;\n this.hash = this.convertToHash(this.originalHash);\n }\n if(value !== null && value !== '')\n this.hash[key] = value;\n else\n delete this.hash[key];\n return this;\n }\n\n serialize(){\n let res = '';\n for(let key in this.hash)\n {\n let val = this.hash[key];\n if(res !== '') res += '&';\n res += key+'='+val;\n }\n return res;\n }\n}\n","const __WEBPACK_NAMESPACE_OBJECT__ = require(\"use-sync-external-store/shim\");","const __WEBPACK_NAMESPACE_OBJECT__ = require(\"use-sync-external-store/shim/with-selector\");","/**\n * Copyright 2026 OpenStack Foundation\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * createExternalStore - Factory for creating React-optimized external stores.\n *\n * Problem:\n * When a frequently-updating data source (clock, WebSocket, polling, etc.)\n * pushes its value into shared state (global store, lifted useState, or a\n * context value), every consuming component re-renders on every update,\n * even when they only care about a derived condition that rarely changes.\n *\n * Solution:\n * createExternalStore() returns a Provider and hooks that store the value\n * in a ref (no re-renders) and use useSyncExternalStore so components\n * can opt in to updates selectively:\n *\n * - useValue() → re-renders on every update\n * - useSelector(compute, isEqual) → re-renders only when the computed result changes\n *\n * Components that don't call either hook are never affected by updates.\n *\n * How it works:\n * 1. The Provider stores the value in a ref (writing to a ref never triggers\n * a React re-render) and keeps a Set of listener callbacks.\n * 2. When emit(value) is called, the ref is updated and all listeners are\n * notified. These listeners come from useSyncExternalStore.\n * 3. useSyncExternalStore (React 18, shimmed for 16/17) calls getSnapshot()\n * to read the ref, compares with the previous value, and only re-renders\n * the component if the value changed.\n * 4. useMemo adds a layer on top: it runs a compute function on the raw value\n * and only re-renders if the computed result changed (checked via isEqual).\n *\n * API:\n * createExternalStore(name) returns:\n *\n * - Provider Wraps your component tree. Pass children as a render function\n * to receive the emit callback: (emit) => JSX. Call emit(value)\n * each time your data source has a new value.\n *\n * - useValue() Returns the latest emitted value. The component re-renders\n * on every emit.\n *\n * - useSelector(compute, isEqual?)\n * Returns a derived value. compute(rawValue) runs on every emit,\n * but the component only re-renders when isEqual returns false\n * (default: ===). Useful when you need to derive something that\n * changes less frequently than the raw value.\n *\n * The name parameter is used in error messages. For example,\n * createExternalStore('Clock') throws \"Clock hooks must be used within\n * their Provider\" when a hook is called outside the Provider.\n *\n * For clock-specific usage:\n * A pre-built clock store is available at:\n * import { ClockProvider, useClock, useClockSelector } from 'openstack-uicore-foundation/lib/components/clock-context';\n * This wires createExternalStore to the Clock component so projects don't\n * have to repeat that boilerplate.\n *\n * Custom store example:\n * import { createExternalStore } from 'openstack-uicore-foundation/lib/utils/external-store';\n *\n * const { Provider, useValue, useSelector } = createExternalStore('WebSocket');\n *\n * const WebSocketProvider = ({ url, children }) => (\n * <Provider>\n * {(emit) => (\n * <>\n * <WebSocketSource url={url} onMessage={emit} />\n * {children}\n * </>\n * )}\n * </Provider>\n * );\n *\n * // Re-renders on every message:\n * const message = useValue();\n *\n * // Re-renders only when the derived value changes:\n * const isActive = useSelector((msg) => msg?.status === 'active');\n **/\n\nimport React, { createContext, useContext, useRef, useCallback, useMemo as reactUseMemo } from 'react';\n// Shim for React 16/17 compatibility, falls back to native in React 18+\nimport { useSyncExternalStore } from 'use-sync-external-store/shim';\nimport { useSyncExternalStoreWithSelector } from 'use-sync-external-store/shim/with-selector';\n\nconst strictEqual = (a, b) => a === b;\n\n/**\n * Creates an external store with a Provider and subscription hooks.\n *\n * @param {string} name - Store name, used in error messages (e.g., \"Clock\", \"WebSocket\")\n * @returns {{ Provider, useValue, useSelector }}\n */\nexport function createExternalStore(name = 'ExternalStore') {\n const Context = createContext(null);\n\n const useStoreContext = () => {\n const context = useContext(Context);\n if (context === null) {\n throw new Error(`${name} hooks must be used within their Provider`);\n }\n return context;\n };\n\n /**\n * Provider - Wraps your component tree and provides the store.\n *\n * Pass children as a render function to receive the `emit` callback:\n * <Provider>{(emit) => <Source onUpdate={emit} />}</Provider>\n *\n * Or pass children normally if you wire emit externally.\n *\n * Pass `initialValue` to seed the store synchronously so that hooks\n * called before the first emit see a real value instead of null.\n */\n const Provider = ({ initialValue = null, children }) => {\n const valueRef = useRef(initialValue);\n const listenersRef = useRef(new Set());\n\n const subscribe = useCallback((callback) => {\n listenersRef.current.add(callback);\n return () => listenersRef.current.delete(callback);\n }, []);\n\n const getSnapshot = useCallback(() => valueRef.current, []);\n\n const emit = useCallback((value) => {\n valueRef.current = value;\n listenersRef.current.forEach(listener => listener());\n }, []);\n\n const contextValue = reactUseMemo(() => ({ subscribe, getSnapshot }), [subscribe, getSnapshot]);\n\n return (\n <Context.Provider value={contextValue}>\n {typeof children === 'function' ? children(emit) : children}\n </Context.Provider>\n );\n };\n\n /**\n * useValue - Subscribe to every update.\n * Component re-renders each time emit() is called.\n *\n * @returns {*} The current value, or null before first emit\n */\n const useValue = () => {\n const { subscribe, getSnapshot } = useStoreContext();\n return useSyncExternalStore(subscribe, getSnapshot);\n };\n\n /**\n * useSelector - Subscribe with a selector function.\n * Only re-renders when the selected/derived value changes.\n *\n * @param {Function} compute - (value) => derivedValue\n * @param {Function} isEqual - Optional equality function (default: ===)\n * @returns {*} The computed value\n */\n const useSelector = (compute, isEqual = strictEqual) => {\n const { subscribe, getSnapshot } = useStoreContext();\n return useSyncExternalStoreWithSelector(\n subscribe,\n getSnapshot,\n getSnapshot,\n compute,\n isEqual\n );\n };\n\n return { Provider, useValue, useSelector };\n}\n","/**\n * Copyright 2018 OpenStack Foundation\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n **/\n\nimport moment from 'moment-timezone';\nimport URI from \"urijs\";\n\nexport const findElementPos = (obj) => {\n var curtop = -70;\n if (obj.offsetParent) {\n do {\n curtop += obj.offsetTop;\n } while (obj = obj.offsetParent);\n return [curtop];\n }\n};\n\nexport const epochToMoment = (atime) => {\n if(!atime) return atime;\n atime = atime * 1000;\n return moment(atime);\n};\n\nexport const epochToMomentTimeZone = (atime, time_zone) => {\n if(!atime) return atime;\n atime = atime * 1000;\n return moment(atime).tz(time_zone);\n};\n\nexport const formatEpoch = (atime, format = 'M/D/YYYY h:mm a') => {\n if(!atime) return atime;\n return epochToMoment(atime).format(format);\n};\n\nexport const parseLocationHour = (hour) => {\n let parsedHour = hour.toString();\n if(parsedHour.length < 4) parsedHour = `0${parsedHour}`;\n parsedHour = parsedHour.match(/.{2}/g);\n parsedHour = parsedHour.join(':');\n return parsedHour;\n}\n\nexport const objectToQueryString = (obj) => {\n var str = \"\";\n for (var key in obj) {\n if (str != \"\") {\n str += \"&\";\n }\n str += key + \"=\" + encodeURIComponent(obj[key]);\n }\n\n return str;\n};\n\nexport const getBackURL = () => {\n let url = URI(window.location.href);\n let query = url.search(true);\n let fragment = url.fragment();\n let backUrl = query.hasOwnProperty('BackUrl') ? query['BackUrl'] : null;\n if(backUrl != null && fragment != null && fragment != ''){\n backUrl += `#${fragment}`;\n }\n return backUrl;\n};\n\nexport const toSlug = (text) =>{\n text = text.toLowerCase();\n return text.replace(/[^a-zA-Z0-9]+/g,'_');\n}\n\nexport const getAuthCallback = () => {\n if(typeof window !== 'undefined') {\n return `${window.location.origin}/auth/callback`;\n }\n return null;\n};\n\nexport const getCurrentLocation = () => {\n let location = '';\n if(typeof window !== 'undefined') {\n location = window.location;\n // check if we are on iframe\n if (window.top)\n location = window.top.location;\n }\n return location;\n};\n\nexport const getOrigin = () => {\n if(typeof window !== 'undefined') {\n return window.location.origin;\n }\n return null;\n};\n\nexport const getCurrentPathName = () => {\n if(typeof window !== 'undefined') {\n return window.location.pathname;\n }\n return null;\n};\n\nexport const getCurrentHref = () => {\n if(typeof window !== 'undefined') {\n return window.location.href;\n }\n return null;\n};\n\nexport const getAllowedUserGroups = () => {\n if(typeof window !== 'undefined') {\n return window.ALLOWED_USER_GROUPS || '';\n }\n return null;\n};\n\nexport const buildAPIBaseUrl = (relativeUrl) => {\n if(typeof window !== 'undefined'){\n return `${window.API_BASE_URL}${relativeUrl}`;\n }\n return null``;\n};\n\nexport const putOnLocalStorage = (key, value) => {\n if(typeof window !== 'undefined') {\n window.localStorage.setItem(key, value);\n }\n};\n\nexport const getFromLocalStorage = (key, removeIt) => {\n if(typeof window !== 'undefined') {\n let val = window.localStorage.getItem(key);\n if(removeIt){\n console.log(`getFromLocalStorage removing key ${key}`);\n removeFromLocalStorage(key);\n }\n return val;\n }\n return null;\n};\n\nexport const removeFromLocalStorage = (key) => {\n if(typeof window !== 'undefined') {\n window.localStorage.removeItem(key);\n }\n}\n\nexport const isClearingSessionState = () => {\n if(typeof window !== 'undefined') {\n return window.clearing_session_state;\n }\n return false;\n};\n\nexport const setSessionClearingState = (val) => {\n if(typeof window !== 'undefined') {\n window.clearing_session_state = val;\n }\n};\n\nexport const getCurrentUserLanguage = () => {\n let language = 'en';\n if(typeof navigator !== 'undefined') {\n language = (navigator.languages && navigator.languages[0]) || navigator.language || navigator.userLanguage;\n }\n return language;\n};\n\nexport const scrollToError = (errors) => {\n if(Object.keys(errors).length > 0) {\n const firstError = Object.keys(errors)[0];\n const firstNode = document.getElementById(firstError);\n if (firstNode) window.scrollTo(0, findElementPos(firstNode));\n }\n};\n\nexport const hasErrors = (field, errors) => {\n if(field in errors) {\n return errors[field];\n }\n return '';\n};\n\nexport const shallowEqual = (object1, object2) => {\n const keys1 = Object.keys(object1);\n const keys2 = Object.keys(object2);\n\n if (keys1.length !== keys2.length) {\n return false;\n }\n\n for (let key of keys1) {\n if (object1[key] !== object2[key]) {\n return false;\n }\n }\n\n return true;\n};\n\nexport const arraysEqual = (a1, a2) =>\n a1.length === a2.length && a1.every((o, idx) => shallowEqual(o, a2[idx]));\n\nexport const isEmpty = (obj) => {\n return Object.keys(obj).length === 0;\n};\n\n\nexport const base64URLEncode = (str) => {\n return str\n .toString('base64')\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_')\n .replace(/=/g, '')\n}\n\nexport const retryPromise = async (\n cb,\n maxNumberOfRetries = 3\n) => {\n for (let i = 0; i < maxNumberOfRetries; i++) {\n if (await cb()) {\n return true;\n }\n }\n\n return false;\n}\n\nexport const getTimeServiceUrl = () => {\n if(typeof window !== 'undefined') {\n return window.TIMEINTERVALSINCE1970_API_URL || process.env.TIMEINTERVALSINCE1970_API_URL;\n }\n return null;\n};\n\nexport const getEventLocation = (event, summitVenueCount, summitShowLocDate = null, nowUtc = null) => {\n const shouldShowVenues = (summitShowLocDate && nowUtc) ? summitShowLocDate * 1000 < nowUtc : true;\n const locationName = [];\n const { location } = event;\n\n if (!shouldShowVenues) return 'TBA';\n\n if (!location) return 'TBA';\n\n if (summitVenueCount > 1 && location.venue?.name) locationName.push(location.venue.name);\n if (location.floor?.name) locationName.push(location.floor.name);\n if (location.name) locationName.push(location.name);\n\n return locationName.length > 0 ? locationName.join(' - ') : 'TBA';\n};\n\nexport const getEventHosts = (event) => {\n let hosts = [];\n if (event.speakers?.length > 0) {\n hosts = [...event.speakers];\n }\n if (event.moderator) hosts.push(event.moderator);\n\n return hosts;\n};\n\nconst loadImage = async url => {\n const img = document.createElement('img')\n img.src = url\n img.crossOrigin = 'anonymous'\n\n return new Promise((resolve, reject) => {\n img.onload = () => resolve(img)\n img.onerror = reject\n })\n}\n\nexport const convertSVGtoImg = async (svgUrl) => {\n const img = await loadImage(svgUrl)\n const newWidth = 100\n const newHeight = Math.floor(img.naturalHeight * 100 / img.naturalWidth)\n\n const canvas = document.createElement('canvas')\n canvas.width = newWidth\n canvas.height = newHeight\n canvas.getContext('2d').drawImage(img, 0, 0, newWidth, newHeight)\n\n const url = await canvas.toDataURL(`image/png`, 1.0)\n console.log(url, newWidth, newHeight);\n return {url, width: newWidth, height: newHeight}\n}\n\nexport const isRateEnabled = (value) =>\n value !== null && value !== undefined && value !== \"\";\n\n/**\n * Returns true if value is null, undefined, empty/whitespace string,\n * empty array, or empty object.\n */\nexport const empty = (value) => {\n if (value === null || value === undefined) return true;\n if (typeof value === \"string\") return value.trim().length === 0;\n if (Array.isArray(value)) return value.length === 0;\n if (typeof value === \"object\") return Object.keys(value).length === 0;\n return false;\n};\n\nexport const isSentryInitialized = () => typeof window !== \"undefined\" && !!window.SENTRY_DSN;\n","module.exports = require(\"@babel/runtime/helpers/defineProperty\");","module.exports = require(\"moment-timezone\");","module.exports = require(\"react\");","module.exports = require(\"urijs\");","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n","// getDefaultExport function for compatibility with non-harmony modules\n__webpack_require__.n = (module) => {\n\tvar getter = module && module.__esModule ?\n\t\t() => (module['default']) :\n\t\t() => (module);\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","/**\n * Copyright 2026 OpenStack Foundation\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * Pre-built clock store using createExternalStore.\n *\n * Wires the Clock component (server-synced, ticks every second) into a\n * createExternalStore instance. Components choose their update strategy:\n *\n * - useClock() re-renders every second (for countdowns, live displays)\n * - useClockSelector(compute) only re-renders when the computed result changes\n * - Components that use neither are never affected by clock ticks\n *\n * Usage:\n * import { ClockProvider, useClock, useClockSelector } from 'openstack-uicore-foundation/lib/components/clock-context';\n *\n * <ClockProvider timezone={summit.time_zone_id}>\n * <App />\n * </ClockProvider>\n *\n * const nowUtc = useClock();\n *\n * const phase = useClockSelector((nowUtc) => {\n * if (nowUtc < event.start) return 'before';\n * if (nowUtc <= event.end) return 'during';\n * return 'after';\n * });\n *\n * For custom (non-clock) stores, see createExternalStore in utils/external-store.js.\n **/\n\nimport React from 'react';\nimport { createExternalStore } from '../utils/external-store';\nimport Clock from './clock';\n\nconst { Provider, useValue: useClock, useSelector: useClockSelector } = createExternalStore('Clock');\n\n/**\n * ClockProvider - Wraps your app with server-synced clock context.\n *\n * @param {string} timezone - Timezone for the clock (e.g., \"America/New_York\")\n * @param {number} now - Optional initial timestamp (for testing or manual override)\n * @param {React.ReactNode} children - Child components\n */\nexport const ClockProvider = ({ timezone, now, children }) => (\n <Provider initialValue={now}>\n {(emit) => (\n <>\n <Clock onTick={emit} timezone={timezone} now={now} />\n {children}\n </>\n )}\n </Provider>\n);\n\nexport { useClock, useClockSelector };\n"],"names":["root","factory","exports","module","define","amd","this","Clock","React","constructor","props","super","_defineProperty","response","localBefore","localAfter","moment","unix","timestamp","_isMounted","console","log","setState","onTick","timeServiceUrl","getTimeServiceUrl","fetch","then","async","status","json","Promise","reject","catch","err","state","fragmentParser","FragmentParser","interval","manualSet","onVisibilityChange","bind","componentDidMount","timezone","now","nowQS","getParam","momentQS","isValid","valueOf","getServerTime","processServerTimeResponse","processServerTimeResponseError","setInterval","tick","document","addEventListener","visibilityState","componentWillUnmount","clearInterval","removeEventListener","render","display","style","marginTop","textAlign","fontSize","format","originalHash","hash","convertToHash","strHash","params","substr","toLowerCase","split","res","param","length","val","trim","clearParams","key","window","location","hasOwnProperty","getParams","_objectSpread","deleteParam","deleteParams","includes","setParam","value","serialize","require","strictEqual","a","b","createExternalStore","name","Context","createContext","useStoreContext","context","useContext","Error","Provider","initialValue","children","valueRef","useRef","listenersRef","Set","subscribe","useCallback","callback","current","add","delete","getSnapshot","emit","forEach","listener","contextValue","reactUseMemo","useValue","useSyncExternalStore","useSelector","compute","isEqual","useSyncExternalStoreWithSelector","TIMEINTERVALSINCE1970_API_URL","process","env","__webpack_module_cache__","__webpack_require__","moduleId","cachedModule","undefined","__webpack_modules__","n","getter","__esModule","d","definition","o","Object","defineProperty","enumerable","get","obj","prop","prototype","call","r","Symbol","toStringTag","useClock","useClockSelector","ClockProvider"],"sourceRoot":""}
|