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

[next] feat(isMobile): use inject/provide #4758

Closed
wants to merge 2 commits into from

Conversation

raimund-schluessler
Copy link
Contributor

@raimund-schluessler raimund-schluessler commented Nov 4, 2023

☑️ Resolves

This PR introduces a couple of changes

<body>
	...
	<div id="content-vue" class="app-files content">
	</div>
</body>

to

<body>
	...
	<main id="content" class="app-files">
		<div id="content-vue" class="app-files content">
		</div>
	</main>
</body>
  • the isMobile and isFullscreen mixins were changed to use provide/inject. This allows to only use one event listener for the resize event, which should be more performant than creating a new one each time the mixins are used. This means that the mixins only work when NcContent is an ancestor, but since this is required anyway, it should not be an issue (as long as you use nextcloud-vue as intended).

Signed-off-by: Raimund Schlüßler <raimund.schluessler@mailbox.org>
@raimund-schluessler raimund-schluessler added this to the 9.0.0 next Vue 3 milestone Nov 4, 2023
@raimund-schluessler raimund-schluessler added 2. developing Work in progress vue 3 Related to the vue 3 migration labels Nov 4, 2023
@raimund-schluessler raimund-schluessler changed the title feat(isMobile): use inject/provide [next] feat(isMobile): use inject/provide Nov 4, 2023
@raimund-schluessler raimund-schluessler marked this pull request as ready for review November 4, 2023 11:38
@raimund-schluessler raimund-schluessler added 3. to review Waiting for reviews and removed 2. developing Work in progress labels Nov 4, 2023
Signed-off-by: Raimund Schlüßler <raimund.schluessler@mailbox.org>
@raimund-schluessler
Copy link
Contributor Author

I now also migrated NcAppContent* to show that the injection works and is reactive.

Copy link
Contributor

@ShGKme ShGKme left a comment

Choose a reason for hiding this comment

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

Hi @raimund-schluessler,

I'm not sure this is a good replacement for isMobile.

The main problem: with this solution isMobile only works inside NcContent component. While all the apps should use this component for app's content, sometimes we use components as just UI components, for example, in NcHeaderItem, in dialogs like Password Confirmation dialogs etc.

isMobile is not a NcContent's state and not a "context" state in general, it is a global state (it is not possible that one subtree context is "in mobile" and another subtree context is not in mobile).

Also, it mixes all isMobile logic and state with NcContent component.


We can make it a global reactive state, aka:

// 📁 aka utils/isMobile.js or composables/useIsMobile.js

// Aka private local isMobile in a module-scope
const _isMobile = ref(false)

function handleWindowResize() {
  // ...
}

window.addEventListener('resize', handleWindowResize)

handleWindowResize()

// Export readonly proxy to isMobile to save from mutation outside the module
export const isMobile = readonly(_isMobile)

For Composition API usage we can add a super simple composable here as well:

// 📁 composables/useIsMobile.js
export function useIsMobile() {
  // Just return the value
  return isMobile
}

For a simple migration, we can also add a mixin. But I'm also fine with just dropping the mixin as we can use composable in Options API as well.

// 📁 mixins/isMobile.js
import { isMobile as isMobileState } from 'path to util'

// Should be marked deprecated
export const isMobile = {
  // P.S. We cannot use setup in a mixin by design
  // But we can create a computed and return a ref value directly
  computed: {
    return isMobileState.value
  }
}

With this approach isMobile is not created in a component reactive scope and there is no removeEventListener. There is always one global (module-scope) reactive variable with one global event listener. It could be not great. We can make it a little bit better by allowing us to use this state only via useIsMobile to count its users, create an event listener only when needed, only once, and clear it when there is no component that uses it. But it won't work in a mixin.

const isMobile = ref(false)
const handleWindowResize = () => {}

let usersCount = 0

export function useIsMixin() {
  usersCount += 1
  if (usersCount === 1) {
    handleWindowResize()
    addEventListener()
  }

  onUnmounted(() => {
    usersCount -= 1
    if (usersCount === 0) {
      removeEventListener()
    }
  })
  
  return readonly(isMobile)
}

