1
0
mirror of https://github.com/chinchang/web-maker.git synced 2025-08-01 19:10:22 +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'
: */ '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({
user,
item,
@@ -26,6 +75,8 @@ export function Share({
onProBtnClick
}) {
const [publicItemCount, setPublicItemCount] = useState();
const [selectedLayout, setSelectedLayout] = useState(2); // Default to layout 2
useEffect(() => {
if (!user) return;
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 = () => {
navigator.clipboard.writeText(`${BASE_URL}/create/${item.id}`);
navigator.clipboard.writeText(getShareUrl());
alertsService.add('URL copied to clipboard');
};
if (!user) {
return (
<HStack justify="center" gap={2}>
@@ -106,10 +163,11 @@ export function Share({
</p>
)}
{item.isPublic && (
<VStack gap={2} align="stretch">
<p>
Public at{' '}
<a href={`${BASE_URL}/create/${item.id}`} target="_blank">
{BASE_URL}/create/{item.id}
<a href={getShareUrl()} target="_blank">
{getShareUrl()}
</a>{' '}
<Button
class="btn btn--dark hint--bottom hint--rounded"
@@ -119,6 +177,56 @@ export function Share({
<Icon name="copy" /> Copy
</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>
</div>

View File

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

View File

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