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:
@@ -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>
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
|
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
Reference in New Issue
Block a user