And the same with isFullscreen.

Comment on lines +122 to +124
// if the window height is equal to the screen height,
// we're in full screen mode
this.isFullscreen = (window.outerHeight === screen.height)
Copy link
Contributor

Choose a reason for hiding this comment

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

After dropping IE11 support, maybe we can use a native event?
https://caniuse.com/?search=fullscreenchange

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fine with me, but for Safari on iOS we would seem to need a non-standard fallback.

@raimund-schluessler
Copy link
Contributor Author

Hi @raimund-schluessler,

I'm not sure this is a good replacement for isMobile.

The main problem: with this solution isMobile only works inside NcContent component. While all the apps should use this component for app's content, sometimes we use components as just UI components, for example, in NcHeaderItem, in dialogs like Password Confirmation dialogs etc.

From the vue-components side, I think this is not really an issue. The components that use isMobile are

  • NcAppNavigation
  • NcAppContent
  • NcAppNavigationItem
  • NcAppSettingsDialog

I would say all of them only work if they are used together with NcContent.

However, I see the point that one could use the mixin isMobile in an app that does not use NcContent for which then the injection would fail. How about we introduce a fallback here and provide a factory function that creates a separate eventListener, much like the current isMobile mixin? We wouldn't need this reference counting approach and it would continue to work without NcContent.

isMobile is not a NcContent's state and not a "context" state in general, it is a global state (it is not possible that one subtree context is "in mobile" and another subtree context is not in mobile).

Also, it mixes all isMobile logic and state with NcContent component.

I don't really think that this is an issue. The implementation in master behaves similar when using the isMobile mixin.

@ShGKme
Copy link
Contributor

ShGKme commented Nov 6, 2023

The components that use isMobile are

But isMobile is an exported mixin and, in theory, could be used anywhere. It is already used in many apps, and I'd like to use it in a dashboard that is not inside NcContent.

https://github.com/search?q=org%3Anextcloud+import+isMobile+from+%27%40nextcloud%2Fvue%2Fdist%2FMixins%2FisMobile.js%27&type=code

@ShGKme
Copy link
Contributor

ShGKme commented Nov 6, 2023

How about we introduce a fallback here and provide a factory function that creates a separate eventListener, much like the current isMobile mixin?

I think that would make it more complex and solve the problem that should have been avoided.

This is not the purpose of provide/inject. isMobile is not a context-level state, and it doesn't need to be injected on runtime (no other "isMobile" could be injected, not like a vue-router).

This is an app-level state. In nextcloud-vue components we can use it directly without injecting to this. Outside we could use it directly. In mixin for backward compatibility, we can just add a computed.

Even with provide/inject, I don't get why NcContent should be responsible for isMobile state implementation. Even if we go with provide/inject, I'd move the implementation either to a composable or to a component-provider.

@ShGKme
Copy link
Contributor

ShGKme commented Nov 6, 2023

We wouldn't need this reference counting approach and it would continue to work without NcContent.

This counter is useful only in one edge case, when isMobile is used on the app with no isMobile usage except one optional usage (Which, IMO, still better than N dynamically added in the current mixin solution). In any other case, there will always be 1 useful listener.

Without it isMobile util can be implemented in about 7 lines.

@ShGKme
Copy link
Contributor

ShGKme commented Nov 6, 2023

The implementation in master behaves similar when using the isMobile mixin.

Which is also not a good solution. It creates a new copy of the state with a new event listener for each component that uses the mixin. This is also not how mixins should be used, because this is not a part of a component local state.

So I'd backport a new implementation of isMobile to master.

@raimund-schluessler
Copy link
Contributor Author

So I'd backport a new implementation of isMobile to master.

Or we fix it in master first and just use it here.

However, since I don't really understand your solution / approach and am not well familiar with composables and composition API, please feel free to go ahead here (or in master) and implement your solution. 👍

@raimund-schluessler
Copy link
Contributor Author

Superseded by #4761. Thanks a lot @ShGKme!

@raimund-schluessler raimund-schluessler deleted the feat/2154/content branch November 9, 2023 12:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
3. to review Waiting for reviews vue 3 Related to the vue 3 migration
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants