1
0
mirror of https://github.com/chinchang/web-maker.git synced 2025-08-04 20:37:29 +02:00

add layout option in share modal

This commit is contained in:
Kushagra Gour
2025-06-27 15:21:10 +05:30
parent e89bb4c5db
commit 0d4ba6314d
3 changed files with 238 additions and 33 deletions

View File

@@ -18,6 +18,55 @@ const TOGGLE_VISIBILITY_API =
? 'http://127.0.0.1:5001/web-maker-app/us-central1/toggleVisibility' ? 'http://127.0.0.1:5001/web-maker-app/us-central1/toggleVisibility'
: */ 'https://togglevisibility-ajhkrtmkaq-uc.a.run.app'; : */ 'https://togglevisibility-ajhkrtmkaq-uc.a.run.app';
// Layout mode definitions with icons
const LAYOUT_MODES = [
{
id: 1,
name: 'Preview on right',
icon: (
<svg viewBox="0 0 100 100" style="transform:rotate(-90deg)">
<use xlinkHref="#mode-icon" />
</svg>
)
},
{
id: 2,
name: 'Preview on bottom',
icon: (
<svg viewBox="0 0 100 100">
<use xlinkHref="#mode-icon" />
</svg>
)
},
{
id: 3,
name: 'Preview on left',
icon: (
<svg viewBox="0 0 100 100" style="transform:rotate(90deg)">
<use xlinkHref="#mode-icon" />
</svg>
)
},
{
id: 4,
name: 'Full screen preview',
icon: (
<svg viewBox="0 0 100 100">
<rect x="0" y="0" width="100" height="100" />
</svg>
)
},
{
id: 5,
name: 'Three-pane horizontal',
icon: (
<svg viewBox="0 0 100 100">
<use xlinkHref="#vertical-mode-icon" />
</svg>
)
}
];
export function Share({ export function Share({
user, user,
item, item,
@@ -26,6 +75,8 @@ export function Share({
onProBtnClick onProBtnClick
}) { }) {
const [publicItemCount, setPublicItemCount] = useState(); const [publicItemCount, setPublicItemCount] = useState();
const [selectedLayout, setSelectedLayout] = useState(2); // Default to layout 2
useEffect(() => { useEffect(() => {
if (!user) return; if (!user) return;
window.db.getPublicItemCount(user.uid).then(c => { window.db.getPublicItemCount(user.uid).then(c => {
@@ -75,10 +126,16 @@ export function Share({
} }
}; };
const getShareUrl = () => {
const baseUrl = `${BASE_URL}/create/${item.id}`;
return selectedLayout ? `${baseUrl}?layout=${selectedLayout}` : baseUrl;
};
const copyUrl = () => { const copyUrl = () => {
navigator.clipboard.writeText(`${BASE_URL}/create/${item.id}`); navigator.clipboard.writeText(getShareUrl());
alertsService.add('URL copied to clipboard'); alertsService.add('URL copied to clipboard');
}; };
if (!user) { if (!user) {
return ( return (
<HStack justify="center" gap={2}> <HStack justify="center" gap={2}>
@@ -106,19 +163,70 @@ export function Share({
</p> </p>
)} )}
{item.isPublic && ( {item.isPublic && (
<p> <VStack gap={2} align="stretch">
Public at{' '} <p>
<a href={`${BASE_URL}/create/${item.id}`} target="_blank"> Public at{' '}
{BASE_URL}/create/{item.id} <a href={getShareUrl()} target="_blank">
</a>{' '} {getShareUrl()}
<Button </a>{' '}
class="btn btn--dark hint--bottom hint--rounded" <Button
onClick={copyUrl} class="btn btn--dark hint--bottom hint--rounded"
aria-label="Copy" onClick={copyUrl}
> aria-label="Copy"
<Icon name="copy" /> Copy >
</Button> <Icon name="copy" /> Copy
</p> </Button>
</p>
{/* Layout Mode Selector */}
<HStack gap={3} align="center">
<label style="display: block; font-weight: 500;">
Layout Mode:
</label>
<HStack gap={1} align="center">
{LAYOUT_MODES.map(layout => (
<label
key={layout.id}
style={{
display: 'flex',
cursor: 'pointer',
padding: '8px',
border:
selectedLayout === layout.id
? '2px solid var(--clr-brand-2)'
: '2px solid transparent',
borderRadius: '0.5rem',
transition: 'all 0.2s ease'
}}
>
<input
type="radio"
name="layout-mode"
value={layout.id}
checked={selectedLayout === layout.id}
onChange={e =>
setSelectedLayout(parseInt(e.target.value, 10))
}
style={{ display: 'none' }}
/>
<div
style={{
fill:
selectedLayout === layout.id
? 'var(--clr-brand-2)'
: 'currentColor',
width: '26px',
height: '26px',
display: 'flex'
}}
>
{layout.icon}
</div>
</label>
))}
</HStack>
</HStack>
</VStack>
)} )}
</VStack> </VStack>
</div> </div>

View File

@@ -14,6 +14,7 @@
--color-text-light: #b0a5d6; --color-text-light: #b0a5d6;
--color-text-lightest-final: #787090; --color-text-lightest-final: #787090;
--clr-brand: purple; --clr-brand: purple;
--clr-brand-2: #e3ba26;
--color-pro-1: #1fffb3; --color-pro-1: #1fffb3;
--color-pro-2: #f2ff00; --color-pro-2: #f2ff00;
@@ -808,7 +809,7 @@ li.CodeMirror-hint-active {
/*color: white;*/ /*color: white;*/
} }
[data-type="css"] .cm-error { [data-type='css'] .cm-error {
background: inherit !important; background: inherit !important;
} }

View File

@@ -54,15 +54,48 @@ describe('Layout Parameter Tests', () => {
const params = new URLSearchParams(global.window.location.search); const params = new URLSearchParams(global.window.location.search);
global.window.codeHtml = params.get('html') || ''; global.window.codeHtml = params.get('html') || '';
global.window.codeCss = params.get('css') || ''; global.window.codeCss = params.get('css') || '';
global.window.codeLayout = params.get('layout') global.window.codeLayout = (() => {
? parseInt(params.get('layout'), 10) const layout = params.get('layout');
: null; if (!layout) return null;
if (layout === 'full') {
return 4;
}
const _val = parseInt(layout, 10);
if (_val >= 1 && _val <= 5) {
return _val;
}
return null;
})();
} }
expect(global.window.codeLayout).toBe(3); expect(global.window.codeLayout).toBe(3);
expect(global.window.codeHtml).toBe('<div>test</div>'); expect(global.window.codeHtml).toBe('<div>test</div>');
}); });
test('should handle "full" alias for layout mode 4', () => {
// Set up URL with "full" layout parameter
global.window.location.search = '?layout=full&html=<div>test</div>';
// Simulate the URL parsing logic
if (global.window.location.search) {
const params = new URLSearchParams(global.window.location.search);
global.window.codeLayout = (() => {
const layout = params.get('layout');
if (!layout) return null;
if (layout === 'full') {
return 4;
}
const _val = parseInt(layout, 10);
if (_val >= 1 && _val <= 5) {
return _val;
}
return null;
})();
}
expect(global.window.codeLayout).toBe(4);
});
test('should handle invalid layout parameter gracefully', () => { test('should handle invalid layout parameter gracefully', () => {
// Set up URL with invalid layout parameter // Set up URL with invalid layout parameter
global.window.location.search = '?layout=invalid&html=<div>test</div>'; global.window.location.search = '?layout=invalid&html=<div>test</div>';
@@ -70,12 +103,21 @@ describe('Layout Parameter Tests', () => {
// Simulate the URL parsing logic // Simulate the URL parsing logic
if (global.window.location.search) { if (global.window.location.search) {
const params = new URLSearchParams(global.window.location.search); const params = new URLSearchParams(global.window.location.search);
global.window.codeLayout = params.get('layout') global.window.codeLayout = (() => {
? parseInt(params.get('layout'), 10) const layout = params.get('layout');
: null; if (!layout) return null;
if (layout === 'full') {
return 4;
}
const _val = parseInt(layout, 10);
if (_val >= 1 && _val <= 5) {
return _val;
}
return null;
})();
} }
expect(global.window.codeLayout).toBe(NaN); expect(global.window.codeLayout).toBe(null);
}); });
test('should handle layout parameter out of range', () => { test('should handle layout parameter out of range', () => {
@@ -87,13 +129,22 @@ describe('Layout Parameter Tests', () => {
if (global.window.location.search) { if (global.window.location.search) {
const params = new URLSearchParams(global.window.location.search); const params = new URLSearchParams(global.window.location.search);
global.window.codeLayout = params.get('layout') global.window.codeLayout = (() => {
? parseInt(params.get('layout'), 10) const layout = params.get('layout');
: null; if (!layout) return null;
if (layout === 'full') {
return 4;
}
const _val = parseInt(layout, 10);
if (_val >= 1 && _val <= 5) {
return _val;
}
return null;
})();
} }
// The layout should be parsed but may be outside valid range // The layout should be null for out-of-range values
expect(global.window.codeLayout).toBe(layoutValue); expect(global.window.codeLayout).toBe(null);
}); });
}); });
@@ -120,9 +171,18 @@ describe('Layout Parameter Tests', () => {
const params = new URLSearchParams(global.window.location.search); const params = new URLSearchParams(global.window.location.search);
global.window.codeHtml = params.get('html') || ''; global.window.codeHtml = params.get('html') || '';
global.window.codeCss = params.get('css') || ''; global.window.codeCss = params.get('css') || '';
global.window.codeLayout = params.get('layout') global.window.codeLayout = (() => {
? parseInt(params.get('layout'), 10) const layout = params.get('layout');
: null; if (!layout) return null;
if (layout === 'full') {
return 4;
}
const _val = parseInt(layout, 10);
if (_val >= 1 && _val <= 5) {
return _val;
}
return null;
})();
} }
expect(global.window.codeLayout).toBe(2); expect(global.window.codeLayout).toBe(2);
@@ -138,11 +198,47 @@ describe('Layout Parameter Tests', () => {
// Simulate the URL parsing logic // Simulate the URL parsing logic
if (global.window.location.search) { if (global.window.location.search) {
const params = new URLSearchParams(global.window.location.search); const params = new URLSearchParams(global.window.location.search);
global.window.codeLayout = params.get('layout') global.window.codeLayout = (() => {
? parseInt(params.get('layout'), 10) const layout = params.get('layout');
: null; if (!layout) return null;
if (layout === 'full') {
return 4;
}
const _val = parseInt(layout, 10);
if (_val >= 1 && _val <= 5) {
return _val;
}
return null;
})();
} }
expect(global.window.codeLayout).toBe(null); expect(global.window.codeLayout).toBe(null);
}); });
test('should handle all valid layout values', () => {
// Test all valid layout values (1-5)
const validLayouts = [1, 2, 3, 4, 5];
validLayouts.forEach(layoutValue => {
global.window.location.search = `?layout=${layoutValue}`;
if (global.window.location.search) {
const params = new URLSearchParams(global.window.location.search);
global.window.codeLayout = (() => {
const layout = params.get('layout');
if (!layout) return null;
if (layout === 'full') {
return 4;
}
const _val = parseInt(layout, 10);
if (_val >= 1 && _val <= 5) {
return _val;
}
return null;
})();
}
expect(global.window.codeLayout).toBe(layoutValue);
});
});
}); });