Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 8 additions & 0 deletions draw/api/drive_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,14 @@ def register_in_drive(diagram_name: str, team: str | None = None) -> str | None:
return drive_file.name


@frappe.whitelist()
def is_available() -> dict:
"""Whether the Drive integration can be used (Drive installed + a team exists).
The editor calls this once to decide whether to show the "Add to Drive" action."""
installed = drive_installed()
return {"installed": installed, "ready": bool(installed and _default_team())}


@frappe.whitelist()
def add_to_drive(name: str) -> dict:
"""Whitelisted entry point for an "Add to Drive" action. Returns whether Drive
Expand Down
11 changes: 11 additions & 0 deletions draw/draw/doctype/draw_diagram/test_draw_diagram.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,17 @@ def test_register_in_drive_when_available(self):
# Idempotent — a second call reuses the same File.
self.assertEqual(register_in_drive(doc.name, team=team), file_name)

def test_drive_is_available_reports_status(self):
# The editor calls is_available() to decide whether to show "Add to Drive".
# It must always return the two booleans without raising, Drive or not.
from draw.api.drive_integration import drive_installed, is_available

status = is_available()
self.assertEqual(status["installed"], drive_installed())
self.assertIn("ready", status)
if not drive_installed():
self.assertFalse(status["ready"])

def test_drive_registration_noops_without_team(self):
# Registration is a safe no-op when Drive isn't set up / not installed.
from draw.api import drive_integration
Expand Down
52 changes: 52 additions & 0 deletions frontend/src/components/toolbar/DriveMenu.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<script setup>
// "Add to Drive" action — appears only when the optional Frappe Drive integration
// is available (Drive installed + a team set up). Registers the diagram in Drive
// as a link file that opens right back in this editor. Idempotent server-side, so
// clicking again just re-uses the existing Drive file.
import { ref, onMounted } from 'vue'
import { useRoute } from 'vue-router'
import { Button, Tooltip, call, toast } from 'frappe-ui'
import LucideIcon from '@/icons/LucideIcon.vue'

const route = useRoute()
const available = ref(false)
const busy = ref(false)

onMounted(async () => {
try {
const status = await call('draw.api.drive_integration.is_available')
available.value = !!status?.ready
} catch {
available.value = false // Drive not installed / API absent → stay hidden.
}
})

async function addToDrive() {
if (busy.value) return
busy.value = true
try {
const res = await call('draw.api.drive_integration.add_to_drive', { name: route.params.name })
if (res?.file) {
toast.success('Added to Drive', {
text: 'Opens in Draw from your Drive.',
action: { label: 'Open Drive', onClick: () => window.open('/drive', '_blank') },
})
} else {
toast.error('Could not add to Drive')
}
} catch (e) {
toast.error('Could not add to Drive', { text: e?.message || '' })
} finally {
busy.value = false
}
}
</script>

<template>
<Tooltip v-if="available" text="Add this diagram to Frappe Drive">
<Button variant="outline" :loading="busy" aria-label="Add to Drive" @click="addToDrive">
<template #prefix><LucideIcon name="hard-drive" class="h-4 w-4" /></template>
Add to Drive
</Button>
</Tooltip>
</template>
2 changes: 2 additions & 0 deletions frontend/src/components/toolbar/TopToolbar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import TitleEditor from './TitleEditor.vue'
import SaveIndicator from './SaveIndicator.vue'
import ExportMenu from './ExportMenu.vue'
import ShareMenu from './ShareMenu.vue'
import DriveMenu from './DriveMenu.vue'
import PresenceAvatars from './PresenceAvatars.vue'

const props = defineProps({
Expand Down Expand Up @@ -76,6 +77,7 @@ function print() {

<div class="ml-auto flex items-center gap-2">
<ExportMenu />
<DriveMenu />
<ShareMenu />

<Tooltip text="Print">
Expand Down
Loading