Skip to content

Commit

Permalink
updated url params to JSON.stringify + base 64 encode + debounce slid…
Browse files Browse the repository at this point in the history
…er params update
  • Loading branch information
kwongz committed Feb 11, 2025
1 parent decaf66 commit 85ded9a
Show file tree
Hide file tree
Showing 10 changed files with 171 additions and 44 deletions.
69 changes: 59 additions & 10 deletions packages/lib/sdk/src/utils/svelte/useUrlParams.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,10 @@ import { browser } from '$app/environment';
*/
export function hydrateFromUrlParam(key, hydrate) {
if (browser) {
// window.location
// onMount(() => {
const url = new URL(window.location.href);
if (url.searchParams.has(key)) {
hydrate?.(url.searchParams.get(key));
}
// });
let _value = parseUrlValue(url.searchParams.get(key));
console.log('hydrateFromUrlParam', key, _value);
hydrate?.(_value);
}
}

Expand All @@ -69,13 +66,65 @@ export function hydrateFromUrlParam(key, hydrate) {
export function updateUrlParam(key, value) {
if (browser) {
const url = new URL(window.location.href);
if (!url.searchParams.has(key) && value) {
url.searchParams.append(key, value);
} else if (value) {
url.searchParams.set(key, value);

// if (!url.searchParams.has(key) && value) {
// url.searchParams.append(key, value);
// } else if (value) {
// url.searchParams.set(key, value);
// } else {
// url.searchParams.delete(key);
// }

if (value !== null) {
url.searchParams.set(key, encodeUrlValue(value));
} else {
url.searchParams.delete(key);
}

history.replaceState(null, '', `?${url.searchParams.toString()}`);
}
}

/**
* Encodes a value for a URL parameter.
* @param {any} value
* @returns {string}
*/
function encodeUrlValue(value) {
// Convert value to a JSON string
const jsonString = JSON.stringify(value);

// Base64 encode it (btoa only works with strings)
const base64Encoded = btoa(jsonString);

// Encode for safe URL usage
return base64Encoded;
}

/**
* Parses a value retrieved from a URL parameter.
* @param {string | null} value
* @returns {any}
*/
function parseUrlValue(value) {
if (value === null) return null;

let parsed;

// Try to decode as Base64 and parse as JSON
try {
const base64Decoded = atob(value);
parsed = JSON.parse(base64Decoded);
console.log('parsed', parsed);
// Return the parsed object if it's a valid object or array
if (typeof parsed === 'object' && parsed !== null) {
return parsed;
}
} catch {
// If Base64 decoding or JSON parsing fails, simply return the value as is
console.log('Error decoding or parsing');
}

// Return the value as is (it could be a primitive value or non-Base64 string)
return parsed || value;
}
Original file line number Diff line number Diff line change
Expand Up @@ -248,10 +248,11 @@
<Story name="URL Params Hard Coded Entries" let:args>
<div class="mb-8">
<ButtonGroup {...args}>
<ButtonGroupItem valueLabel="Option 1" value={1} />
<ButtonGroupItem valueLabel="Option 2" value={2} />
<ButtonGroupItem valueLabel="Option 3" value={3} default />
<ButtonGroupItem valueLabel="Option 4" value={4} />
<ButtonGroupItem valueLabel="Num 1" value={1} />
<ButtonGroupItem valueLabel="Num 2" value={2} />
<ButtonGroupItem valueLabel="Num 3" value={3} default />
<ButtonGroupItem valueLabel="Num 4" value={4} />
<ButtonGroupItem valueLabel="String 4" value={'4'} />
</ButtonGroup>
</div>

Expand All @@ -275,7 +276,7 @@
}}
>
<div class="mb-8">
<ButtonGroup {...args} defaultValue={2} />
<ButtonGroup {...args} defaultValue={1} />
</div>

Current Value: {$inputStore[args.name]}
Expand Down Expand Up @@ -314,7 +315,6 @@

