Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pro 6518 mobile preview #4720

Merged
merged 49 commits into from
Oct 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
38e5020
experiment with mobile preview
haroun Sep 6, 2024
be578e8
fix lint issue
haroun Sep 6, 2024
60de087
add media to container query loader
haroun Sep 6, 2024
4c20516
clean up media to container queries loader
haroun Sep 10, 2024
62fd03a
use apostrophe options to toggle mobile preview
haroun Sep 10, 2024
c9579c8
add breakpoints support
haroun Sep 10, 2024
895fb42
experiment with screen options
haroun Sep 10, 2024
7db9493
refactor as device preview mode to avoid name conflicts with preview …
haroun Sep 11, 2024
8991290
set todos
haroun Sep 11, 2024
6806ded
rename devicePreview into devicePreviewMode and validate screens
haroun Sep 12, 2024
174c897
set active style
haroun Sep 12, 2024
4573f98
set active style and keyboard shortcuts
haroun Sep 12, 2024
e52a478
restore previous filename logic
haroun Sep 12, 2024
bf528e8
revert plugins
haroun Sep 12, 2024
ca2bb37
note about deep merge
haroun Sep 12, 2024
c296953
Merge branch 'main' into pro-6518-mobile-preview
haroun Sep 12, 2024
e6e4cd1
update changelog
haroun Sep 12, 2024
52c8cb3
remove test styles
haroun Sep 12, 2024
129cc3a
fix mixed declaration warnings
haroun Sep 12, 2024
0ac0e49
translate device
haroun Sep 12, 2024
f7eaa02
translate additional keys in keyboard shortcut label
haroun Sep 12, 2024
070fc88
add transform function
haroun Sep 13, 2024
1329f7e
explain available transform options
haroun Sep 13, 2024
64e6c27
up to 9 shortcuts
haroun Sep 13, 2024
5b8b7a3
Merge branch 'main' into pro-6518-mobile-preview
haroun Sep 23, 2024
b2c051a
use flexbox
haroun Sep 23, 2024
ec60630
rename devicePreviewMode options
haroun Sep 23, 2024
bffb915
add label
haroun Sep 23, 2024
c93db00
fix lint issue
haroun Sep 23, 2024
180a12d
support preview mode background
haroun Sep 23, 2024
3b887ff
style tweaks
stuartromanek Sep 23, 2024
05f0f75
lint
stuartromanek Sep 23, 2024
0d072dd
save device preview state, transition only for non-resizable containers
haroun Sep 24, 2024
d08b8fd
save device preview mode state inside own component
haroun Sep 24, 2024
f86f60a
update background on body
haroun Sep 24, 2024
7da9500
keep state
haroun Sep 24, 2024
c8ccaac
Merge branch 'main' into pro-6518-mobile-preview
haroun Sep 25, 2024
e023214
Merge branch 'main' into pro-6518-mobile-preview
haroun Sep 25, 2024
881919b
replace background and not just color
haroun Sep 25, 2024
6116ee9
add selector prefix for mobile first responsive
haroun Sep 25, 2024
adc5e6b
add empty lines
haroun Sep 26, 2024
a363ade
exclude content with \n from being replaced
haroun Sep 26, 2024
ad3412a
match only single escaped backslash
haroun Sep 27, 2024
54b2574
update transform example
haroun Sep 27, 2024
6d0ec03
improve css specificity for media queries
haroun Sep 27, 2024
3b30474
do not increase specificity with where
haroun Sep 30, 2024
89524be
simplify regex for container query and use postcss to parse media and…
haroun Sep 30, 2024
b04e3af
set body to media query
haroun Sep 30, 2024
89739a5
add context label
haroun Oct 1, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
* Adds focus states for media library's Uploader tile
* Adds focus states file attachment's input UI
* Simplified importing rich text widgets via the REST API. If you you have HTML that contains `img` tags pointing to existing images, you can now import them all quickly. When supplying the rich text widget object, include an `import` property with an `html` subproperty, rather than the usual `content` property. You can optionally provide a `baseUrl` subproperty as well. Any images present in `html` will be imported automatically and the correct `figure` tags will be added to the new rich text widget, along with any other markup acceptable to the widget's configuration.
* Add mobile preview feature to the admin UI. The feature can be enabled using the `@apostrophecms/asset` module new `devicePreviewMode` option. Once enabled, the asset build process will duplicate existing media queries as container queries. There are some limitations in the equivalence media queries / container queries. You can refer to the [CSS @container at-rule](https://developer.mozilla.org/en-US/docs/Web/CSS/@container) documentation for more information. You can also enable `devicePreviewMode.debug` to be notified in the console when the build encounter an unsupported media query.

### Changes

Expand Down
63 changes: 61 additions & 2 deletions modules/@apostrophecms/admin-bar/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,56 @@ module.exports = {
pageTree: true
},
commands(self) {
const devicePreviewModeScreens = (
self.apos.asset.options.devicePreviewMode?.enable &&
self.apos.asset.options.devicePreviewMode?.screens
) || {};
const devicePreviewModeCommands = {
[`${self.__meta.name}:toggle-device-preview-mode:exit`]: {
type: 'item',
label: {
key: 'apostrophe:commandMenuToggleDevicePreviewMode',
device: '$t(apostrophe:devicePreviewExit)'
},
action: {
type: 'command-menu-admin-bar-toggle-device-preview-mode',
payload: {
mode: null,
width: null,
height: null
}
},
shortcut: 'P,0'
}
};
let index = 1;
for (const [ name, screen ] of Object.entries(devicePreviewModeScreens)) {
// Up to 9 shortcuts available
if (index === 9) {
break;
}

devicePreviewModeCommands[`${self.__meta.name}:toggle-device-preview-mode:${name}`] = {
type: 'item',
label: {
key: 'apostrophe:commandMenuToggleDevicePreviewMode',
device: `$t(${screen.label})`
},
action: {
type: 'command-menu-admin-bar-toggle-device-preview-mode',
payload: {
mode: name,
label: `$t(${screen.label})`,
width: screen.width,
height: screen.height
}
},
shortcut: `P,${index}`
Copy link
Contributor

@ETLaurent ETLaurent Sep 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
shortcut: `P,${index}`
shortcut: `P ${index}`

seems more UX-friendly to me

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will not work with current keyboard shortcut listener implementation

P+0 is supposed to be P and 0, but it comes with additional challenges (the order you're pressing the keys is important P+0 and 0+P should be different shortcuts)

P,0 is translated to P then 0 (you still can hold P while pressing 0), I think this is what you're looking for.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P,[n] is a bit slow if we want to quickly switch between modes.
But it's not that bad.

};

index += 1;
};

return {
add: {
[`${self.__meta.name}:undo`]: {
Expand Down Expand Up @@ -63,7 +113,8 @@ module.exports = {
type: 'command-menu-admin-bar-toggle-publish-draft'
},
shortcut: 'Ctrl+Shift+D Meta+Shift+D'
}
},
...devicePreviewModeCommands
},
modal: {
default: {
Expand All @@ -80,7 +131,8 @@ module.exports = {
label: 'apostrophe:commandMenuMode',
commands: [
`${self.__meta.name}:toggle-edit-preview-mode`,
`${self.__meta.name}:toggle-published-draft-document`
`${self.__meta.name}:toggle-published-draft-document`,
...Object.keys(devicePreviewModeCommands)
]
}
}
Expand Down Expand Up @@ -355,6 +407,13 @@ module.exports = {
aposLocale: context.aposLocale,
aposDocId: context.aposDocId
},
devicePreviewMode: self.apos.asset.options.devicePreviewMode ||
{
enable: false,
debug: false,
resizable: false,
screens: {}
},
// Base API URL appropriate to the context document
contextBar: context && self.apos.doc.getManager(context.type).options.contextBar,
showAdminBar: self.getShowAdminBar(req),
Expand Down
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a good reason not to use Vue 3 here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to do like the existing vue components in the project

Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
<template>
<div
data-apos-test="devicePreviewMode"
class="apos-admin-bar__device-preview-mode"
>
<component
:is="'AposButton'"
v-for="(screen, name) in screens"
:key="name"
:data-apos-test="`devicePreviewMode:${name}`"
:modifiers="['small', 'no-motion']"
:label="screen.label"
:title="$t(screen.label)"
:icon="screen.icon"
:icon-only="true"
type="subtle"
class="apos-admin-bar__device-preview-mode-button"
:class="{ 'apos-is-active': mode === name }"
@click="toggleDevicePreviewMode({ mode: name, label: screen.label, width: screen.width, height: screen.height })"
/>
</div>
</template>
<script>

export default {
name: 'TheAposContextDevicePreview',
props: {
// { screenName: { label: string, width: string, height: string, icon: string } }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wanted comment?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep, to explain the expected props

screens: {
type: Object,
validator(value, props) {
return Object.values(value).every(screen =>
typeof screen.label === 'string' &&
typeof screen.width === 'string' &&
typeof screen.height === 'string' &&
typeof screen.icon === 'string'
);
},
default: () => {
return {};
}
},
resizable: {
type: Boolean,
default: false
}
},
emits: [ 'switch-device-preview-mode', 'reset-device-preview-mode' ],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't find where these are being listened to?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's not, but if you want to listen to it and do something you can

data() {
return {
mode: null,
originalBodyBackground: null
};
},
mounted() {
apos.bus.$on('command-menu-admin-bar-toggle-device-preview-mode', this.toggleDevicePreviewMode);

this.originalBodyBackground = window.getComputedStyle(document.querySelector('body'))?.background ||
'#fff';

const state = this.loadState();
if (state.mode) {
this.toggleDevicePreviewMode(state);
}
},
unmounted() {
apos.bus.$off('command-menu-admin-bar-toggle-device-preview-mode', this.toggleDevicePreviewMode);
},
methods: {
switchDevicePreviewMode({
mode,
label,
width,
height
}) {
document.querySelector('body').setAttribute('data-device-preview-mode', mode);
document.querySelector('[data-apos-refreshable]').setAttribute('data-resizable', this.resizable);
document.querySelector('[data-apos-refreshable]').setAttribute('data-label', this.$t(label));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we're not displaying it anymore then we can remove this line, as well as in toggleDevicePreviewMode and saveStatemethods.

document.querySelector('[data-apos-refreshable]').style.width = width;
document.querySelector('[data-apos-refreshable]').style.height = height;
document.querySelector('[data-apos-refreshable]').style.background = this.originalBodyBackground;

this.mode = mode;
this.$emit('switch-device-preview-mode', {
mode,
label,
width,
height
});
this.saveState({
mode,
label,
width,
height
});
},
toggleDevicePreviewMode({
mode,
label,
width,
height
}) {
if (this.mode === mode || mode === null) {
document.querySelector('body').removeAttribute('data-device-preview-mode');
document.querySelector('[data-apos-refreshable]').removeAttribute('data-resizable');
document.querySelector('[data-apos-refreshable]').removeAttribute('data-label');
document.querySelector('[data-apos-refreshable]').style.removeProperty('width');
document.querySelector('[data-apos-refreshable]').style.removeProperty('height');
document.querySelector('[data-apos-refreshable]').style.removeProperty('background');

this.mode = null;
this.$emit('reset-device-preview-mode');
this.saveState({ mode: this.mode });

return;
}

this.switchDevicePreviewMode({
mode,
label,
width,
height
});
},
loadState() {
return JSON.parse(sessionStorage.getItem('aposDevicePreviewMode') || '{}');
},
saveState({
mode = null,
label = null,
width = null,
height = null
} = {}) {
const state = this.loadState();
if (state.mode !== mode) {
sessionStorage.setItem(
'aposDevicePreviewMode',
JSON.stringify({
mode,
label,
width,
height
})
);
}
}
}
};
</script>
<style lang="scss" scoped>
.apos-admin-bar__device-preview-mode {
display: flex;
gap: $spacing-half;
margin-left: $spacing-double;
}

.apos-admin-bar__device-preview-mode-button {
&.apos-is-active {
color: var(--a-text-primary);
text-decoration: none;
background-color: var(--a-base-10);
border-radius: var(--a-border-radius);
outline: 1px solid var(--a-base-7);
}
}
</style>
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,13 @@
:tooltip="tooltip"
:modifiers="modifiers"
/>
<TheAposContextDevicePreviewMode
v-if="isDevicePreviewModeEnabled"
:screens="devicePreviewModeScreens"
:resizable="devicePreviewModeResizable"
@switch-device-preview-mode="addContextLabel"
@reset-device-preview-mode="removeContextLabel"
/>
</span>
</transition-group>
</template>
Expand Down Expand Up @@ -94,6 +101,15 @@ export default {
isUnpublished() {
return !this.context.lastPublishedAt;
},
isDevicePreviewModeEnabled() {
return this.moduleOptions.devicePreviewMode.enable || false;
},
devicePreviewModeScreens() {
return this.moduleOptions.devicePreviewMode.screens || {};
},
devicePreviewModeResizable() {
return this.moduleOptions.devicePreviewMode.resizable || false;
},
docTooltip() {
return {
key: 'apostrophe:lastUpdatedBy',
Expand Down Expand Up @@ -142,6 +158,15 @@ export default {
},
switchDraftMode(mode) {
this.$emit('switch-draft-mode', mode);
},
addContextLabel({
label
}) {
document.querySelector('[data-apos-context-label]')
?.replaceChildren(document.createTextNode(this.$t(label)));
},
removeContextLabel() {
document.querySelector('[data-apos-context-label]')?.replaceChildren();
}
}
};
Expand Down
42 changes: 41 additions & 1 deletion modules/@apostrophecms/asset/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,47 @@ module.exports = {
rebundleModules: undefined,
// In case of external front end like Astro, this option allows to
// disable the build of the public UI assets.
publicBundle: true
publicBundle: true,
// Device preview in the admin UI.
// NOTE: the whole devicePreviewMode option must be carried over
// to the project for override to work properly.
// Nested object options are not deep merged in Apostrophe.
Comment on lines +49 to +51
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Worth mentioning that Option transformation feature will arrive soon?
We'll need to remove or edit this note when the said feature will be available.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it will be in the doc, I think we will update it when this feature lands in apostrophe

devicePreviewMode: {
// Enable device preview mode
enable: false,
// Warn during build about unsupported media queries.
debug: false,
// If we can resize the preview container?
resizable: false,
// Screens with icons
// For adding icons, please refer to the icons documentation
// https://docs.apostrophecms.org/reference/module-api/module-overview.html#icons
screens: {
desktop: {
label: 'apostrophe:devicePreviewDesktop',
width: '1500px',
height: '900px',
icon: 'monitor-icon'
},
tablet: {
label: 'apostrophe:devicePreviewTablet',
width: '1024px',
height: '768px',
icon: 'tablet-icon'
},
mobile: {
label: 'apostrophe:devicePreviewMobile',
width: '480px',
height: '1000px',
icon: 'cellphone-icon'
}
},
// Transform method used on media feature
// Can be either:
// - (mediaFeature) => { return mediaFeature.replaceAll('xx', 'yy'); }
// - null
transform: null
}
},

async init(self) {
Expand Down
3 changes: 3 additions & 0 deletions modules/@apostrophecms/asset/lib/globalIcons.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ module.exports = {
'arrow-up-icon': 'ArrowUp',
'binoculars-icon': 'Binoculars',
'calendar-icon': 'Calendar',
'cellphone-icon': 'Cellphone',
'check-all-icon': 'CheckAll',
'check-bold-icon': 'CheckBold',
'check-circle-icon': 'CheckCircle',
Expand Down Expand Up @@ -98,6 +99,7 @@ module.exports = {
'menu-down-icon': 'MenuDown',
'minus-box-icon': 'MinusBox',
'minus-icon': 'Minus',
'monitor-icon': 'Monitor',
'paperclip-icon': 'Paperclip',
'pencil-icon': 'Pencil',
'phone-icon': 'Phone',
Expand All @@ -107,6 +109,7 @@ module.exports = {
'refresh-icon': 'Refresh',
'shape-icon': 'Shape',
'sign-text-icon': 'SignText',
'tablet-icon': 'Tablet',
'tag-icon': 'Tag',
'text-box-icon': 'TextBox',
'text-box-multiple-icon': 'TextBoxMultiple',
Expand Down
Loading
Loading