Current Value: {$inputStore['buttonGroup_B']}
<div class="mt-4">URL: {storyIframeURL}</div>
<div>URL: {storyIframeURL}</div>
<button
class="mt-4 p-1 border bg-info/60 hover:bg-info/40 active:bg-info/20 rounded-md text-sm
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,17 +49,14 @@
hydrateFromUrlParam(name, (v) => (defaultValue = v));
setContext('button-group-defaultValue', writable(defaultValue));
$: console.log(defaultValue, 'defaultValue');
// TODO: Use getInputSetter instead
setButtonGroupContext((v) => {
$valueStore = v;
// the assignment to $inputs is necessary to trigger the change on SSR
$inputs[name] = v?.value ?? null;
// updateSearchParams(name, v?.value);
updateUrlParam(name, String(v?.value));
// hydrateFromUrlParam(name, (newVal) => v.value = newVal)
// if($page.url.searchParams.has(name)){
// v.value = $page.url.searchParams.get(name);
// }
//
updateUrlParam(name, v?.value);
}, readonly(valueStore));
/////
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,7 @@
let _default = false;
export { _default as default };
if (
$buttonGroupDefaultValue?.toString() === value.toString() ||
defaultValue?.toString() === value.toString()
) {
if ($buttonGroupDefaultValue === value || defaultValue === value) {
update({ valueLabel, value });
} else if (_default && !buttonGroupDefaultValue) {
update({ valueLabel, value });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,12 @@
</script>

<script>
import { hydrateFromUrlParam, updateUrlParam } from '@evidence-dev/sdk/utils/svelte';
import { hydrateFromUrlParam, updateUrlParam } from '@evidence-dev/sdk/utils/svelte';
import { dropdownOptionStore } from './dropdownOptionStore.js';
import { onDestroy, setContext } from 'svelte';
import { DropdownContext } from './constants.js';
import DropdownOption from './helpers/DropdownOption.svelte';
import { page } from '$app/stores';
import { browser } from '$app/environment';
import { buildReactiveInputQuery } from '@evidence-dev/component-utilities/buildQuery';
import { duckdbSerialize } from '@evidence-dev/sdk/usql';
import { getInputContext } from '@evidence-dev/sdk/utils/svelte';
Expand Down Expand Up @@ -98,10 +97,9 @@
name in $inputs && 'rawValues' in $inputs[name] && Array.isArray($inputs[name].rawValues)
? $inputs[name].rawValues
: [];
hydrateFromUrlParam(name, (v) => defaultValue = [v])
// hydrateFromUrlParam(name, (v) => (defaultValue = v));
hydrateFromUrlParam(name, (v) => (defaultValue = v));
const state = dropdownOptionStore({
multiselect: multiple,
Expand Down Expand Up @@ -131,8 +129,9 @@
$inputs[name] = newValue;
}
// updateSearchParams(name, newValue.value);
updateUrlParam(name, newValue.value);
const paramValue = multiple ? newValue.rawValues.map((x) => x.value) : newValue.value;
updateUrlParam(name, paramValue);
};
let opts = [];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,11 @@ export const dropdownOptionStore = (opts = {}) => {
}

// Apply defaults
if (option.value !== null && defaults.has(option.value)) {
if (
option.value !== null &&
// ensures urlParams in string format pass condition when values are type numbers
(defaults.has(String(option.value)) || defaults.has(option.value))
) {
option.selected = true;
defaults.delete(option.value);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,35 @@
await userEvent.click(dropdown, { delay: 100 });
await userEvent.keyboard('{Enter}');
};
let storyIframeURL = '';
const updateURL = () => {
storyIframeURL = window.location.href;
// Try forcing Storybook to recognize the change
const iframe = document.querySelector('iframe');
if (iframe) {
iframe.src = iframe.src; // Force reload
}
};
(function () {
const pushState = history.pushState;
const replaceState = history.replaceState;
history.pushState = function () {
pushState.apply(history, arguments);
updateURL();
};
history.replaceState = function () {
replaceState.apply(history, arguments);
updateURL();
};
window.addEventListener('popstate', updateURL);
})();
</script>

<Story name="Basic Usage">
Expand Down Expand Up @@ -281,6 +310,34 @@
</Dropdown>
</Story>

<Story name="URL Parameter">
{@const data = Query.create(`SELECT id as value, tag as label from hashtags`, query)}
<Dropdown name="urlParam" {data} value="value" label="label" defaultValue={1} title="url" />

<div class="mt-4">URL: {storyIframeURL}</div>
<button
class="mt-4 p-1 border bg-info/60 hover:bg-info/40 active:bg-info/20 rounded-md text-sm
"
on:click={() => window.open(storyIframeURL, '_blank')}>Go to URL</button
>
</Story>
<Story name="URL Parameter w/ multiplehardcoded options">
<Dropdown name="urlParamMultiple" multiple defaultValue={['1', '2']} title="url multiple">
<DropdownOption value={'1'} />
<DropdownOption value={2} />
<DropdownOption value={3} />
<DropdownOption value={4} />
</Dropdown>
<div class="mt-4">URL: {storyIframeURL}</div>
<button
class="mt-4 p-1 border bg-info/60 hover:bg-info/40 active:bg-info/20 rounded-md text-sm
"
on:click={() => window.open(storyIframeURL, '_blank')}>Go to URL</button
>
</Story>

<!--
Stories with:
Synced dropdowns on multiple tabs
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,24 @@
hydrateFromUrlParam(name, (v) => {
value = [v] ?? [defaultValue];
});
$: updateUrlParam(name, value);
const debounce = (fn, delay) => {
let timeout;
return (...args) => {
clearTimeout(timeout);
timeout = setTimeout(() => {
fn(...args);
}, delay);
};
};
let debouncedUpdateUrl = debounce((name, value) => {
updateUrlParam(name, value);
}, 75); // Adjust the debounce delay (500ms) as needed
$: if (value) {
debouncedUpdateUrl(name, value);
}
const renderSize = (size) => {
const sizeMap = {
Expand Down Expand Up @@ -169,12 +186,12 @@
'No data provided. If you referenced a query result, check that the name is correct.'
);
} else {
hydrateFromUrlParam(name, value, defaultValue, (v) => {
value = [v] ?? [defaultValue];
});
if ($page.url.searchParams.has(name)) {
value = [$page.url.searchParams.get(name)];
}
// hydrateFromUrlParam(name, value, defaultValue, (v) => {
// value = [v] ?? [defaultValue];
// });
// if ($page.url.searchParams.has(name)) {
// value = [$page.url.searchParams.get(name)];
// }
initialized = true;
}
} catch (e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,5 +71,11 @@

<Story name="URL Params">
<TextInput name="URLParams" title="update url params" />
<div class="mt-4">URL: {storyIframeURL}</div>
</Story>
<div>URL: {storyIframeURL}</div>
<button
class="mt-4 p-1 border bg-info/60 hover:bg-info/40 active:bg-info/20 rounded-md text-sm
"
on:click={() => window.open(storyIframeURL, '_blank')}>Go to URL</button
></Story
>
1 change: 1 addition & 0 deletions sites/docs/pages/components/inputs/dropdown/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ Note:
name=category_multi
value=category_name
multiple=true
defaultValue={['Cursed Sporting Goods','Mysterious Apparel']}
/>
Selected: {inputs.category_multi.value}
Expand Down

0 comments on commit 85ded9a

Please sign in to comment.